diff --git a/.gitignore b/.gitignore index 7bca71531..03e7c35cc 100644 --- a/.gitignore +++ b/.gitignore @@ -167,4 +167,15 @@ examples/cross_calculator/android/tags /run.json /testresults.html /testresults.json +/tests/caas/SymbolProcRun.*/ +/tests/caas/absurd_nesting +/tests/caas/forward_declarations +/tests/caas/idetools_api +/tests/caas/imported +/tests/caas/issue_416_template_shift +/tests/caas/issue_452_export_shift +/tests/caas/issue_477_dynamic_dispatch +/tests/caas/its_full_of_procs +/tests/caas/main +/tests/caasdriver /tools/nimgrep diff --git a/compiler/ast.nim b/compiler/ast.nim index e69b83181..3456177d3 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -278,11 +278,12 @@ const # the compiler will avoid printing such names # in user messages. - sfHoist* = sfVolatile ## proc return value can be hoisted - sfNoForward* = sfRegister # forward declarations are not required (per module) + sfNoRoot* = sfBorrow # a local variable is provably no root so it doesn't + # require RC ops + const # getting ready for the future expr/stmt merge nkWhen* = nkWhenStmt @@ -353,7 +354,7 @@ type nfSem # node has been checked for semantics TNodeFlags* = set[TNodeFlag] - TTypeFlag* = enum # keep below 32 for efficiency reasons (now: 19) + TTypeFlag* = enum # keep below 32 for efficiency reasons (now: 23) tfVarargs, # procedure has C styled varargs tfNoSideEffect, # procedure type does not allow side effects tfFinal, # is the object final? @@ -380,7 +381,13 @@ type tfByRef, # pass object/tuple by reference (C backend) tfIterator, # type is really an iterator, not a tyProc tfShared, # type is 'shared' - tfNotNil # type cannot be 'nil' + tfNotNil, # type cannot be 'nil' + + tfNeedsInit, # type constains a "not nil" constraint somewhere or some + # other type so that it requires inititalization + tfHasShared, # type constains a "shared" constraint modifier somewhere + tfHasMeta, # type has "typedesc" or "expr" somewhere + tfHasGCedMem, # type contains GC'ed memory TTypeFlags* = set[TTypeFlag] @@ -659,12 +666,15 @@ type # (or not in symbol table) # for modules, an unique index corresponding # to the module's fileIdx + # for variables a slot index for the evaluator offset*: int # offset of record field loc*: TLoc annex*: PLib # additional fields (seldom used, so we use a # reference to another object to safe space) - constraint*: PNode # additional constraints like 'lit|result' + constraint*: PNode # additional constraints like 'lit|result'; also + # misused for the codegenDecl pragma in the hope + # it won't cause problems TTypeSeq* = seq[PType] TType* {.acyclic.} = object of TIdObj # \ @@ -920,7 +930,10 @@ proc discardSons(father: PNode) = father.sons = nil when defined(useNodeIds): - const nodeIdToDebug = 140600 + const nodeIdToDebug = 612777 # 612794 + #612840 # 612905 # 614635 # 614637 # 614641 + # 423408 + #429107 # 430443 # 441048 # 441090 # 441153 var gNodeId: int proc newNode(kind: TNodeKind): PNode = @@ -933,6 +946,7 @@ proc newNode(kind: TNodeKind): PNode = when defined(useNodeIds): result.id = gNodeId if result.id == nodeIdToDebug: + echo "KIND ", result.kind writeStackTrace() inc gNodeId @@ -973,6 +987,12 @@ proc newNodeI(kind: TNodeKind, info: TLineInfo): PNode = new(result) result.kind = kind result.info = info + when defined(useNodeIds): + result.id = gNodeId + if result.id == nodeIdToDebug: + echo "KIND ", result.kind + writeStackTrace() + inc gNodeId proc newNodeI*(kind: TNodeKind, info: TLineInfo, children: int): PNode = new(result) @@ -980,6 +1000,12 @@ proc newNodeI*(kind: TNodeKind, info: TLineInfo, children: int): PNode = result.info = info if children > 0: newSeq(result.sons, children) + when defined(useNodeIds): + result.id = gNodeId + if result.id == nodeIdToDebug: + echo "KIND ", result.kind + writeStackTrace() + inc gNodeId proc newNode*(kind: TNodeKind, info: TLineInfo, sons: TNodeSeq = @[], typ: PType = nil): PNode = @@ -989,6 +1015,12 @@ proc newNode*(kind: TNodeKind, info: TLineInfo, sons: TNodeSeq = @[], result.typ = typ # XXX use shallowCopy here for ownership transfer: result.sons = sons + when defined(useNodeIds): + result.id = gNodeId + if result.id == nodeIdToDebug: + echo "KIND ", result.kind + writeStackTrace() + inc gNodeId proc newNodeIT(kind: TNodeKind, info: TLineInfo, typ: PType): PNode = result = newNode(kind) @@ -1146,14 +1178,33 @@ proc newSons(father: PNode, length: int) = else: setlen(father.sons, length) -proc addSon*(father, son: PType) {.deprecated.} = - if isNil(father.sons): father.sons = @[] - add(father.sons, son) - #assert((father.kind != tyGenericInvokation) or (son.kind != tyGenericInst)) +proc propagateToOwner*(owner, elem: PType) = + const HaveTheirOwnEmpty = {tySequence, tySet} + owner.flags = owner.flags + (elem.flags * {tfHasShared, tfHasMeta, + tfHasGCedMem}) + if tfNotNil in elem.flags: + if owner.kind in {tyGenericInst, tyGenericBody, tyGenericInvokation}: + owner.flags.incl tfNotNil + elif owner.kind notin HaveTheirOwnEmpty: + owner.flags.incl tfNeedsInit + + if tfNeedsInit in elem.flags: + if owner.kind in HaveTheirOwnEmpty: nil + else: owner.flags.incl tfNeedsInit + + if tfShared in elem.flags: + owner.flags.incl tfHasShared + + if elem.kind in {tyExpr, tyTypeDesc}: + owner.flags.incl tfHasMeta + elif elem.kind in {tyString, tyRef, tySequence} or + elem.kind == tyProc and elem.callConv == ccClosure: + owner.flags.incl tfHasGCedMem proc rawAddSon*(father, son: PType) = if isNil(father.sons): father.sons = @[] add(father.sons, son) + if not son.isNil: propagateToOwner(father, son) proc addSon(father, son: PNode) = assert son != nil @@ -1178,6 +1229,9 @@ proc copyNode(src: PNode): PNode = result.info = src.info result.typ = src.typ result.flags = src.flags * PersistentNodeFlags + when defined(useNodeIds): + if result.id == nodeIdToDebug: + echo "COMES FROM ", src.id case src.Kind of nkCharLit..nkUInt64Lit: result.intVal = src.intVal of nkFloatLit..nkFloat128Lit: result.floatVal = src.floatVal @@ -1193,6 +1247,9 @@ proc shallowCopy*(src: PNode): PNode = result.info = src.info result.typ = src.typ result.flags = src.flags * PersistentNodeFlags + when defined(useNodeIds): + if result.id == nodeIdToDebug: + echo "COMES FROM ", src.id case src.Kind of nkCharLit..nkUInt64Lit: result.intVal = src.intVal of nkFloatLit..nkFloat128Lit: result.floatVal = src.floatVal @@ -1209,6 +1266,9 @@ proc copyTree(src: PNode): PNode = result.info = src.info result.typ = src.typ result.flags = src.flags * PersistentNodeFlags + when defined(useNodeIds): + if result.id == nodeIdToDebug: + echo "COMES FROM ", src.id case src.Kind of nkCharLit..nkUInt64Lit: result.intVal = src.intVal of nkFloatLit..nkFloat128Lit: result.floatVal = src.floatVal diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index 427b0ec86..4d0fae451 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -651,7 +651,9 @@ proc finishTypeDescriptions(m: BModule) = while i < len(m.typeStack): discard getTypeDesc(m, m.typeStack[i]) inc(i) - + +template cgDeclFrmt*(s: PSym): string = s.constraint.strVal + proc genProcHeader(m: BModule, prc: PSym): PRope = var rettype, params: PRope @@ -669,12 +671,16 @@ proc genProcHeader(m: BModule, prc: PSym): PRope = genProcParams(m, prc.typ, rettype, params, check) # careful here! don't access ``prc.ast`` as that could reload large parts of # the object graph! - appf(result, "$1($2, $3)$4", - [toRope(CallingConvToStr[prc.typ.callConv]), rettype, prc.loc.r, params]) + if prc.constraint.isNil: + appf(result, "$1($2, $3)$4", + [toRope(CallingConvToStr[prc.typ.callConv]), rettype, prc.loc.r, + params]) + else: + result = ropef(prc.cgDeclFrmt, [rettype, prc.loc.r, params]) # ------------------ type info generation ------------------------------------- -proc genTypeInfo(m: BModule, typ: PType): PRope +proc genTypeInfo(m: BModule, t: PType): PRope proc getNimNode(m: BModule): PRope = result = ropef("$1[$2]", [m.typeNodesName, toRope(m.typeNodes)]) inc(m.typeNodes) @@ -692,7 +698,7 @@ proc isObjLackingTypeField(typ: PType): bool {.inline.} = result = (typ.kind == tyObject) and ((tfFinal in typ.flags) and (typ.sons[0] == nil) or isPureObject(typ)) -proc genTypeInfoAuxBase(m: BModule, typ: PType, name, base: PRope) = +proc genTypeInfoAuxBase(m: BModule, typ: PType, name, base: PRope) = var nimtypeKind: int #allocMemTI(m, typ, name) if isObjLackingTypeField(typ): @@ -901,21 +907,21 @@ type include ccgtrav -proc genTypeInfo(m: BModule, typ: PType): PRope = - var t = getUniqueType(typ) +proc genTypeInfo(m: BModule, t: PType): PRope = + var t = getUniqueType(t) result = ropef("NTI$1", [toRope(t.id)]) - let owner = typ.skipTypes(typedescPtrs).owner.getModule + if ContainsOrIncl(m.typeInfoMarker, t.id): + return con("(&".toRope, result, ")".toRope) + let owner = t.skipTypes(typedescPtrs).owner.getModule if owner != m.module: # make sure the type info is created in the owner module - discard genTypeInfo(owner.bmod, typ) - # refenrece the type info as extern here + discard genTypeInfo(owner.bmod, t) + # reference the type info as extern here discard cgsym(m, "TNimType") discard cgsym(m, "TNimNode") appf(m.s[cfsVars], "extern TNimType $1; /* $2 */$n", [result, toRope(typeToString(t))]) return con("(&".toRope, result, ")".toRope) - if ContainsOrIncl(m.typeInfoMarker, t.id): - return con("(&".toRope, result, ")".toRope) case t.kind of tyEmpty: result = toRope"0" of tyPointer, tyBool, tyChar, tyCString, tyString, tyInt..tyUInt64, tyVar: diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 7e782fc12..18f33f32a 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -488,12 +488,15 @@ proc assignLocalVar(p: BProc, s: PSym) = fillLoc(s.loc, locLocalVar, s.typ, mangleName(s), OnStack) if s.kind == skLet: incl(s.loc.flags, lfNoDeepCopy) var decl = getTypeDesc(p.module, s.loc.t) - if sfRegister in s.flags: app(decl, " register") - #elif skipTypes(s.typ, abstractInst).kind in GcTypeKinds: - # app(decl, " GC_GUARD") - if sfVolatile in s.flags or p.nestedTryStmts.len > 0: - app(decl, " volatile") - appf(decl, " $1;$n", [s.loc.r]) + if s.constraint.isNil: + if sfRegister in s.flags: app(decl, " register") + #elif skipTypes(s.typ, abstractInst).kind in GcTypeKinds: + # app(decl, " GC_GUARD") + if sfVolatile in s.flags or p.nestedTryStmts.len > 0: + app(decl, " volatile") + appf(decl, " $1;$n", [s.loc.r]) + else: + decl = ropef(s.cgDeclFrmt & ";$n", decl, s.loc.r) line(p, cpsLocals, decl) localDebugInfo(p, s) @@ -518,11 +521,17 @@ proc assignGlobalVar(p: BProc, s: PSym) = if sfThread in s.flags: declareThreadVar(p.module, s, sfImportc in s.flags) else: - if sfImportc in s.flags: app(p.module.s[cfsVars], "extern ") - app(p.module.s[cfsVars], getTypeDesc(p.module, s.loc.t)) - if sfRegister in s.flags: app(p.module.s[cfsVars], " register") - if sfVolatile in s.flags: app(p.module.s[cfsVars], " volatile") - appf(p.module.s[cfsVars], " $1;$n", [s.loc.r]) + var decl: PRope = nil + var td = getTypeDesc(p.module, s.loc.t) + if s.constraint.isNil: + if sfImportc in s.flags: app(decl, "extern ") + app(decl, td) + if sfRegister in s.flags: app(decl, " register") + if sfVolatile in s.flags: app(decl, " volatile") + appf(decl, " $1;$n", [s.loc.r]) + else: + decl = ropef(s.cgDeclFrmt & ";$n", td, s.loc.r) + app(p.module.s[cfsVars], decl) if p.withinLoop > 0: # fixes tests/run/tzeroarray: resetLoc(p, s.loc) @@ -1031,7 +1040,7 @@ proc genInitCode(m: BModule) = var procname = CStringLit(m.initProc, prc, m.module.name.s) app(prc, initFrame(m.initProc, procname, m.module.info.quotedFilename)) else: - app(prc, ~"\tvolatile TFrame F; F.len = 0;$N") + app(prc, ~"\tTFrame F; F.len = 0;$N") app(prc, genSectionStart(cpsInit)) app(prc, m.preInitProc.s(cpsInit)) diff --git a/compiler/evals.nim b/compiler/evals.nim index 6b756ab36..9655ef952 100644 --- a/compiler/evals.nim +++ b/compiler/evals.nim @@ -23,12 +23,12 @@ when hasFFI: type PStackFrame* = ref TStackFrame - TStackFrame*{.final.} = object - mapping*: TIdNodeTable # mapping from symbols to nodes - prc*: PSym # current prc; proc that is evaluated - call*: PNode - next*: PStackFrame # for stacking - params*: TNodeSeq # parameters passed to the proc + TStackFrame* = object + prc: PSym # current prc; proc that is evaluated + slots: TNodeSeq # parameters passed to the proc + locals; + # parameters come first + call: PNode + next: PStackFrame # for stacking TEvalMode* = enum ## reason for evaluation emRepl, ## evaluate because in REPL mode @@ -67,10 +67,9 @@ const # other idea: use a timeout! -> Wether code compiles depends on the machine # the compiler runs on then! Bad idea! -proc newStackFrame*(): PStackFrame = +proc newStackFrame*(): PStackFrame = new(result) - initIdNodeTable(result.mapping) - result.params = @[] + result.slots = @[] proc newEvalContext*(module: PSym, mode: TEvalMode): PEvalContext = new(result) @@ -287,62 +286,86 @@ proc getNullValue(typ: PType, info: TLineInfo): PNode = result = newNodeIT(nkCurly, info, t) else: InternalError("getNullValue: " & $t.kind) -proc evalVar(c: PEvalContext, n: PNode): PNode = - for i in countup(0, sonsLen(n) - 1): - var a = n.sons[i] - if a.kind == nkCommentStmt: continue - if a.kind != nkIdentDefs: return raiseCannotEval(c, n.info) - # XXX var (x, y) = z support? +proc evalVarValue(c: PEvalContext, n: PNode): PNode = + result = evalAux(c, n, {}) + if result.kind in {nkType..nkNilLit}: result = result.copyNode + +proc setSlot(c: PStackFrame, sym: PSym, val: PNode) = + assert sym.owner == c.prc + var idx = sym.position + if idx == 0: + idx = c.slots.len + if idx == 0: idx = 1 + sym.position = idx + setLen(c.slots, max(idx+1, c.slots.len)) + c.slots[idx] = val + +proc setVar(c: PEvalContext, v: PSym, n: PNode) = + if sfGlobal notin v.flags: setSlot(c.tos, v, n) + else: IdNodeTablePut(c.globals, v, n) + +proc evalVar(c: PEvalContext, n: PNode): PNode = + for i in countup(0, sonsLen(n) - 1): + let a = n.sons[i] + if a.kind == nkCommentStmt: continue #assert(a.sons[0].kind == nkSym) can happen for transformed vars - if a.sons[2].kind != nkEmpty: - result = evalAux(c, a.sons[2], {}) - if result.kind in {nkType..nkNilLit}: + if a.kind == nkVarTuple: + result = evalVarValue(c, a.lastSon) + if result.kind in {nkType..nkNilLit}: result = result.copyNode if isSpecial(result): return + if result.kind != nkPar: + return raiseCannotEval(c, n.info) + for i in 0 .. a.len-3: + var v = a.sons[i].sym + setVar(c, v, result.sons[i]) else: - result = getNullValue(a.sons[0].typ, a.sons[0].info) - if a.sons[0].kind == nkSym: - var v = a.sons[0].sym - IdNodeTablePut(c.tos.mapping, v, result) - else: - # assign to a.sons[0]: - var x = result - result = evalAux(c, a.sons[0], {}) - if isSpecial(result): return - myreset(x) - x.kind = result.kind - x.typ = result.typ - case x.kind - of nkCharLit..nkInt64Lit: x.intVal = result.intVal - of nkFloatLit..nkFloat64Lit: x.floatVal = result.floatVal - of nkStrLit..nkTripleStrLit: x.strVal = result.strVal - of nkIdent: x.ident = result.ident - of nkSym: x.sym = result.sym + if a.sons[2].kind != nkEmpty: + result = evalVarValue(c, a.sons[2]) + if isSpecial(result): return else: - if x.kind notin {nkEmpty..nkNilLit}: - discardSons(x) - for j in countup(0, sonsLen(result) - 1): addSon(x, result.sons[j]) + result = getNullValue(a.sons[0].typ, a.sons[0].info) + if a.sons[0].kind == nkSym: + var v = a.sons[0].sym + setVar(c, v, result) + else: + # assign to a.sons[0]: + var x = result + result = evalAux(c, a.sons[0], {}) + if isSpecial(result): return + myreset(x) + x.kind = result.kind + x.typ = result.typ + case x.kind + of nkCharLit..nkInt64Lit: x.intVal = result.intVal + of nkFloatLit..nkFloat64Lit: x.floatVal = result.floatVal + of nkStrLit..nkTripleStrLit: x.strVal = result.strVal + of nkIdent: x.ident = result.ident + of nkSym: x.sym = result.sym + else: + if x.kind notin {nkEmpty..nkNilLit}: + discardSons(x) + for j in countup(0, sonsLen(result) - 1): addSon(x, result.sons[j]) result = emptyNode proc aliasNeeded(n: PNode, flags: TEvalFlags): bool = result = efLValue in flags or n.typ == nil or n.typ.kind in {tyExpr, tyStmt, tyTypeDesc} -proc evalVariable(c: PStackFrame, sym: PSym, flags: TEvalFlags): PNode = +proc evalVariable(c: PStackFrame, sym: PSym, flags: TEvalFlags): PNode = # We need to return a node to the actual value, # which can be modified. + assert sym.position != 0 or skResult == sym.kind var x = c - while x != nil: - if sym.kind == skResult and x.params.len > 0: - result = x.params[0] - if result == nil: result = emptyNode + while x != nil: + if sym.owner == x.prc: + result = x.slots[sym.position] + assert result != nil + if not aliasNeeded(result, flags): + result = copyTree(result) return - result = IdNodeTableGet(x.mapping, sym) - if result != nil and not aliasNeeded(result, flags): - result = copyTree(result) - if result != nil: return x = x.next - #internalError(sym.info, "cannot eval " & sym.name.s) + #internalError(sym.info, "cannot eval " & sym.name.s & " " & $sym.position) result = raiseCannotEval(nil, sym.info) #result = emptyNode @@ -374,12 +397,12 @@ proc evalCall(c: PEvalContext, n: PNode): PNode = d.call = n var prc = n.sons[0] let isClosure = prc.kind == nkClosure - setlen(d.params, sonsLen(n) + ord(isClosure)) + setlen(d.slots, sonsLen(n) + ord(isClosure)) if isClosure: #debug prc result = evalAux(c, prc.sons[1], {efLValue}) if isSpecial(result): return - d.params[sonsLen(n)] = result + d.slots[sonsLen(n)] = result result = evalAux(c, prc.sons[0], {}) else: result = evalAux(c, prc, {}) @@ -397,21 +420,21 @@ proc evalCall(c: PEvalContext, n: PNode): PNode = for i in countup(1, sonsLen(n) - 1): result = evalAux(c, n.sons[i], {}) if isSpecial(result): return - d.params[i] = result - if n.typ != nil: d.params[0] = getNullValue(n.typ, n.info) + d.slots[i] = result + if n.typ != nil: d.slots[0] = getNullValue(n.typ, n.info) when hasFFI: if sfImportc in prc.sym.flags and allowFFI in c.features: var newCall = newNodeI(nkCall, n.info, n.len) newCall.sons[0] = evalGlobalVar(c, prc.sym, {}) for i in 1 .. we really need to support 1 deref op even if this + # is technically wrong due to aliasing :-( We could introduce "soft" facts + # for this; this would still be very useful for warnings and also nicely + # solves the 'var' problems. For now we fix this by requiring much more + # restrictive expressions for the 'not nil' checking. + var n = m + var derefs = 0 + while true: + case n.kind + of nkDotExpr, nkCheckedFieldExpr, nkObjUpConv, nkObjDownConv: + n = n.sons[0] + of nkDerefExpr, nkHiddenDeref: + n = n.sons[0] + inc derefs + of nkBracketExpr: + if isConstExpr(n.sons[1]) or isLet(n.sons[1]): + n = n.sons[0] + else: return + of nkHiddenStdConv, nkHiddenSubConv, nkConv: + n = n.sons[1] + else: + break + result = n.isLet and derefs <= ord(isApprox) + if not result and isApprox: + result = isVar(n) + +proc interestingCaseExpr*(m: PNode): bool = isLetLocation(m, true) + +proc swapArgs(fact: PNode, newOp: string, m: TMagic): PNode = + result = newNodeI(nkCall, fact.info, 3) + result.sons[0] = newSymNode(getSysMagic(newOp, m)) + result.sons[1] = fact.sons[2] + result.sons[2] = fact.sons[1] + +proc neg(n: PNode): PNode = + if n == nil: return nil + case n.getMagic + of mNot: + result = n.sons[1] + of someLt: + # not (a < b) == a >= b == b <= a + result = swapArgs(n, "<=", mLeI) + of someLe: + result = swapArgs(n, "<", mLtI) + of mInSet: + if n.sons[1].kind != nkCurly: return nil + let t = n.sons[2].typ.skipTypes(abstractInst) + result = newNodeI(nkCall, n.info, 3) + result.sons[0] = n.sons[0] + result.sons[2] = n.sons[2] + if t.kind == tyEnum: + var s = newNodeIT(nkCurly, n.info, n.sons[1].typ) + for e in t.n: + let eAsNode = newIntNode(nkIntLit, e.sym.position) + if not inSet(n.sons[1], eAsNode): s.add eAsNode + result.sons[1] = s + elif lengthOrd(t) < 1000: + result.sons[1] = complement(n.sons[1]) + else: + # not ({2, 3, 4}.contains(x)) x != 2 and x != 3 and x != 4 + # XXX todo + result = nil + of mOr: + # not (a or b) --> not a and not b + let + a = n.sons[1].neg + b = n.sons[2].neg + if a != nil and b != nil: + result = newNodeI(nkCall, n.info, 3) + result.sons[0] = newSymNode(getSysMagic("and", mAnd)) + result.sons[1] = a + result.sons[2] = b + elif a != nil: + result = a + elif b != nil: + result = b + else: + # leave not (a == 4) as it is + result = newNodeI(nkCall, n.info, 2) + result.sons[0] = newSymNode(getSysMagic("not", mNot)) + result.sons[1] = n + +proc buildIsNil(arg: PNode): PNode = + result = newNodeI(nkCall, arg.info, 2) + result.sons[0] = newSymNode(getSysMagic("isNil", mIsNil)) + result.sons[1] = arg + +proc usefulFact(n: PNode): PNode = + case n.getMagic + of someEq: + if skipConv(n.sons[2]).kind == nkNilLit and ( + isLetLocation(n.sons[1], false) or isVar(n.sons[1])): + result = buildIsNil(n.sons[1]) + else: + if isLetLocation(n.sons[1], true) or isLetLocation(n.sons[2], true): + # XXX algebraic simplifications! 'i-1 < a.len' --> 'i < a.len+1' + result = n + of someLe+someLt: + if isLetLocation(n.sons[1], true) or isLetLocation(n.sons[2], true): + # XXX algebraic simplifications! 'i-1 < a.len' --> 'i < a.len+1' + result = n + of mIsNil: + if isLetLocation(n.sons[1], false) or isVar(n.sons[1]): + result = n + of someIn: + if isLetLocation(n.sons[1], true): + result = n + of mAnd: + let + a = usefulFact(n.sons[1]) + b = usefulFact(n.sons[2]) + if a != nil and b != nil: + result = newNodeI(nkCall, n.info, 3) + result.sons[0] = newSymNode(getSysMagic("and", mAnd)) + result.sons[1] = a + result.sons[2] = b + elif a != nil: + result = a + elif b != nil: + result = b + of mNot: + let a = usefulFact(n.sons[1]) + if a != nil: + result = a.neg + of mOr: + # 'or' sucks! (p.isNil or q.isNil) --> hard to do anything + # with that knowledge... + # DeMorgan helps a little though: + # not a or not b --> not (a and b) + # (x == 3) or (y == 2) ---> not ( not (x==3) and not (y == 2)) + # not (x != 3 and y != 2) + let + a = usefulFact(n.sons[1]).neg + b = usefulFact(n.sons[2]).neg + if a != nil and b != nil: + result = newNodeI(nkCall, n.info, 3) + result.sons[0] = newSymNode(getSysMagic("and", mAnd)) + result.sons[1] = a + result.sons[2] = b + result = result.neg + elif n.kind == nkSym and n.sym.kind == skLet: + # consider: + # let a = 2 < x + # if a: + # ... + # We make can easily replace 'a' by '2 < x' here: + result = usefulFact(n.sym.ast) + elif n.kind == nkStmtListExpr: + result = usefulFact(n.lastSon) + +type + TModel* = seq[PNode] # the "knowledge base" + +proc addFact*(m: var TModel, n: PNode) = + let n = usefulFact(n) + if n != nil: m.add n + +proc addFactNeg*(m: var TModel, n: PNode) = + let n = n.neg + if n != nil: addFact(m, n) + +proc sameTree(a, b: PNode): bool = + result = false + if a == b: + result = true + elif (a != nil) and (b != nil) and (a.kind == b.kind): + case a.kind + of nkSym: result = a.sym == b.sym + of nkIdent: result = a.ident.id == b.ident.id + of nkCharLit..nkInt64Lit: result = a.intVal == b.intVal + of nkFloatLit..nkFloat64Lit: result = a.floatVal == b.floatVal + of nkStrLit..nkTripleStrLit: result = a.strVal == b.strVal + of nkType: result = a.typ == b.typ + of nkEmpty, nkNilLit: result = true + else: + if sonsLen(a) == sonsLen(b): + for i in countup(0, sonsLen(a) - 1): + if not sameTree(a.sons[i], b.sons[i]): return + result = true + +proc hasSubTree(n, x: PNode): bool = + if n.sameTree(x): result = true + else: + for i in 0..safeLen(n)-1: + if hasSubTree(n.sons[i], x): return true + +proc invalidateFacts*(m: var TModel, n: PNode) = + # We are able to guard local vars (as opposed to 'let' variables)! + # 'while p != nil: f(p); p = p.next' + # This is actually quite easy to do: + # Re-assignments (incl. pass to a 'var' param) trigger an invalidation + # of every fact that contains 'v'. + # + # if x < 4: + # if y < 5 + # x = unknown() + # # we invalidate 'x' here but it's known that x >= 4 + # # for the else anyway + # else: + # echo x + # + # The same mechanism could be used for more complex data stored on the heap; + # procs that 'write: []' cannot invalidate 'n.kind' for instance. In fact, we + # could CSE these expressions then and help C's optimizer. + for i in 0..high(m): + if m[i] != nil and m[i].hasSubTree(n): m[i] = nil + +proc valuesUnequal(a, b: PNode): bool = + if a.isValue and b.isValue: + result = not SameValue(a, b) + +proc pred(n: PNode): PNode = + if n.kind in {nkCharLit..nkUInt64Lit} and n.intVal != low(biggestInt): + result = copyNode(n) + dec result.intVal + else: + result = n + +type + TImplication* = enum + impUnknown, impNo, impYes + +proc impliesEq(fact, eq: PNode): TImplication = + let (loc, val) = if isLocation(eq.sons[1]): (1, 2) else: (2, 1) + + case fact.sons[0].sym.magic + of someEq: + if sameTree(fact.sons[1], eq.sons[loc]): + # this is not correct; consider: a == b; a == 1 --> unknown! + if sameTree(fact.sons[2], eq.sons[val]): result = impYes + elif valuesUnequal(fact.sons[2], eq.sons[val]): result = impNo + elif sameTree(fact.sons[2], eq.sons[loc]): + if sameTree(fact.sons[1], eq.sons[val]): result = impYes + elif valuesUnequal(fact.sons[1], eq.sons[val]): result = impNo + of mInSet: + # remember: mInSet is 'contains' so the set comes first! + if sameTree(fact.sons[2], eq.sons[loc]) and isValue(eq.sons[val]): + if inSet(fact.sons[1], eq.sons[val]): result = impYes + else: result = impNo + of mNot, mOr, mAnd: internalError(eq.info, "impliesEq") + else: discard + +proc leImpliesIn(x, c, aSet: PNode): TImplication = + if c.kind in {nkCharLit..nkUInt64Lit}: + # fact: x <= 4; question x in {56}? + # --> true if every value <= 4 is in the set {56} + # + var value = newIntNode(c.kind, firstOrd(x.typ)) + # don't iterate too often: + if c.intVal - value.intVal < 1000: + var i, pos, neg: int + while value.intVal <= c.intVal: + if inSet(aSet, value): inc pos + else: inc neg + inc i; inc value.intVal + if pos == i: result = impYes + elif neg == i: result = impNo + +proc geImpliesIn(x, c, aSet: PNode): TImplication = + if c.kind in {nkCharLit..nkUInt64Lit}: + # fact: x >= 4; question x in {56}? + # --> true iff every value >= 4 is in the set {56} + # + var value = newIntNode(c.kind, c.intVal) + let max = lastOrd(x.typ) + # don't iterate too often: + if max - value.intVal < 1000: + var i, pos, neg: int + while value.intVal <= max: + if inSet(aSet, value): inc pos + else: inc neg + inc i; inc value.intVal + if pos == i: result = impYes + elif neg == i: result = impNo + +proc compareSets(a, b: PNode): TImplication = + if equalSets(a, b): result = impYes + elif intersectSets(a, b).len == 0: result = impNo + +proc impliesIn(fact, loc, aSet: PNode): TImplication = + case fact.sons[0].sym.magic + of someEq: + if sameTree(fact.sons[1], loc): + if inSet(aSet, fact.sons[2]): result = impYes + else: result = impNo + elif sameTree(fact.sons[2], loc): + if inSet(aSet, fact.sons[1]): result = impYes + else: result = impNo + of mInSet: + if sameTree(fact.sons[2], loc): + result = compareSets(fact.sons[1], aSet) + of someLe: + if sameTree(fact.sons[1], loc): + result = leImpliesIn(fact.sons[1], fact.sons[2], aSet) + elif sameTree(fact.sons[2], loc): + result = geImpliesIn(fact.sons[2], fact.sons[1], aSet) + of someLt: + if sameTree(fact.sons[1], loc): + result = leImpliesIn(fact.sons[1], fact.sons[2].pred, aSet) + elif sameTree(fact.sons[2], loc): + # 4 < x --> 3 <= x + result = geImpliesIn(fact.sons[2], fact.sons[1].pred, aSet) + of mNot, mOr, mAnd: internalError(loc.info, "impliesIn") + else: discard + +proc valueIsNil(n: PNode): TImplication = + if n.kind == nkNilLit: impYes + elif n.kind in {nkStrLit..nkTripleStrLit, nkBracket, nkObjConstr}: impNo + else: impUnknown + +proc impliesIsNil(fact, eq: PNode): TImplication = + case fact.sons[0].sym.magic + of mIsNil: + if sameTree(fact.sons[1], eq.sons[1]): + result = impYes + of someEq: + if sameTree(fact.sons[1], eq.sons[1]): + result = valueIsNil(fact.sons[2].skipConv) + elif sameTree(fact.sons[2], eq.sons[1]): + result = valueIsNil(fact.sons[1].skipConv) + of mNot, mOr, mAnd: internalError(eq.info, "impliesIsNil") + else: discard + +proc impliesGe(fact, x, c: PNode): TImplication = + InternalAssert isLocation(x) + case fact.sons[0].sym.magic + of someEq: + if sameTree(fact.sons[1], x): + if isValue(fact.sons[2]) and isValue(c): + # fact: x = 4; question x >= 56? --> true iff 4 >= 56 + if leValue(c, fact.sons[2]): result = impYes + else: result = impNo + elif sameTree(fact.sons[2], x): + if isValue(fact.sons[1]) and isValue(c): + if leValue(c, fact.sons[1]): result = impYes + else: result = impNo + of someLt: + if sameTree(fact.sons[1], x): + if isValue(fact.sons[2]) and isValue(c): + # fact: x < 4; question N <= x? --> false iff N <= 4 + if leValue(fact.sons[2], c): result = impNo + # fact: x < 4; question 2 <= x? --> we don't know + elif sameTree(fact.sons[2], x): + # fact: 3 < x; question: N-1 < x ? --> true iff N-1 <= 3 + if isValue(fact.sons[1]) and isValue(c): + if leValue(c.pred, fact.sons[1]): result = impYes + of someLe: + if sameTree(fact.sons[1], x): + if isValue(fact.sons[2]) and isValue(c): + # fact: x <= 4; question x >= 56? --> false iff 4 <= 56 + if leValue(fact.sons[2], c): result = impNo + # fact: x <= 4; question x >= 2? --> we don't know + elif sameTree(fact.sons[2], x): + # fact: 3 <= x; question: x >= 2 ? --> true iff 2 <= 3 + if isValue(fact.sons[1]) and isValue(c): + if leValue(c, fact.sons[1]): result = impYes + of mNot, mOr, mAnd: internalError(x.info, "impliesGe") + else: discard + +proc impliesLe(fact, x, c: PNode): TImplication = + if not isLocation(x): + return impliesGe(fact, c, x) + case fact.sons[0].sym.magic + of someEq: + if sameTree(fact.sons[1], x): + if isValue(fact.sons[2]) and isValue(c): + # fact: x = 4; question x <= 56? --> true iff 4 <= 56 + if leValue(fact.sons[2], c): result = impYes + else: result = impNo + elif sameTree(fact.sons[2], x): + if isValue(fact.sons[1]) and isValue(c): + if leValue(fact.sons[1], c): result = impYes + else: result = impNo + of someLt: + if sameTree(fact.sons[1], x): + if isValue(fact.sons[2]) and isValue(c): + # fact: x < 4; question x <= N? --> true iff N-1 <= 4 + if leValue(fact.sons[2], c.pred): result = impYes + # fact: x < 4; question x <= 2? --> we don't know + elif sameTree(fact.sons[2], x): + # fact: 3 < x; question: x <= 1 ? --> false iff 1 <= 3 + if isValue(fact.sons[1]) and isValue(c): + if leValue(c, fact.sons[1]): result = impNo + + of someLe: + if sameTree(fact.sons[1], x): + if isValue(fact.sons[2]) and isValue(c): + # fact: x <= 4; question x <= 56? --> true iff 4 <= 56 + if leValue(fact.sons[2], c): result = impYes + # fact: x <= 4; question x <= 2? --> we don't know + + elif sameTree(fact.sons[2], x): + # fact: 3 <= x; question: x <= 2 ? --> false iff 2 < 3 + if isValue(fact.sons[1]) and isValue(c): + if leValue(c, fact.sons[1].pred): result = impNo + + of mNot, mOr, mAnd: internalError(x.info, "impliesLe") + else: nil + +proc impliesLt(fact, x, c: PNode): TImplication = + # x < 3 same as x <= 2: + let p = c.pred + if p != c: + result = impliesLe(fact, x, p) + else: + # 4 < x same as 3 <= x + let q = x.pred + if q != x: + result = impliesLe(fact, q, c) + +proc `~`(x: TImplication): TImplication = + case x + of impUnknown: impUnknown + of impNo: impYes + of impYes: impNo + +proc factImplies(fact, prop: PNode): TImplication = + case fact.getMagic + of mNot: + # Consider: + # enum nkBinary, nkTernary, nkStr + # fact: not (k <= nkBinary) + # question: k in {nkStr} + # --> 'not' for facts is entirely different than 'not' for questions! + # it's provably wrong if every value > 4 is in the set {56} + # That's because we compute the implication and 'a -> not b' cannot + # be treated the same as 'not a -> b' + + # (not a) -> b compute as not (a -> b) ??? + # == not a or not b == not (a and b) + let arg = fact.sons[1] + case arg.getMagic + of mIsNil: + return ~factImplies(arg, prop) + of mAnd: + # not (a and b) means not a or not b: + # a or b --> both need to imply 'prop' + let a = factImplies(arg.sons[1], prop) + let b = factImplies(arg.sons[2], prop) + if a == b: return ~a + return impUnknown + else: + InternalError(fact.info, "invalid fact") + of mAnd: + result = factImplies(fact.sons[1], prop) + if result != impUnknown: return result + return factImplies(fact.sons[2], prop) + else: discard + + case prop.sons[0].sym.magic + of mNot: + result = ~fact.factImplies(prop.sons[1]) + of mIsNil: + result = impliesIsNil(fact, prop) + of someEq: + result = impliesEq(fact, prop) + of someLe: + result = impliesLe(fact, prop.sons[1], prop.sons[2]) + of someLt: + result = impliesLt(fact, prop.sons[1], prop.sons[2]) + of mInSet: + result = impliesIn(fact, prop.sons[2], prop.sons[1]) + else: + internalError(prop.info, "invalid proposition") + +proc doesImply*(facts: TModel, prop: PNode): TImplication = + assert prop.kind in nkCallKinds + for f in facts: + # facts can be invalidated, in which case they are 'nil': + if not f.isNil: + result = f.factImplies(prop) + if result != impUnknown: return + +proc impliesNotNil*(facts: TModel, arg: PNode): TImplication = + result = doesImply(facts, buildIsNil(arg).neg) + +proc settype(n: PNode): PType = + result = newType(tySet, n.typ.owner) + addSonSkipIntLit(result, n.typ) + +proc buildOf(it, loc: PNode): PNode = + var s = newNodeI(nkCurly, it.info, it.len-1) + s.typ = settype(loc) + for i in 0..it.len-2: s.sons[i] = it.sons[i] + result = newNodeI(nkCall, it.info, 3) + result.sons[0] = newSymNode(getSysMagic("contains", mInSet)) + result.sons[1] = s + result.sons[2] = loc + +proc buildElse(n: PNode): PNode = + var s = newNodeIT(nkCurly, n.info, settype(n.sons[0])) + for i in 1..n.len-2: + let branch = n.sons[i] + assert branch.kind == nkOfBranch + for j in 0..branch.len-2: + s.add(branch.sons[j]) + result = newNodeI(nkCall, n.info, 3) + result.sons[0] = newSymNode(getSysMagic("contains", mInSet)) + result.sons[1] = s + result.sons[2] = n.sons[0] + +proc addDiscriminantFact*(m: var TModel, n: PNode) = + var fact = newNodeI(nkCall, n.info, 3) + fact.sons[0] = newSymNode(getSysMagic("==", mEqI)) + fact.sons[1] = n.sons[0] + fact.sons[2] = n.sons[1] + m.add fact + +proc addAsgnFact*(m: var TModel, key, value: PNode) = + var fact = newNodeI(nkCall, key.info, 3) + fact.sons[0] = newSymNode(getSysMagic("==", mEqI)) + fact.sons[1] = key + fact.sons[2] = value + m.add fact + +proc addCaseBranchFacts*(m: var TModel, n: PNode, i: int) = + let branch = n.sons[i] + if branch.kind == nkOfBranch: + m.add buildOf(branch, n.sons[0]) + else: + m.add n.buildElse.neg + +proc buildProperFieldCheck(access, check: PNode): PNode = + if check.sons[1].kind == nkCurly: + result = copyTree(check) + if access.kind == nkDotExpr: + var a = copyTree(access) + a.sons[1] = check.sons[2] + result.sons[2] = a + # 'access.kind != nkDotExpr' can happen for object constructors + # which we don't check yet + else: + # it is some 'not' + assert check.getMagic == mNot + result = buildProperFieldCheck(access, check.sons[1]).neg + +proc checkFieldAccess*(m: TModel, n: PNode) = + for i in 1..n.len-1: + let check = buildProperFieldCheck(n.sons[0], n.sons[i]) + if m.doesImply(check) != impYes: + Message(n.info, warnProveField, renderTree(n.sons[0])); break diff --git a/compiler/lexer.nim b/compiler/lexer.nim index 6660ff65c..f4fc4247a 100644 --- a/compiler/lexer.nim +++ b/compiler/lexer.nim @@ -235,9 +235,9 @@ proc matchUnderscoreChars(L: var TLexer, tok: var TToken, chars: TCharSet) = proc matchTwoChars(L: TLexer, first: Char, second: TCharSet): bool = result = (L.buf[L.bufpos] == first) and (L.buf[L.bufpos + 1] in Second) -proc isFloatLiteral(s: string): bool = - for i in countup(0, len(s) + 0 - 1): - if s[i] in {'.', 'e', 'E'}: +proc isFloatLiteral(s: string): bool = + for i in countup(0, len(s) - 1): + if s[i] in {'.', 'e', 'E'}: return true result = false @@ -250,25 +250,26 @@ proc GetNumber(L: var TLexer): TToken = result.literal = "" result.base = base10 # BUGFIX pos = L.bufpos # make sure the literal is correct for error messages: + var eallowed = false if L.buf[pos] == '0' and L.buf[pos+1] in {'X', 'x'}: matchUnderscoreChars(L, result, {'A'..'F', 'a'..'f', '0'..'9', 'X', 'x'}) else: matchUnderscoreChars(L, result, {'0'..'9', 'b', 'B', 'o', 'c', 'C'}) + eallowed = true if (L.buf[L.bufpos] == '.') and (L.buf[L.bufpos + 1] in {'0'..'9'}): add(result.literal, '.') - inc(L.bufpos) - #matchUnderscoreChars(L, result, ['A'..'Z', 'a'..'z', '0'..'9']) + inc(L.bufpos) matchUnderscoreChars(L, result, {'0'..'9'}) - if L.buf[L.bufpos] in {'e', 'E'}: - add(result.literal, 'e') + eallowed = true + if eallowed and L.buf[L.bufpos] in {'e', 'E'}: + add(result.literal, 'e') + inc(L.bufpos) + if L.buf[L.bufpos] in {'+', '-'}: + add(result.literal, L.buf[L.bufpos]) inc(L.bufpos) - if L.buf[L.bufpos] in {'+', '-'}: - add(result.literal, L.buf[L.bufpos]) - inc(L.bufpos) - matchUnderscoreChars(L, result, {'0'..'9'}) + matchUnderscoreChars(L, result, {'0'..'9'}) endpos = L.bufpos if L.buf[endpos] in {'\'', 'f', 'F', 'i', 'I', 'u', 'U'}: - #matchUnderscoreChars(L, result, ['''', 'f', 'F', 'i', 'I', '0'..'9']); if L.buf[endpos] == '\'': inc(endpos) L.bufpos = pos # restore position case L.buf[endpos] diff --git a/compiler/lookups.nim b/compiler/lookups.nim index 191a7aa55..e1ec9e14b 100644 --- a/compiler/lookups.nim +++ b/compiler/lookups.nim @@ -91,7 +91,8 @@ proc errorSym*(c: PContext, n: PNode): PSym = result.typ = errorType(c) incl(result.flags, sfDiscardable) # pretend it's imported from some unknown module to prevent cascading errors: - c.importTable.addSym(result) + if gCmd != cmdInteractive: + c.importTable.addSym(result) type TOverloadIterMode* = enum diff --git a/compiler/magicsys.nim b/compiler/magicsys.nim index 352b6ca04..1972dec98 100644 --- a/compiler/magicsys.nim +++ b/compiler/magicsys.nim @@ -43,6 +43,18 @@ proc getSysSym(name: string): PSym = result.typ = newType(tyError, systemModule) if result.kind == skStub: loadStub(result) +proc getSysMagic*(name: string, m: TMagic): PSym = + var ti: TIdentIter + let id = getIdent(name) + result = InitIdentIter(ti, systemModule.tab, id) + while result != nil: + if result.kind == skStub: loadStub(result) + if result.magic == m: return result + result = NextIdentIter(ti, systemModule.tab) + rawMessage(errSystemNeeds, name) + result = newSym(skError, id, systemModule, systemModule.info) + result.typ = newType(tyError, systemModule) + proc sysTypeFromName*(name: string): PType = result = getSysSym(name).typ @@ -111,7 +123,9 @@ proc skipIntLit*(t: PType): PType {.inline.} = proc AddSonSkipIntLit*(father, son: PType) = if isNil(father.sons): father.sons = @[] - add(father.sons, son.skipIntLit) + let s = son.skipIntLit + add(father.sons, s) + propagateToOwner(father, s) proc setIntLitType*(result: PNode) = let i = result.intVal diff --git a/compiler/msgs.nim b/compiler/msgs.nim index 16ef06b61..711a3c733 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -106,11 +106,13 @@ type warnUnknownSubstitutionX, warnLanguageXNotSupported, warnCommentXIgnored, warnNilStatement, warnAnalysisLoophole, warnDifferentHeaps, warnWriteToForeignHeap, warnImplicitClosure, - warnEachIdentIsTuple, warnShadowIdent, warnUser, - hintSuccess, hintSuccessX, - hintLineTooLong, hintXDeclaredButNotUsed, hintConvToBaseNotNeeded, - hintConvFromXtoItselfNotNeeded, hintExprAlwaysX, hintQuitCalled, - hintProcessing, hintCodeBegin, hintCodeEnd, hintConf, hintPath, + warnEachIdentIsTuple, warnShadowIdent, + warnProveInit, warnProveField, warnProveIndex, + warnUninit, warnUser, + hintSuccess, hintSuccessX, + hintLineTooLong, hintXDeclaredButNotUsed, hintConvToBaseNotNeeded, + hintConvFromXtoItselfNotNeeded, hintExprAlwaysX, hintQuitCalled, + hintProcessing, hintCodeBegin, hintCodeEnd, hintConf, hintPath, hintConditionAlwaysTrue, hintPattern, hintUser @@ -355,6 +357,10 @@ const warnImplicitClosure: "implicit closure convention: '$1' [ImplicitClosure]", warnEachIdentIsTuple: "each identifier is a tuple [EachIdentIsTuple]", warnShadowIdent: "shadowed identifier: '$1' [ShadowIdent]", + warnProveInit: "Cannot prove that '$1' is initialized. This will become a compile time error in the future. [ProveInit]", + warnProveField: "cannot prove that field '$1' is accessible [ProveField]", + warnProveIndex: "cannot prove index '$1' is valid [ProveIndex]", + warnUninit: "'$1' might not have been initialized [Uninit]", warnUser: "$1 [User]", hintSuccess: "operation successful [Success]", hintSuccessX: "operation successful ($# lines compiled; $# sec total; $#) [SuccessX]", @@ -374,14 +380,15 @@ const hintUser: "$1 [User]"] const - WarningsToStr*: array[0..19, string] = ["CannotOpenFile", "OctalEscape", + WarningsToStr*: array[0..23, string] = ["CannotOpenFile", "OctalEscape", "XIsNeverRead", "XmightNotBeenInit", "Deprecated", "ConfigDeprecated", "SmallLshouldNotBeUsed", "UnknownMagic", "RedefinitionOfLabel", "UnknownSubstitutionX", "LanguageXNotSupported", "CommentXIgnored", "NilStmt", "AnalysisLoophole", "DifferentHeaps", "WriteToForeignHeap", - "ImplicitClosure", "EachIdentIsTuple", "ShadowIdent", "User"] + "ImplicitClosure", "EachIdentIsTuple", "ShadowIdent", + "ProveInit", "ProveField", "ProveIndex", "Uninit", "User"] HintsToStr*: array[0..15, string] = ["Success", "SuccessX", "LineTooLong", "XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded", @@ -512,7 +519,9 @@ proc raiseRecoverableError*(msg: string) {.noinline, noreturn.} = proc sourceLine*(i: TLineInfo): PRope var - gNotes*: TNoteKinds = {low(TNoteKind)..high(TNoteKind)} - {warnShadowIdent} + gNotes*: TNoteKinds = {low(TNoteKind)..high(TNoteKind)} - + {warnShadowIdent, warnUninit, + warnProveField, warnProveIndex} gErrorCounter*: int = 0 # counts the number of errors gHintCounter*: int = 0 gWarnCounter*: int = 0 @@ -789,11 +798,12 @@ proc addSourceLine*(fileIdx: int32, line: string) = proc sourceLine*(i: TLineInfo): PRope = if i.fileIndex < 0: return nil - if not optPreserveOrigSource and - fileInfos[i.fileIndex].lines.len == 0: - for line in lines(i.toFullPath): - addSourceLine i.fileIndex, line.string - + if not optPreserveOrigSource and fileInfos[i.fileIndex].lines.len == 0: + try: + for line in lines(i.toFullPath): + addSourceLine i.fileIndex, line.string + except EIO: + discard InternalAssert i.fileIndex < fileInfos.len # can happen if the error points to EOF: if i.line > fileInfos[i.fileIndex].lines.len: return nil diff --git a/compiler/nimrod.ini b/compiler/nimrod.ini index bc2f775b6..49dcd25ba 100644 --- a/compiler/nimrod.ini +++ b/compiler/nimrod.ini @@ -64,7 +64,6 @@ Files: "bin/empty.txt" [Lib] Files: "lib/nimbase.h" -Files: "lib/copying.txt" Files: "lib/*.nim" Files: "lib/*.cfg" diff --git a/compiler/nimsets.nim b/compiler/nimsets.nim index 93bccae24..f874ccdca 100644 --- a/compiler/nimsets.nim +++ b/compiler/nimsets.nim @@ -156,6 +156,12 @@ proc equalSets(a, b: PNode): bool = toBitSet(b, y) result = bitSetEquals(x, y) +proc complement*(a: PNode): PNode = + var x: TBitSet + toBitSet(a, x) + for i in countup(0, high(x)): x[i] = not x[i] + result = toTreeSet(x, a.typ, a.info) + proc cardSet(s: PNode): BiggestInt = # here we can do better than converting it into a compact set # we just count the elements directly diff --git a/compiler/options.nim b/compiler/options.nim index 6d93048fe..5f173d240 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -14,7 +14,7 @@ const hasTinyCBackend* = defined(tinyc) useEffectSystem* = true hasFFI* = defined(useFFI) - newScopeForIf* = false # XXX activate for 0.9.4 + newScopeForIf* = true type # please make sure we have under 32 options # (improves code efficiency a lot!) diff --git a/compiler/parampatterns.nim b/compiler/parampatterns.nim index de7bcaeee..283f83906 100644 --- a/compiler/parampatterns.nim +++ b/compiler/parampatterns.nim @@ -50,6 +50,8 @@ proc add(code: var TPatternCode, op: TOpcode) {.inline.} = proc whichAlias*(p: PSym): TAliasRequest = if p.constraint != nil: result = TAliasRequest(p.constraint.strVal[0].ord) + else: + result = aqNone proc compileConstraints(p: PNode, result: var TPatternCode) = case p.kind diff --git a/compiler/parser.nim b/compiler/parser.nim index 7cfd35a41..46294925d 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -1748,7 +1748,7 @@ proc complexOrSimpleStmt(p: var TParser): PNode = proc parseStmt(p: var TParser): PNode = #| stmt = (IND{>} complexOrSimpleStmt^+(IND{=} / ';') DED) - #| / simpleStmt + #| / simpleStmt ^+ ';' if p.tok.indent > p.currInd: result = newNodeP(nkStmtList, p) withInd(p): @@ -1779,10 +1779,14 @@ proc parseStmt(p: var TParser): PNode = parMessage(p, errComplexStmtRequiresInd) result = ast.emptyNode else: - if p.tok.indent >= 0: parMessage(p, errInvalidIndentation) - result = simpleStmt(p) - if result.kind == nkEmpty: parMessage(p, errExprExpected, p.tok) - #while p.tok.tokType == tkSemicolon: getTok(p) + result = newNodeP(nkStmtList, p) + while true: + if p.tok.indent >= 0: parMessage(p, errInvalidIndentation) + let a = simpleStmt(p) + if a.kind == nkEmpty: parMessage(p, errExprExpected, p.tok) + result.add(a) + if p.tok.tokType != tkSemicolon: break + getTok(p) proc parseAll(p: var TParser): PNode = result = newNodeP(nkStmtList, p) diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index cc432aea8..8f3da9f38 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -23,7 +23,7 @@ const wMagic, wNosideEffect, wSideEffect, wNoreturn, wDynLib, wHeader, wCompilerProc, wProcVar, wDeprecated, wVarargs, wCompileTime, wMerge, wBorrow, wExtern, wImportCompilerProc, wThread, wImportCpp, wImportObjC, - wNoStackFrame, wError, wDiscardable, wNoInit, wDestructor, wHoist, + wNoStackFrame, wError, wDiscardable, wNoInit, wDestructor, wCodegenDecl, wGenSym, wInject, wRaises, wTags} converterPragmas* = procPragmas methodPragmas* = procPragmas @@ -50,13 +50,13 @@ const typePragmas* = {wImportc, wExportc, wDeprecated, wMagic, wAcyclic, wNodecl, wPure, wHeader, wCompilerProc, wFinal, wSize, wExtern, wShallow, wImportcpp, wImportobjc, wError, wIncompleteStruct, wByCopy, wByRef, - wInheritable, wGenSym, wInject} + wInheritable, wGenSym, wInject, wRequiresInit} fieldPragmas* = {wImportc, wExportc, wDeprecated, wExtern, wImportcpp, wImportobjc, wError} varPragmas* = {wImportc, wExportc, wVolatile, wRegister, wThreadVar, wNodecl, wMagic, wHeader, wDeprecated, wCompilerProc, wDynLib, wExtern, wImportcpp, wImportobjc, wError, wNoInit, wCompileTime, wGlobal, - wGenSym, wInject} + wGenSym, wInject, wCodegenDecl} constPragmas* = {wImportc, wExportc, wHeader, wDeprecated, wMagic, wNodecl, wExtern, wImportcpp, wImportobjc, wError, wGenSym, wInject} letPragmas* = varPragmas @@ -147,7 +147,10 @@ proc expectIntLit(c: PContext, n: PNode): int = proc getOptionalStr(c: PContext, n: PNode, defaultStr: string): string = if n.kind == nkExprColonExpr: result = expectStrLit(c, n) else: result = defaultStr - + +proc processCodegenDecl(c: PContext, n: PNode, sym: PSym) = + sym.constraint = getStrLitNode(c, n) + proc processMagic(c: PContext, n: PNode, s: PSym) = #if sfSystemModule notin c.module.flags: # liMessage(n.info, errMagicOnlyInSystem) @@ -253,11 +256,11 @@ proc processNote(c: PContext, n: PNode) = of wHint: var x = findStr(msgs.HintsToStr, n.sons[0].sons[1].ident.s) if x >= 0: nk = TNoteKind(x + ord(hintMin)) - else: invalidPragma(n) + else: invalidPragma(n); return of wWarning: var x = findStr(msgs.WarningsToStr, n.sons[0].sons[1].ident.s) if x >= 0: nk = TNoteKind(x + ord(warnMin)) - else: InvalidPragma(n) + else: InvalidPragma(n); return else: invalidPragma(n) return @@ -669,9 +672,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int, of wNoInit: noVal(it) if sym != nil: incl(sym.flags, sfNoInit) - of wHoist: - noVal(it) - if sym != nil: incl(sym.flags, sfHoist) + of wCodegenDecl: processCodegenDecl(c, it, sym) of wChecks, wObjChecks, wFieldChecks, wRangechecks, wBoundchecks, wOverflowchecks, wNilchecks, wAssertions, wWarnings, wHints, wLinedir, wStacktrace, wLinetrace, wOptimization, @@ -695,6 +696,10 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int, noVal(it) if sym.typ == nil: invalidPragma(it) else: incl(sym.typ.flags, tfIncompleteStruct) + of wRequiresInit: + noVal(it) + if sym.typ == nil: invalidPragma(it) + else: incl(sym.typ.flags, tfNeedsInit) of wByRef: noVal(it) if sym == nil or sym.typ == nil: diff --git a/compiler/renderer.nim b/compiler/renderer.nim index d68d2e549..70ce5c27d 100644 --- a/compiler/renderer.nim +++ b/compiler/renderer.nim @@ -33,6 +33,7 @@ type # indentation value comStack*: seq[PNode] # comment stack flags*: TRenderFlags + checkAnon: bool # we're in a context that can contain sfAnon proc renderModule*(n: PNode, filename: string, renderFlags: TRenderFlags = {}) @@ -70,7 +71,8 @@ proc InitSrcGen(g: var TSrcGen, renderFlags: TRenderFlags) = g.idx = 0 g.buf = "" g.flags = renderFlags - g.pendingNL = - 1 + g.pendingNL = -1 + g.checkAnon = false proc addTok(g: var TSrcGen, kind: TTokType, s: string) = var length = len(g.tokens) @@ -493,13 +495,15 @@ proc putWithSpace(g: var TSrcGen, kind: TTokType, s: string) = proc gcommaAux(g: var TSrcGen, n: PNode, ind: int, start: int = 0, theEnd: int = - 1, separator = tkComma) = - for i in countup(start, sonsLen(n) + theEnd): + for i in countup(start, sonsLen(n) + theEnd): var c = i < sonsLen(n) + theEnd var sublen = lsub(n.sons[i]) + ord(c) if not fits(g, sublen) and (ind + sublen < maxLineLen): optNL(g, ind) + let oldLen = g.tokens.len gsub(g, n.sons[i]) - if c: - putWithSpace(g, separator, TokTypeToStr[separator]) + if c: + if g.tokens.len > oldLen: + putWithSpace(g, separator, TokTypeToStr[separator]) if hasCom(n.sons[i]): gcoms(g) optNL(g, ind) @@ -673,7 +677,10 @@ proc gproc(g: var TSrcGen, n: PNode) = if n.sons[patternPos].kind != nkEmpty: gpattern(g, n.sons[patternPos]) + let oldCheckAnon = g.checkAnon + g.checkAnon = true gsub(g, n.sons[genericParamsPos]) + g.checkAnon = oldCheckAnon gsub(g, n.sons[paramsPos]) gsub(g, n.sons[pragmasPos]) if renderNoBody notin g.flags: @@ -724,7 +731,8 @@ proc gasm(g: var TSrcGen, n: PNode) = gcoms(g) gsub(g, n.sons[1]) -proc gident(g: var TSrcGen, n: PNode) = +proc gident(g: var TSrcGen, n: PNode) = + if g.checkAnon and n.kind == nkSym and sfAnon in n.sym.flags: return var t: TTokType var s = atom(n) if (s[0] in lexer.SymChars): @@ -897,8 +905,8 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = gsub(g, n.sons[pragmasPos]) put(g, tkColon, ":") gsub(g, n.sons[bodyPos]) - of nkConstDef, nkIdentDefs: - gcomma(g, n, 0, - 3) + of nkConstDef, nkIdentDefs: + gcomma(g, n, 0, -3) var L = sonsLen(n) if n.sons[L - 2].kind != nkEmpty: putWithSpace(g, tkColon, ":") @@ -909,7 +917,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = gsub(g, n.sons[L - 1], c) of nkVarTuple: put(g, tkParLe, "(") - gcomma(g, n, 0, - 3) + gcomma(g, n, 0, -3) put(g, tkParRi, ")") put(g, tkSpaces, Space) putWithSpace(g, tkEquals, "=") diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index fba028a46..ff68d6b8e 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -590,7 +590,7 @@ proc evalAtCompileTime(c: PContext, n: PNode): PNode = call.add(a) #echo "NOW evaluating at compile time: ", call.renderTree if sfCompileTime in callee.flags: - result = evalStaticExpr(c.module, call) + result = evalStaticExpr(c.module, call, c.p.owner) if result.isNil: LocalError(n.info, errCannotInterpretNodeX, renderTree(call)) else: @@ -601,9 +601,10 @@ proc evalAtCompileTime(c: PContext, n: PNode): PNode = proc semStaticExpr(c: PContext, n: PNode): PNode = let a = semExpr(c, n.sons[0]) - result = evalStaticExpr(c.module, a) + result = evalStaticExpr(c.module, a, c.p.owner) if result.isNil: LocalError(n.info, errCannotInterpretNodeX, renderTree(n)) + result = emptyNode proc semOverloadedCallAnalyseEffects(c: PContext, n: PNode, nOrig: PNode, flags: TExprFlags): PNode = @@ -1290,6 +1291,7 @@ proc newAnonSym(kind: TSymKind, info: TLineInfo, proc semExpandToAst(c: PContext, n: PNode): PNode = var macroCall = n[1] var expandedSym = expectMacroOrTemplateCall(c, macroCall) + if expandedSym.kind == skError: return n macroCall.sons[0] = newSymNode(expandedSym, macroCall.info) markUsed(n, expandedSym) @@ -1601,6 +1603,25 @@ proc semTuplePositionsConstr(c: PContext, n: PNode, flags: TExprFlags): PNode = addSonSkipIntLit(typ, n.sons[i].typ) result.typ = typ +proc checkInitialized(n: PNode, ids: TIntSet, info: TLineInfo) = + case n.kind + of nkRecList: + for i in countup(0, sonsLen(n) - 1): + checkInitialized(n.sons[i], ids, info) + of nkRecCase: + if (n.sons[0].kind != nkSym): InternalError(info, "checkInitialized") + checkInitialized(n.sons[0], ids, info) + when false: + # XXX we cannot check here, as we don't know the branch! + for i in countup(1, sonsLen(n) - 1): + case n.sons[i].kind + of nkOfBranch, nkElse: checkInitialized(lastSon(n.sons[i]), ids, info) + else: internalError(info, "checkInitialized") + of nkSym: + if tfNeedsInit in n.sym.typ.flags and n.sym.name.id notin ids: + Message(info, errGenerated, "field not initialized: " & n.sym.name.s) + else: internalError(info, "checkInitialized") + proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode = var t = semTypeNode(c, n.sons[0], nil) result = n @@ -1611,6 +1632,7 @@ proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode = if t.kind != tyObject: localError(n.info, errGenerated, "object constructor needs an object type") return + var objType = t var ids = initIntSet() for i in 1.. we need a stack of scopes for this analysis - - - Effect tracking: - - We track the effects per proc; forward declarations and indirect calls cause - problems: Forward declarations are computed lazily (we do this pass after - a whole module) and indirect calls are assumed the worst, unless they have - an effect annotation. """ type @@ -80,9 +65,55 @@ type tags: PNode # list of tags bottom: int owner: PSym - + init: seq[int] # list of initialized variables + guards: TModel # nested guards + locked: seq[PNode] # locked locations PEffects = var TEffects +proc isLocalVar(a: PEffects, s: PSym): bool = + s.kind in {skVar, skResult} and sfGlobal notin s.flags and s.owner == a.owner + +proc initVar(a: PEffects, n: PNode) = + if n.kind != nkSym: return + let s = n.sym + if isLocalVar(a, s): + for x in a.init: + if x == s.id: return + a.init.add s.id + +proc initVarViaNew(a: PEffects, n: PNode) = + if n.kind != nkSym: return + let s = n.sym + if {tfNeedsInit, tfNotNil} * s.typ.flags == {tfNotNil}: + # 'x' is not nil, but that doesn't mean it's not nil children + # are initialized: + initVarViaNew(a, n) + +proc useVar(a: PEffects, n: PNode) = + let s = n.sym + if isLocalVar(a, s): + if s.id notin a.init: + if {tfNeedsInit, tfNotNil} * s.typ.flags != {}: + when true: + Message(n.info, warnProveInit, s.name.s) + else: + Message(n.info, errGenerated, + "'$1' might not have been initialized" % s.name.s) + else: + Message(n.info, warnUninit, s.name.s) + # prevent superfluous warnings about the same variable: + a.init.add s.id + +type + TIntersection = seq[tuple[id, count: int]] # a simple count table + +proc addToIntersection(inter: var TIntersection, s: int) = + for j in 0.. = toCover: tracked.init.add id + # else we can't merge + setLen(tracked.guards, oldFacts) + +proc trackIf(tracked: PEffects, n: PNode) = + track(tracked, n.sons[0].sons[0]) + let oldFacts = tracked.guards.len + addFact(tracked.guards, n.sons[0].sons[0]) + let oldState = tracked.init.len + + var inter: TIntersection = @[] + var toCover = 0 + track(tracked, n.sons[0].sons[1]) + if not breaksBlock(n.sons[0].sons[1]): inc toCover + for i in oldState.. 1: + addFact(tracked.guards, branch.sons[0]) + setLen(tracked.init, oldState) + for i in 0 .. = toCover: tracked.init.add id + # else we can't merge as it is not exhaustive + setLen(tracked.guards, oldFacts) + +proc trackBlock(tracked: PEffects, n: PNode) = + if n.kind in {nkStmtList, nkStmtListExpr}: + var oldState = -1 + for i in 0.. 'y' not defined after block! + if oldState < 0: oldState = tracked.init.len + track(tracked, n.sons[i]) + if oldState > 0: setLen(tracked.init, oldState) + else: + track(tracked, n) + +proc isTrue(n: PNode): bool = + n.kind == nkSym and n.sym.kind == skEnumField and n.sym.position != 0 or + n.kind == nkIntLit and n.intVal != 0 + +proc paramType(op: PType, i: int): PType = + if op != nil and i < op.len: result = op.sons[i] proc track(tracked: PEffects, n: PNode) = case n.kind + of nkSym: + useVar(tracked, n) of nkRaiseStmt: n.sons[0].info = n.info throws(tracked.exc, n.sons[0]) + for i in 0 .. } complexOrSimpleStmt^+(IND{=} / ';') DED) - / simpleStmt + / simpleStmt ^+ ';' diff --git a/doc/idetools.txt b/doc/idetools.txt new file mode 100644 index 000000000..0ae29e824 --- /dev/null +++ b/doc/idetools.txt @@ -0,0 +1,467 @@ +================================ + Nimrod IDE Integration Guide +================================ + +:Author: Britney Spears +:Version: |nimrodversion| + +.. contents:: + + +Nimrod differs from many other compilers in that it is really fast, +and being so fast makes it suited to provide external queries for +text editors about the source code being written. Through the +``idetools`` command of `the compiler `_, any IDE can +query a ``.nim`` source file and obtain useful information like +definition of symbols or suggestions for completion. + +This document will guide you through the available options. If you +want to look at practical examples of idetools support you can look +at the test files found in ``tests/caas/*.txt`` or `various editor +integrations `_ +already available. + + +Idetools invokation +=================== + +Specifying the location of the query +------------------------------------ + +All of the available idetools commands require you to specify a +query location through the ``--track`` or ``--trackDirty`` switches. +The general idetools invokations are:: + + nimrod idetools --track:FILE,LINE,COL proj.nim + +Or:: + + nimrod idetools --trackDirty:DIRTY_FILE,FILE,LINE,COL proj.nim + +``proj.nim`` + This is the main *project* filename. Most of the time you will + pass in the same as **FILE**, but for bigger projects this is + the file which is used as main entry point for the program, the + one which users compile to generate a final binary. + +```` + This would be any of the other idetools available options, like + ``--def`` or ``--suggest`` explained in the following sections. + +``COL`` + An integer with the column you are going to query. For the + compiler columns start at zero, so the first column will be + **0** and the last in an 80 column terminal will be **79**. + +``LINE`` + An integer with the line you are going to query. For the compiler + lines start at **1**. + +``FILE`` + The file you want to perform the query on. Usually you will + pass in the same value as **proj.nim**. + +``DIRTY_FILE`` + The **FILE** paramater is enough for static analysis, but IDEs + tend to have *unsaved buffers* where the user may still be in + the middle of typing a line. In such situations the IDE can + save the current contents to a temporary file and then use the + ``--trackDirty`` switch. + + Dirty files are likely to contain errors and they are usually + compiled partially only to the point needed to service the + idetool request. The compiler discriminates them to ensure that + **a)** they won't be cached and **b)** they won't invalidate + the cached contents of the original module. + + The other reason is that the dirty file can appear anywhere on + disk (e.g. in tmpfs), but it must be treated as having a path + matching the original module when it comes to usage of relative + paths, etc. Queries, however, will refer to the dirty module + name in their answers instead of the normal filename. + + +Definitions +----------- + +The ``--def`` idetools switch performs a query about the definition +of a specific symbol. If available, idetools will answer with the +type, source file, line/column information and other accessory data +if available like a docstring. With this information an IDE can +provide the typical *Jump to definition* where a user puts the +cursor on a symbol or uses the mouse to select it and is redirected +to the place where the symbol is located. + +Since Nimrod is implemented in Nimrod, one of the nice things of +this feature is that any user with an IDE supporting it can quickly +jump around the standard library implementation and see exactly +what a proc does, learning about the language and seeing real life +examples of how to write/implement specific features. + +Idetools will always answer with a single definition or none if it +can't find any valid symbol matching the position of the query. + + +Suggestions +----------- + +The ``--suggest`` idetools switch performs a query about possible +completion symbols at some point in the file. IDEs can easily provide +an autocompletion feature where the IDE scans the current file (and +related ones, if it knows about the language being edited and follows +includes/imports) and when the user starts typing something a +completion box with different options appears. + +However such features are not context sensitive and work simply on +string matching, which can be problematic in Nimrod especially due +to the case insensitiveness of the language (plus underscores as +separators!). + +The typical usage scenario for this option is to call it after the +user has typed the dot character for `the object oriented call +syntax `_. + + +Invokation context +------------------ + +The ``--context`` idetools switch is very similar to the suggestions +switch, but instead of being used after the user has typed a dot +character, this one is meant to be used after the user has typed +an opening brace to start typing parameters. + + +Symbol usages +------------- + +The ``--usages`` idetools switch lists all usages of the symbol at +a position. IDEs can use this to find all the places in the file +where the symbol is used and offer the user to rename it in all +places at the same time. Again, a pure string based search and +replace may catch symbols out of the scope of a funcion/loop. + +For this kind of query the IDE will most likely ignore all the +type/signature info provided by idetools and concentrate on the +filename, line and column position of the multiple returned answers. + + +Expression evaluation +--------------------- + +This feature is still under development. In the future it will allow +an IDE to evaluate an expression in the context of the currently +running/debugged user project. + + +Compiler as a service (CAAS) +============================ + +The ocasional use of idetools is acceptable for things like +definitions, where the user puts the cursor on a symbol or double +clicks it and after a second or two the IDE displays where that +symbol is defined. Such latencies would be terrible for features +like symbol suggestion, plus why wait at all if we can avoid it? + +The idetools command can be run as a compiler service, where you +first launch the compiler and it will stay online as a server, +accepting queries in a telnet like fashion. The advantage of staying +on is that for many queries the compiler can cache the results of +the compilation, and subsequent queries should be fast in the +millisecond range, thus being responsive enough for IDEs. + +If you want to start the server using stdin/stdout as communication +you need to type:: + + nimrod serve --server.type:stdin proj.nim + +If you want to start the server using tcp and a port, you need to type:: + + nimrod serve --server.type:tcp --server.port:6000 \ + --server.address:hostname proj.nim + +In both cases the server will start up and await further commands. +The syntax of the commands you can now send to the server is +practically the same as running the nimrod compiler on the commandline, +you only need to remove the name of the compiler since you are +already talking to it. The server will answer with as many lines +of text it thinks necessary plus an empty line to indicate the end +of the answer. + +You can find examples of client/server communication in the idetools +tests found in ``tests/caas/*.txt``. + + +Parsing idetools output +======================= + +Idetools outputs is always returned on single lines separated by +tab characters (``\t``). The values of each column are: + +1. Three characters indicating the type of returned answer (e.g. + def for definition, ``sug`` for suggestion, etc). +2. Type of the symbol. This can be ``skProc``, ``skLet``, and just + about any of the enums defined in the module ``compiler/ast.nim``. +3. Full qualitifed path of the symbol. If you are querying a symbol + defined in the ``proj.nim`` file, this would have the form + ``proj.symbolName``. +4. Type/signature. For variables and enums this will contain the + type of the symbol, for procs, methods and templates this will + contain the full unique signature (e.g. ``proc (TFile)``). +5. Full path to the file containing the symbol. +6. Line where the symbol is located in the file. Lines start to + count at **1**. +7. Column where the symbol is located in the file. Columns start + to count at **0**. +8. Docstring for the symbol if available or the empty string. To + differentiate the docstring from end of answer in server mode, + the docstring is always provided enclosed in double quotes, and + if the docstring spans multiple lines, all following lines of the + docstring will start with a blank space to align visually with + the starting quote. + + Also, you won't find raw ``\n`` characters breaking the one + answer per line format. Instead you will need to parse sequences + in the form ``\xHH``, where *HH* is a hexadecimal value (e.g. + newlines generate the sequence ``\x0A``). + +The following sections define the expected output for each kind of +symbol for which idetools returns valid output. + + +skConst +------- + +| **Third column**: module + [n scope nesting] + const name. +| **Fourth column**: the type of the const value. +| **Docstring**: always the empty string. + +.. code-block:: nimrod + const SOME_SEQUENCE = @[1, 2] + --> col 2: $MODULE.SOME_SEQUENCE + col 3: seq[int] + col 7: "" + + +skEnumField +----------- + +| **Third column**: module + [n scope nesting] + enum type + enum field name. +| **Fourth column**: enum type grouping other enum fields. +| **Docstring**: always the empty string. + +.. code-block:: nimrod + Open(filename, fmWrite) + --> col 2: system.TFileMode.fmWrite + col 3: TFileMode + col 7: "" + + +skForVar +-------- + +| **Third column**: module + [n scope nesting] + var name. +| **Fourth column**: type of the var. +| **Docstring**: always the empty string. + +.. code-block:: nimrod + proc looper(filename = "tests.nim") = + for letter in filename: + echo letter + --> col 2: $MODULE.looper.letter + col 3: char + col 7: "" + + +skIterator +---------- + +The fourth column will be the empty string if the iterator is being +defined, since at that point in the file the parser hasn't processed +the full line yet. The signature will be returned complete in +posterior instances of the iterator. + +| **Third column**: module + [n scope nesting] + iterator name. +| **Fourth column**: signature of the iterator including return type. +| **Docstring**: docstring if available. + +.. code-block:: nimrod + let + text = "some text" + letters = toSeq(runes(text)) + --> col 2: unicode.runes + col 3: iterator (string): TRune + col 7: "iterates over any unicode character of the string `s`." + + +skLet +----- + +| **Third column**: module + [n scope nesting] + let name. +| **Fourth column**: the type of the let variable. +| **Docstring**: always the empty string. + +.. code-block:: nimrod + let + text = "some text" + --> col 2: $MODULE.text + col 3: TaintedString + col 7: "" + + +skMethod +-------- + +The fourth column will be the empty string if the method is being +defined, since at that point in the file the parser hasn't processed +the full line yet. The signature will be returned complete in +posterior instances of the method. + +Methods imply `dynamic dispatch `_ and +idetools performs a static analysis on the code. For this reason +idetools may not return the definition of the correct method you +are querying because it may be impossible to know until the code +is executed. It will try to return the method which covers the most +possible cases (i.e. for variations of different classes in a +hierarchy it will prefer methods using the base class). + +While at the language level a method is differentiated from others +by the parameters and return value, the signature of the method +returned by idetools returns also the pragmas for the method. + +Note that at the moment the word ``proc`` is returned for the +signature of the found method instead of the expected ``method``. +This may change in the future. + +| **Third column**: module + [n scope nesting] + method name. +| **Fourth column**: signature of the method including return type. +| **Docstring**: docstring if available. + +.. code-block:: nimrod + method eval(e: PExpr): int = quit "to override!" + method eval(e: PLiteral): int = e.x + method eval(e: PPlusExpr): int = eval(e.a) + eval(e.b) + echo eval(newPlus(newPlus(newLit(1), newLit(2)), newLit(4))) + --> col 2: $MODULE.eval + col 3: proc (PPlusExpr): int + col 7: "" + + +skParam +------- + +| **Third column**: module + [n scope nesting] + param name. +| **Fourth column**: the type of the parameter. +| **Docstring**: always the empty string. + +.. code-block:: nimrod + proc reader(filename = "tests.nim") = + let text = readFile(filename) + --> col 2: $MODULE.reader.filename + col 3: string + col 7: "" + + +skProc +------ + +The fourth column will be the empty string if the proc is being +defined, since at that point in the file the parser hasn't processed +the full line yet. The signature will be returned complete in +posterior instances of the proc. + +While at the language level a proc is differentiated from others +by the parameters and return value, the signature of the proc +returned by idetools returns also the pragmas for the proc. + +| **Third column**: module + [n scope nesting] + proc name. +| **Fourth column**: signature of the proc including return type. +| **Docstring**: docstring if available. + +.. code-block:: nimrod + Open(filename, fmWrite) + --> col 2: system.Open + col 3: proc (var TFile, string, TFileMode, int): bool + col 7: + "Opens a file named `filename` with given `mode`. + + Default mode is readonly. Returns true iff the file could be opened. + This throws no exception if the file could not be opened." + + +skResult +-------- + +| **Third column**: module + [n scope nesting] + result. +| **Fourth column**: the type of the result. +| **Docstring**: always the empty string. + +.. code-block:: nimrod + proc getRandomValue() : int = + return 4 + --> col 2: $MODULE.getRandomValue.result + col 3: int + col 7: "" + + +skTemplate +---------- + +The fourth column will be the empty string if the template is being +defined, since at that point in the file the parser hasn't processed +the full line yet. The signature will be returned complete in +posterior instances of the template. + +| **Third column**: module + [n scope nesting] + template name. +| **Fourth column**: signature of the template including return type. +| **Docstring**: docstring if available. + +.. code-block:: nimrod + let + text = "some text" + letters = toSeq(runes(text)) + --> col 2: sequtils.toSeq + col 3: proc (expr): expr + col 7: + "Transforms any iterator into a sequence. + + Example: + + .. code-block:: nimrod + let + numeric = @[1, 2, 3, 4, 5, 6, 7, 8, 9] + odd_numbers = toSeq(filter(numeric) do (x: int) -> bool: + if x mod 2 == 1: + result = true) + assert odd_numbers == @[1, 3, 5, 7, 9]" + + +skType +------ + +| **Third column**: module + [n scope nesting] + type name. +| **Fourth column**: the type. +| **Docstring**: always the empty string. + +.. code-block:: nimrod + proc writeTempFile() = + var output: TFile + --> col 2: system.TFile + col 3: TFile + col 7: "" + + +skVar +----- + +| **Third column**: module + [n scope nesting] + var name. +| **Fourth column**: the type of the var. +| **Docstring**: always the empty string. + +.. code-block:: nimrod + proc writeTempFile() = + var output: TFile + output.open("/tmp/somefile", fmWrite) + output.write("test") + --> col 2: $MODULE.writeTempFile.output + col 3: TFile + col 7: "" diff --git a/doc/lib.txt b/doc/lib.txt index 2f781f375..1b004aa9d 100644 --- a/doc/lib.txt +++ b/doc/lib.txt @@ -255,6 +255,22 @@ Parsers This is a low level module that implements an extremely efficient buffering scheme for lexers and parsers. This is used by the diverse parsing modules. +* `highlite `_ + Source highlighter for programming or markup languages. Currently + only few languages are supported, other languages may be added. + The interface supports one language nested in another. + +* `rst `_ + This module implements a reStructuredText parser. A large subset + is implemented. Some features of the markdown wiki syntax are + also supported. + +* `rstast `_ + This module implements an AST for the reStructuredText parser. + +* `rstgen `_ + This module implements a generator of HTML/Latex from reStructuredText. + XML Processing -------------- diff --git a/doc/manual.txt b/doc/manual.txt index 08e5f723b..5fdc5a884 100644 --- a/doc/manual.txt +++ b/doc/manual.txt @@ -327,36 +327,36 @@ Numerical constants `Numerical constants`:idx: are of a single type and have the form:: - hexdigit ::= digit | 'A'..'F' | 'a'..'f' - octdigit ::= '0'..'7' - bindigit ::= '0'..'1' - HEX_LIT ::= '0' ('x' | 'X' ) hexdigit ( ['_'] hexdigit )* - DEC_LIT ::= digit ( ['_'] digit )* - OCT_LIT ::= '0o' octdigit ( ['_'] octdigit )* - BIN_LIT ::= '0' ('b' | 'B' ) bindigit ( ['_'] bindigit )* + hexdigit = digit | 'A'..'F' | 'a'..'f' + octdigit = '0'..'7' + bindigit = '0'..'1' + HEX_LIT = '0' ('x' | 'X' ) hexdigit ( ['_'] hexdigit )* + DEC_LIT = digit ( ['_'] digit )* + OCT_LIT = '0o' octdigit ( ['_'] octdigit )* + BIN_LIT = '0' ('b' | 'B' ) bindigit ( ['_'] bindigit )* - INT_LIT ::= HEX_LIT - | DEC_LIT - | OCT_LIT - | BIN_LIT + INT_LIT = HEX_LIT + | DEC_LIT + | OCT_LIT + | BIN_LIT - INT8_LIT ::= INT_LIT ['\''] ('i' | 'I') '8' - INT16_LIT ::= INT_LIT ['\''] ('i' | 'I') '16' - INT32_LIT ::= INT_LIT ['\''] ('i' | 'I') '32' - INT64_LIT ::= INT_LIT ['\''] ('i' | 'I') '64' + INT8_LIT = INT_LIT ['\''] ('i' | 'I') '8' + INT16_LIT = INT_LIT ['\''] ('i' | 'I') '16' + INT32_LIT = INT_LIT ['\''] ('i' | 'I') '32' + INT64_LIT = INT_LIT ['\''] ('i' | 'I') '64' - UINT8_LIT ::= INT_LIT ['\''] ('u' | 'U') - UINT8_LIT ::= INT_LIT ['\''] ('u' | 'U') '8' - UINT16_LIT ::= INT_LIT ['\''] ('u' | 'U') '16' - UINT32_LIT ::= INT_LIT ['\''] ('u' | 'U') '32' - UINT64_LIT ::= INT_LIT ['\''] ('u' | 'U') '64' + UINT8_LIT = INT_LIT ['\''] ('u' | 'U') + UINT8_LIT = INT_LIT ['\''] ('u' | 'U') '8' + UINT16_LIT = INT_LIT ['\''] ('u' | 'U') '16' + UINT32_LIT = INT_LIT ['\''] ('u' | 'U') '32' + UINT64_LIT = INT_LIT ['\''] ('u' | 'U') '64' - exponent ::= ('e' | 'E' ) ['+' | '-'] digit ( ['_'] digit )* - FLOAT_LIT ::= digit (['_'] digit)* ('.' (['_'] digit)* [exponent] |exponent) - FLOAT32_LIT ::= HEX_LIT '\'' ('f'|'F') '32' - | (FLOAT_LIT | DEC_LIT | OCT_LIT | BIN_LIT) ['\''] ('f'|'F') '32' - FLOAT64_LIT ::= HEX_LIT '\'' ('f'|'F') '64' - | (FLOAT_LIT | DEC_LIT | OCT_LIT | BIN_LIT) ['\''] ('f'|'F') '64' + exponent = ('e' | 'E' ) ['+' | '-'] digit ( ['_'] digit )* + FLOAT_LIT = digit (['_'] digit)* (('.' (['_'] digit)* [exponent]) |exponent) + FLOAT32_LIT = HEX_LIT '\'' ('f'|'F') '32' + | (FLOAT_LIT | DEC_LIT | OCT_LIT | BIN_LIT) ['\''] ('f'|'F') '32' + FLOAT64_LIT = HEX_LIT '\'' ('f'|'F') '64' + | (FLOAT_LIT | DEC_LIT | OCT_LIT | BIN_LIT) ['\''] ('f'|'F') '64' As can be seen in the productions, numerical constants can contain underscores @@ -1388,7 +1388,7 @@ accesses its environment. If it does so, it has the calling convention Distinct type ------------- -A distinct type is new type derived from a `base type`:idx: that is +A `distinct type`:idx: is new type derived from a `base type`:idx: that is incompatible with its base type. In particular, it is an essential property of a distinct type that it **does not** imply a subtype relation between it and its base type. Explicit type conversions from a distinct type to its @@ -1435,7 +1435,7 @@ number without unit; and the same holds for division: This quickly gets tedious. The implementations are trivial and the compiler should not generate all this code only to optimize it away later - after all ``+`` for dollars should produce the same binary code as ``+`` for ints. -The pragma ``borrow`` has been designed to solve this problem; in principle +The pragma `borrow`:idx: has been designed to solve this problem; in principle it generates the above trivial implementations: .. code-block:: nimrod @@ -1818,6 +1818,24 @@ If a proc is annotated with the ``noinit`` pragma this refers to its implicit proc returnUndefinedValue: int {.noinit.} = nil +The implicit initialization can be also prevented by the `requiresInit`:idx: +type pragma. The compiler requires an explicit initialization then. However +it does a `control flow analysis`:idx: to prove the variable has been +initialized and does not rely on syntactic properties: + +.. code-block:: nimrod + type + TMyObject = object {.requiresInit.} + + proc p() = + # the following is valid: + var x: TMyObject + if someCondition(): + x = a() + else: + x = a() + use x + let statement ------------- @@ -1920,8 +1938,6 @@ the corresponding *then* block: In the example the scopes have been enclosed in ``{| |}``. -**Note**: These scoping rules will be active in 0.9.4. - Case statement -------------- diff --git a/doc/nimrodc.txt b/doc/nimrodc.txt index 7f77a50d2..fd420168e 100644 --- a/doc/nimrodc.txt +++ b/doc/nimrodc.txt @@ -20,7 +20,7 @@ on the different supported platforms. It is not a definition of the Nimrod programming language (therefore is the `manual `_). Nimrod is free software; it is licensed under the -`GNU General Public License `_. +`MIT License `_. Compiler Usage @@ -399,6 +399,21 @@ interfacing with libraries written in Objective C: The compiler needs to be told to generate Objective C (command ``objc``) for this to work. The conditional symbol ``objc`` is defined when the compiler emits Objective C code. + + +CodegenDecl pragma +------------------ + +The `codegenDecl`:idx: pragma can be used to directly influence Nimrod's code +generator. It receives a format string that determines how the variable or +proc is declared in the generated code: + +.. code-block:: nimrod + var + a {.codegenDecl: "$# progmem $#".}: int + + proc myinterrupt() {.codegenDecl: "__interrupt $# $#$#".} = + echo "realistic interrupt handler" LineDir option @@ -443,6 +458,14 @@ in C/C++). **Note**: This pragma will not exist for the LLVM backend. +Nimrod idetools integration +======================= + +Nimrod provides language integration with external IDEs through the +idetools command. See the documentation of `idetools `_ +for further information. + + Nimrod interactive mode ======================= diff --git a/doc/tut1.txt b/doc/tut1.txt index 28e23b0f0..0cc9b05c1 100644 --- a/doc/tut1.txt +++ b/doc/tut1.txt @@ -1182,6 +1182,21 @@ type and instead write it embedded directly as the type of the first dimension: type TLightTower = array[1..10, array[north..west, TBlinkLights]] +It is quite frequent to have arrays start at zero, so there's a shortcut syntax +to specify a range from zero to the specified index minus one: + +.. code-block:: nimrod + type + TIntArray = array[0..5, int] # an array that is indexed with 0..5 + TQuickArray = array[6, int] # an array that is indexed with 0..5 + var + x: TIntArray + y: TQuickArray + x = [1, 2, 3, 4, 5, 6] + y = x + for i in low(x)..high(x): + echo(x[i], y[i]) + Sequences --------- diff --git a/koch.nim b/koch.nim index 883fffb6d..30ad8c597 100644 --- a/koch.nim +++ b/koch.nim @@ -64,13 +64,16 @@ proc tryExec(cmd: string): bool = echo(cmd) result = execShellCmd(cmd) == 0 +const + compileNimInst = "-d:useLibzipSrc tools/niminst/niminst" + proc csource(args: string) = - exec("nimrod cc $1 -r tools/niminst/niminst --var:version=$2 csource compiler/nimrod.ini $1" % - [args, NimrodVersion]) + exec("nimrod cc $1 -r $3 --var:version=$2 csource compiler/nimrod.ini $1" % + [args, NimrodVersion, compileNimInst]) proc zip(args: string) = - exec("nimrod cc -r tools/niminst/niminst --var:version=$# zip compiler/nimrod.ini" % - NimrodVersion) + exec("nimrod cc -r $2 --var:version=$1 zip compiler/nimrod.ini" % + [NimrodVersion, compileNimInst]) proc buildTool(toolname, args: string) = exec("nimrod cc $# $#" % [args, toolname]) @@ -85,9 +88,9 @@ proc inno(args: string) = NimrodVersion) proc install(args: string) = - exec("nimrod cc -r tools/niminst/niminst --var:version=$# scripts compiler/nimrod.ini" % - NimrodVersion) - exec("sh ./install.sh $#" % args) + exec("nimrod cc -r $# --var:version=$# scripts compiler/nimrod.ini" % + [compileNimInst, NimrodVersion]) + exec("sh ./install.sh $#" % args) proc web(args: string) = exec(("nimrod cc -r tools/nimweb.nim web/nimrod --putenv:nimrodversion=$#" & diff --git a/lib/copying.txt b/lib/copying.txt deleted file mode 100644 index 0ff6b7d87..000000000 --- a/lib/copying.txt +++ /dev/null @@ -1,24 +0,0 @@ -=============================================================================== -Nimrod -- a Compiler for Nimrod. http://nimrod-code.org/ - -Copyright (C) 2004-2013 Andreas Rumpf. All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -[ MIT license: http://www.opensource.org/licenses/mit-license.php ] \ No newline at end of file diff --git a/lib/core/macros.nim b/lib/core/macros.nim index bbf970102..720b15189 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -390,7 +390,7 @@ proc lispRepr*(n: PNimrodNode): string {.compileTime.} = add(result, ")") -macro dumpTree*(s: stmt): stmt = echo s.treeRepr +macro dumpTree*(s: stmt): stmt {.immediate.} = echo s.treeRepr ## Accepts a block of nimrod code and prints the parsed abstract syntax ## tree using the `toTree` function. Printing is done *at compile time*. ## @@ -398,16 +398,16 @@ macro dumpTree*(s: stmt): stmt = echo s.treeRepr ## tree and to discover what kind of nodes must be created to represent ## a certain expression/statement. -macro dumpLisp*(s: stmt): stmt = echo s.lispRepr +macro dumpLisp*(s: stmt): stmt {.immediate.} = echo s.lispRepr ## Accepts a block of nimrod code and prints the parsed abstract syntax ## tree using the `toLisp` function. Printing is done *at compile time*. ## ## See `dumpTree`. -macro dumpTreeImm*(s: stmt): stmt {.immediate.} = echo s.treeRepr +macro dumpTreeImm*(s: stmt): stmt {.immediate, deprecated.} = echo s.treeRepr ## The ``immediate`` version of `dumpTree`. -macro dumpLispImm*(s: stmt): stmt {.immediate.} = echo s.lispRepr +macro dumpLispImm*(s: stmt): stmt {.immediate, deprecated.} = echo s.lispRepr ## The ``immediate`` version of `dumpLisp`. diff --git a/lib/core/typeinfo.nim b/lib/core/typeinfo.nim index 6f8081b5f..4ce65ae91 100644 --- a/lib/core/typeinfo.nim +++ b/lib/core/typeinfo.nim @@ -379,7 +379,7 @@ proc getBiggestInt*(x: TAny): biggestInt = of tyInt8: result = biggestInt(cast[ptr int8](x.value)[]) of tyInt16: result = biggestInt(cast[ptr int16](x.value)[]) of tyInt32: result = biggestInt(cast[ptr int32](x.value)[]) - of tyInt64: result = biggestInt(cast[ptr int64](x.value)[]) + of tyInt64, tyUInt64: result = biggestInt(cast[ptr int64](x.value)[]) of tyBool: result = biggestInt(cast[ptr bool](x.value)[]) of tyChar: result = biggestInt(cast[ptr char](x.value)[]) of tyEnum, tySet: @@ -389,6 +389,10 @@ proc getBiggestInt*(x: TAny): biggestInt = of 4: result = biggestInt(cast[ptr int32](x.value)[]) of 8: result = biggestInt(cast[ptr int64](x.value)[]) else: assert false + of tyUInt: result = biggestInt(cast[ptr uint](x.value)[]) + of tyUInt8: result = biggestInt(cast[ptr uint8](x.value)[]) + of tyUInt16: result = biggestInt(cast[ptr uint16](x.value)[]) + of tyUInt32: result = biggestInt(cast[ptr uint32](x.value)[]) else: assert false proc setBiggestInt*(x: TAny, y: biggestInt) = @@ -400,7 +404,7 @@ proc setBiggestInt*(x: TAny, y: biggestInt) = of tyInt8: cast[ptr int8](x.value)[] = int8(y) of tyInt16: cast[ptr int16](x.value)[] = int16(y) of tyInt32: cast[ptr int32](x.value)[] = int32(y) - of tyInt64: cast[ptr int64](x.value)[] = int64(y) + of tyInt64, tyUInt64: cast[ptr int64](x.value)[] = int64(y) of tyBool: cast[ptr bool](x.value)[] = y != 0 of tyChar: cast[ptr char](x.value)[] = chr(y.int) of tyEnum, tySet: @@ -410,6 +414,10 @@ proc setBiggestInt*(x: TAny, y: biggestInt) = of 4: cast[ptr int32](x.value)[] = int32(y) of 8: cast[ptr int64](x.value)[] = y else: assert false + of tyUInt: cast[ptr uint](x.value)[] = uint(y) + of tyUInt8: cast[ptr uint8](x.value)[] = uint8(y) + of tyUInt16: cast[ptr uint16](x.value)[] = uint16(y) + of tyUInt32: cast[ptr uint32](x.value)[] = uint32(y) else: assert false proc getChar*(x: TAny): char = diff --git a/lib/impure/re.nim b/lib/impure/re.nim index 5041ed3ba..ef02a3b1d 100644 --- a/lib/impure/re.nim +++ b/lib/impure/re.nim @@ -242,7 +242,6 @@ template `=~` *(s: string, pattern: TRegEx): expr = bind maxSubPatterns when not definedInScope(matches): var matches {.inject.}: array[0..maxSubPatterns-1, string] - {.warning: "injected 'matches' might be affected by new scoping rules in 0.9.4".} match(s, pattern, matches) # ------------------------- more string handling ------------------------------ diff --git a/lib/nimbase.h b/lib/nimbase.h index 8e6bffa98..dd6eb36bf 100644 --- a/lib/nimbase.h +++ b/lib/nimbase.h @@ -443,11 +443,11 @@ struct TFrame { }; #define nimfr(proc, file) \ - volatile TFrame F; \ + TFrame F; \ F.procname = proc; F.filename = file; F.line = 0; F.len = 0; nimFrame(&F); #define nimfrs(proc, file, slots, length) \ - volatile struct {TFrame* prev;NCSTRING procname;NI line;NCSTRING filename; NI len; TVarSlot s[slots];} F; \ + struct {TFrame* prev;NCSTRING procname;NI line;NCSTRING filename; NI len; TVarSlot s[slots];} F; \ F.procname = proc; F.filename = file; F.line = 0; F.len = length; nimFrame((TFrame*)&F); #define nimln(n, file) \ diff --git a/lib/packages/docutils/highlite.nim b/lib/packages/docutils/highlite.nim index 21dd1543a..69da2bba8 100644 --- a/lib/packages/docutils/highlite.nim +++ b/lib/packages/docutils/highlite.nim @@ -44,7 +44,17 @@ const "Assembler", "Preprocessor", "Directive", "Command", "Rule", "Hyperlink", "Label", "Reference", "Other"] - nimrodKeywords = slurp("doc/keywords.txt").split + # The following list comes from doc/keywords.txt, make sure it is + # synchronized with this array by running the module itself as a test case. + nimrodKeywords = ["addr", "and", "as", "asm", "atomic", "bind", "block", + "break", "case", "cast", "const", "continue", "converter", "discard", + "distinct", "div", "do", "elif", "else", "end", "enum", "except", "export", + "finally", "for", "from", "generic", "if", "import", "in", "include", + "interface", "is", "isnot", "iterator", "lambda", "let", "macro", "method", + "mixin", "mod", "nil", "not", "notin", "object", "of", "or", "out", "proc", + "ptr", "raise", "ref", "return", "shared", "shl", "shr", "static", + "template", "try", "tuple", "type", "var", "when", "while", "with", + "without", "xor", "yield"] proc getSourceLanguage*(name: string): TSourceLanguage = for i in countup(succ(low(TSourceLanguage)), high(TSourceLanguage)): @@ -535,3 +545,16 @@ proc getNextToken*(g: var TGeneralTokenizer, lang: TSourceLanguage) = of langC: cNextToken(g) of langJava: javaNextToken(g) +when isMainModule: + var keywords: seq[string] + # Try to work running in both the subdir or at the root. + for filename in ["doc/keywords.txt", "../../../doc/keywords.txt"]: + except: echo filename, " not found" + let input = string(readFile(filename)) + keywords = input.split() + break + doAssert (not keywords.isNil, "Couldn't read any keywords.txt file!") + doAssert keywords.len == nimrodKeywords.len, "No matching lengths" + for i in 0..keywords.len-1: + #echo keywords[i], " == ", nimrodKeywords[i] + doAssert keywords[i] == nimrodKeywords[i], "Unexpected keyword" diff --git a/lib/packages/docutils/rst.nim b/lib/packages/docutils/rst.nim index 54c17367f..91664cd50 100644 --- a/lib/packages/docutils/rst.nim +++ b/lib/packages/docutils/rst.nim @@ -7,7 +7,7 @@ # distribution, for details about the copyright. # -## This module implements a `reStructuredText`:idx parser. A large +## This module implements a `reStructuredText`:idx: parser. A large ## subset is implemented. Some features of the `markdown`:idx: wiki syntax are ## also supported. diff --git a/lib/packages/docutils/rstast.nim b/lib/packages/docutils/rstast.nim index 23233fd39..3d191dacb 100644 --- a/lib/packages/docutils/rstast.nim +++ b/lib/packages/docutils/rstast.nim @@ -7,7 +7,7 @@ # distribution, for details about the copyright. # -## This module implements an AST for the `reStructuredText`:idx parser. +## This module implements an AST for the `reStructuredText`:idx: parser. import strutils diff --git a/lib/packages/docutils/rstgen.nim b/lib/packages/docutils/rstgen.nim index 53bd8188e..a393943fb 100644 --- a/lib/packages/docutils/rstgen.nim +++ b/lib/packages/docutils/rstgen.nim @@ -7,7 +7,7 @@ # distribution, for details about the copyright. # -## This module implements a generator of HTML/Latex from `reStructuredText`:idx. +## This module implements a generator of HTML/Latex from `reStructuredText`:idx:. import strutils, os, hashes, strtabs, rstast, rst, highlite @@ -692,4 +692,4 @@ proc rstToHtml*(s: string, options: TRstParseOptions, var rst = rstParse(s, filen, 0, 1, dummyHasToc, options) result = "" renderRstToOut(d, rst, result) - \ No newline at end of file + diff --git a/lib/pure/asyncio.nim b/lib/pure/asyncio.nim index 403401ff1..4ff6e0ced 100644 --- a/lib/pure/asyncio.nim +++ b/lib/pure/asyncio.nim @@ -167,7 +167,7 @@ proc AsyncSocket*(domain: TDomain = AF_INET, typ: TType = SOCK_STREAM, result = newAsyncSocket() result.socket = socket(domain, typ, protocol, buffered) result.proto = protocol - if result.socket == InvalidSocket: OSError() + if result.socket == InvalidSocket: OSError(OSLastError()) result.socket.setBlocking(false) proc toAsyncSocket*(sock: TSocket, state: TInfo = SockConnected): PAsyncSocket = @@ -349,7 +349,7 @@ proc acceptAddr*(server: PAsyncSocket, client: var PAsyncSocket, client.sslNeedAccept = false client.info = SockConnected - if c == InvalidSocket: OSError() + if c == InvalidSocket: SocketError(server.socket) c.setBlocking(false) # TODO: Needs to be tested. # deleg.open is set in ``toDelegate``. @@ -423,6 +423,10 @@ proc isConnecting*(s: PAsyncSocket): bool = proc isClosed*(s: PAsyncSocket): bool = ## Determines whether ``s`` has been closed. return s.info == SockClosed +proc isSendDataBuffered*(s: PAsyncSocket): bool = + ## Determines whether ``s`` has data waiting to be sent, i.e. whether this + ## socket's sendBuffer contains data. + return s.sendBuffer.len != 0 proc setHandleWrite*(s: PAsyncSocket, handleWrite: proc (s: PAsyncSocket) {.closure.}) = @@ -638,8 +642,7 @@ when isMainModule: proc testRead(s: PAsyncSocket, no: int) = echo("Reading! " & $no) var data = "" - if not s.readLine(data): - OSError() + if not s.readLine(data): return if data == "": echo("Closing connection. " & $no) s.close() diff --git a/lib/pure/htmlparser.nim b/lib/pure/htmlparser.nim index ad952fc41..e1a0096f6 100644 --- a/lib/pure/htmlparser.nim +++ b/lib/pure/htmlparser.nim @@ -1,7 +1,7 @@ # # # Nimrod's Runtime Library -# (c) Copyright 2010 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -35,8 +35,12 @@ type tagAddress, ## the HTML ``address`` element tagApplet, ## the deprecated HTML ``applet`` element tagArea, ## the HTML ``area`` element + tagArticle, ## the HTML ``article`` element + tagAside, ## the HTML ``aside`` element + tagAudio, ## the HTML ``audio`` element tagB, ## the HTML ``b`` element tagBase, ## the HTML ``base`` element + tagBdi, ## the HTML ``bdi`` element tagBdo, ## the deprecated HTML ``dbo`` element tagBasefont, ## the deprecated HTML ``basefont`` element tagBig, ## the HTML ``big`` element @@ -44,22 +48,31 @@ type tagBody, ## the HTML ``body`` element tagBr, ## the HTML ``br`` element tagButton, ## the HTML ``button`` element + tagCanvas, ## the HTML ``canvas`` element tagCaption, ## the HTML ``caption`` element tagCenter, ## the deprecated HTML ``center`` element tagCite, ## the HTML ``cite`` element tagCode, ## the HTML ``code`` element tagCol, ## the HTML ``col`` element tagColgroup, ## the HTML ``colgroup`` element + tagCommand, ## the HTML ``command`` element + tagDatalist, ## the HTML ``datalist`` element tagDd, ## the HTML ``dd`` element tagDel, ## the HTML ``del`` element + tagDetails, ## the HTML ``details`` element tagDfn, ## the HTML ``dfn`` element + tagDialog, ## the HTML ``dialog`` element tagDiv, ## the HTML ``div`` element tagDir, ## the deprecated HTLM ``dir`` element tagDl, ## the HTML ``dl`` element tagDt, ## the HTML ``dt`` element tagEm, ## the HTML ``em`` element + tagEmbed, ## the HTML ``embed`` element tagFieldset, ## the HTML ``fieldset`` element + tagFigcaption, ## the HTML ``figcaption`` element + tagFigure, ## the HTML ``figure`` element tagFont, ## the deprecated HTML ``font`` element + tagFooter, ## the HTML ``footer`` element tagForm, ## the HTML ``form`` element tagFrame, ## the HTML ``frame`` element tagFrameset, ## the deprecated HTML ``frameset`` element @@ -70,6 +83,8 @@ type tagH5, ## the HTML ``h5`` element tagH6, ## the HTML ``h6`` element tagHead, ## the HTML ``head`` element + tagHeader, ## the HTML ``header`` element + tagHgroup, ## the HTML ``hgroup`` element tagHtml, ## the HTML ``html`` element tagHr, ## the HTML ``hr`` element tagI, ## the HTML ``i`` element @@ -79,13 +94,17 @@ type tagIns, ## the HTML ``ins`` element tagIsindex, ## the deprecated HTML ``isindex`` element tagKbd, ## the HTML ``kbd`` element + tagKeygen, ## the HTML ``keygen`` element tagLabel, ## the HTML ``label`` element tagLegend, ## the HTML ``legend`` element tagLi, ## the HTML ``li`` element tagLink, ## the HTML ``link`` element tagMap, ## the HTML ``map`` element + tagMark, ## the HTML ``mark`` element tagMenu, ## the deprecated HTML ``menu`` element tagMeta, ## the HTML ``meta`` element + tagMeter, ## the HTML ``meter`` element + tagNav, ## the HTML ``nav`` element tagNobr, ## the deprecated HTML ``nobr`` element tagNoframes, ## the deprecated HTML ``noframes`` element tagNoscript, ## the HTML ``noscript`` element @@ -93,20 +112,28 @@ type tagOl, ## the HTML ``ol`` element tagOptgroup, ## the HTML ``optgroup`` element tagOption, ## the HTML ``option`` element + tagOutput, ## the HTML ``output`` element tagP, ## the HTML ``p`` element tagParam, ## the HTML ``param`` element tagPre, ## the HTML ``pre`` element + tagProgress, ## the HTML ``progress`` element tagQ, ## the HTML ``q`` element + tagRp, ## the HTML ``rp`` element + tagRt, ## the HTML ``rt`` element + tagRuby, ## the HTML ``ruby`` element tagS, ## the deprecated HTML ``s`` element tagSamp, ## the HTML ``samp`` element tagScript, ## the HTML ``script`` element + tagSection, ## the HTML ``section`` element tagSelect, ## the HTML ``select`` element tagSmall, ## the HTML ``small`` element + tagSource, ## the HTML ``source`` element tagSpan, ## the HTML ``span`` element tagStrike, ## the deprecated HTML ``strike`` element tagStrong, ## the HTML ``strong`` element tagStyle, ## the HTML ``style`` element tagSub, ## the HTML ``sub`` element + tagSummary, ## the HTML ``summary`` element tagSup, ## the HTML ``sup`` element tagTable, ## the HTML ``table`` element tagTbody, ## the HTML ``tbody`` element @@ -115,37 +142,46 @@ type tagTfoot, ## the HTML ``tfoot`` element tagTh, ## the HTML ``th`` element tagThead, ## the HTML ``thead`` element + tagTime, ## the HTML ``time`` element tagTitle, ## the HTML ``title`` element tagTr, ## the HTML ``tr`` element + tagTrack, ## the HTML ``track`` element tagTt, ## the HTML ``tt`` element tagU, ## the deprecated HTML ``u`` element tagUl, ## the HTML ``ul`` element - tagVar ## the HTML ``var`` element + tagVar, ## the HTML ``var`` element + tagVideo, ## the HTML ``video`` element + tagWbr ## the HTML ``wbr`` element const tagToStr* = [ - "a", "abbr", "acronym", "address", "applet", "area", - "b", "base", "basefont", "bdo", "big", "blockquote", "body", - "br", "button", "caption", "center", "cite", "code", - "col", "colgroup", "dd", "del", "dfn", "div", - "dir", "dl", "dt", "em", "fieldset", "font", + "a", "abbr", "acronym", "address", "applet", "area", "article", + "aside", "audio", + "b", "base", "basefont", "bdi", "bdo", "big", "blockquote", "body", + "br", "button", "canvas", "caption", "center", "cite", "code", + "col", "colgroup", "command", + "datalist", "dd", "del", "details", "dfn", "dialog", "div", + "dir", "dl", "dt", "em", "embed", "fieldset", + "figcaption", "figure", "font", "footer", "form", "frame", "frameset", "h1", "h2", "h3", - "h4", "h5", "h6", "head", "html", "hr", + "h4", "h5", "h6", "head", "header", "hgroup", "html", "hr", "i", "iframe", "img", "input", "ins", "isindex", - "kbd", "label", "legend", "li", "link", "map", - "menu", "meta", "nobr", "noframes", "noscript", "object", "ol", - "optgroup", "option", "p", "param", "pre", "q", - "s", "samp", "script", "select", "small", "span", - "strike", "strong", "style", "sub", "sup", "table", - "tbody", "td", "textarea", "tfoot", "th", "thead", - "title", "tr", "tt", "u", "ul", "var"] + "kbd", "keygen", "label", "legend", "li", "link", "map", "mark", + "menu", "meta", "meter", "nav", "nobr", "noframes", "noscript", + "object", "ol", + "optgroup", "option", "output", "p", "param", "pre", "progress", "q", + "rp", "rt", "ruby", "s", "samp", "script", "section", "select", "small", + "source", "span", "strike", "strong", "style", + "sub", "summary", "sup", "table", + "tbody", "td", "textarea", "tfoot", "th", "thead", "time", + "title", "tr", "track", "tt", "u", "ul", "var", "video", "wbr"] InlineTags* = {tagA, tagAbbr, tagAcronym, tagApplet, tagB, tagBasefont, tagBdo, tagBig, tagBr, tagButton, tagCite, tagCode, tagDel, tagDfn, tagEm, tagFont, tagI, tagImg, tagIns, tagInput, tagIframe, tagKbd, tagLabel, tagMap, tagObject, tagQ, tagSamp, tagScript, tagSelect, tagSmall, tagSpan, tagStrong, tagSub, tagSup, tagTextarea, tagTt, tagVar, tagApplet, tagBasefont, tagFont, tagIframe, tagU, tagS, - tagStrike} + tagStrike, tagWbr} BlockTags* = {tagAddress, tagBlockquote, tagCenter, tagDel, tagDir, tagDiv, tagDl, tagFieldset, tagForm, tagH1, tagH2, tagH3, tagH4, tagH5, tagH6, tagHr, tagIns, tagIsindex, tagMenu, tagNoframes, tagNoscript, @@ -153,7 +189,7 @@ const tagMenu, tagNoframes} SingleTags* = {tagArea, tagBase, tagBasefont, tagBr, tagCol, tagFrame, tagHr, tagImg, tagIsindex, - tagLink, tagMeta, tagParam} + tagLink, tagMeta, tagParam, tagWbr} Entities = [ ("nbsp", 0x00A0), ("iexcl", 0x00A1), ("cent", 0x00A2), ("pound", 0x00A3), @@ -228,28 +264,149 @@ const ("loz", 0x25CA), ("spades", 0x2660), ("clubs", 0x2663), ("hearts", 0x2665), ("diams", 0x2666)] -proc binaryStrSearch(x: openarray[string], y: string): int = - ## XXX put this into the library somewhere! - var a = 0 - var b = len(x) - 1 - while a <= b: - var mid = (a + b) div 2 - var c = cmp(x[mid], y) - if c < 0: a = mid + 1 - elif c > 0: b = mid - 1 - else: return mid - result = - 1 +proc allLower(s: string): bool = + for c in s: + if c < 'a' or c > 'z': return false + return true + +proc toHtmlTag(s: string): THtmlTag = + case s + of "a": tagA + of "abbr": tagAbbr + of "acronym": tagAcronym + of "address": tagAddress + of "applet": tagApplet + of "area": tagArea + of "article": tagArticle + of "aside": tagAside + of "audio": tagAudio + of "b": tagB + of "base": tagBase + of "basefont": tagBasefont + of "bdi": tagBdi + of "bdo": tagBdo + of "big": tagBig + of "blockquote": tagBlockquote + of "body": tagBody + of "br": tagBr + of "button": tagButton + of "canvas": tagCanvas + of "caption": tagCaption + of "center": tagCenter + of "cite": tagCite + of "code": tagCode + of "col": tagCol + of "colgroup": tagColgroup + of "command": tagCommand + of "datalist": tagDatalist + of "dd": tagDd + of "del": tagDel + of "details": tagDetails + of "dfn": tagDfn + of "dialog": tagDialog + of "div": tagDiv + of "dir": tagDir + of "dl": tagDl + of "dt": tagDt + of "em": tagEm + of "embed": tagEmbed + of "fieldset": tagFieldset + of "figcaption": tagFigcaption + of "figure": tagFigure + of "font": tagFont + of "footer": tagFooter + of "form": tagForm + of "frame": tagFrame + of "frameset": tagFrameset + of "h1": tagH1 + of "h2": tagH2 + of "h3": tagH3 + of "h4": tagH4 + of "h5": tagH5 + of "h6": tagH6 + of "head": tagHead + of "header": tagHeader + of "hgroup": tagHgroup + of "html": tagHtml + of "hr": tagHr + of "i": tagI + of "iframe": tagIframe + of "img": tagImg + of "input": tagInput + of "ins": tagIns + of "isindex": tagIsindex + of "kbd": tagKbd + of "keygen": tagKeygen + of "label": tagLabel + of "legend": tagLegend + of "li": tagLi + of "link": tagLink + of "map": tagMap + of "mark": tagMark + of "menu": tagMenu + of "meta": tagMeta + of "meter": tagMeter + of "nav": tagNav + of "nobr": tagNobr + of "noframes": tagNoframes + of "noscript": tagNoscript + of "object": tagObject + of "ol": tagOl + of "optgroup": tagOptgroup + of "option": tagOption + of "output": tagOutput + of "p": tagP + of "param": tagParam + of "pre": tagPre + of "progress": tagProgress + of "q": tagQ + of "rp": tagRp + of "rt": tagRt + of "ruby": tagRuby + of "s": tagS + of "samp": tagSamp + of "script": tagScript + of "section": tagSection + of "select": tagSelect + of "small": tagSmall + of "source": tagSource + of "span": tagSpan + of "strike": tagStrike + of "strong": tagStrong + of "style": tagStyle + of "sub": tagSub + of "summary": tagSummary + of "sup": tagSup + of "table": tagTable + of "tbody": tagTbody + of "td": tagTd + of "textarea": tagTextarea + of "tfoot": tagTfoot + of "th": tagTh + of "thead": tagThead + of "time": tagTime + of "title": tagTitle + of "tr": tagTr + of "track": tagTrack + of "tt": tagTt + of "u": tagU + of "ul": tagUl + of "var": tagVar + of "video": tagVideo + of "wbr": tagWbr + else: tagUnknown proc htmlTag*(n: PXmlNode): THtmlTag = ## gets `n`'s tag as a ``THtmlTag``. if n.clientData == 0: - n.clientData = binaryStrSearch(tagToStr, n.tag)+1 + n.clientData = toHtmlTag(n.tag).ord result = THtmlTag(n.clientData) proc htmlTag*(s: string): THtmlTag = ## converts `s` to a ``THtmlTag``. If `s` is no HTML tag, ``tagUnknown`` is ## returned. - result = THtmlTag(binaryStrSearch(tagToStr, s.toLower)+1) + let s = if allLower(s): s else: s.toLower + result = toHtmlTag(s) proc entityToUtf8*(entity: string): string = ## converts an HTML entity name like ``Ü`` to its UTF-8 equivalent. @@ -267,11 +424,13 @@ proc parse(x: var TXmlParser, errors: var seq[string]): PXmlNode proc expected(x: var TXmlParser, n: PXmlNode): string = result = errorMsg(x, " expected") +template elemName(x: expr): expr = rawData(x) + proc untilElementEnd(x: var TXmlParser, result: PXmlNode, errors: var seq[string]) = # we parsed e.g. ``
`` and don't really expect a ``
``: if result.htmlTag in singleTags: - if x.kind != xmlElementEnd or cmpIgnoreCase(x.elementName, result.tag) != 0: + if x.kind != xmlElementEnd or cmpIgnoreCase(x.elemName, result.tag) != 0: return while true: case x.kind @@ -279,28 +438,29 @@ proc untilElementEnd(x: var TXmlParser, result: PXmlNode, case result.htmlTag of tagLi, tagP, tagDt, tagDd, tagInput, tagOption: # some tags are common to have no ````, like ``
  • ``: - if htmlTag(x.elementName) in {tagLi, tagP, tagDt, tagDd, tagInput, - tagOption}: + if htmlTag(x.elemName) in {tagLi, tagP, tagDt, tagDd, tagInput, + tagOption}: errors.add(expected(x, result)) break - when false: - if htmlTag(x.elementName) notin InlineTags: - errors.add(expected(x, result)) - break - of tagTr, tagTd, tagTh, tagTfoot, tagThead: - if htmlTag(x.elementName) in {tagTr, tagTd, tagTh, tagTfoot, tagThead}: + of tagTd, tagTh, tagTfoot, tagThead: + if htmlTag(x.elemName) in {tagTr, tagTd, tagTh, tagTfoot, tagThead}: + errors.add(expected(x, result)) + break + of tagTr: + if htmlTag(x.elemName) == tagTr: errors.add(expected(x, result)) break of tagOptgroup: - if htmlTag(x.elementName) in {tagOption, tagOptgroup}: + if htmlTag(x.elemName) in {tagOption, tagOptgroup}: errors.add(expected(x, result)) break else: nil result.addNode(parse(x, errors)) of xmlElementEnd: - if cmpIgnoreCase(x.elementName, result.tag) == 0: + if cmpIgnoreCase(x.elemName, result.tag) == 0: next(x) else: + echo "5; expected: ", result.htmltag, " ", x.elemName errors.add(expected(x, result)) # do not skip it here! break @@ -313,10 +473,10 @@ proc untilElementEnd(x: var TXmlParser, result: PXmlNode, proc parse(x: var TXmlParser, errors: var seq[string]): PXmlNode = case x.kind of xmlComment: - result = newComment(x.charData) + result = newComment(x.rawData) next(x) of xmlCharData, xmlWhitespace: - result = newText(x.charData) + result = newText(x.rawData) next(x) of xmlPI, xmlSpecial: # we just ignore processing instructions for now @@ -325,19 +485,19 @@ proc parse(x: var TXmlParser, errors: var seq[string]): PXmlNode = errors.add(errorMsg(x)) next(x) of xmlElementStart: - result = newElement(x.elementName.toLower) + result = newElement(x.elemName.toLower) next(x) untilElementEnd(x, result, errors) of xmlElementEnd: - errors.add(errorMsg(x, "unexpected ending tag: " & x.elementName)) + errors.add(errorMsg(x, "unexpected ending tag: " & x.elemName)) of xmlElementOpen: - result = newElement(x.elementName.toLower) + result = newElement(x.elemName.toLower) next(x) result.attrs = newStringTable() while true: case x.kind of xmlAttribute: - result.attrs[x.attrKey] = x.attrValue + result.attrs[x.rawData] = x.rawData2 next(x) of xmlElementClose: next(x) @@ -355,10 +515,10 @@ proc parse(x: var TXmlParser, errors: var seq[string]): PXmlNode = errors.add(errorMsg(x, " expected")) next(x) of xmlCData: - result = newCData(x.charData) + result = newCData(x.rawData) next(x) of xmlEntity: - var u = entityToUtf8(x.entityName) + var u = entityToUtf8(x.rawData) if u.len != 0: result = newText(u) next(x) of xmlEof: nil @@ -372,16 +532,20 @@ proc parseHtml*(s: PStream, filename: string, next(x) # skip the DOCTYPE: if x.kind == xmlSpecial: next(x) - result = parse(x, errors) - if x.kind != xmlEof: - errors.add(errorMsg(x, "EOF expected")) + + result = newElement("document") + result.addNode(parse(x, errors)) + #if x.kind != xmlEof: + # errors.add(errorMsg(x, "EOF expected")) while x.kind != xmlEof: var oldPos = x.bufpos # little hack to see if we made any progess result.addNode(parse(x, errors)) - if x.bufpos == oldPos: + if x.bufpos == oldPos: # force progress! - next(x) + next(x) close(x) + if result.len == 1: + result = result[0] proc parseHtml*(s: PStream): PXmlNode = ## parses the XTML from stream `s` and returns a ``PXmlNode``. All parsing @@ -400,22 +564,9 @@ proc loadHtml*(path: string, errors: var seq[string]): PXmlNode = proc loadHtml*(path: string): PXmlNode = ## Loads and parses HTML from file specified by ``path``, and returns ## a ``PXmlNode``. All parsing errors are ignored. - var errors: seq[string] = @[] + var errors: seq[string] = @[] result = loadHtml(path, errors) -when true: - nil -else: - proc checkHtmlAux(n: PXmlNode, errors: var seq[string]) = - nil - - proc checkHtmlStructure*(n: PXmlNode, errors: var seq[string]) = - ## checks the HTML structure after parsing for other errors like - ## a ``

    `` element within a ``

    `` element. - if n == nil or n.htmlTag != tagHtml: - errors.add(" tag expected") - checkHtmlAux(n, errors) - when isMainModule: import os @@ -429,4 +580,3 @@ when isMainModule: f.close() else: quit("cannot write test.txt") - diff --git a/lib/pure/httpserver.nim b/lib/pure/httpserver.nim index 61399c2d8..e24709451 100644 --- a/lib/pure/httpserver.nim +++ b/lib/pure/httpserver.nim @@ -47,10 +47,11 @@ proc badRequest(client: TSocket) = send(client, "

    Your browser sent a bad request, " & "such as a POST without a Content-Length.

    " & wwwNL) -proc cannotExec(client: TSocket) = - send(client, "HTTP/1.1 500 Internal Server Error" & wwwNL) - sendTextContentType(client) - send(client, "

    Error prohibited CGI execution." & wwwNL) +when false: + proc cannotExec(client: TSocket) = + send(client, "HTTP/1.1 500 Internal Server Error" & wwwNL) + sendTextContentType(client) + send(client, "

    Error prohibited CGI execution." & wwwNL) proc headers(client: TSocket, filename: string) = # XXX could use filename to determine file type @@ -79,11 +80,11 @@ proc unimplemented(client: TSocket) = # ----------------- file serving --------------------------------------------- -proc discardHeaders(client: TSocket) = skip(client) +when false: + proc discardHeaders(client: TSocket) = skip(client) proc serveFile*(client: TSocket, filename: string) = ## serves a file to the client. - when false: discardHeaders(client) var f: TFile if open(f, filename): headers(client, filename) @@ -96,7 +97,7 @@ proc serveFile*(client: TSocket, filename: string) = if bytesread != bytesWritten: dealloc(buf) close(f) - OSError() + OSError(OSLastError()) if bytesread != bufSize: break dealloc(buf) close(f) @@ -104,108 +105,109 @@ proc serveFile*(client: TSocket, filename: string) = notFound(client) # ------------------ CGI execution ------------------------------------------- +when false: + # TODO: Fix this, or get rid of it. + type + TRequestMethod = enum reqGet, reqPost -type - TRequestMethod = enum reqGet, reqPost + proc executeCgi(client: TSocket, path, query: string, meth: TRequestMethod) = + var env = newStringTable(modeCaseInsensitive) + var contentLength = -1 + case meth + of reqGet: + discardHeaders(client) -proc executeCgi(client: TSocket, path, query: string, meth: TRequestMethod) = - var env = newStringTable(modeCaseInsensitive) - var contentLength = -1 - case meth - of reqGet: - discardHeaders(client) + env["REQUEST_METHOD"] = "GET" + env["QUERY_STRING"] = query + of reqPost: + var buf = TaintedString"" + var dataAvail = false + while dataAvail: + dataAvail = recvLine(client, buf) # TODO: This is incorrect. + var L = toLower(buf.string) + if L.startsWith("content-length:"): + var i = len("content-length:") + while L[i] in Whitespace: inc(i) + contentLength = parseInt(substr(L, i)) - env["REQUEST_METHOD"] = "GET" - env["QUERY_STRING"] = query - of reqPost: - var buf = TaintedString"" - var dataAvail = false - while dataAvail: - dataAvail = recvLine(client, buf) # TODO: This is incorrect. - var L = toLower(buf.string) - if L.startsWith("content-length:"): - var i = len("content-length:") - while L[i] in Whitespace: inc(i) - contentLength = parseInt(substr(L, i)) + if contentLength < 0: + badRequest(client) + return - if contentLength < 0: - badRequest(client) - return + env["REQUEST_METHOD"] = "POST" + env["CONTENT_LENGTH"] = $contentLength - env["REQUEST_METHOD"] = "POST" - env["CONTENT_LENGTH"] = $contentLength + send(client, "HTTP/1.0 200 OK" & wwwNL) - send(client, "HTTP/1.0 200 OK" & wwwNL) - - var process = startProcess(command=path, env=env) - if meth == reqPost: - # get from client and post to CGI program: - var buf = alloc(contentLength) - if recv(client, buf, contentLength) != contentLength: + var process = startProcess(command=path, env=env) + if meth == reqPost: + # get from client and post to CGI program: + var buf = alloc(contentLength) + if recv(client, buf, contentLength) != contentLength: + dealloc(buf) + OSError() + var inp = process.inputStream + inp.writeData(buf, contentLength) dealloc(buf) - OSError() - var inp = process.inputStream - inp.writeData(buf, contentLength) - dealloc(buf) - var outp = process.outputStream - var line = newStringOfCap(120).TaintedString - while true: - if outp.readLine(line): - send(client, line.string) - send(client, wwwNL) - elif not running(process): break + var outp = process.outputStream + var line = newStringOfCap(120).TaintedString + while true: + if outp.readLine(line): + send(client, line.string) + send(client, wwwNL) + elif not running(process): break -# --------------- Server Setup ----------------------------------------------- + # --------------- Server Setup ----------------------------------------------- -proc acceptRequest(client: TSocket) = - var cgi = false - var query = "" - var buf = TaintedString"" - discard recvLine(client, buf) - var path = "" - var data = buf.string.split() - var meth = reqGet + proc acceptRequest(client: TSocket) = + var cgi = false + var query = "" + var buf = TaintedString"" + discard recvLine(client, buf) + var path = "" + var data = buf.string.split() + var meth = reqGet - var q = find(data[1], '?') + var q = find(data[1], '?') - # extract path - if q >= 0: - # strip "?..." from path, this may be found in both POST and GET - path = "." & data[1].substr(0, q-1) - else: - path = "." & data[1] - # path starts with "/", by adding "." in front of it we serve files from cwd - - if cmpIgnoreCase(data[0], "GET") == 0: + # extract path if q >= 0: + # strip "?..." from path, this may be found in both POST and GET + path = "." & data[1].substr(0, q-1) + else: + path = "." & data[1] + # path starts with "/", by adding "." in front of it we serve files from cwd + + if cmpIgnoreCase(data[0], "GET") == 0: + if q >= 0: + cgi = true + query = data[1].substr(q+1) + elif cmpIgnoreCase(data[0], "POST") == 0: cgi = true - query = data[1].substr(q+1) - elif cmpIgnoreCase(data[0], "POST") == 0: - cgi = true - meth = reqPost - else: - unimplemented(client) - - if path[path.len-1] == '/' or existsDir(path): - path = path / "index.html" - - if not ExistsFile(path): - discardHeaders(client) - notFound(client) - else: - when defined(Windows): - var ext = splitFile(path).ext.toLower - if ext == ".exe" or ext == ".cgi": - # XXX: extract interpreter information here? - cgi = true + meth = reqPost else: - if {fpUserExec, fpGroupExec, fpOthersExec} * path.getFilePermissions != {}: - cgi = true - if not cgi: - serveFile(client, path) + unimplemented(client) + + if path[path.len-1] == '/' or existsDir(path): + path = path / "index.html" + + if not ExistsFile(path): + discardHeaders(client) + notFound(client) else: - executeCgi(client, path, query, meth) + when defined(Windows): + var ext = splitFile(path).ext.toLower + if ext == ".exe" or ext == ".cgi": + # XXX: extract interpreter information here? + cgi = true + else: + if {fpUserExec, fpGroupExec, fpOthersExec} * path.getFilePermissions != {}: + cgi = true + if not cgi: + serveFile(client, path) + else: + executeCgi(client, path, query, meth) type TServer* = object of TObject ## contains the current server state @@ -226,7 +228,7 @@ proc open*(s: var TServer, port = TPort(80)) = ## creates a new server at port `port`. If ``port == 0`` a free port is ## acquired that can be accessed later by the ``port`` proc. s.socket = socket(AF_INET) - if s.socket == InvalidSocket: OSError() + if s.socket == InvalidSocket: OSError(OSLastError()) bindAddr(s.socket, port) listen(s.socket) diff --git a/lib/pure/marshal.nim b/lib/pure/marshal.nim index f52b554cc..789f6ad76 100644 --- a/lib/pure/marshal.nim +++ b/lib/pure/marshal.nim @@ -252,7 +252,7 @@ when isMainModule: ["test", "1", "2", "3", "4"], ["test", "1", "2", "3", "4"], ["test", "1", "2", "3", "4"]] testit(x) - var test2: tuple[name: string, s: int] = ("tuple test", 56) + var test2: tuple[name: string, s: uint] = ("tuple test", 56u) testit(test2) type diff --git a/lib/pure/memfiles.nim b/lib/pure/memfiles.nim index 84a87ba15..9f4094b40 100644 --- a/lib/pure/memfiles.nim +++ b/lib/pure/memfiles.nim @@ -50,11 +50,11 @@ proc open*(filename: string, mode: TFileMode = fmRead, result.size = 0 when defined(windows): - template fail(msg: expr) = + template fail(errCode: TOSErrorCode, msg: expr) = rollback() if result.fHandle != 0: discard CloseHandle(result.fHandle) if result.mapHandle != 0: discard CloseHandle(result.mapHandle) - OSError() + OSError(errCode) # return false #raise newException(EIO, msg) @@ -74,7 +74,7 @@ proc open*(filename: string, mode: TFileMode = fmRead, result.fHandle = callCreateFile(CreateFileA, filename) if result.fHandle == INVALID_HANDLE_VALUE: - fail "error opening file" + fail(OSLastError(), "error opening file") if newFileSize != -1: var @@ -83,9 +83,10 @@ proc open*(filename: string, mode: TFileMode = fmRead, var status = SetFilePointer(result.fHandle, sizeLow, addr(sizeHigh), FILE_BEGIN) - if (status == INVALID_SET_FILE_POINTER and GetLastError() != NO_ERROR) or + let lastErr = OSLastError() + if (status == INVALID_SET_FILE_POINTER and lastErr.int32 != NO_ERROR) or (SetEndOfFile(result.fHandle) == 0): - fail "error setting file size" + fail(lastErr, "error setting file size") # since the strings are always 'nil', we simply always call # CreateFileMappingW which should be slightly faster anyway: @@ -95,7 +96,7 @@ proc open*(filename: string, mode: TFileMode = fmRead, 0, 0, nil) if result.mapHandle == 0: - fail "error creating mapping" + fail(OSLastError(), "error creating mapping") result.mem = MapViewOfFileEx( result.mapHandle, @@ -106,22 +107,22 @@ proc open*(filename: string, mode: TFileMode = fmRead, nil) if result.mem == nil: - fail "error mapping view" + fail(OSLastError(), "error mapping view") var hi, low: int32 low = GetFileSize(result.fHandle, addr(hi)) if low == INVALID_FILE_SIZE: - fail "error getting file size" + fail(OSLastError(), "error getting file size") else: var fileSize = (int64(hi) shr 32) or low if mappedSize != -1: result.size = min(fileSize, mappedSize).int else: result.size = fileSize.int else: - template fail(msg: expr) = + template fail(errCode: TOSErrorCode, msg: expr) = rollback() if result.handle != 0: discard close(result.handle) - OSError() + OSError(errCode) var flags = if readonly: O_RDONLY else: O_RDWR @@ -132,11 +133,11 @@ proc open*(filename: string, mode: TFileMode = fmRead, if result.handle == -1: # XXX: errno is supposed to be set here # Is there an exception that wraps it? - fail "error opening file" + fail(OSLastError(), "error opening file") if newFileSize != -1: if ftruncate(result.handle, newFileSize) == -1: - fail "error setting file size" + fail(OSLastError(), "error setting file size") if mappedSize != -1: result.size = mappedSize @@ -147,7 +148,7 @@ proc open*(filename: string, mode: TFileMode = fmRead, # Why is mmap taking int anyway? result.size = int(stat.st_size) else: - fail "error getting file size" + fail(OSLastError(), "error getting file size") result.mem = mmap( nil, @@ -158,21 +159,24 @@ proc open*(filename: string, mode: TFileMode = fmRead, offset) if result.mem == cast[pointer](MAP_FAILED): - fail "file mapping failed" + fail(OSLastError(), "file mapping failed") proc close*(f: var TMemFile) = ## closes the memory mapped file `f`. All changes are written back to the ## file system, if `f` was opened with write access. var error = false + var lastErr: TOSErrorCode when defined(windows): if f.fHandle != INVALID_HANDLE_VALUE: + lastErr = OSLastError() error = UnmapViewOfFile(f.mem) == 0 error = (CloseHandle(f.mapHandle) == 0) or error error = (CloseHandle(f.fHandle) == 0) or error else: if f.handle != 0: + lastErr = OSLastError() error = munmap(f.mem, f.size) != 0 error = (close(f.handle) != 0) or error @@ -185,5 +189,5 @@ proc close*(f: var TMemFile) = else: f.handle = 0 - if error: OSError() + if error: OSError(lastErr) diff --git a/lib/pure/os.nim b/lib/pure/os.nim index bf00afed2..eaa22d351 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -39,6 +39,8 @@ type FWriteDir* = object of FWriteIO ## effect that denotes a write operation to ## the directory structure + TOSErrorCode* = distinct int32 ## Specifies an OS Error Code. + const doslike = defined(windows) or defined(OS2) or defined(DOS) # DOS-like filesystem @@ -171,10 +173,13 @@ const ## The character which separates the base filename from the extension; ## for example, the '.' in ``os.nim``. -proc OSErrorMsg*(): string {.rtl, extern: "nos$1".} = +proc OSErrorMsg*(): string {.rtl, extern: "nos$1", deprecated.} = ## Retrieves the operating system's error flag, ``errno``. ## On Windows ``GetLastError`` is checked before ``errno``. ## Returns "" if no error occured. + ## + ## **Deprecated since version 0.9.4**: use the other ``OSErrorMsg`` proc. + result = "" when defined(Windows): var err = GetLastError() @@ -194,17 +199,89 @@ proc OSErrorMsg*(): string {.rtl, extern: "nos$1".} = if errno != 0'i32: result = $os.strerror(errno) -proc OSError*(msg: string = "") {.noinline, rtl, extern: "nos$1".} = +{.push warning[deprecated]: off.} +proc OSError*(msg: string = "") {.noinline, rtl, extern: "nos$1", deprecated.} = ## raises an EOS exception with the given message ``msg``. ## If ``msg == ""``, the operating system's error flag ## (``errno``) is converted to a readable error message. On Windows ## ``GetLastError`` is checked before ``errno``. ## If no error flag is set, the message ``unknown OS error`` is used. + ## + ## **Deprecated since version 0.9.4**: use the other ``OSError`` proc. if len(msg) == 0: var m = OSErrorMsg() raise newException(EOS, if m.len > 0: m else: "unknown OS error") else: raise newException(EOS, msg) +{.pop.} + +proc `==`*(err1, err2: TOSErrorCode): bool {.borrow.} +proc `$`*(err: TOSErrorCode): string {.borrow.} + +proc OSErrorMsg*(errorCode: TOSErrorCode): string = + ## Converts an OS error code into a human readable string. + ## + ## The error code can be retrieved using the ``OSLastError`` proc. + ## + ## If conversion fails, or ``errorCode`` is ``0`` then ``""`` will be + ## returned. + ## + ## On Windows, the ``-d:useWinAnsi`` compilation flag can be used to + ## make this procedure use the non-unicode Win API calls to retrieve the + ## message. + result = "" + when defined(Windows): + if errorCode != TOSErrorCode(0'i32): + when useWinUnicode: + var msgbuf: widecstring + if FormatMessageW(0x00000100 or 0x00001000 or 0x00000200, + nil, errorCode.int32, 0, addr(msgbuf), 0, nil) != 0'i32: + result = $msgbuf + if msgbuf != nil: LocalFree(cast[pointer](msgbuf)) + else: + var msgbuf: cstring + if FormatMessageA(0x00000100 or 0x00001000 or 0x00000200, + nil, errorCode.int32, 0, addr(msgbuf), 0, nil) != 0'i32: + result = $msgbuf + if msgbuf != nil: LocalFree(msgbuf) + else: + if errorCode != TOSErrorCode(0'i32): + result = $os.strerror(errorCode.int32) + +proc OSError*(errorCode: TOSErrorCode) = + ## Raises an ``EOS`` exception. The ``errorCode`` will determine the + ## message, ``OSErrorMsg`` will be used to get this message. + ## + ## The error code can be retrieved using the ``OSLastError`` proc. + ## + ## If the error code is ``0`` or an error message could not be retrieved, + ## the message ``unknown OS error`` will be used. + let msg = OSErrorMsg(errorCode) + if msg == "": + raise newException(EOS, "unknown OS error") + else: + raise newException(EOS, msg) + +{.push stackTrace:off.} +proc OSLastError*(): TOSErrorCode = + ## Retrieves the last operating system error code. + ## + ## This procedure is useful in the event when an OS call fails. In that case + ## this procedure will return the error code describing the reason why the + ## OS call failed. The ``OSErrorMsg`` procedure can then be used to convert + ## this code into a string. + ## + ## **Warning**: + ## The behaviour of this procedure varies between Windows and POSIX systems. + ## On Windows some OS calls can reset the error code to ``0`` causing this + ## procedure to return ``0``. It is therefore advised to call this procedure + ## immediately after an OS call fails. On POSIX systems this is not a problem. + + when defined(windows): + result = TOSErrorCode(GetLastError()) + else: + result = TOSErrorCode(errno) +{.pop.} proc UnixToNativePath*(path: string): string {. noSideEffect, rtl, extern: "nos$1".} = @@ -311,12 +388,12 @@ proc getLastModificationTime*(file: string): TTime {.rtl, extern: "nos$1".} = ## Returns the `file`'s last modification time. when defined(posix): var res: TStat - if stat(file, res) < 0'i32: OSError() + if stat(file, res) < 0'i32: OSError(OSLastError()) return res.st_mtime else: var f: TWIN32_Find_Data var h = findfirstFile(file, f) - if h == -1'i32: OSError() + if h == -1'i32: OSError(OSLastError()) result = winTimeToUnixTime(rdFileTime(f.ftLastWriteTime)) findclose(h) @@ -324,12 +401,12 @@ proc getLastAccessTime*(file: string): TTime {.rtl, extern: "nos$1".} = ## Returns the `file`'s last read or write access time. when defined(posix): var res: TStat - if stat(file, res) < 0'i32: OSError() + if stat(file, res) < 0'i32: OSError(OSLastError()) return res.st_atime else: var f: TWIN32_Find_Data var h = findfirstFile(file, f) - if h == -1'i32: OSError() + if h == -1'i32: OSError(OSLastError()) result = winTimeToUnixTime(rdFileTime(f.ftLastAccessTime)) findclose(h) @@ -337,12 +414,12 @@ proc getCreationTime*(file: string): TTime {.rtl, extern: "nos$1".} = ## Returns the `file`'s creation time. when defined(posix): var res: TStat - if stat(file, res) < 0'i32: OSError() + if stat(file, res) < 0'i32: OSError(OSLastError()) return res.st_ctime else: var f: TWIN32_Find_Data var h = findfirstFile(file, f) - if h == -1'i32: OSError() + if h == -1'i32: OSError(OSLastError()) result = winTimeToUnixTime(rdFileTime(f.ftCreationTime)) findclose(h) @@ -358,30 +435,30 @@ proc getCurrentDir*(): string {.rtl, extern: "nos$1", tags: [].} = when useWinUnicode: var res = newWideCString("", bufsize) var L = GetCurrentDirectoryW(bufsize, res) - if L == 0'i32: OSError() + if L == 0'i32: OSError(OSLastError()) result = res$L else: result = newString(bufsize) var L = GetCurrentDirectoryA(bufsize, result) - if L == 0'i32: OSError() + if L == 0'i32: OSError(OSLastError()) setLen(result, L) else: result = newString(bufsize) if getcwd(result, bufsize) != nil: setlen(result, c_strlen(result)) else: - OSError() + OSError(OSLastError()) proc setCurrentDir*(newDir: string) {.inline, tags: [].} = ## Sets the `current working directory`:idx:; `EOS` is raised if ## `newDir` cannot been set. when defined(Windows): when useWinUnicode: - if SetCurrentDirectoryW(newWideCString(newDir)) == 0'i32: OSError() + if SetCurrentDirectoryW(newWideCString(newDir)) == 0'i32: OSError(OSLastError()) else: - if SetCurrentDirectoryA(newDir) == 0'i32: OSError() + if SetCurrentDirectoryA(newDir) == 0'i32: OSError(OSLastError()) else: - if chdir(newDir) != 0'i32: OSError() + if chdir(newDir) != 0'i32: OSError(OSLastError()) proc JoinPath*(head, tail: string): string {. noSideEffect, rtl, extern: "nos$1".} = @@ -571,23 +648,23 @@ proc expandFilename*(filename: string): string {.rtl, extern: "nos$1", var res = newWideCString("", bufsize div 2) var L = GetFullPathNameW(newWideCString(filename), bufsize, res, unused) if L <= 0'i32 or L >= bufsize: - OSError() + OSError(OSLastError()) result = res$L else: var unused: cstring result = newString(bufsize) var L = GetFullPathNameA(filename, bufsize, result, unused) - if L <= 0'i32 or L >= bufsize: OSError() + if L <= 0'i32 or L >= bufsize: OSError(OSLastError()) setLen(result, L) elif defined(macosx) or defined(bsd): # On Mac OS X 10.5, realpath does not allocate the buffer on its own var pathBuffer: cstring = newString(pathMax) var resultBuffer = realpath(filename, pathBuffer) - if resultBuffer == nil: OSError() + if resultBuffer == nil: OSError(OSLastError()) result = $resultBuffer else: var res = realpath(filename, nil) - if res == nil: OSError() + if res == nil: OSError(OSLastError()) result = $res c_free(res) @@ -677,6 +754,7 @@ proc sameFile*(path1, path2: string): bool {.rtl, extern: "nos$1", var f1 = OpenHandle(path1) var f2 = OpenHandle(path2) + var lastErr: TOSErrorCode if f1 != INVALID_HANDLE_VALUE and f2 != INVALID_HANDLE_VALUE: var fi1, fi2: TBY_HANDLE_FILE_INFORMATION @@ -685,17 +763,21 @@ proc sameFile*(path1, path2: string): bool {.rtl, extern: "nos$1", result = fi1.dwVolumeSerialNumber == fi2.dwVolumeSerialNumber and fi1.nFileIndexHigh == fi2.nFileIndexHigh and fi1.nFileIndexLow == fi2.nFileIndexLow - else: success = false - else: success = false + else: + lastErr = OSLastError() + success = false + else: + lastErr = OSLastError() + success = false discard CloseHandle(f1) discard CloseHandle(f2) - if not success: OSError() + if not success: OSError(lastErr) else: var a, b: TStat if stat(path1, a) < 0'i32 or stat(path2, b) < 0'i32: - OSError() + OSError(OSLastError()) else: result = a.st_dev == b.st_dev and a.st_ino == b.st_ino @@ -738,17 +820,17 @@ proc copyFile*(source, dest: string) {.rtl, extern: "nos$1", when useWinUnicode: let s = newWideCString(source) let d = newWideCString(dest) - if CopyFileW(s, d, 0'i32) == 0'i32: OSError() + if CopyFileW(s, d, 0'i32) == 0'i32: OSError(OSLastError()) else: - if CopyFileA(source, dest, 0'i32) == 0'i32: OSError() + if CopyFileA(source, dest, 0'i32) == 0'i32: OSError(OSLastError()) else: # generic version of copyFile which works for any platform: const bufSize = 8000 # better for memory manager var d, s: TFile - if not open(s, source): OSError() + if not open(s, source): OSError(OSLastError()) if not open(d, dest, fmWrite): close(s) - OSError() + OSError(OSLastError()) var buf = alloc(bufsize) while True: var bytesread = readBuffer(s, buf, bufsize) @@ -758,7 +840,7 @@ proc copyFile*(source, dest: string) {.rtl, extern: "nos$1", dealloc(buf) close(s) close(d) - OSError() + OSError(OSLastError()) if bytesread != bufSize: break dealloc(buf) close(s) @@ -767,7 +849,8 @@ proc copyFile*(source, dest: string) {.rtl, extern: "nos$1", proc moveFile*(source, dest: string) {.rtl, extern: "nos$1", tags: [FReadIO, FWriteIO].} = ## Moves a file from `source` to `dest`. If this fails, `EOS` is raised. - if crename(source, dest) != 0'i32: OSError() + if crename(source, dest) != 0'i32: + raise newException(EOS, $strerror(errno)) when not defined(ENOENT): var ENOENT {.importc, header: "".}: cint @@ -775,7 +858,8 @@ when not defined(ENOENT): proc removeFile*(file: string) {.rtl, extern: "nos$1", tags: [FWriteDir].} = ## Removes the `file`. If this fails, `EOS` is raised. This does not fail ## if the file never existed in the first place. - if cremove(file) != 0'i32 and errno != ENOENT: OSError() + if cremove(file) != 0'i32 and errno != ENOENT: + raise newException(EOS, $strerror(errno)) proc execShellCmd*(command: string): int {.rtl, extern: "nos$1", tags: [FExecIO].} = @@ -907,14 +991,14 @@ proc putEnv*(key, val: string) {.tags: [FWriteEnv].} = indx = high(environment) when defined(unix): if cputenv(environment[indx]) != 0'i32: - OSError() + OSError(OSLastError()) else: when useWinUnicode: var k = newWideCString(key) var v = newWideCString(val) - if SetEnvironmentVariableW(k, v) == 0'i32: OSError() + if SetEnvironmentVariableW(k, v) == 0'i32: OSError(OSLastError()) else: - if SetEnvironmentVariableA(key, val) == 0'i32: OSError() + if SetEnvironmentVariableA(key, val) == 0'i32: OSError(OSLastError()) iterator envPairs*(): tuple[key, value: TaintedString] {.tags: [FReadEnv].} = ## Iterate over all `environments variables`:idx:. In the first component @@ -1044,10 +1128,12 @@ proc rawRemoveDir(dir: string) = wrapUnary(res, RemoveDirectoryW, dir) else: var res = RemoveDirectoryA(dir) - if res == 0'i32 and GetLastError() != 3'i32 and - GetLastError() != 18'i32: OSError() + let lastError = OSLastError() + if res == 0'i32 and lastError.int32 != 3'i32 and + lastError.int32 != 18'i32 and lastError.int32 != 2'i32: + OSError(lastError) else: - if rmdir(dir) != 0'i32 and errno != ENOENT: OSError() + if rmdir(dir) != 0'i32 and errno != ENOENT: OSError(OSLastError()) proc removeDir*(dir: string) {.rtl, extern: "nos$1", tags: [ FWriteDir, FReadDir].} = @@ -1065,14 +1151,14 @@ proc removeDir*(dir: string) {.rtl, extern: "nos$1", tags: [ proc rawCreateDir(dir: string) = when defined(unix): if mkdir(dir, 0o711) != 0'i32 and errno != EEXIST: - OSError() + OSError(OSLastError()) else: when useWinUnicode: wrapUnary(res, CreateDirectoryW, dir) else: var res = CreateDirectoryA(dir) if res == 0'i32 and GetLastError() != 183'i32: - OSError() + OSError(OSLastError()) proc createDir*(dir: string) {.rtl, extern: "nos$1", tags: [FWriteDir].} = ## Creates the `directory`:idx: `dir`. @@ -1213,7 +1299,7 @@ proc getFilePermissions*(filename: string): set[TFilePermission] {. ## permission is available in any case. when defined(posix): var a: TStat - if stat(filename, a) < 0'i32: OSError() + if stat(filename, a) < 0'i32: OSError(OSLastError()) result = {} if (a.st_mode and S_IRUSR) != 0'i32: result.incl(fpUserRead) if (a.st_mode and S_IWUSR) != 0'i32: result.incl(fpUserWrite) @@ -1231,7 +1317,7 @@ proc getFilePermissions*(filename: string): set[TFilePermission] {. wrapUnary(res, GetFileAttributesW, filename) else: var res = GetFileAttributesA(filename) - if res == -1'i32: OSError() + if res == -1'i32: OSError(OSLastError()) if (res and FILE_ATTRIBUTE_READONLY) != 0'i32: result = {fpUserExec, fpUserRead, fpGroupExec, fpGroupRead, fpOthersExec, fpOthersRead} @@ -1257,13 +1343,13 @@ proc setFilePermissions*(filename: string, permissions: set[TFilePermission]) {. if fpOthersWrite in permissions: p = p or S_IWOTH if fpOthersExec in permissions: p = p or S_IXOTH - if chmod(filename, p) != 0: OSError() + if chmod(filename, p) != 0: OSError(OSLastError()) else: when useWinUnicode: wrapUnary(res, GetFileAttributesW, filename) else: var res = GetFileAttributesA(filename) - if res == -1'i32: OSError() + if res == -1'i32: OSError(OSLastError()) if fpUserWrite in permissions: res = res and not FILE_ATTRIBUTE_READONLY else: @@ -1272,7 +1358,7 @@ proc setFilePermissions*(filename: string, permissions: set[TFilePermission]) {. wrapBinary(res2, SetFileAttributesW, filename, res) else: var res2 = SetFileAttributesA(filename, res) - if res2 == - 1'i32: OSError() + if res2 == - 1'i32: OSError(OSLastError()) proc inclFilePermissions*(filename: string, permissions: set[TFilePermission]) {. @@ -1368,6 +1454,9 @@ when defined(macosx): proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [FReadIO].} = ## Returns the filename of the application's executable. + ## + ## This procedure will resolve symlinks. + ## ## **Note**: This does not work reliably on BSD. # Linux: /proc//exe @@ -1397,6 +1486,8 @@ proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [FReadIO].} = result = newString(int(size)) if getExecPath2(result, size): result = "" # error! + if result.len > 0: + result = result.expandFilename else: # little heuristic that may work on other POSIX-like systems: result = string(getEnv("_")) @@ -1443,7 +1534,7 @@ proc getFileSize*(file: string): biggestInt {.rtl, extern: "nos$1", when defined(windows): var a: TWin32FindData var resA = findfirstFile(file, a) - if resA == -1: OSError() + if resA == -1: OSError(OSLastError()) result = rdFileSize(a) findclose(resA) else: @@ -1451,7 +1542,7 @@ proc getFileSize*(file: string): biggestInt {.rtl, extern: "nos$1", if open(f, file): result = getFileSize(f) close(f) - else: OSError() + else: OSError(OSLastError()) proc findExe*(exe: string): string {.tags: [FReadDir, FReadEnv].} = ## Searches for `exe` in the current working directory and then diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index aa4fbe32d..b9bde73bc 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -264,7 +264,7 @@ when defined(Windows) and not defined(useNimRtl): # TRUE and zero bytes returned (EOF). # TRUE and n (>0) bytes returned (good data). # FALSE and bytes returned undefined (system error). - if a == 0 and br != 0: OSError() + if a == 0 and br != 0: OSError(OSLastError()) s.atTheEnd = br < bufLen result = br @@ -272,7 +272,7 @@ when defined(Windows) and not defined(useNimRtl): var s = PFileHandleStream(s) var bytesWritten: int32 var a = winlean.writeFile(s.handle, buffer, bufLen.cint, bytesWritten, nil) - if a == 0: OSError() + if a == 0: OSError(OSLastError()) proc newFileHandleStream(handle: THandle): PFileHandleStream = new(result) @@ -313,7 +313,7 @@ when defined(Windows) and not defined(useNimRtl): piInheritablePipe.lpSecurityDescriptor = nil piInheritablePipe.Binherithandle = 1 if CreatePipe(Rdhandle, Wrhandle, piInheritablePipe, 1024) == 0'i32: - OSError() + OSError(OSLastError()) proc fileClose(h: THandle) {.inline.} = if h > 4: discard CloseHandle(h) @@ -370,6 +370,7 @@ when defined(Windows) and not defined(useNimRtl): else: success = winlean.CreateProcessA(nil, cmdl, nil, nil, 1, NORMAL_PRIORITY_CLASS, e, wd, SI, ProcInfo) + let lastError = OSLastError() if poParentStreams notin options: FileClose(si.hStdInput) @@ -379,7 +380,7 @@ when defined(Windows) and not defined(useNimRtl): if e != nil: dealloc(e) dealloc(cmdl) - if success == 0: OSError() + if success == 0: OSError(lastError) # Close the handle now so anyone waiting is woken: discard closeHandle(procInfo.hThread) result.FProcessHandle = procInfo.hProcess @@ -450,7 +451,7 @@ when defined(Windows) and not defined(useNimRtl): var res = winlean.CreateProcessA(nil, command, nil, nil, 0, NORMAL_PRIORITY_CLASS, nil, nil, SI, ProcInfo) if res == 0: - OSError() + OSError(OSLastError()) else: Process = ProcInfo.hProcess discard CloseHandle(ProcInfo.hThread) @@ -473,7 +474,7 @@ when defined(Windows) and not defined(useNimRtl): of WAIT_TIMEOUT: return 0 of WAIT_FAILED: - OSError() + OSError(OSLastError()) else: var i = ret - WAIT_OBJECT_0 readfds.del(i) @@ -531,7 +532,7 @@ elif not defined(useNimRtl): if poParentStreams notin options: if pipe(p_stdin) != 0'i32 or pipe(p_stdout) != 0'i32 or pipe(p_stderr) != 0'i32: - OSError() + OSError(OSLastError()) var pid: TPid when defined(posix_spawn) and not defined(useFork): @@ -539,7 +540,7 @@ elif not defined(useNimRtl): var fops: Tposix_spawn_file_actions template chck(e: expr) = - if e != 0'i32: OSError() + if e != 0'i32: OSError(OSLastError()) chck posix_spawn_file_actions_init(fops) chck posix_spawnattr_init(attr) @@ -584,20 +585,20 @@ elif not defined(useNimRtl): else: Pid = fork() - if Pid < 0: OSError() + if Pid < 0: OSError(OSLastError()) if pid == 0: ## child process: if poParentStreams notin options: discard close(p_stdin[writeIdx]) - if dup2(p_stdin[readIdx], readIdx) < 0: OSError() + if dup2(p_stdin[readIdx], readIdx) < 0: OSError(OSLastError()) discard close(p_stdout[readIdx]) - if dup2(p_stdout[writeIdx], writeIdx) < 0: OSError() + if dup2(p_stdout[writeIdx], writeIdx) < 0: OSError(OSLastError()) discard close(p_stderr[readIdx]) if poStdErrToStdOut in options: - if dup2(p_stdout[writeIdx], 2) < 0: OSError() + if dup2(p_stdout[writeIdx], 2) < 0: OSError(OSLastError()) else: - if dup2(p_stderr[writeIdx], 2) < 0: OSError() + if dup2(p_stderr[writeIdx], 2) < 0: OSError(OSLastError()) # Create a new process group if setpgid(0, 0) == -1: quit("setpgid call failed: " & $strerror(errno)) @@ -652,10 +653,10 @@ elif not defined(useNimRtl): discard close(p.errorHandle) proc suspend(p: PProcess) = - if kill(-p.id, SIGSTOP) != 0'i32: OSError() + if kill(-p.id, SIGSTOP) != 0'i32: OSError(OSLastError()) proc resume(p: PProcess) = - if kill(-p.id, SIGCONT) != 0'i32: OSError() + if kill(-p.id, SIGCONT) != 0'i32: OSError(OSLastError()) proc running(p: PProcess): bool = var ret = waitPid(p.id, p.exitCode, WNOHANG) @@ -665,8 +666,8 @@ elif not defined(useNimRtl): proc terminate(p: PProcess) = if kill(-p.id, SIGTERM) == 0'i32: if p.running(): - if kill(-p.id, SIGKILL) != 0'i32: OSError() - else: OSError() + if kill(-p.id, SIGKILL) != 0'i32: OSError(OSLastError()) + else: OSError(OSLastError()) proc waitForExit(p: PProcess, timeout: int = -1): int = #if waitPid(p.id, p.exitCode, 0) == int(p.id): @@ -676,7 +677,7 @@ elif not defined(useNimRtl): if p.exitCode != -3: return p.exitCode if waitPid(p.id, p.exitCode, 0) < 0: p.exitCode = -3 - OSError() + OSError(OSLastError()) result = int(p.exitCode) shr 8 proc peekExitCode(p: PProcess): int = @@ -690,7 +691,7 @@ elif not defined(useNimRtl): proc createStream(stream: var PStream, handle: var TFileHandle, fileMode: TFileMode) = var f: TFile - if not open(f, handle, fileMode): OSError() + if not open(f, handle, fileMode): OSError(OSLastError()) stream = newFileStream(f) proc inputStream(p: PProcess): PStream = diff --git a/lib/pure/parsecfg.nim b/lib/pure/parsecfg.nim index 6a0f2ed49..e8014cece 100644 --- a/lib/pure/parsecfg.nim +++ b/lib/pure/parsecfg.nim @@ -67,7 +67,8 @@ type # implementation const - SymChars: TCharSet = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\x80'..'\xFF', '.'} + SymChars: TCharSet = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\x80'..'\xFF', '.', + '/', '\\'} proc rawGetTok(c: var TCfgParser, tok: var TToken) @@ -149,7 +150,7 @@ proc getEscapedChar(c: var TCfgParser, tok: var TToken) = of 't', 'T': add(tok.literal, '\t') Inc(c.bufpos) - of '\'', '\"': + of '\'', '"': add(tok.literal, c.buf[c.bufpos]) Inc(c.bufpos) of '\\': @@ -178,7 +179,7 @@ proc getString(c: var TCfgParser, tok: var TToken, rawMode: bool) = var pos = c.bufPos + 1 # skip " var buf = c.buf # put `buf` in a register tok.kind = tkSymbol - if (buf[pos] == '\"') and (buf[pos + 1] == '\"'): + if (buf[pos] == '"') and (buf[pos + 1] == '"'): # long string literal: inc(pos, 2) # skip "" # skip leading newline: @@ -186,9 +187,9 @@ proc getString(c: var TCfgParser, tok: var TToken, rawMode: bool) = buf = c.buf while true: case buf[pos] - of '\"': - if (buf[pos + 1] == '\"') and (buf[pos + 2] == '\"'): break - add(tok.literal, '\"') + of '"': + if (buf[pos + 1] == '"') and (buf[pos + 2] == '"'): break + add(tok.literal, '"') Inc(pos) of '\c', '\L': pos = HandleCRLF(c, pos) @@ -205,7 +206,7 @@ proc getString(c: var TCfgParser, tok: var TToken, rawMode: bool) = # ordinary string literal while true: var ch = buf[pos] - if ch == '\"': + if ch == '"': inc(pos) # skip '"' break if ch in {'\c', '\L', lexbase.EndOfFile}: @@ -278,7 +279,7 @@ proc rawGetTok(c: var TCfgParser, tok: var TToken) = tok.kind = tkBracketRi Inc(c.bufpos) tok.literal = "]" - of '\"': + of '"': getString(c, tok, false) of lexbase.EndOfFile: tok.kind = tkEof @@ -351,7 +352,7 @@ proc next*(c: var TCfgParser): TCfgEvent {.rtl, extern: "npc$1".} = rawGetTok(c, c.tok) else: result.kind = cfgError - result.msg = errorStr(c, "\']\' expected, but found: " & c.tok.literal) + result.msg = errorStr(c, "']' expected, but found: " & c.tok.literal) of tkInvalid, tkEquals, tkColon, tkBracketRi: result.kind = cfgError result.msg = errorStr(c, "invalid token: " & c.tok.literal) diff --git a/lib/pure/parsexml.nim b/lib/pure/parsexml.nim index c49986087..667b8aed6 100644 --- a/lib/pure/parsexml.nim +++ b/lib/pure/parsexml.nim @@ -131,6 +131,10 @@ proc close*(my: var TXmlParser) {.inline.} = ## closes the parser `my` and its associated input stream. lexbase.close(my) +proc kind*(my: TXmlParser): TXmlEventKind {.inline.} = + ## returns the current event type for the XML parser + return my.kind + proc charData*(my: TXmlParser): string {.inline.} = ## returns the character data for the events: ``xmlCharData``, ## ``xmlWhitespace``, ``xmlComment``, ``xmlCData``, ``xmlSpecial`` @@ -138,10 +142,6 @@ proc charData*(my: TXmlParser): string {.inline.} = xmlSpecial}) return my.a -proc kind*(my: TXmlParser): TXmlEventKind {.inline.} = - ## returns the current event type for the XML parser - return my.kind - proc elementName*(my: TXmlParser): string {.inline.} = ## returns the element name for the events: ``xmlElementStart``, ## ``xmlElementEnd``, ``xmlElementOpen`` @@ -173,6 +173,16 @@ proc PIRest*(my: TXmlParser): string {.inline.} = assert(my.kind == xmlPI) return my.b +proc rawData*(my: TXmlParser): string {.inline.} = + ## returns the underlying 'data' string by reference. + ## This is only used for speed hacks. + shallowCopy(result, my.a) + +proc rawData2*(my: TXmlParser): string {.inline.} = + ## returns the underlying second 'data' string by reference. + ## This is only used for speed hacks. + shallowCopy(result, my.b) + proc getColumn*(my: TXmlParser): int {.inline.} = ## get the current column the parser has arrived at. result = getColNumber(my, my.bufPos) @@ -439,6 +449,7 @@ proc parseTag(my: var TXmlParser) = if my.buf[my.bufpos] == '/' and my.buf[my.bufpos+1] == '>': inc(my.bufpos, 2) my.state = stateEmptyElementTag + my.c = nil elif my.buf[my.bufpos] == '>': inc(my.bufpos) else: diff --git a/lib/pure/pegs.nim b/lib/pure/pegs.nim index dd694fe04..d8fed845a 100644 --- a/lib/pure/pegs.nim +++ b/lib/pure/pegs.nim @@ -82,7 +82,7 @@ type of pkChar, pkGreedyRepChar: ch: char of pkCharChoice, pkGreedyRepSet: charChoice: ref set[char] of pkNonTerminal: nt: PNonTerminal - of pkBackRef..pkBackRefIgnoreStyle: index: range[1..MaxSubpatterns] + of pkBackRef..pkBackRefIgnoreStyle: index: range[0..MaxSubpatterns] else: sons: seq[TNode] PNonTerminal* = ref TNonTerminal @@ -870,7 +870,6 @@ template `=~`*(s: string, pattern: TPeg): bool = bind maxSubpatterns when not definedInScope(matches): var matches {.inject.}: array[0..maxSubpatterns-1, string] - {.warning: "injected 'matches' might be affected by new scoping rules in 0.9.4".} match(s, pattern, matches) # ------------------------- more string handling ------------------------------ diff --git a/lib/pure/redis.nim b/lib/pure/redis.nim index bb6ea6768..8171dc12b 100644 --- a/lib/pure/redis.nim +++ b/lib/pure/redis.nim @@ -36,7 +36,7 @@ proc open*(host = "localhost", port = 6379.TPort): TRedis = ## Opens a connection to the redis server. result.socket = socket(buffered = false) if result.socket == InvalidSocket: - OSError() + OSError(OSLastError()) result.socket.connect(host, port) proc raiseInvalidReply(expected, got: char) = @@ -50,34 +50,31 @@ proc raiseNoOK(status: string) = proc parseStatus(r: TRedis): TRedisStatus = var line = "" - if r.socket.recvLine(line): - if line == "": - raise newException(ERedis, "Server closed connection prematurely") + r.socket.readLine(line) + if line == "": + raise newException(ERedis, "Server closed connection prematurely") + + if line[0] == '-': + raise newException(ERedis, strip(line)) + if line[0] != '+': + raiseInvalidReply('+', line[0]) - if line[0] == '-': - raise newException(ERedis, strip(line)) - if line[0] != '+': - raiseInvalidReply('+', line[0]) - - return line.substr(1) # Strip '+' - else: - OSError() + return line.substr(1) # Strip '+' proc parseInteger(r: TRedis): TRedisInteger = var line = "" - if r.socket.recvLine(line): - if line == "": - raise newException(ERedis, "Server closed connection prematurely") + r.socket.readLine(line) + if line == "": + raise newException(ERedis, "Server closed connection prematurely") - if line[0] == '-': - raise newException(ERedis, strip(line)) - if line[0] != ':': - raiseInvalidReply(':', line[0]) - - # Strip ':' - if parseBiggestInt(line, result, 1) == 0: - raise newException(EInvalidReply, "Unable to parse integer.") - else: OSError() + if line[0] == '-': + raise newException(ERedis, strip(line)) + if line[0] != ':': + raiseInvalidReply(':', line[0]) + + # Strip ':' + if parseBiggestInt(line, result, 1) == 0: + raise newException(EInvalidReply, "Unable to parse integer.") proc recv(sock: TSocket, size: int): TaintedString = result = newString(size).TaintedString @@ -86,8 +83,7 @@ proc recv(sock: TSocket, size: int): TaintedString = proc parseBulk(r: TRedis, allowMBNil = False): TRedisString = var line = "" - if not r.socket.recvLine(line.TaintedString): - raise newException(EInvalidReply, "recvLine failed") + r.socket.readLine(line.TaintedString) # Error. if line[0] == '-': @@ -110,8 +106,7 @@ proc parseBulk(r: TRedis, allowMBNil = False): TRedisString = proc parseMultiBulk(r: TRedis): TRedisList = var line = TaintedString"" - if not r.socket.recvLine(line): - raise newException(EInvalidReply, "recvLine failed") + r.socket.readLine(line) if line.string[0] != '*': raiseInvalidReply('*', line.string[0]) @@ -848,10 +843,8 @@ proc shutdown*(r: TRedis) = ## Synchronously save the dataset to disk and then shut down the server r.sendCommand("SHUTDOWN") var s = "".TaintedString - if r.socket.recvLine(s): - if s.string.len != 0: raise newException(ERedis, s.string) - else: - OSError() + r.socket.readLine(s) + if s.string.len != 0: raise newException(ERedis, s.string) proc slaveof*(r: TRedis, host: string, port: string) = ## Make the server a slave of another instance, or promote it as master diff --git a/lib/pure/scgi.nim b/lib/pure/scgi.nim index 57fa0b144..8e45032c8 100644 --- a/lib/pure/scgi.nim +++ b/lib/pure/scgi.nim @@ -175,6 +175,15 @@ proc recvBufferAsync(client: PAsyncClient, L: int): TReadLineResult = if ret == L: return ReadFullLine +proc checkCloseSocket(client: PAsyncClient) = + if not client.c.isClosed: + if client.c.isSendDataBuffered: + client.c.setHandleWrite do (s: PAsyncSocket): + if not s.isClosed and not s.isSendDataBuffered: + s.close() + s.delHandleWrite() + else: client.c.close() + proc handleClientRead(client: PAsyncClient, s: PAsyncScgiState) = case client.mode of ClientReadChar: @@ -206,7 +215,7 @@ proc handleClientRead(client: PAsyncClient, s: PAsyncScgiState) = client.mode = ClientReadContent else: s.handleRequest(client.c, client.input, client.headers) - if not client.c.isClosed: client.c.close() + checkCloseSocket(client) of ReadPartialLine, ReadDisconnected, ReadNone: return of ClientReadContent: let L = parseInt(client.headers["CONTENT_LENGTH"])-client.input.len @@ -215,11 +224,11 @@ proc handleClientRead(client: PAsyncClient, s: PAsyncScgiState) = case ret of ReadFullLine: s.handleRequest(client.c, client.input, client.headers) - if not client.c.isClosed: client.c.close() + checkCloseSocket(client) of ReadPartialLine, ReadDisconnected, ReadNone: return else: s.handleRequest(client.c, client.input, client.headers) - if not client.c.isClosed: client.c.close() + checkCloseSocket(client) proc handleAccept(sock: PAsyncSocket, s: PAsyncScgiState) = var client: PAsyncSocket diff --git a/lib/pure/sockets.nim b/lib/pure/sockets.nim index 603fd612b..33df72d38 100644 --- a/lib/pure/sockets.nim +++ b/lib/pure/sockets.nim @@ -231,7 +231,7 @@ when defined(ssl): if err == 0: raise newException(ESSL, "No error reported.") if err == -1: - OSError() + OSError(OSLastError()) var errStr = ErrErrorString(err, nil) raise newException(ESSL, $errStr) @@ -347,24 +347,23 @@ proc SocketError*(socket: TSocket, err: int = -1, async = false) = else: SSLError("Unknown Error") if err == -1 and not (when defined(ssl): socket.isSSL else: false): + let lastError = OSLastError() if async: when defined(windows): - # TODO: Test on Windows - var err = WSAGetLastError() - if err == WSAEWOULDBLOCK: + if lastError.int32 == WSAEWOULDBLOCK: return - else: OSError() + else: OSError(lastError) else: - if errno == EAGAIN or errno == EWOULDBLOCK: + if lastError.int32 == EAGAIN or lastError.int32 == EWOULDBLOCK: return - else: OSError() - else: OSError() + else: OSError(lastError) + else: OSError(lastError) proc listen*(socket: TSocket, backlog = SOMAXCONN) {.tags: [FReadIO].} = ## Marks ``socket`` as accepting connections. ## ``Backlog`` specifies the maximum length of the ## queue of pending connections. - if listen(socket.fd, cint(backlog)) < 0'i32: OSError() + if listen(socket.fd, cint(backlog)) < 0'i32: OSError(OSLastError()) proc invalidIp4(s: string) {.noreturn, noinline.} = raise newException(EInvalidValue, "invalid ip4 address: " & s) @@ -403,7 +402,7 @@ template gaiNim(a, p, h, list: expr): stmt = var gaiResult = getAddrInfo(a, $p, addr(h), list) if gaiResult != 0'i32: when defined(windows): - OSError() + OSError(OSLastError()) else: OSError($gai_strerror(gaiResult)) @@ -423,7 +422,7 @@ proc bindAddr*(socket: TSocket, port = TPort(0), address = "") {. name.sin_addr.s_addr = sockets.htonl(INADDR_ANY) if bindSocket(socket.fd, cast[ptr TSockAddr](addr(name)), sizeof(name).TSockLen) < 0'i32: - OSError() + OSError(OSLastError()) else: var hints: TAddrInfo var aiList: ptr TAddrInfo = nil @@ -432,21 +431,7 @@ proc bindAddr*(socket: TSocket, port = TPort(0), address = "") {. hints.ai_protocol = toInt(IPPROTO_TCP) gaiNim(address, port, hints, aiList) if bindSocket(socket.fd, aiList.ai_addr, aiList.ai_addrLen.TSockLen) < 0'i32: - OSError() - -when false: - proc bindAddr*(socket: TSocket, port = TPort(0)) = - ## binds a port number to a socket. - var name: Tsockaddr_in - when defined(Windows): - name.sin_family = int16(ord(AF_INET)) - else: - name.sin_family = posix.AF_INET - name.sin_port = sockets.htons(int16(port)) - name.sin_addr.s_addr = sockets.htonl(INADDR_ANY) - if bindSocket(cint(socket), cast[ptr TSockAddr](addr(name)), - sizeof(name).TSockLen) < 0'i32: - OSError() + OSError(OSLastError()) proc getSockName*(socket: TSocket): TPort = ## returns the socket's associated port number. @@ -460,7 +445,7 @@ proc getSockName*(socket: TSocket): TPort = var namelen = sizeof(name).TSockLen if getsockname(socket.fd, cast[ptr TSockAddr](addr(name)), addr(namelen)) == -1'i32: - OSError() + OSError(OSLastError()) result = TPort(sockets.ntohs(name.sin_port)) template acceptAddrPlain(noClientRet, successRet: expr, @@ -472,26 +457,25 @@ template acceptAddrPlain(noClientRet, successRet: expr, addr(addrLen)) if sock < 0: - # TODO: Test on Windows. + let err = OSLastError() when defined(windows): - var err = WSAGetLastError() - if err == WSAEINPROGRESS: + if err.int32 == WSAEINPROGRESS: client = InvalidSocket address = "" when noClientRet.int == -1: return else: return noClientRet - else: OSError() + else: OSError(err) else: - if errno == EAGAIN or errno == EWOULDBLOCK: + if err.int32 == EAGAIN or err.int32 == EWOULDBLOCK: client = InvalidSocket address = "" when noClientRet.int == -1: return else: return noClientRet - else: OSError() + else: OSError(err) else: client.fd = sock client.isBuffered = server.isBuffered @@ -644,7 +628,7 @@ proc getServByName*(name, proto: string): TServent {.tags: [FReadIO].} = var s = winlean.getservbyname(name, proto) else: var s = posix.getservbyname(name, proto) - if s == nil: OSError() + if s == nil: OSError(OSLastError()) result.name = $s.s_name result.aliases = cstringArrayToSeq(s.s_aliases) result.port = TPort(s.s_port) @@ -656,7 +640,7 @@ proc getServByPort*(port: TPort, proto: string): TServent {.tags: [FReadIO].} = var s = winlean.getservbyport(ze(int16(port)).cint, proto) else: var s = posix.getservbyport(ze(int16(port)).cint, proto) - if s == nil: OSError() + if s == nil: OSError(OSLastError()) result.name = $s.s_name result.aliases = cstringArrayToSeq(s.s_aliases) result.port = TPort(s.s_port) @@ -670,7 +654,7 @@ proc getHostByAddr*(ip: string): THostEnt {.tags: [FReadIO].} = when defined(windows): var s = winlean.gethostbyaddr(addr(myaddr), sizeof(myaddr).cuint, cint(sockets.AF_INET)) - if s == nil: OSError() + if s == nil: OSError(OSLastError()) else: var s = posix.gethostbyaddr(addr(myaddr), sizeof(myaddr).TSockLen, cint(posix.AF_INET)) @@ -687,7 +671,7 @@ proc getHostByAddr*(ip: string): THostEnt {.tags: [FReadIO].} = elif s.h_addrtype == posix.AF_INET6: result.addrType = AF_INET6 else: - OSError("unknown h_addrtype") + raise newException(EOS, "unknown h_addrtype") result.addrList = cstringArrayToSeq(s.h_addr_list) result.length = int(s.h_length) @@ -697,7 +681,7 @@ proc getHostByName*(name: string): THostEnt {.tags: [FReadIO].} = var s = winlean.gethostbyname(name) else: var s = posix.gethostbyname(name) - if s == nil: OSError() + if s == nil: OSError(OSLastError()) result.name = $s.h_name result.aliases = cstringArrayToSeq(s.h_aliases) when defined(windows): @@ -708,7 +692,7 @@ proc getHostByName*(name: string): THostEnt {.tags: [FReadIO].} = elif s.h_addrtype == posix.AF_INET6: result.addrType = AF_INET6 else: - OSError("unknown h_addrtype") + raise newException(EOS, "unknown h_addrtype") result.addrList = cstringArrayToSeq(s.h_addr_list) result.length = int(s.h_length) @@ -719,7 +703,7 @@ proc getSockOptInt*(socket: TSocket, level, optname: int): int {. var size = sizeof(res).TSockLen if getsockopt(socket.fd, cint(level), cint(optname), addr(res), addr(size)) < 0'i32: - OSError() + OSError(OSLastError()) result = int(res) proc setSockOptInt*(socket: TSocket, level, optname, optval: int) {. @@ -728,7 +712,7 @@ proc setSockOptInt*(socket: TSocket, level, optname, optval: int) {. var value = cint(optval) if setsockopt(socket.fd, cint(level), cint(optname), addr(value), sizeof(value).TSockLen) < 0'i32: - OSError() + OSError(OSLastError()) proc connect*(socket: TSocket, address: string, port = TPort(0), af: TDomain = AF_INET) {.tags: [FReadIO].} = @@ -746,15 +730,17 @@ proc connect*(socket: TSocket, address: string, port = TPort(0), gaiNim(address, port, hints, aiList) # try all possibilities: var success = false + var lastError: TOSErrorCode var it = aiList while it != nil: if connect(socket.fd, it.ai_addr, it.ai_addrlen.TSockLen) == 0'i32: success = true break + else: lastError = OSLastError() it = it.ai_next freeaddrinfo(aiList) - if not success: OSError() + if not success: OSError(lastError) when defined(ssl): if socket.isSSL: @@ -807,6 +793,7 @@ proc connectAsync*(socket: TSocket, name: string, port = TPort(0), gaiNim(name, port, hints, aiList) # try all possibilities: var success = false + var lastError: TOSErrorCode var it = aiList while it != nil: var ret = connect(socket.fd, it.ai_addr, it.ai_addrlen.TSockLen) @@ -814,22 +801,21 @@ proc connectAsync*(socket: TSocket, name: string, port = TPort(0), success = true break else: - # TODO: Test on Windows. + lastError = OSLastError() when defined(windows): - var err = WSAGetLastError() # Windows EINTR doesn't behave same as POSIX. - if err == WSAEWOULDBLOCK: + if lastError.int32 == WSAEWOULDBLOCK: success = true break else: - if errno == EINTR or errno == EINPROGRESS: + if lastError.int32 == EINTR or lastError.int32 == EINPROGRESS: success = true break it = it.ai_next freeaddrinfo(aiList) - if not success: OSError() + if not success: OSError(lastError) when defined(ssl): if socket.isSSL: socket.sslNoHandshake = true @@ -1110,7 +1096,7 @@ proc waitFor(socket: TSocket, waited: var float, timeout, size: int, var s = @[socket] var startTime = epochTime() let selRet = select(s, timeout - int(waited * 1000.0)) - if selRet < 0: OSError() + if selRet < 0: OSError(OSLastError()) if selRet != 1: raise newException(ETimeout, "Call to '" & funcName & "' timed out.") waited += (epochTime() - startTime) @@ -1259,14 +1245,14 @@ proc readLine*(socket: TSocket, line: var TaintedString, timeout = -1) {. var c: char discard waitFor(socket, waited, timeout, 1, "readLine") var n = recv(socket, addr(c), 1) - if n < 0: OSError() + if n < 0: OSError(OSLastError()) elif n == 0: return if c == '\r': discard waitFor(socket, waited, timeout, 1, "readLine") n = peekChar(socket, c) if n > 0 and c == '\L': discard recv(socket, addr(c), 1) - elif n <= 0: OSError() + elif n <= 0: OSError(OSLastError()) addNlIfEmpty() return elif c == '\L': @@ -1326,6 +1312,7 @@ proc readLineAsync*(socket: TSocket, while true: var c: char var n = recv(socket, addr(c), 1) + #echo(n) if n < 0: if line.len == 0: errorOrNone else: return ReadPartialLine elif n == 0: @@ -1352,7 +1339,7 @@ proc recv*(socket: TSocket): TaintedString {.tags: [FReadIO], deprecated.} = var pos = 0 while true: var bytesRead = recv(socket, addr(string(result)[pos]), bufSize-1) - if bytesRead == -1: OSError() + if bytesRead == -1: OSError(OSLastError()) setLen(result.string, pos + bytesRead) if bytesRead != bufSize-1: break # increase capacity: @@ -1364,7 +1351,7 @@ proc recv*(socket: TSocket): TaintedString {.tags: [FReadIO], deprecated.} = while true: var bytesRead = recv(socket, cstring(buf), bufSize-1) # Error - if bytesRead == -1: OSError() + if bytesRead == -1: OSError(OSLastError()) buf[bytesRead] = '\0' # might not be necessary setLen(buf, bytesRead) @@ -1421,16 +1408,15 @@ proc recvAsync*(socket: TSocket, s: var TaintedString): bool {. else: SSLError("Unknown Error") if bytesRead == -1 and not (when defined(ssl): socket.isSSL else: false): + let err = OSLastError() when defined(windows): - # TODO: Test on Windows - var err = WSAGetLastError() - if err == WSAEWOULDBLOCK: + if err.int32 == WSAEWOULDBLOCK: return False - else: OSError() + else: OSError(err) else: - if errno == EAGAIN or errno == EWOULDBLOCK: + if err.int32 == EAGAIN or err.int32 == EWOULDBLOCK: return False - else: OSError() + else: OSError(err) setLen(s.string, pos + bytesRead) if bytesRead != bufSize-1: break @@ -1475,16 +1461,15 @@ proc recvFromAsync*(socket: TSocket, data: var String, length: int, result = true var callRes = recvFrom(socket, data, length, address, port, flags) if callRes < 0: + let err = OSLastError() when defined(windows): - # TODO: Test on Windows - var err = WSAGetLastError() - if err == WSAEWOULDBLOCK: + if err.int32 == WSAEWOULDBLOCK: return False - else: OSError() + else: OSError(err) else: - if errno == EAGAIN or errno == EWOULDBLOCK: + if err.int32 == EAGAIN or err.int32 == EWOULDBLOCK: return False - else: OSError() + else: OSError(err) proc skip*(socket: TSocket) {.tags: [FReadIO], deprecated.} = ## skips all the data that is pending for the socket @@ -1531,7 +1516,7 @@ proc send*(socket: TSocket, data: string) {.tags: [FWriteIO].} = if socket.isSSL: SSLError() - OSError() + OSError(OSLastError()) proc sendAsync*(socket: TSocket, data: string): int {.tags: [FWriteIO].} = ## sends data to a non-blocking socket. @@ -1561,16 +1546,15 @@ proc sendAsync*(socket: TSocket, data: string): int {.tags: [FWriteIO].} = else: return if result == -1: + let err = OSLastError() when defined(windows): - var err = WSAGetLastError() - # TODO: Test on windows. - if err == WSAEINPROGRESS: + if err.int32 == WSAEINPROGRESS: return 0 - else: OSError() + else: OSError(err) else: - if errno == EAGAIN or errno == EWOULDBLOCK: + if err.int32 == EAGAIN or err.int32 == EWOULDBLOCK: return 0 - else: OSError() + else: OSError(err) proc trySend*(socket: TSocket, data: string): bool {.tags: [FWriteIO].} = @@ -1626,15 +1610,15 @@ proc setBlocking(s: TSocket, blocking: bool) = when defined(Windows): var mode = clong(ord(not blocking)) # 1 for non-blocking, 0 for blocking if ioctlsocket(TWinSocket(s.fd), FIONBIO, addr(mode)) == -1: - OSError() + OSError(OSLastError()) else: # BSD sockets var x: int = fcntl(s.fd, F_GETFL, 0) if x == -1: - OSError() + OSError(OSLastError()) else: var mode = if blocking: x and not O_NONBLOCK else: x or O_NONBLOCK if fcntl(s.fd, F_SETFL, mode) == -1: - OSError() + OSError(OSLastError()) s.nonblocking = not blocking proc connect*(socket: TSocket, address: string, port = TPort(0), timeout: int, @@ -1665,6 +1649,6 @@ proc getFD*(socket: TSocket): cint = return socket.fd when defined(Windows): var wsa: TWSADATA - if WSAStartup(0x0101'i16, wsa) != 0: OSError() + if WSAStartup(0x0101'i16, addr wsa) != 0: OSError(OSLastError()) diff --git a/lib/pure/terminal.nim b/lib/pure/terminal.nim index 3be6088ed..501184aca 100644 --- a/lib/pure/terminal.nim +++ b/lib/pure/terminal.nim @@ -1,312 +1,310 @@ -# -# -# Nimrod's Runtime Library -# (c) Copyright 2012 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## This module contains a few procedures to control the *terminal* -## (also called *console*). On UNIX, the implementation simply uses ANSI escape -## sequences and does not depend on any other module, on Windows it uses the -## Windows API. -## Changing the style is permanent even after program termination! Use the -## code ``system.addQuitProc(resetAttributes)`` to restore the defaults. - -import macros - -when defined(windows): - import windows, os - - var - conHandle: THandle - # = createFile("CONOUT$", GENERIC_WRITE, 0, nil, OPEN_ALWAYS, 0, 0) - - block: - var hTemp = GetStdHandle(STD_OUTPUT_HANDLE) - if DuplicateHandle(GetCurrentProcess(), hTemp, GetCurrentProcess(), - addr(conHandle), 0, 1, DUPLICATE_SAME_ACCESS) == 0: - OSError() - - proc getCursorPos(): tuple [x,y: int] = - var c: TCONSOLE_SCREEN_BUFFER_INFO - if GetConsoleScreenBufferInfo(conHandle, addr(c)) == 0: OSError() - return (int(c.dwCursorPosition.x), int(c.dwCursorPosition.y)) - - proc getAttributes(): int16 = - var c: TCONSOLE_SCREEN_BUFFER_INFO - # workaround Windows bugs: try several times - if GetConsoleScreenBufferInfo(conHandle, addr(c)) != 0: - return c.wAttributes - else: - OSError() - return 0x70'i16 # ERROR: return white background, black text - - var - oldAttr = getAttributes() - -proc setCursorPos*(x, y: int) = - ## sets the terminal's cursor to the (x,y) position. (0,0) is the - ## upper left of the screen. - when defined(windows): - var c: TCoord - c.x = int16(x) - c.y = int16(y) - if SetConsoleCursorPosition(conHandle, c) == 0: OSError() - else: - stdout.write("\e[" & $y & ';' & $x & 'f') - -proc setCursorXPos*(x: int) = - ## sets the terminal's cursor to the x position. The y position is - ## not changed. - when defined(windows): - var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO - var hStdout = conHandle - if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError() - var origin = scrbuf.dwCursorPosition - origin.x = int16(x) - if SetConsoleCursorPosition(conHandle, origin) == 0: OSError() - else: - stdout.write("\e[" & $x & 'G') - -when defined(windows): - proc setCursorYPos*(y: int) = - ## sets the terminal's cursor to the y position. The x position is - ## not changed. **Warning**: This is not supported on UNIX! - when defined(windows): - var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO - var hStdout = conHandle - if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError() - var origin = scrbuf.dwCursorPosition - origin.y = int16(y) - if SetConsoleCursorPosition(conHandle, origin) == 0: OSError() - else: - nil - -proc CursorUp*(count=1) = - ## Moves the cursor up by `count` rows. - when defined(windows): - var p = getCursorPos() - dec(p.y, count) - setCursorPos(p.x, p.y) - else: - stdout.write("\e[" & $count & 'A') - -proc CursorDown*(count=1) = - ## Moves the cursor down by `count` rows. - when defined(windows): - var p = getCursorPos() - inc(p.y, count) - setCursorPos(p.x, p.y) - else: - stdout.write("\e[" & $count & 'B') - -proc CursorForward*(count=1) = - ## Moves the cursor forward by `count` columns. - when defined(windows): - var p = getCursorPos() - inc(p.x, count) - setCursorPos(p.x, p.y) - else: - stdout.write("\e[" & $count & 'C') - -proc CursorBackward*(count=1) = - ## Moves the cursor backward by `count` columns. - when defined(windows): - var p = getCursorPos() - dec(p.x, count) - setCursorPos(p.x, p.y) - else: - stdout.write("\e[" & $count & 'D') - -when true: - nil -else: - proc EraseLineEnd* = - ## Erases from the current cursor position to the end of the current line. - when defined(windows): - nil - else: - stdout.write("\e[K") - - proc EraseLineStart* = - ## Erases from the current cursor position to the start of the current line. - when defined(windows): - nil - else: - stdout.write("\e[1K") - - proc EraseDown* = - ## Erases the screen from the current line down to the bottom of the screen. - when defined(windows): - nil - else: - stdout.write("\e[J") - - proc EraseUp* = - ## Erases the screen from the current line up to the top of the screen. - when defined(windows): - nil - else: - stdout.write("\e[1J") - -proc EraseLine* = - ## Erases the entire current line. - when defined(windows): - var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO - var numwrote: DWORD - var hStdout = conHandle - if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError() - var origin = scrbuf.dwCursorPosition - origin.x = 0'i16 - if SetConsoleCursorPosition(conHandle, origin) == 0: OSError() - var ht = scrbuf.dwSize.Y - origin.Y - var wt = scrbuf.dwSize.X - origin.X - if FillConsoleOutputCharacter(hStdout,' ', ht*wt, - origin, addr(numwrote)) == 0: - OSError() - if FillConsoleOutputAttribute(hStdout, scrbuf.wAttributes, ht * wt, - scrbuf.dwCursorPosition, addr(numwrote)) == 0: - OSError() - else: - stdout.write("\e[2K") - setCursorXPos(0) - -proc EraseScreen* = - ## Erases the screen with the background colour and moves the cursor to home. - when defined(windows): - var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO - var numwrote: DWORD - var origin: TCoord # is inititalized to 0, 0 - var hStdout = conHandle - if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError() - if FillConsoleOutputCharacter(hStdout, ' ', scrbuf.dwSize.X*scrbuf.dwSize.Y, - origin, addr(numwrote)) == 0: - OSError() - if FillConsoleOutputAttribute(hStdout, scrbuf.wAttributes, - scrbuf.dwSize.X * scrbuf.dwSize.Y, - origin, addr(numwrote)) == 0: - OSError() - setCursorXPos(0) - else: - stdout.write("\e[2J") - -proc ResetAttributes* {.noconv.} = - ## resets all attributes; it is advisable to register this as a quit proc - ## with ``system.addQuitProc(resetAttributes)``. - when defined(windows): - discard SetConsoleTextAttribute(conHandle, oldAttr) - else: - stdout.write("\e[0m") - -type - TStyle* = enum ## different styles for text output - styleBright = 1, ## bright text - styleDim, ## dim text - styleUnknown, ## unknown - styleUnderscore = 4, ## underscored text - styleBlink, ## blinking/bold text - styleReverse = 7, ## unknown - styleHidden ## hidden text - -when not defined(windows): - var - # XXX: These better be thread-local - gFG = 0 - gBG = 0 - -proc setStyle*(style: set[TStyle]) = - ## sets the terminal style - when defined(windows): - var a = 0'i16 - if styleBright in style: a = a or int16(FOREGROUND_INTENSITY) - if styleBlink in style: a = a or int16(BACKGROUND_INTENSITY) - if styleReverse in style: a = a or 0x4000'i16 # COMMON_LVB_REVERSE_VIDEO - if styleUnderscore in style: a = a or 0x8000'i16 # COMMON_LVB_UNDERSCORE - discard SetConsoleTextAttribute(conHandle, a) - else: - for s in items(style): - stdout.write("\e[" & $ord(s) & 'm') - -proc WriteStyled*(txt: string, style: set[TStyle] = {styleBright}) = - ## writes the text `txt` in a given `style`. - when defined(windows): - var old = getAttributes() - setStyle(style) - stdout.write(txt) - discard SetConsoleTextAttribute(conHandle, old) - else: - setStyle(style) - stdout.write(txt) - resetAttributes() - if gFG != 0: - stdout.write("\e[" & $ord(gFG) & 'm') - if gBG != 0: - stdout.write("\e[" & $ord(gBG) & 'm') - -type - TForegroundColor* = enum ## terminal's foreground colors - fgBlack = 30, ## black - fgRed, ## red - fgGreen, ## green - fgYellow, ## yellow - fgBlue, ## blue - fgMagenta, ## magenta - fgCyan, ## cyan - fgWhite ## white - - TBackgroundColor* = enum ## terminal's background colors - bgBlack = 40, ## black - bgRed, ## red - bgGreen, ## green - bgYellow, ## yellow - bgBlue, ## blue - bgMagenta, ## magenta - bgCyan, ## cyan - bgWhite ## white - -proc setForegroundColor*(fg: TForegroundColor, bright=false) = - ## sets the terminal's foreground color - when defined(windows): - var old = getAttributes() and not 0x0007 - if bright: - old = old or FOREGROUND_INTENSITY - const lookup: array [TForegroundColor, int] = [ - 0, - (FOREGROUND_RED), - (FOREGROUND_GREEN), - (FOREGROUND_RED or FOREGROUND_GREEN), - (FOREGROUND_BLUE), - (FOREGROUND_RED or FOREGROUND_BLUE), - (FOREGROUND_BLUE or FOREGROUND_GREEN), - (FOREGROUND_BLUE or FOREGROUND_GREEN or FOREGROUND_RED)] - discard SetConsoleTextAttribute(conHandle, toU16(old or lookup[fg])) - else: - gFG = ord(fg) - if bright: inc(gFG, 60) - stdout.write("\e[" & $gFG & 'm') - -proc setBackgroundColor*(bg: TBackgroundColor, bright=false) = - ## sets the terminal's background color - when defined(windows): - var old = getAttributes() and not 0x0070 - if bright: - old = old or BACKGROUND_INTENSITY - const lookup: array [TBackgroundColor, int] = [ - 0, - (BACKGROUND_RED), - (BACKGROUND_GREEN), - (BACKGROUND_RED or BACKGROUND_GREEN), - (BACKGROUND_BLUE), - (BACKGROUND_RED or BACKGROUND_BLUE), - (BACKGROUND_BLUE or BACKGROUND_GREEN), - (BACKGROUND_BLUE or BACKGROUND_GREEN or BACKGROUND_RED)] - discard SetConsoleTextAttribute(conHandle, toU16(old or lookup[bg])) - else: - gBG = ord(bg) - if bright: inc(gBG, 60) - stdout.write("\e[" & $gBG & 'm') +# +# +# Nimrod's Runtime Library +# (c) Copyright 2012 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module contains a few procedures to control the *terminal* +## (also called *console*). On UNIX, the implementation simply uses ANSI escape +## sequences and does not depend on any other module, on Windows it uses the +## Windows API. +## Changing the style is permanent even after program termination! Use the +## code ``system.addQuitProc(resetAttributes)`` to restore the defaults. + +import macros + +when defined(windows): + import windows, os + + var + conHandle: THandle + # = createFile("CONOUT$", GENERIC_WRITE, 0, nil, OPEN_ALWAYS, 0, 0) + + block: + var hTemp = GetStdHandle(STD_OUTPUT_HANDLE) + if DuplicateHandle(GetCurrentProcess(), hTemp, GetCurrentProcess(), + addr(conHandle), 0, 1, DUPLICATE_SAME_ACCESS) == 0: + OSError(OSLastError()) + + proc getCursorPos(): tuple [x,y: int] = + var c: TCONSOLE_SCREEN_BUFFER_INFO + if GetConsoleScreenBufferInfo(conHandle, addr(c)) == 0: OSError(OSLastError()) + return (int(c.dwCursorPosition.x), int(c.dwCursorPosition.y)) + + proc getAttributes(): int16 = + var c: TCONSOLE_SCREEN_BUFFER_INFO + # workaround Windows bugs: try several times + if GetConsoleScreenBufferInfo(conHandle, addr(c)) != 0: + return c.wAttributes + return 0x70'i16 # ERROR: return white background, black text + + var + oldAttr = getAttributes() + +proc setCursorPos*(x, y: int) = + ## sets the terminal's cursor to the (x,y) position. (0,0) is the + ## upper left of the screen. + when defined(windows): + var c: TCoord + c.x = int16(x) + c.y = int16(y) + if SetConsoleCursorPosition(conHandle, c) == 0: OSError(OSLastError()) + else: + stdout.write("\e[" & $y & ';' & $x & 'f') + +proc setCursorXPos*(x: int) = + ## sets the terminal's cursor to the x position. The y position is + ## not changed. + when defined(windows): + var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO + var hStdout = conHandle + if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError(OSLastError()) + var origin = scrbuf.dwCursorPosition + origin.x = int16(x) + if SetConsoleCursorPosition(conHandle, origin) == 0: OSError(OSLastError()) + else: + stdout.write("\e[" & $x & 'G') + +when defined(windows): + proc setCursorYPos*(y: int) = + ## sets the terminal's cursor to the y position. The x position is + ## not changed. **Warning**: This is not supported on UNIX! + when defined(windows): + var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO + var hStdout = conHandle + if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError(OSLastError()) + var origin = scrbuf.dwCursorPosition + origin.y = int16(y) + if SetConsoleCursorPosition(conHandle, origin) == 0: OSError(OSLastError()) + else: + nil + +proc CursorUp*(count=1) = + ## Moves the cursor up by `count` rows. + when defined(windows): + var p = getCursorPos() + dec(p.y, count) + setCursorPos(p.x, p.y) + else: + stdout.write("\e[" & $count & 'A') + +proc CursorDown*(count=1) = + ## Moves the cursor down by `count` rows. + when defined(windows): + var p = getCursorPos() + inc(p.y, count) + setCursorPos(p.x, p.y) + else: + stdout.write("\e[" & $count & 'B') + +proc CursorForward*(count=1) = + ## Moves the cursor forward by `count` columns. + when defined(windows): + var p = getCursorPos() + inc(p.x, count) + setCursorPos(p.x, p.y) + else: + stdout.write("\e[" & $count & 'C') + +proc CursorBackward*(count=1) = + ## Moves the cursor backward by `count` columns. + when defined(windows): + var p = getCursorPos() + dec(p.x, count) + setCursorPos(p.x, p.y) + else: + stdout.write("\e[" & $count & 'D') + +when true: + nil +else: + proc EraseLineEnd* = + ## Erases from the current cursor position to the end of the current line. + when defined(windows): + nil + else: + stdout.write("\e[K") + + proc EraseLineStart* = + ## Erases from the current cursor position to the start of the current line. + when defined(windows): + nil + else: + stdout.write("\e[1K") + + proc EraseDown* = + ## Erases the screen from the current line down to the bottom of the screen. + when defined(windows): + nil + else: + stdout.write("\e[J") + + proc EraseUp* = + ## Erases the screen from the current line up to the top of the screen. + when defined(windows): + nil + else: + stdout.write("\e[1J") + +proc EraseLine* = + ## Erases the entire current line. + when defined(windows): + var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO + var numwrote: DWORD + var hStdout = conHandle + if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError(OSLastError()) + var origin = scrbuf.dwCursorPosition + origin.x = 0'i16 + if SetConsoleCursorPosition(conHandle, origin) == 0: OSError(OSLastError()) + var ht = scrbuf.dwSize.Y - origin.Y + var wt = scrbuf.dwSize.X - origin.X + if FillConsoleOutputCharacter(hStdout,' ', ht*wt, + origin, addr(numwrote)) == 0: + OSError(OSLastError()) + if FillConsoleOutputAttribute(hStdout, scrbuf.wAttributes, ht * wt, + scrbuf.dwCursorPosition, addr(numwrote)) == 0: + OSError(OSLastError()) + else: + stdout.write("\e[2K") + setCursorXPos(0) + +proc EraseScreen* = + ## Erases the screen with the background colour and moves the cursor to home. + when defined(windows): + var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO + var numwrote: DWORD + var origin: TCoord # is inititalized to 0, 0 + var hStdout = conHandle + if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError(OSLastError()) + if FillConsoleOutputCharacter(hStdout, ' ', scrbuf.dwSize.X*scrbuf.dwSize.Y, + origin, addr(numwrote)) == 0: + OSError(OSLastError()) + if FillConsoleOutputAttribute(hStdout, scrbuf.wAttributes, + scrbuf.dwSize.X * scrbuf.dwSize.Y, + origin, addr(numwrote)) == 0: + OSError(OSLastError()) + setCursorXPos(0) + else: + stdout.write("\e[2J") + +proc ResetAttributes* {.noconv.} = + ## resets all attributes; it is advisable to register this as a quit proc + ## with ``system.addQuitProc(resetAttributes)``. + when defined(windows): + discard SetConsoleTextAttribute(conHandle, oldAttr) + else: + stdout.write("\e[0m") + +type + TStyle* = enum ## different styles for text output + styleBright = 1, ## bright text + styleDim, ## dim text + styleUnknown, ## unknown + styleUnderscore = 4, ## underscored text + styleBlink, ## blinking/bold text + styleReverse = 7, ## unknown + styleHidden ## hidden text + +when not defined(windows): + var + # XXX: These better be thread-local + gFG = 0 + gBG = 0 + +proc setStyle*(style: set[TStyle]) = + ## sets the terminal style + when defined(windows): + var a = 0'i16 + if styleBright in style: a = a or int16(FOREGROUND_INTENSITY) + if styleBlink in style: a = a or int16(BACKGROUND_INTENSITY) + if styleReverse in style: a = a or 0x4000'i16 # COMMON_LVB_REVERSE_VIDEO + if styleUnderscore in style: a = a or 0x8000'i16 # COMMON_LVB_UNDERSCORE + discard SetConsoleTextAttribute(conHandle, a) + else: + for s in items(style): + stdout.write("\e[" & $ord(s) & 'm') + +proc WriteStyled*(txt: string, style: set[TStyle] = {styleBright}) = + ## writes the text `txt` in a given `style`. + when defined(windows): + var old = getAttributes() + setStyle(style) + stdout.write(txt) + discard SetConsoleTextAttribute(conHandle, old) + else: + setStyle(style) + stdout.write(txt) + resetAttributes() + if gFG != 0: + stdout.write("\e[" & $ord(gFG) & 'm') + if gBG != 0: + stdout.write("\e[" & $ord(gBG) & 'm') + +type + TForegroundColor* = enum ## terminal's foreground colors + fgBlack = 30, ## black + fgRed, ## red + fgGreen, ## green + fgYellow, ## yellow + fgBlue, ## blue + fgMagenta, ## magenta + fgCyan, ## cyan + fgWhite ## white + + TBackgroundColor* = enum ## terminal's background colors + bgBlack = 40, ## black + bgRed, ## red + bgGreen, ## green + bgYellow, ## yellow + bgBlue, ## blue + bgMagenta, ## magenta + bgCyan, ## cyan + bgWhite ## white + +proc setForegroundColor*(fg: TForegroundColor, bright=false) = + ## sets the terminal's foreground color + when defined(windows): + var old = getAttributes() and not 0x0007 + if bright: + old = old or FOREGROUND_INTENSITY + const lookup: array [TForegroundColor, int] = [ + 0, + (FOREGROUND_RED), + (FOREGROUND_GREEN), + (FOREGROUND_RED or FOREGROUND_GREEN), + (FOREGROUND_BLUE), + (FOREGROUND_RED or FOREGROUND_BLUE), + (FOREGROUND_BLUE or FOREGROUND_GREEN), + (FOREGROUND_BLUE or FOREGROUND_GREEN or FOREGROUND_RED)] + discard SetConsoleTextAttribute(conHandle, toU16(old or lookup[fg])) + else: + gFG = ord(fg) + if bright: inc(gFG, 60) + stdout.write("\e[" & $gFG & 'm') + +proc setBackgroundColor*(bg: TBackgroundColor, bright=false) = + ## sets the terminal's background color + when defined(windows): + var old = getAttributes() and not 0x0070 + if bright: + old = old or BACKGROUND_INTENSITY + const lookup: array [TBackgroundColor, int] = [ + 0, + (BACKGROUND_RED), + (BACKGROUND_GREEN), + (BACKGROUND_RED or BACKGROUND_GREEN), + (BACKGROUND_BLUE), + (BACKGROUND_RED or BACKGROUND_BLUE), + (BACKGROUND_BLUE or BACKGROUND_GREEN), + (BACKGROUND_BLUE or BACKGROUND_GREEN or BACKGROUND_RED)] + discard SetConsoleTextAttribute(conHandle, toU16(old or lookup[bg])) + else: + gBG = ord(bg) + if bright: inc(gBG, 60) + stdout.write("\e[" & $gBG & 'm') proc isatty*(f: TFile): bool = ## returns true if `f` is associated with a terminal device. @@ -318,33 +316,33 @@ proc isatty*(f: TFile): bool = importc: "_isatty", header: "".} result = isatty(fileHandle(f)) != 0'i32 - -proc styledEchoProcessArg(s: string) = write stdout, s -proc styledEchoProcessArg(style: TStyle) = setStyle({style}) -proc styledEchoProcessArg(style: set[TStyle]) = setStyle style -proc styledEchoProcessArg(color: TForegroundColor) = setForeGroundColor color -proc styledEchoProcessArg(color: TBackgroundColor) = setBackGroundColor color - + +proc styledEchoProcessArg(s: string) = write stdout, s +proc styledEchoProcessArg(style: TStyle) = setStyle({style}) +proc styledEchoProcessArg(style: set[TStyle]) = setStyle style +proc styledEchoProcessArg(color: TForegroundColor) = setForeGroundColor color +proc styledEchoProcessArg(color: TBackgroundColor) = setBackGroundColor color + macro styledEcho*(m: varargs[expr]): stmt = ## to be documented. let m = callsite() - result = newNimNode(nnkStmtList) - - for i in countup(1, m.len - 1): - result.add(newCall(bindSym"styledEchoProcessArg", m[i])) - - result.add(newCall(bindSym"write", bindSym"stdout", newStrLitNode("\n"))) - result.add(newCall(bindSym"resetAttributes")) - -when isMainModule: - system.addQuitProc(resetAttributes) - write(stdout, "never mind") - eraseLine() - #setCursorPos(2, 2) - writeStyled("styled text ", {styleBright, styleBlink, styleUnderscore}) - setBackGroundColor(bgCyan, true) - setForeGroundColor(fgBlue) - writeln(stdout, "ordinary text") - + result = newNimNode(nnkStmtList) + + for i in countup(1, m.len - 1): + result.add(newCall(bindSym"styledEchoProcessArg", m[i])) + + result.add(newCall(bindSym"write", bindSym"stdout", newStrLitNode("\n"))) + result.add(newCall(bindSym"resetAttributes")) + +when isMainModule: + system.addQuitProc(resetAttributes) + write(stdout, "never mind") + eraseLine() + #setCursorPos(2, 2) + writeStyled("styled text ", {styleBright, styleBlink, styleUnderscore}) + setBackGroundColor(bgCyan, true) + setForeGroundColor(fgBlue) + writeln(stdout, "ordinary text") + styledEcho("styled text ", {styleBright, styleBlink, styleUnderscore}) \ No newline at end of file diff --git a/lib/pure/times.nim b/lib/pure/times.nim index 2eb8d692b..80a5ad8d3 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -1,7 +1,7 @@ # # # Nimrod's Runtime Library -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -338,24 +338,24 @@ when not defined(JS): const weekDays: array [0..6, TWeekDay] = [ dSun, dMon, dTue, dWed, dThu, dFri, dSat] - result.second = int(tm.second) - result.minute = int(tm.minute) - result.hour = int(tm.hour) - result.monthday = int(tm.monthday) - result.month = TMonth(tm.month) - result.year = tm.year + 1900'i32 - result.weekday = weekDays[int(tm.weekDay)] - result.yearday = int(tm.yearday) - result.isDST = tm.isDST > 0 - if local: - if result.isDST: - result.tzname = getTzname().DST - else: - result.tzname = getTzname().nonDST - else: - result.tzname = "UTC" - - result.timezone = if local: getTimezone() else: 0 + TTimeInfo(second: int(tm.second), + minute: int(tm.minute), + hour: int(tm.hour), + monthday: int(tm.monthday), + month: TMonth(tm.month), + year: tm.year + 1900'i32, + weekday: weekDays[int(tm.weekDay)], + yearday: int(tm.yearday), + isDST: tm.isDST > 0, + tzname: if local: + if tm.isDST > 0: + getTzname().DST + else: + getTzname().nonDST + else: + "UTC", + timezone: if local: getTimezone() else: 0 + ) proc timeInfoToTM(t: TTimeInfo): structTM = const diff --git a/lib/pure/xmlparser.nim b/lib/pure/xmlparser.nim index 8c5c5f5ab..355053e0d 100644 --- a/lib/pure/xmlparser.nim +++ b/lib/pure/xmlparser.nim @@ -110,11 +110,9 @@ proc parseXml*(s: PStream, filename: string, of xmlElementOpen, xmlElementStart: result = parse(x, errors) break - of xmlComment, xmlWhitespace: nil # just skip it + of xmlComment, xmlWhitespace, xmlSpecial: nil # just skip it of xmlError: errors.add(errorMsg(x)) - of xmlSpecial: - errors.add(errorMsg(x, " expected")) else: errors.add(errorMsg(x, " expected")) break diff --git a/lib/pure/xmltree.nim b/lib/pure/xmltree.nim index e3fc27237..d5821d8f3 100644 --- a/lib/pure/xmltree.nim +++ b/lib/pure/xmltree.nim @@ -71,6 +71,16 @@ proc text*(n: PXmlNode): string {.inline.} = assert n.k in {xnText, xnComment, xnCData, xnEntity} result = n.fText +proc rawText*(n: PXmlNode): string {.inline.} = + ## returns the underlying 'text' string by reference. + ## This is only used for speed hacks. + shallowCopy(result, n.fText) + +proc rawTag*(n: PXmlNode): string {.inline.} = + ## returns the underlying 'tag' string by reference. + ## This is only used for speed hacks. + shallowCopy(result, n.fTag) + proc innerText*(n: PXmlNode): string = ## gets the inner text of `n`. `n` has to be an ``xnElement`` node. Only ## ``xnText`` and ``xnEntity`` nodes are considered part of `n`'s inner text, diff --git a/lib/system/syslocks.nim b/lib/system/syslocks.nim index 4619eaddb..4d81dee01 100644 --- a/lib/system/syslocks.nim +++ b/lib/system/syslocks.nim @@ -19,7 +19,7 @@ when defined(Windows): OwningThread: int LockSemaphore: int Reserved: int32 - + TSysCond = THandle proc InitSysLock(L: var TSysLock) {.stdcall, noSideEffect, diff --git a/lib/windows/winlean.nim b/lib/windows/winlean.nim index fa4925ee6..d448d2b10 100644 --- a/lib/windows/winlean.nim +++ b/lib/windows/winlean.nim @@ -489,7 +489,7 @@ proc FD_SET*(Socket: TWinSocket, FDSet: var TFDSet) = proc FD_ZERO*(FDSet: var TFDSet) = FDSet.fd_count = 0 -proc WSAStartup*(wVersionRequired: int16, WSData: var TWSAData): cint {. +proc WSAStartup*(wVersionRequired: int16, WSData: ptr TWSAData): cint {. stdcall, importc: "WSAStartup", dynlib: ws2dll.} proc getaddrinfo*(nodename, servname: cstring, hints: ptr TAddrInfo, diff --git a/lib/wrappers/gtk/glib2.nim b/lib/wrappers/gtk/glib2.nim index 4151ba954..3fa672c85 100644 --- a/lib/wrappers/gtk/glib2.nim +++ b/lib/wrappers/gtk/glib2.nim @@ -27,7 +27,7 @@ type gshort* = cshort glong* = clong gint* = cint - gboolean* = bool + gboolean* = distinct gint guchar* = char gushort* = int16 gulong* = int @@ -173,6 +173,12 @@ type TGBoxedFreeFunc* = proc (boxed: gpointer){.cdecl.} PGsource = pointer # I don't know and don't care +converter gbool*(nimbool: bool): gboolean = + return ord(nimbool).gboolean + +converter toBool*(gbool: gboolean): bool = + return int(gbool) == 1 + const G_TYPE_FUNDAMENTAL_SHIFT* = 2 G_TYPE_FUNDAMENTAL_MAX* = 255 shl G_TYPE_FUNDAMENTAL_SHIFT diff --git a/lib/wrappers/gtk/gtk2.nim b/lib/wrappers/gtk/gtk2.nim index 6b418024e..63e69130a 100644 --- a/lib/wrappers/gtk/gtk2.nim +++ b/lib/wrappers/gtk/gtk2.nim @@ -2,6 +2,8 @@ import glib2, atk, pango, gdk2pixbuf, gdk2 +export gbool, toBool + when defined(win32): const lib = "libgtk-win32-2.0-0.dll" diff --git a/lib/wrappers/lua/lauxlib.nim b/lib/wrappers/lua/lauxlib.nim index bcb806f74..b985858d3 100644 --- a/lib/wrappers/lua/lauxlib.nim +++ b/lib/wrappers/lua/lauxlib.nim @@ -26,9 +26,9 @@ import proc pushstring*(L: PState, s: string) # compatibilty macros -proc getn*(L: PState, n: int): int +proc getn*(L: PState, n: cint): cint # calls lua_objlen -proc setn*(L: PState, t, n: int) +proc setn*(L: PState, t, n: cint) # does nothing! type Treg*{.final.} = object @@ -37,58 +37,45 @@ type Preg* = ptr Treg -proc openlib*(L: PState, libname: cstring, lr: Preg, nup: int){.cdecl, - dynlib: lua.LIB_NAME, importc: "luaL_openlib".} -proc register*(L: PState, libname: cstring, lr: Preg){.cdecl, - dynlib: lua.LIB_NAME, importc: "luaL_register".} -proc getmetafield*(L: PState, obj: int, e: cstring): int{.cdecl, - dynlib: lua.LIB_NAME, importc: "luaL_getmetafield".} -proc callmeta*(L: PState, obj: int, e: cstring): int{.cdecl, - dynlib: LIB_NAME, importc: "luaL_callmeta".} -proc typerror*(L: PState, narg: int, tname: cstring): int{.cdecl, - dynlib: LIB_NAME, importc: "luaL_typerror".} -proc argerror*(L: PState, numarg: int, extramsg: cstring): int{.cdecl, - dynlib: LIB_NAME, importc: "luaL_argerror".} -proc checklstring*(L: PState, numArg: int, len: ptr int): cstring{.cdecl, - dynlib: LIB_NAME, importc: "luaL_checklstring".} -proc optlstring*(L: PState, numArg: int, def: cstring, len: ptr int): cstring{. - cdecl, dynlib: LIB_NAME, importc: "luaL_optlstring".} -proc checknumber*(L: PState, numArg: int): Number{.cdecl, - dynlib: LIB_NAME, importc: "luaL_checknumber".} -proc optnumber*(L: PState, nArg: int, def: Number): Number{.cdecl, - dynlib: LIB_NAME, importc: "luaL_optnumber".} -proc checkinteger*(L: PState, numArg: int): Integer{.cdecl, - dynlib: LIB_NAME, importc: "luaL_checkinteger".} -proc optinteger*(L: PState, nArg: int, def: Integer): Integer{. - cdecl, dynlib: LIB_NAME, importc: "luaL_optinteger".} -proc checkstack*(L: PState, sz: int, msg: cstring){.cdecl, - dynlib: LIB_NAME, importc: "luaL_checkstack".} -proc checktype*(L: PState, narg, t: int){.cdecl, dynlib: LIB_NAME, - importc: "luaL_checktype".} -proc checkany*(L: PState, narg: int){.cdecl, dynlib: LIB_NAME, - importc: "luaL_checkany".} -proc newmetatable*(L: PState, tname: cstring): int{.cdecl, - dynlib: LIB_NAME, importc: "luaL_newmetatable".} -proc checkudata*(L: PState, ud: int, tname: cstring): Pointer{.cdecl, - dynlib: LIB_NAME, importc: "luaL_checkudata".} -proc where*(L: PState, lvl: int){.cdecl, dynlib: LIB_NAME, - importc: "luaL_where".} -proc error*(L: PState, fmt: cstring): int{.cdecl, varargs, - dynlib: LIB_NAME, importc: "luaL_error".} -proc checkoption*(L: PState, narg: int, def: cstring, lst: cstringArray): int{. - cdecl, dynlib: LIB_NAME, importc: "luaL_checkoption".} -proc reference*(L: PState, t: int): int{.cdecl, dynlib: LIB_NAME, - importc: "luaL_ref".} -proc unref*(L: PState, t, theref: int){.cdecl, dynlib: LIB_NAME, - importc: "luaL_unref".} -proc loadfile*(L: PState, filename: cstring): int{.cdecl, - dynlib: LIB_NAME, importc: "luaL_loadfile".} -proc loadbuffer*(L: PState, buff: cstring, size: int, name: cstring): int{. - cdecl, dynlib: LIB_NAME, importc: "luaL_loadbuffer".} -proc loadstring*(L: PState, s: cstring): int{.cdecl, dynlib: LIB_NAME, - importc: "luaL_loadstring".} -proc newstate*(): PState{.cdecl, dynlib: LIB_NAME, - importc: "luaL_newstate".} + +{.push callConv: cdecl, dynlib: lua.LIB_NAME.} +{.push importc: "luaL_$1".} + +proc openlib*(L: PState, libname: cstring, lr: Preg, nup: cint) +proc register*(L: PState, libname: cstring, lr: Preg) + +proc getmetafield*(L: PState, obj: cint, e: cstring): cint +proc callmeta*(L: PState, obj: cint, e: cstring): cint +proc typerror*(L: PState, narg: cint, tname: cstring): cint +proc argerror*(L: PState, numarg: cint, extramsg: cstring): cint +proc checklstring*(L: PState, numArg: cint, len: ptr int): cstring +proc optlstring*(L: PState, numArg: cint, def: cstring, len: ptr cint): cstring +proc checknumber*(L: PState, numArg: cint): Number +proc optnumber*(L: PState, nArg: cint, def: Number): Number +proc checkinteger*(L: PState, numArg: cint): Integer +proc optinteger*(L: PState, nArg: cint, def: Integer): Integer +proc checkstack*(L: PState, sz: cint, msg: cstring) +proc checktype*(L: PState, narg, t: cint) + +proc checkany*(L: PState, narg: cint) +proc newmetatable*(L: PState, tname: cstring): cint + +proc checkudata*(L: PState, ud: cint, tname: cstring): Pointer +proc where*(L: PState, lvl: cint) +proc error*(L: PState, fmt: cstring): cint{.varargs.} +proc checkoption*(L: PState, narg: cint, def: cstring, lst: cstringArray): cint + +proc unref*(L: PState, t, theref: cint) +proc loadfile*(L: PState, filename: cstring): cint +proc loadbuffer*(L: PState, buff: cstring, size: cint, name: cstring): cint +proc loadstring*(L: PState, s: cstring): cint +proc newstate*(): PState + +{.pop.} +proc reference*(L: PState, t: cint): cint{.importc: "luaL_ref".} + +{.pop.} + proc open*(): PState # compatibility; moved from unit lua to lauxlib because it needs luaL_newstate # @@ -96,18 +83,18 @@ proc open*(): PState #** some useful macros #** =============================================================== # -proc argcheck*(L: PState, cond: bool, numarg: int, extramsg: cstring) -proc checkstring*(L: PState, n: int): cstring -proc optstring*(L: PState, n: int, d: cstring): cstring -proc checkint*(L: PState, n: int): int -proc checklong*(L: PState, n: int): int32 -proc optint*(L: PState, n: int, d: float64): int -proc optlong*(L: PState, n: int, d: float64): int32 -proc dofile*(L: PState, filename: cstring): int -proc dostring*(L: PState, str: cstring): int +proc argcheck*(L: PState, cond: bool, numarg: cint, extramsg: cstring) +proc checkstring*(L: PState, n: cint): cstring +proc optstring*(L: PState, n: cint, d: cstring): cstring +proc checkint*(L: PState, n: cint): cint +proc checklong*(L: PState, n: cint): clong +proc optint*(L: PState, n: cint, d: float64): cint +proc optlong*(L: PState, n: cint, d: float64): clong +proc dofile*(L: PState, filename: cstring): cint +proc dostring*(L: PState, str: cstring): cint proc getmetatable*(L: PState, tname: cstring) # not translated: - # #define luaL_opt(L,f,n,d) (lua_isnoneornil(L,(n)) ? (d) : f(L,(n))) + # #define luaL_opt(L,f,n,d) (lua_isnoneornil(L,(n)) ? (d) : f(L,(n))) # #** ======================================================= #** Generic Buffer manipulation @@ -119,7 +106,7 @@ const # note: this is just arbitrary, as it related to t type Buffer*{.final.} = object p*: cstring # current position in buffer - lvl*: int # number of strings in the stack (level) + lvl*: cint # number of strings in the stack (level) L*: PState buffer*: array[0..BUFFERSIZE - 1, Char] # warning: see note above about LUAL_BUFFERSIZE @@ -130,80 +117,76 @@ proc addchar*(B: PBuffer, c: Char) # compatibility only (alias for luaL_addchar) proc putchar*(B: PBuffer, c: Char) # warning: see note above about LUAL_BUFFERSIZE -proc addsize*(B: PBuffer, n: int) -proc buffinit*(L: PState, B: PBuffer){.cdecl, dynlib: LIB_NAME, - importc: "luaL_buffinit".} -proc prepbuffer*(B: PBuffer): cstring{.cdecl, dynlib: LIB_NAME, - importc: "luaL_prepbuffer".} -proc addlstring*(B: PBuffer, s: cstring, L: int){.cdecl, - dynlib: LIB_NAME, importc: "luaL_addlstring".} -proc addstring*(B: PBuffer, s: cstring){.cdecl, dynlib: LIB_NAME, - importc: "luaL_addstring".} -proc addvalue*(B: PBuffer){.cdecl, dynlib: LIB_NAME, - importc: "luaL_addvalue".} -proc pushresult*(B: PBuffer){.cdecl, dynlib: LIB_NAME, - importc: "luaL_pushresult".} -proc gsub*(L: PState, s, p, r: cstring): cstring{.cdecl, - dynlib: LIB_NAME, importc: "luaL_gsub".} -proc findtable*(L: PState, idx: int, fname: cstring, szhint: int): cstring{. - cdecl, dynlib: LIB_NAME, importc: "luaL_findtable".} +proc addsize*(B: PBuffer, n: cint) + +{.push callConv: cdecl, dynlib: lua.LIB_NAME, importc: "luaL_$1".} +proc buffinit*(L: PState, B: PBuffer) +proc prepbuffer*(B: PBuffer): cstring +proc addlstring*(B: PBuffer, s: cstring, L: cint) +proc addstring*(B: PBuffer, s: cstring) +proc addvalue*(B: PBuffer) +proc pushresult*(B: PBuffer) +proc gsub*(L: PState, s, p, r: cstring): cstring +proc findtable*(L: PState, idx: cint, fname: cstring, szhint: cint): cstring # compatibility with ref system # pre-defined references +{.pop.} + const NOREF* = - 2 REFNIL* = - 1 -proc unref*(L: PState, theref: int) -proc getref*(L: PState, theref: int) +proc unref*(L: PState, theref: cint) +proc getref*(L: PState, theref: cint) # #** Compatibility macros and functions # # implementation proc pushstring(L: PState, s: string) = - pushlstring(L, cstring(s), len(s)) + pushlstring(L, cstring(s), s.len.cint) -proc getn(L: PState, n: int): int = +proc getn(L: PState, n: cint): cint = Result = objlen(L, n) -proc setn(L: PState, t, n: int) = +proc setn(L: PState, t, n: cint) = # does nothing as this operation is deprecated nil proc open(): PState = Result = newstate() -proc dofile(L: PState, filename: cstring): int = +proc dofile(L: PState, filename: cstring): cint = Result = loadfile(L, filename) if Result == 0: Result = pcall(L, 0, MULTRET, 0) -proc dostring(L: PState, str: cstring): int = +proc dostring(L: PState, str: cstring): cint = Result = loadstring(L, str) if Result == 0: Result = pcall(L, 0, MULTRET, 0) proc getmetatable(L: PState, tname: cstring) = getfield(L, REGISTRYINDEX, tname) -proc argcheck(L: PState, cond: bool, numarg: int, extramsg: cstring) = +proc argcheck(L: PState, cond: bool, numarg: cint, extramsg: cstring) = if not cond: discard argerror(L, numarg, extramsg) -proc checkstring(L: PState, n: int): cstring = +proc checkstring(L: PState, n: cint): cstring = Result = checklstring(L, n, nil) -proc optstring(L: PState, n: int, d: cstring): cstring = +proc optstring(L: PState, n: cint, d: cstring): cstring = Result = optlstring(L, n, d, nil) -proc checkint(L: PState, n: int): int = - Result = toInt(checknumber(L, n)) +proc checkint(L: PState, n: cint): cint = + Result = cint(checknumber(L, n)) -proc checklong(L: PState, n: int): int32 = +proc checklong(L: PState, n: cint): clong = Result = int32(ToInt(checknumber(L, n))) -proc optint(L: PState, n: int, d: float64): int = - Result = int(ToInt(optnumber(L, n, d))) +proc optint(L: PState, n: cint, d: float64): cint = + Result = optnumber(L, n, d).cint -proc optlong(L: PState, n: int, d: float64): int32 = +proc optlong(L: PState, n: cint, d: float64): clong = Result = int32(ToInt(optnumber(L, n, d))) proc addchar(B: PBuffer, c: Char) = @@ -215,11 +198,11 @@ proc addchar(B: PBuffer, c: Char) = proc putchar(B: PBuffer, c: Char) = addchar(B, c) -proc addsize(B: PBuffer, n: int) = +proc addsize(B: PBuffer, n: cint) = B.p = cast[cstring](cast[int](B.p) + n) -proc unref(L: PState, theref: int) = +proc unref(L: PState, theref: cint) = unref(L, REGISTRYINDEX, theref) -proc getref(L: PState, theref: int) = +proc getref(L: PState, theref: cint) = rawgeti(L, REGISTRYINDEX, theref) diff --git a/lib/wrappers/lua/lua.nim b/lib/wrappers/lua/lua.nim index 0346c4285..153714cee 100644 --- a/lib/wrappers/lua/lua.nim +++ b/lib/wrappers/lua/lua.nim @@ -51,16 +51,16 @@ when defined(useLuajit): else: when defined(MACOSX): const - NAME* = "liblua(|5.2|5.1|5.0).dylib" - LIB_NAME* = "liblua(|5.2|5.1|5.0).dylib" + NAME* = "liblua(|5.1|5.0).dylib" + LIB_NAME* = "liblua(|5.1|5.0).dylib" elif defined(UNIX): const - NAME* = "liblua(|5.2|5.1|5.0).so(|.0)" - LIB_NAME* = "liblua(|5.2|5.1|5.0).so(|.0)" + NAME* = "liblua(|5.1|5.0).so(|.0)" + LIB_NAME* = "liblua(|5.1|5.0).so(|.0)" else: const - NAME* = "lua(|5.2|5.1|5.0).dll" - LIB_NAME* = "lua(|5.2|5.1|5.0).dll" + NAME* = "lua(|5.1|5.0).dll" + LIB_NAME* = "lua(|5.1|5.0).dll" const VERSION* = "Lua 5.1" @@ -76,7 +76,7 @@ const ENVIRONINDEX* = - 10001 GLOBALSINDEX* = - 10002 -proc upvalueindex*(I: int): int +proc upvalueindex*(I: cint): cint const # thread status; 0 is OK constYIELD* = 1 ERRRUN* = 2 @@ -86,16 +86,16 @@ const # thread status; 0 is OK type PState* = Pointer - CFunction* = proc (L: PState): int{.cdecl.} + CFunction* = proc (L: PState): cint{.cdecl.} # #** functions that read/write blocks when loading/dumping Lua chunks # type - Reader* = proc (L: PState, ud: Pointer, sz: ptr int): cstring{.cdecl.} - Writer* = proc (L: PState, p: Pointer, sz: int, ud: Pointer): int{.cdecl.} - Alloc* = proc (ud, theptr: Pointer, osize, nsize: int){.cdecl.} + Reader* = proc (L: PState, ud: Pointer, sz: ptr cint): cstring{.cdecl.} + Writer* = proc (L: PState, p: Pointer, sz: cint, ud: Pointer): cint{.cdecl.} + Alloc* = proc (ud, theptr: Pointer, osize, nsize: cint){.cdecl.} const TNONE* = - 1 @@ -112,130 +112,86 @@ const type # Type of Numbers in Lua Number* = float - Integer* = int + Integer* = cint + +{.pragma: ilua, importc: "lua_$1".} + +{.push callConv: cdecl, dynlib: LibName.} +#{.push importc: "lua_$1".} + +proc newstate*(f: Alloc, ud: Pointer): PState {.ilua.} + +proc close*(L: PState){.ilua.} +proc newthread*(L: PState): PState{.ilua.} +proc atpanic*(L: PState, panicf: CFunction): CFunction{.ilua.} + +proc gettop*(L: PState): cint{.ilua.} +proc settop*(L: PState, idx: cint){.ilua.} +proc pushvalue*(L: PState, Idx: cint){.ilua.} +proc remove*(L: PState, idx: cint){.ilua.} +proc insert*(L: PState, idx: cint){.ilua.} +proc replace*(L: PState, idx: cint){.ilua.} +proc checkstack*(L: PState, sz: cint): cint{.ilua.} +proc xmove*(`from`, `to`: PState, n: cint){.ilua.} +proc isnumber*(L: PState, idx: cint): cint{.ilua.} +proc isstring*(L: PState, idx: cint): cint{.ilua.} +proc iscfunction*(L: PState, idx: cint): cint{.ilua.} +proc isuserdata*(L: PState, idx: cint): cint{.ilua.} +proc luatype*(L: PState, idx: cint): cint{.importc: "lua_type".} +proc typename*(L: PState, tp: cint): cstring{.ilua.} +proc equal*(L: PState, idx1, idx2: cint): cint{.ilua.} +proc rawequal*(L: PState, idx1, idx2: cint): cint{.ilua.} +proc lessthan*(L: PState, idx1, idx2: cint): cint{.ilua.} +proc tonumber*(L: PState, idx: cint): Number{.ilua.} +proc tointeger*(L: PState, idx: cint): Integer{.ilua.} +proc toboolean*(L: PState, idx: cint): cint{.ilua.} +proc tolstring*(L: PState, idx: cint, length: ptr cint): cstring{.ilua.} +proc objlen*(L: PState, idx: cint): cint{.ilua.} +proc tocfunction*(L: PState, idx: cint): CFunction{.ilua.} +proc touserdata*(L: PState, idx: cint): Pointer{.ilua.} +proc tothread*(L: PState, idx: cint): PState{.ilua.} +proc topointer*(L: PState, idx: cint): Pointer{.ilua.} +proc pushnil*(L: PState){.ilua.} +proc pushnumber*(L: PState, n: Number){.ilua.} +proc pushinteger*(L: PState, n: Integer){.ilua.} +proc pushlstring*(L: PState, s: cstring, len: cint){.ilua.} +proc pushstring*(L: PState, s: cstring){.ilua.} +proc pushvfstring*(L: PState, fmt: cstring, argp: Pointer): cstring{.ilua.} +proc pushfstring*(L: PState, fmt: cstring): cstring{.varargs,ilua.} +proc pushcclosure*(L: PState, fn: CFunction, n: cint){.ilua.} +proc pushboolean*(L: PState, b: cint){.ilua.} +proc pushlightuserdata*(L: PState, p: Pointer){.ilua.} +proc pushthread*(L: PState){.ilua.} +proc gettable*(L: PState, idx: cint){.ilua.} +proc getfield*(L: Pstate, idx: cint, k: cstring){.ilua.} +proc rawget*(L: PState, idx: cint){.ilua.} +proc rawgeti*(L: PState, idx, n: cint){.ilua.} +proc createtable*(L: PState, narr, nrec: cint){.ilua.} +proc newuserdata*(L: PState, sz: cint): Pointer{.ilua.} +proc getmetatable*(L: PState, objindex: cint): cint{.ilua.} +proc getfenv*(L: PState, idx: cint){.ilua.} +proc settable*(L: PState, idx: cint){.ilua.} +proc setfield*(L: PState, idx: cint, k: cstring){.ilua.} +proc rawset*(L: PState, idx: cint){.ilua.} +proc rawseti*(L: PState, idx, n: cint){.ilua.} +proc setmetatable*(L: PState, objindex: cint): cint{.ilua.} +proc setfenv*(L: PState, idx: cint): cint{.ilua.} +proc call*(L: PState, nargs, nresults: cint){.ilua.} +proc pcall*(L: PState, nargs, nresults, errf: cint): cint{.ilua.} +proc cpcall*(L: PState, func: CFunction, ud: Pointer): cint{.ilua.} +proc load*(L: PState, reader: Reader, dt: Pointer, chunkname: cstring): cint{.ilua.} +proc dump*(L: PState, writer: Writer, data: Pointer): cint{.ilua.} +proc luayield*(L: PState, nresults: cint): cint{.importc: "lua_yield".} +proc resume*(L: PState, narg: cint): cint{.ilua.} +proc status*(L: PState): cint{.ilua.} +proc gc*(L: PState, what, data: cint): cint{.ilua.} +proc error*(L: PState): cint{.ilua.} +proc next*(L: PState, idx: cint): cint{.ilua.} +proc concat*(L: PState, n: cint){.ilua.} +proc getallocf*(L: PState, ud: ptr Pointer): Alloc{.ilua.} +proc setallocf*(L: PState, f: Alloc, ud: Pointer){.ilua.} +{.pop.} -proc newstate*(f: Alloc, ud: Pointer): PState{.cdecl, dynlib: NAME, - importc: "lua_newstate".} -proc close*(L: PState){.cdecl, dynlib: NAME, importc: "lua_close".} -proc newthread*(L: PState): PState{.cdecl, dynlib: NAME, - importc: "lua_newthread".} -proc atpanic*(L: PState, panicf: CFunction): CFunction{.cdecl, dynlib: NAME, - importc: "lua_atpanic".} -proc gettop*(L: PState): int{.cdecl, dynlib: NAME, importc: "lua_gettop".} -proc settop*(L: PState, idx: int){.cdecl, dynlib: NAME, importc: "lua_settop".} -proc pushvalue*(L: PState, Idx: int){.cdecl, dynlib: NAME, - importc: "lua_pushvalue".} -proc remove*(L: PState, idx: int){.cdecl, dynlib: NAME, importc: "lua_remove".} -proc insert*(L: PState, idx: int){.cdecl, dynlib: NAME, importc: "lua_insert".} -proc replace*(L: PState, idx: int){.cdecl, dynlib: NAME, importc: "lua_replace".} -proc checkstack*(L: PState, sz: int): cint{.cdecl, dynlib: NAME, - importc: "lua_checkstack".} -proc xmove*(`from`, `to`: PState, n: int){.cdecl, dynlib: NAME, - importc: "lua_xmove".} -proc isnumber*(L: PState, idx: int): cint{.cdecl, dynlib: NAME, - importc: "lua_isnumber".} -proc isstring*(L: PState, idx: int): cint{.cdecl, dynlib: NAME, - importc: "lua_isstring".} -proc iscfunction*(L: PState, idx: int): cint{.cdecl, dynlib: NAME, - importc: "lua_iscfunction".} -proc isuserdata*(L: PState, idx: int): cint{.cdecl, dynlib: NAME, - importc: "lua_isuserdata".} -proc luatype*(L: PState, idx: int): int{.cdecl, dynlib: NAME, importc: "lua_type".} -proc typename*(L: PState, tp: int): cstring{.cdecl, dynlib: NAME, - importc: "lua_typename".} -proc equal*(L: PState, idx1, idx2: int): cint{.cdecl, dynlib: NAME, - importc: "lua_equal".} -proc rawequal*(L: PState, idx1, idx2: int): cint{.cdecl, dynlib: NAME, - importc: "lua_rawequal".} -proc lessthan*(L: PState, idx1, idx2: int): cint{.cdecl, dynlib: NAME, - importc: "lua_lessthan".} -proc tonumber*(L: PState, idx: int): Number{.cdecl, dynlib: NAME, - importc: "lua_tonumber".} -proc tointeger*(L: PState, idx: int): Integer{.cdecl, dynlib: NAME, - importc: "lua_tointeger".} -proc toboolean*(L: PState, idx: int): cint{.cdecl, dynlib: NAME, - importc: "lua_toboolean".} -proc tolstring*(L: PState, idx: int, length: ptr int): cstring{.cdecl, - dynlib: NAME, importc: "lua_tolstring".} -proc objlen*(L: PState, idx: int): int{.cdecl, dynlib: NAME, - importc: "lua_objlen".} -proc tocfunction*(L: PState, idx: int): CFunction{.cdecl, dynlib: NAME, - importc: "lua_tocfunction".} -proc touserdata*(L: PState, idx: int): Pointer{.cdecl, dynlib: NAME, - importc: "lua_touserdata".} -proc tothread*(L: PState, idx: int): PState{.cdecl, dynlib: NAME, - importc: "lua_tothread".} -proc topointer*(L: PState, idx: int): Pointer{.cdecl, dynlib: NAME, - importc: "lua_topointer".} -proc pushnil*(L: PState){.cdecl, dynlib: NAME, importc: "lua_pushnil".} -proc pushnumber*(L: PState, n: Number){.cdecl, dynlib: NAME, - importc: "lua_pushnumber".} -proc pushinteger*(L: PState, n: Integer){.cdecl, dynlib: NAME, - importc: "lua_pushinteger".} -proc pushlstring*(L: PState, s: cstring, len: int){.cdecl, dynlib: NAME, - importc: "lua_pushlstring".} -proc pushstring*(L: PState, s: cstring){.cdecl, dynlib: NAME, - importc: "lua_pushstring".} -proc pushvfstring*(L: PState, fmt: cstring, argp: Pointer): cstring{.cdecl, - dynlib: NAME, importc: "lua_pushvfstring".} -proc pushfstring*(L: PState, fmt: cstring): cstring{.cdecl, varargs, - dynlib: NAME, importc: "lua_pushfstring".} -proc pushcclosure*(L: PState, fn: CFunction, n: int){.cdecl, dynlib: NAME, - importc: "lua_pushcclosure".} -proc pushboolean*(L: PState, b: cint){.cdecl, dynlib: NAME, - importc: "lua_pushboolean".} -proc pushlightuserdata*(L: PState, p: Pointer){.cdecl, dynlib: NAME, - importc: "lua_pushlightuserdata".} -proc pushthread*(L: PState){.cdecl, dynlib: NAME, importc: "lua_pushthread".} -proc gettable*(L: PState, idx: int){.cdecl, dynlib: NAME, - importc: "lua_gettable".} -proc getfield*(L: Pstate, idx: int, k: cstring){.cdecl, dynlib: NAME, - importc: "lua_getfield".} -proc rawget*(L: PState, idx: int){.cdecl, dynlib: NAME, importc: "lua_rawget".} -proc rawgeti*(L: PState, idx, n: int){.cdecl, dynlib: NAME, - importc: "lua_rawgeti".} -proc createtable*(L: PState, narr, nrec: int){.cdecl, dynlib: NAME, - importc: "lua_createtable".} -proc newuserdata*(L: PState, sz: int): Pointer{.cdecl, dynlib: NAME, - importc: "lua_newuserdata".} -proc getmetatable*(L: PState, objindex: int): int{.cdecl, dynlib: NAME, - importc: "lua_getmetatable".} -proc getfenv*(L: PState, idx: int){.cdecl, dynlib: NAME, importc: "lua_getfenv".} -proc settable*(L: PState, idx: int){.cdecl, dynlib: NAME, - importc: "lua_settable".} -proc setfield*(L: PState, idx: int, k: cstring){.cdecl, dynlib: NAME, - importc: "lua_setfield".} -proc rawset*(L: PState, idx: int){.cdecl, dynlib: NAME, importc: "lua_rawset".} -proc rawseti*(L: PState, idx, n: int){.cdecl, dynlib: NAME, - importc: "lua_rawseti".} -proc setmetatable*(L: PState, objindex: int): int{.cdecl, dynlib: NAME, - importc: "lua_setmetatable".} -proc setfenv*(L: PState, idx: int): int{.cdecl, dynlib: NAME, - importc: "lua_setfenv".} -proc call*(L: PState, nargs, nresults: int){.cdecl, dynlib: NAME, - importc: "lua_call".} -proc pcall*(L: PState, nargs, nresults, errf: int): int{.cdecl, dynlib: NAME, - importc: "lua_pcall".} -proc cpcall*(L: PState, func: CFunction, ud: Pointer): int{.cdecl, dynlib: NAME, - importc: "lua_cpcall".} -proc load*(L: PState, reader: Reader, dt: Pointer, chunkname: cstring): int{. - cdecl, dynlib: NAME, importc: "lua_load".} -proc dump*(L: PState, writer: Writer, data: Pointer): int{.cdecl, dynlib: NAME, - importc: "lua_dump".} -proc luayield*(L: PState, nresults: int): int{.cdecl, dynlib: NAME, - importc: "lua_yield".} -proc resume*(L: PState, narg: int): int{.cdecl, dynlib: NAME, - importc: "lua_resume".} -proc status*(L: PState): int{.cdecl, dynlib: NAME, importc: "lua_status".} -proc gc*(L: PState, what, data: int): int{.cdecl, dynlib: NAME, - importc: "lua_gc".} -proc error*(L: PState): int{.cdecl, dynlib: NAME, importc: "lua_error".} -proc next*(L: PState, idx: int): int{.cdecl, dynlib: NAME, importc: "lua_next".} -proc concat*(L: PState, n: int){.cdecl, dynlib: NAME, importc: "lua_concat".} -proc getallocf*(L: PState, ud: ptr Pointer): Alloc{.cdecl, dynlib: NAME, - importc: "lua_getallocf".} -proc setallocf*(L: PState, f: Alloc, ud: Pointer){.cdecl, dynlib: NAME, - importc: "lua_setallocf".} # #** Garbage-collection functions and options # @@ -256,29 +212,29 @@ const #** =============================================================== # -proc pop*(L: PState, n: int) +proc pop*(L: PState, n: cint) proc newtable*(L: Pstate) proc register*(L: PState, n: cstring, f: CFunction) proc pushcfunction*(L: PState, f: CFunction) -proc strlen*(L: Pstate, i: int): int -proc isfunction*(L: PState, n: int): bool -proc istable*(L: PState, n: int): bool -proc islightuserdata*(L: PState, n: int): bool -proc isnil*(L: PState, n: int): bool -proc isboolean*(L: PState, n: int): bool -proc isthread*(L: PState, n: int): bool -proc isnone*(L: PState, n: int): bool -proc isnoneornil*(L: PState, n: int): bool +proc strlen*(L: Pstate, i: cint): cint +proc isfunction*(L: PState, n: cint): bool +proc istable*(L: PState, n: cint): bool +proc islightuserdata*(L: PState, n: cint): bool +proc isnil*(L: PState, n: cint): bool +proc isboolean*(L: PState, n: cint): bool +proc isthread*(L: PState, n: cint): bool +proc isnone*(L: PState, n: cint): bool +proc isnoneornil*(L: PState, n: cint): bool proc pushliteral*(L: PState, s: cstring) proc setglobal*(L: PState, s: cstring) proc getglobal*(L: PState, s: cstring) -proc tostring*(L: PState, i: int): cstring +proc tostring*(L: PState, i: cint): cstring # #** compatibility macros and functions # proc getregistry*(L: PState) -proc getgccount*(L: PState): int +proc getgccount*(L: PState): cint type Chunkreader* = Reader Chunkwriter* = Writer @@ -307,18 +263,18 @@ const type TDebug*{.final.} = object # activation record - event*: int + event*: cint name*: cstring # (n) namewhat*: cstring # (n) `global', `local', `field', `method' what*: cstring # (S) `Lua', `C', `main', `tail' source*: cstring # (S) - currentline*: int # (l) - nups*: int # (u) number of upvalues - linedefined*: int # (S) - lastlinedefined*: int # (S) - short_src*: array[0..IDSIZE - 1, Char] # (S) - # private part - i_ci*: int # active function + currentline*: cint # (l) + nups*: cint # (u) number of upvalues + linedefined*: cint # (S) + lastlinedefined*: cint # (S) + short_src*: array[0.. `_ library by -## Dieter Baron and Thomas Klausner. However, this does not need any external -## library (DLL, lib*.so), as the source for this library is included and -## compiled with this interface. +## Dieter Baron and Thomas Klausner. This version links +## against ``libzip2.so.2`` unless you define the symbol ``useLibzipSrc``; then +## it is compiled against some old ``libizp_all.c`` file. # # zip.h -- exported declarations. @@ -47,9 +47,16 @@ import times -{.compile: "libzip_all.c".} -when defined(unix): - {.passl: "-lz".} +when defined(unix) and not defined(useLibzipSrc): + when defined(macosx): + {.pragma: mydll, dynlib: "libzip2.dylib".} + else: + {.pragma: mydll, dynlib: "libzip2.so(|.2|.1|.0)".} +else: + when defined(unix): + {.passl: "-lz".} + {.compile: "libzip_all.c".} + {.pragma: mydll.} type Tzip_source_cmd* = int32 @@ -57,7 +64,7 @@ type Tzip_source_callback* = proc (state: pointer, data: pointer, length: int, cmd: Tzip_source_cmd): int {.cdecl.} Pzip_stat* = ptr Tzip_stat - Tzip_stat* {.final, pure.} = object + Tzip_stat* = object ## the 'zip_stat' struct name*: cstring ## name of the file index*: int32 ## index within archive crc*: int32 ## crc of file data @@ -67,9 +74,9 @@ type comp_method*: int16 ## compression method used encryption_method*: int16 ## encryption method used - Tzip {.final, pure.} = object - Tzip_source {.final, pure.} = object - Tzip_file {.final, pure.} = object + Tzip = object + Tzip_source = object + Tzip_file = object Pzip* = ptr Tzip ## represents a zip archive Pzip_file* = ptr Tzip_file ## represents a file within an archive @@ -160,82 +167,83 @@ const constZIP_SOURCE_FREE* = 5'i32 ## cleanup and free resources proc zip_add*(para1: Pzip, para2: cstring, para3: Pzip_source): int32 {.cdecl, - importc: "zip_add".} + importc: "zip_add", mydll.} proc zip_add_dir*(para1: Pzip, para2: cstring): int32 {.cdecl, - importc: "zip_add_dir".} -proc zip_close*(para1: Pzip) {.cdecl, importc: "zip_close".} -proc zip_delete*(para1: Pzip, para2: int32): int32 {.cdecl, + importc: "zip_add_dir", mydll.} +proc zip_close*(para1: Pzip) {.cdecl, importc: "zip_close", mydll.} +proc zip_delete*(para1: Pzip, para2: int32): int32 {.cdecl, mydll, importc: "zip_delete".} -proc zip_error_clear*(para1: Pzip) {.cdecl, importc: "zip_error_clear".} +proc zip_error_clear*(para1: Pzip) {.cdecl, importc: "zip_error_clear", mydll.} proc zip_error_get*(para1: Pzip, para2: ptr int32, para3: ptr int32) {.cdecl, - importc: "zip_error_get".} -proc zip_error_get_sys_type*(para1: int32): int32 {.cdecl, + importc: "zip_error_get", mydll.} +proc zip_error_get_sys_type*(para1: int32): int32 {.cdecl, mydll, importc: "zip_error_get_sys_type".} proc zip_error_to_str*(para1: cstring, para2: int, para3: int32, - para4: int32): int32 {.cdecl, + para4: int32): int32 {.cdecl, mydll, importc: "zip_error_to_str".} -proc zip_fclose*(para1: Pzip_file) {.cdecl, +proc zip_fclose*(para1: Pzip_file) {.cdecl, mydll, importc: "zip_fclose".} -proc zip_file_error_clear*(para1: Pzip_file) {.cdecl, +proc zip_file_error_clear*(para1: Pzip_file) {.cdecl, mydll, importc: "zip_file_error_clear".} proc zip_file_error_get*(para1: Pzip_file, para2: ptr int32, para3: ptr int32) {. - cdecl, importc: "zip_file_error_get".} -proc zip_file_strerror*(para1: Pzip_file): cstring {.cdecl, + cdecl, mydll, importc: "zip_file_error_get".} +proc zip_file_strerror*(para1: Pzip_file): cstring {.cdecl, mydll, importc: "zip_file_strerror".} proc zip_fopen*(para1: Pzip, para2: cstring, para3: int32): Pzip_file {.cdecl, - importc: "zip_fopen".} + mydll, importc: "zip_fopen".} proc zip_fopen_index*(para1: Pzip, para2: int32, para3: int32): Pzip_file {. - cdecl, importc: "zip_fopen_index".} + cdecl, mydll, importc: "zip_fopen_index".} proc zip_fread*(para1: Pzip_file, para2: pointer, para3: int): int {. - cdecl, importc: "zip_fread".} + cdecl, mydll, importc: "zip_fread".} proc zip_get_archive_comment*(para1: Pzip, para2: ptr int32, para3: int32): cstring {. - cdecl, importc: "zip_get_archive_comment".} + cdecl, mydll, importc: "zip_get_archive_comment".} proc zip_get_archive_flag*(para1: Pzip, para2: int32, para3: int32): int32 {. - cdecl, importc: "zip_get_archive_flag".} + cdecl, mydll, importc: "zip_get_archive_flag".} proc zip_get_file_comment*(para1: Pzip, para2: int32, para3: ptr int32, - para4: int32): cstring {.cdecl, + para4: int32): cstring {.cdecl, mydll, importc: "zip_get_file_comment".} proc zip_get_name*(para1: Pzip, para2: int32, para3: int32): cstring {.cdecl, - importc: "zip_get_name".} + mydll, importc: "zip_get_name".} proc zip_get_num_files*(para1: Pzip): int32 {.cdecl, - importc: "zip_get_num_files".} + mydll, importc: "zip_get_num_files".} proc zip_name_locate*(para1: Pzip, para2: cstring, para3: int32): int32 {.cdecl, - importc: "zip_name_locate".} + mydll, importc: "zip_name_locate".} proc zip_open*(para1: cstring, para2: int32, para3: ptr int32): Pzip {.cdecl, - importc: "zip_open".} + mydll, importc: "zip_open".} proc zip_rename*(para1: Pzip, para2: int32, para3: cstring): int32 {.cdecl, - importc: "zip_rename".} + mydll, importc: "zip_rename".} proc zip_replace*(para1: Pzip, para2: int32, para3: Pzip_source): int32 {.cdecl, - importc: "zip_replace".} + mydll, importc: "zip_replace".} proc zip_set_archive_comment*(para1: Pzip, para2: cstring, para3: int32): int32 {. - cdecl, importc: "zip_set_archive_comment".} + cdecl, mydll, importc: "zip_set_archive_comment".} proc zip_set_archive_flag*(para1: Pzip, para2: int32, para3: int32): int32 {. - cdecl, importc: "zip_set_archive_flag".} + cdecl, mydll, importc: "zip_set_archive_flag".} proc zip_set_file_comment*(para1: Pzip, para2: int32, para3: cstring, - para4: int32): int32 {.cdecl, + para4: int32): int32 {.cdecl, mydll, importc: "zip_set_file_comment".} proc zip_source_buffer*(para1: Pzip, para2: pointer, para3: int, para4: int32): Pzip_source {. - cdecl, importc: "zip_source_buffer".} + cdecl, mydll, importc: "zip_source_buffer".} proc zip_source_file*(para1: Pzip, para2: cstring, para3: int, para4: int): Pzip_source {. - cdecl, importc: "zip_source_file".} + cdecl, mydll, importc: "zip_source_file".} proc zip_source_filep*(para1: Pzip, para2: TFile, para3: int, para4: int): Pzip_source {. - cdecl, importc: "zip_source_filep".} -proc zip_source_free*(para1: Pzip_source) {.cdecl, + cdecl, mydll, importc: "zip_source_filep".} +proc zip_source_free*(para1: Pzip_source) {.cdecl, mydll, importc: "zip_source_free".} proc zip_source_function*(para1: Pzip, para2: Tzip_source_callback, - para3: pointer): Pzip_source {.cdecl, + para3: pointer): Pzip_source {.cdecl, mydll, importc: "zip_source_function".} proc zip_source_zip*(para1: Pzip, para2: Pzip, para3: int32, para4: int32, - para5: int, para6: int): Pzip_source {.cdecl, + para5: int, para6: int): Pzip_source {.cdecl, mydll, importc: "zip_source_zip".} proc zip_stat*(para1: Pzip, para2: cstring, para3: int32, para4: Pzip_stat): int32 {. - cdecl, importc: "zip_stat".} + cdecl, mydll, importc: "zip_stat".} proc zip_stat_index*(para1: Pzip, para2: int32, para3: int32, para4: Pzip_stat): int32 {. - cdecl, importc: "zip_stat_index".} -proc zip_stat_init*(para1: Pzip_stat) {.cdecl, importc: "zip_stat_init".} -proc zip_strerror*(para1: Pzip): cstring {.cdecl, importc: "zip_strerror".} -proc zip_unchange*(para1: Pzip, para2: int32): int32 {.cdecl, + cdecl, mydll, importc: "zip_stat_index".} +proc zip_stat_init*(para1: Pzip_stat) {.cdecl, mydll, importc: "zip_stat_init".} +proc zip_strerror*(para1: Pzip): cstring {.cdecl, mydll, importc: "zip_strerror".} +proc zip_unchange*(para1: Pzip, para2: int32): int32 {.cdecl, mydll, importc: "zip_unchange".} -proc zip_unchange_all*(para1: Pzip): int32 {.cdecl, importc: "zip_unchange_all".} -proc zip_unchange_archive*(para1: Pzip): int32 {.cdecl, +proc zip_unchange_all*(para1: Pzip): int32 {.cdecl, mydll, + importc: "zip_unchange_all".} +proc zip_unchange_archive*(para1: Pzip): int32 {.cdecl, mydll, importc: "zip_unchange_archive".} diff --git a/tests/caas/absurd_nesting.nim b/tests/caas/absurd_nesting.nim new file mode 100644 index 000000000..136d65cc7 --- /dev/null +++ b/tests/caas/absurd_nesting.nim @@ -0,0 +1,29 @@ +# Tries to test the full ownership path generated by idetools. + +proc lev1(t1: string) = + var temp = t1 + for i in 0..len(temp)-1: + temp[i] = chr(int(temp[i]) + 1) + + proc lev2(t2: string) = + var temp = t2 + for i in 0..len(temp)-1: + temp[i] = chr(int(temp[i]) + 1) + + proc lev3(t3: string) = + var temp = t3 + for i in 0..len(temp)-1: + temp[i] = chr(int(temp[i]) + 1) + + proc lev4(t4: string) = + var temp = t4 + for i in 0..len(temp)-1: + temp[i] = chr(int(temp[i]) + 1) + + echo temp & "(lev4)" + lev4(temp & "(lev3)") + lev3(temp & "(lev2)") + lev2(temp & "(lev1)") + +when isMainModule: + lev1("abcd") diff --git a/tests/caas/absurd_nesting.txt b/tests/caas/absurd_nesting.txt new file mode 100644 index 000000000..986e34836 --- /dev/null +++ b/tests/caas/absurd_nesting.txt @@ -0,0 +1,29 @@ +absurd_nesting.nim + +> c --verbosity:0 --hints:on +SuccessX + +> idetools --track:$TESTNIM,6,6 --def $SILENT +skVar\tabsurd_nesting.lev1.temp\tstring + +> idetools --track:$TESTNIM,21,13 --def $SILENT +skVar\tabsurd_nesting.lev1.lev2.lev3.lev4.temp\tstring + +> idetools --track:$TESTNIM,6,27 --def $SILENT +skForVar\tabsurd_nesting.lev1.i\tint + +> idetools --track:$TESTNIM,21,33 --def $SILENT +skForVar\tabsurd_nesting.lev1.lev1.lev3.lev4.i\tint + +> idetools --track:$TESTNIM,24,8 --def $SILENT +skProc\tabsurd_nesting.lev1.lev1.lev3.lev4\tproc \(string\) + +> idetools --track:$TESTNIM,4,13 --def $SILENT +skParam\tabsurd_nesting.lev1.t1\tstring + +> idetools --track:$TESTNIM,4,13 --def $SILENT +skParam\tabsurd_nesting.lev1.t1\tstring + +> idetools --track:$TESTNIM,19,19 --def $SILENT +skParam\tabsurd_nesting.lev1.lev2.lev3.lev4.t4\tstring + diff --git a/tests/caas/basic-recompile.txt b/tests/caas/basic-recompile.txt index 9c943fe88..620e0c059 100644 --- a/tests/caas/basic-recompile.txt +++ b/tests/caas/basic-recompile.txt @@ -1,7 +1,10 @@ main.nim -> c -SuccessX -> c -! Processing +> c --verbosity:0 --hints:on SuccessX +# The "Processing" string will be found always in proc mode since each +# compilation command will generate it. We need to test it only in Caas mode to +# verify the server is not recompiling again the file. +CaasRun > c --verbosity:0 --hints:on +CaasRun ! Processing +CaasRun SuccessX diff --git a/tests/caas/compile-suggest.txt b/tests/caas/compile-suggest.txt index 4e2ab9729..378320014 100644 --- a/tests/caas/compile-suggest.txt +++ b/tests/caas/compile-suggest.txt @@ -1,7 +1,7 @@ main.nim -> c +> c --verbosity:0 --hints:on SuccessX -> idetools --trackDirty:main_dirty.nim,main.nim,12,7 --suggest main.nim +> idetools --trackDirty:main_dirty.nim,$TESTNIM,12,7 --suggest $SILENT skField\tx skField\ty diff --git a/tests/caas/compile-then-def.txt b/tests/caas/compile-then-def.txt index a5080c50d..72ba46b04 100644 --- a/tests/caas/compile-then-def.txt +++ b/tests/caas/compile-then-def.txt @@ -1,9 +1,11 @@ main.nim -> c +> c --verbosity:0 --hints:on SuccessX -> idetools --track:main.nim,5,18 --def main.nim + +> idetools --track:$TESTNIM,5,18 --def --verbosity:0 --hints:on strutils.toUpper -SuccessX -> idetools --track:main.nim,5,18 --def main.nim +! SuccessX + +> idetools --track:$TESTNIM,5,18 --def --verbosity:0 --hints:on strutils.toUpper -SuccessX +! SuccessX diff --git a/tests/caas/def-def-compile.txt b/tests/caas/def-def-compile.txt index 64002aff1..21d5ea962 100644 --- a/tests/caas/def-def-compile.txt +++ b/tests/caas/def-def-compile.txt @@ -1,10 +1,12 @@ main.nim -> idetools --track:main.nim,5,18 --def main.nim +> idetools --track:$TESTNIM,5,18 --def --verbosity:0 --hints:on strutils.toUpper -SuccessX -> idetools --track:main.nim,5,18 --def main.nim +! SuccessX + +> idetools --track:$TESTNIM,5,18 --def --verbosity:0 --hints:on strutils.toUpper -SuccessX -> c +! SuccessX + +> c --verbosity:0 --hints:on SuccessX diff --git a/tests/caas/def-then-compile.txt b/tests/caas/def-then-compile.txt index efa4dcbb3..2214bf02c 100644 --- a/tests/caas/def-then-compile.txt +++ b/tests/caas/def-then-compile.txt @@ -1,7 +1,8 @@ main.nim -> idetools --track:main.nim,5,18 --def main.nim +> idetools --track:$TESTNIM,5,18 --def --verbosity:0 --hints:on strutils.toUpper -SuccessX -> c +! SuccessX + +> c --verbosity:0 --hints:on SuccessX diff --git a/tests/caas/forward_declarations.nim b/tests/caas/forward_declarations.nim new file mode 100644 index 000000000..177d82f20 --- /dev/null +++ b/tests/caas/forward_declarations.nim @@ -0,0 +1,15 @@ +# This example shows that idetools returns an empty signature for a forward +# declared proc in proc/symproc runs, but correctly returns the full signature +# in caas mode. + +proc echoHello(text: string) + +proc testForward() = + echo "T" + echoHello("T") + +proc echoHello(text: string) = + echo "Hello Mr." & text + +when isMainModule: + testForward() diff --git a/tests/caas/forward_declarations.txt b/tests/caas/forward_declarations.txt new file mode 100644 index 000000000..b1695b9c7 --- /dev/null +++ b/tests/caas/forward_declarations.txt @@ -0,0 +1,9 @@ +forward_declarations.nim + +> idetools --track:$TESTNIM,9,5 --def $SILENT +skProc +proc \(string\) + +> idetools --track:$TESTNIM,5,9 --def $SILENT +skProc +proc \(string\) diff --git a/tests/caas/forward_usages.txt b/tests/caas/forward_usages.txt new file mode 100644 index 000000000..05ef517dc --- /dev/null +++ b/tests/caas/forward_usages.txt @@ -0,0 +1,17 @@ +forward_declarations.nim + +> c --verbosity:0 --hints:on +SuccessX + +# None of the following return three instances of the echoHello proc, the first +# being the forward declaration, the second being the usage inside testForward, +# and the third being the actual implementation. + +> idetools --track:$TESTNIM,5,5 --usages $SILENT +skProc.*\n.*skProc.*\n.*skProc + +> idetools --track:$TESTNIM,9,5 --usages $SILENT +skProc.*\n.*skProc.*\n.*skProc + +> idetools --track:$TESTNIM,11,5 --usages $SILENT +skProc.*\n.*skProc.*\n.*skProc diff --git a/tests/caas/idetools_api.nim b/tests/caas/idetools_api.nim new file mode 100644 index 000000000..6327f4c22 --- /dev/null +++ b/tests/caas/idetools_api.nim @@ -0,0 +1,43 @@ +import unicode, sequtils + +proc test_enums() = + var o: Tfile + if o.open("files " & "test.txt", fmWrite): + o.write("test") + o.close() + +proc test_iterators(filename = "tests.nim") = + let + input = readFile(filename) + letters = toSeq(runes(string(input))) + for letter in letters: echo int(letter) + +const SOME_SEQUENCE = @[1, 2] +type + bad_string = distinct string + TPerson = object of TObject + name*: bad_string + age: int + +proc adder(a, b: int): int = + result = a + b + +type + PExpr = ref object of TObject ## abstract base class for an expression + PLiteral = ref object of PExpr + x: int + PPlusExpr = ref object of PExpr + a, b: PExpr + +# watch out: 'eval' relies on dynamic binding +method eval(e: PExpr): int = + # override this base method + quit "to override!" + +method eval(e: PLiteral): int = e.x +method eval(e: PPlusExpr): int = eval(e.a) + eval(e.b) + +proc newLit(x: int): PLiteral = PLiteral(x: x) +proc newPlus(a, b: PExpr): PPlusExpr = PPlusExpr(a: a, b: b) + +echo eval(newPlus(newPlus(newLit(1), newLit(2)), newLit(4))) diff --git a/tests/caas/idetools_api.txt b/tests/caas/idetools_api.txt new file mode 100644 index 000000000..c4c22399e --- /dev/null +++ b/tests/caas/idetools_api.txt @@ -0,0 +1,44 @@ +idetools_api.nim +> c --verbosity:0 --hints:on +SuccessX +> idetools --track:$TESTNIM,4,11 --def $SILENT +def\tskType\tsystem.TFile\tTFile +> idetools --track:$TESTNIM,5,7 --def $SILENT +def\tskProc\tsystem.Open\tproc \(var TFile, string, TFileMode, int\): bool +> idetools --track:$TESTNIM,5,21 --def $SILENT +def\tskProc\tsystem.\&\tproc \(string, string\): string\{.noSideEffect.\} +> idetools --track:$TESTNIM,5,38 --def $SILENT +def\tskEnumField\tsystem.TFileMode.fmWrite\tTFileMode +> idetools --track:$TESTNIM,7,6 --def $SILENT +def\tskProc\tsystem.Close\tproc \(TFile\) +> idetools --track:$TESTNIM,12,23 --def $SILENT +def\tskIterator\tunicode.runes\titerator \(string\): TRune +> idetools --track:$TESTNIM,12,15 --def $SILENT +def\tskTemplate\tsequtils.toSeq\tproc \(expr\): expr +> idetools --track:$TESTNIM,15,7 --def $SILENT + +# ProcRun mode will fail the next line, because the type is returned empty. +def\tskConst\t$MODULE.SOME_SEQUENCE\tseq\[int\]\t +> idetools --track:$TESTNIM,15,23 --def $SILENT +def\tskProc\tsystem.@\tproc \(array\[IDX, T\]\): seq\[T\]\{.noSideEffect.\} +> idetools --track:$TESTNIM,17,3 --def $SILENT + +# ProcRun mode will fail the next line, because the type is returned empty. +def\tskType\t$MODULE.bad_string\tbad_string\t +> idetools --track:$TESTNIM,11,24 --def $SILENT +def\tskParam\t$MODULE.test_iterators.filename\tstring +> idetools --track:$TESTNIM,6,5 --def $SILENT +def\tskVar\t$MODULE.test_enums.o\tTFile +> idetools --track:$TESTNIM,12,34 --def $SILENT +def\tskLet\t$MODULE.test_iterators.input\tTaintedString +> idetools --track:$TESTNIM,13,35 --def $SILENT +def\tskForVar\t$MODULE.test_iterators.letter\tTRune +> idetools --track:$TESTNIM,23,3 --def $SILENT +def\tskResult\t$MODULE.adder.result\tint +> idetools --track:$TESTNIM,19,6 --def $SILENT + +# ProcRun mode will fail the next line, because the type is returned empty. +def\tskField\t$MODULE.TPerson.name\tbad_string\t + +> idetools --track:$TESTNIM,43,7 --def $SILENT +def\tskMethod\t$MODULE.eval\tproc \(PPlusExpr\): int\t diff --git a/tests/caas/issue_416_template_shift.nim b/tests/caas/issue_416_template_shift.nim new file mode 100644 index 000000000..d52f611d6 --- /dev/null +++ b/tests/caas/issue_416_template_shift.nim @@ -0,0 +1,17 @@ +import unicode, sequtils + +proc test() = + let input = readFile("weird.nim") + for letter in runes(string(input)): + echo int(letter) + +when 1 > 0: + proc failtest() = + let + input = readFile("weird.nim") + letters = toSeq(runes(string(input))) + for letter in letters: + echo int(letter) + +when isMainModule: + test() diff --git a/tests/caas/issue_416_template_shift.txt b/tests/caas/issue_416_template_shift.txt new file mode 100644 index 000000000..b1f47c1ac --- /dev/null +++ b/tests/caas/issue_416_template_shift.txt @@ -0,0 +1,14 @@ +issue_416_template_shift.nim +> c --verbosity:0 --hints:on +SuccessX +> idetools --track:$TESTNIM,12,28 --def $SILENT +def\tskType\tsystem.string\tstring +> idetools --track:$TESTNIM,12,35 --def $SILENT +def\tskLet\t$MODULE.failtest.input\tTaintedString + +# The following fail because they seem shifted one colum to the right. +> idetools --track:$TESTNIM,12,16 --def $SILENT +def\tskTemplate\tsequtils.toSeq\tproc \(expr\): expr +> idetools --track:$TESTNIM,12,22 --def $SILENT +def\tskIterator\tunicode.runes\titerator \(string\): TRune + diff --git a/tests/caas/issue_452_export_shift.nim b/tests/caas/issue_452_export_shift.nim new file mode 100644 index 000000000..46cff6241 --- /dev/null +++ b/tests/caas/issue_452_export_shift.nim @@ -0,0 +1,8 @@ +const + VERSION_STR1* = "0.5.0" ## Idetools shifts this one column. + VERSION_STR2 = "0.5.0" ## This one is ok. + VERSION_STR3* = "0.5.0" ## Bad. + VERSION_STR4 = "0.5.0" ## Ok. + +proc forward1*(): string = result = "" +proc forward2(): string = result = "" diff --git a/tests/caas/issue_452_export_shift.txt b/tests/caas/issue_452_export_shift.txt new file mode 100644 index 000000000..4676ed71e --- /dev/null +++ b/tests/caas/issue_452_export_shift.txt @@ -0,0 +1,11 @@ +issue_452_export_shift.nim +> c --verbosity:0 --hints:on +SuccessX +> idetools --track:$TESTNIM,2,2 --def $SILENT +def\tskConst\t$MODULE.VERSION_STR1\tstring +> idetools --track:$TESTNIM,3,2 --def $SILENT +def\tskConst\t$MODULE.VERSION_STR2\tstring +> idetools --track:$TESTNIM,7,5 --def $SILENT +def\tskProc\t$MODULE.forward1\tproc \(\): string\t +> idetools --track:$TESTNIM,8,5 --def $SILENT +def\tskProc\t$MODULE.forward2\tproc \(\): string\t diff --git a/tests/caas/issue_477_dynamic_dispatch.nim b/tests/caas/issue_477_dynamic_dispatch.nim new file mode 100644 index 000000000..6e6b21ef0 --- /dev/null +++ b/tests/caas/issue_477_dynamic_dispatch.nim @@ -0,0 +1,19 @@ +type + TThing = object of TObject + TUnit = object of TThing + x: int + +method collide(a, b: TThing) {.inline.} = + quit "to override!" + +method collide(a: TThing, b: TUnit) {.inline.} = + echo "collide1" + +method collide(a: TUnit, b: TThing) {.inline.} = + echo "collide2" + +var + a, b: TUnit + +when isMainModule: + collide(a, b) # output: 2 diff --git a/tests/caas/issue_477_dynamic_dispatch.txt b/tests/caas/issue_477_dynamic_dispatch.txt new file mode 100644 index 000000000..12fd750de --- /dev/null +++ b/tests/caas/issue_477_dynamic_dispatch.txt @@ -0,0 +1,5 @@ +issue_477_dynamic_dispatch.nim +> c --run +SuccessX +> idetools --track:issue_477_dynamic_dispatch.nim,19,5 --def $SILENT +def\tskMethod\tissue_477_dynamic_dispatch.collide\tproc \(TUnit, TThing\)\{.inline.\} diff --git a/tests/caas/its_full_of_procs.nim b/tests/caas/its_full_of_procs.nim new file mode 100644 index 000000000..45347490c --- /dev/null +++ b/tests/caas/its_full_of_procs.nim @@ -0,0 +1,29 @@ +import unicode, sequtils + +# This example shows that idetools returns proc as signature for everything +# which can be called. While a clever person would use the second column to +# differentiate betwen procs, methods and others, why does the output contain +# incorrect information? + +type + TThing = object of TObject + TUnit = object of TThing + x: int + +method collide(a, b: TThing) {.inline.} = + quit "to override!" + +method collide(a: TThing, b: TUnit) {.inline.} = + echo "1" + +method collide(a: TUnit, b: TThing) {.inline.} = + echo "2" + +var + a, b: TUnit + +let + input = readFile("its_full_of_procs.nim") + letters = toSeq(runes(string(input))) + +collide(a, b) # output: 2 diff --git a/tests/caas/its_full_of_procs.txt b/tests/caas/its_full_of_procs.txt new file mode 100644 index 000000000..31a2d3baa --- /dev/null +++ b/tests/caas/its_full_of_procs.txt @@ -0,0 +1,20 @@ +its_full_of_procs.nim + +> idetools --track:$TESTNIM,26,15 --def $SILENT +skProc +proc \( + +> idetools --track:$TESTNIM,27,21 --def $SILENT +skIterator +iterator \( +!proc \( + +> idetools --track:$TESTNIM,29,0 --def $SILENT +skMethod +method \( +!proc \( + +> idetools --track:$TESTNIM,27,15 --def $SILENT +skTemplate +template \( +!proc \( diff --git a/tests/caas/suggest-compile.txt b/tests/caas/suggest-compile.txt index 49d0dc431..66ae795ed 100644 --- a/tests/caas/suggest-compile.txt +++ b/tests/caas/suggest-compile.txt @@ -1,7 +1,7 @@ main.nim -> idetools --trackDirty:main_dirty.nim,main.nim,12,7 --suggest main.nim +> idetools --trackDirty:main_dirty.nim,$TESTNIM,12,7 --suggest $SILENT skField\tx skField\ty -> c +> c --verbosity:0 --hints:on SuccessX diff --git a/tests/caasdriver.nim b/tests/caasdriver.nim index 3f3d3671e..f6f3d4e38 100644 --- a/tests/caasdriver.nim +++ b/tests/caasdriver.nim @@ -1,26 +1,101 @@ import osproc, streams, os, strutils, re -type - TNimrodSession* = object - nim: PProcess +## Compiler as a service tester. +## +## This test cases uses the txt files in the caas/ subdirectory. +## +## Each of the text files inside encodes a session with the compiler: +## +## The first line indicates the main project file. +## +## Lines starting with '>' indicate a command to be sent to the compiler and +## the lines following a command include checks for expected or forbidden +## output (! for forbidden). +## +## If a line starts with '#' it will be ignored completely, so you can use that +## for comments. +## +## All the tests are run both in ProcRun (each command creates a separate +## process) and CaasRun (first command starts up a server and it is reused for +## the rest) modes. Since some cases are specific to either ProcRun or CaasRun +## modes, you can prefix a line with the mode and the line will be processed +## only in that mode. +## +## The rest of the line is treated as a regular expression, so be careful +## escaping metacharacters like parenthesis. Before the line is processed as a +## regular expression, some basic variables are searched for and replaced in +## the tests. The variables which will be replaced are: +## +## - $TESTNIM: filename specified in the first line of the script. +## - $MODULE: like $TESTNIM but without extension, useful for expected output. +## +## You can optionally pass parameters at the command line to modify the +## behaviour of the test suite. By default only tests which fail will be echoed +## to stdout. If you want to see all the output pass the word "verbose" as a +## parameter. +## +## If you don't want to run all the test case files, you can pass any substring +## as a parameter. Only files matching the passed substring will be run. The +## filtering doesn't use any globbing metacharacters, it's a plain match. +## +## Example to run only "*-compile*.txt" tests in verbose mode: +## +## ./caasdriver verbose -compile -proc dirname(path: string): string = path.splitPath()[0] + +type + TRunMode = enum + ProcRun, CaasRun, SymbolProcRun + + TNimrodSession* = object + nim: PProcess # Holds the open process for CaasRun sessions, nil otherwise. + mode: TRunMode # Stores the type of run mode the session was started with. + lastOutput: string # Preserves the last output, needed for ProcRun mode. + filename: string # Appended to each command starting with '>'. Also a var. + modname: string # Like filename but without extension. + nimcache: string # Input script based name for the nimcache dir. + +const + modes = [CaasRun, ProcRun, SymbolProcRun] + filenameReplaceVar = "$TESTNIM" + moduleReplaceVar = "$MODULE" + silentReplaceVar = "$SILENT" + silentReplaceText = "--verbosity:0 --hints:off" var TesterDir = getAppDir() NimrodBin = TesterDir / "../bin/nimrod" -proc startNimrodSession*(project: string): TNimrodSession = - result.nim = startProcess(NimrodBin, - workingDir = project.dirname, - args = ["serve", "--server.type:stdin", project]) +proc replaceVars(session: var TNimrodSession, text: string): string = + result = text.replace(filenameReplaceVar, session.filename) + result = result.replace(moduleReplaceVar, session.modname) + result = result.replace(silentReplaceVar, silentReplaceText) -proc doCommand*(session: var TNimrodSession, command: string): string = - session.nim.inputStream.write(command & "\n") +proc startNimrodSession(project, script: string, mode: TRunMode): + TNimrodSession = + let (dir, name, ext) = project.splitFile + result.mode = mode + result.lastOutput = "" + result.filename = name & ext + result.modname = name + + let (nimcacheDir, nimcacheName, nimcacheExt) = script.splitFile + result.nimcache = "SymbolProcRun." & nimcacheName + + if mode == SymbolProcRun: + removeDir(nimcacheDir / result.nimcache) + + if mode == CaasRun: + result.nim = startProcess(NimrodBin, workingDir = dir, + args = ["serve", "--server.type:stdin", name]) + +proc doCaasCommand(session: var TNimrodSession, command: string): string = + assert session.mode == CaasRun + session.nim.inputStream.write(session.replaceVars(command) & "\n") session.nim.inputStream.flush - + result = "" - + while true: var line = TaintedString("") if session.nim.outputStream.readLine(line): @@ -30,38 +105,81 @@ proc doCommand*(session: var TNimrodSession, command: string): string = result = "FAILED TO EXECUTE: " & command & "\n" & result break -proc close(session: var TNimrodSession) {.destructor.} = - session.nim.close +proc doProcCommand(session: var TNimrodSession, command: string): string = + assert session.mode == ProcRun or session.mode == SymbolProcRun + except: result = "FAILED TO EXECUTE: " & command & "\n" & result + var + process = startProcess(NimrodBin, args = session.replaceVars(command).split) + stream = outputStream(process) + line = TaintedString("") -proc doScenario(script: string, output: PStream): bool = + result = "" + while stream.readLine(line): + if result.len > 0: result &= "\n" + result &= line.string + + process.close() + +proc doCommand(session: var TNimrodSession, command: string) = + if session.mode == CaasRun: + session.lastOutput = doCaasCommand(session, + command & " " & session.filename) + else: + var command = command + # For symbol runs we prepend the necessary parameters to avoid clobbering + # the normal nimcache. + if session.mode == SymbolProcRun: + command = "--symbolFiles:on --nimcache:" & session.nimcache & + " " & command + session.lastOutput = doProcCommand(session, + command & " " & session.filename) + +proc close(session: var TNimrodSession) {.destructor.} = + if session.mode == CaasRun: + session.nim.close + +proc doScenario(script: string, output: PStream, mode: TRunMode): bool = result = true var f = open(script) var project = TaintedString("") - + if f.readLine(project): var - s = startNimrodSession(script.dirname / project.string) + s = startNimrodSession(script.parentDir / project.string, script, mode) tline = TaintedString("") - lastOutput = "" ln = 1 while f.readLine(tline): var line = tline.string inc ln + + # Filter lines by run mode, removing the prefix if the mode is current. + for testMode in modes: + if line.startsWith($testMode): + if testMode != mode: + line = "" + else: + line = line[len($testMode)..len(line) - 1].strip + break + if line.strip.len == 0: continue - if line.startsWith(">"): - lastOutput = s.doCommand(line.substr(1).strip) - output.writeln line, "\n", lastOutput + if line.startsWith("#"): + output.writeln line + continue + elif line.startsWith(">"): + s.doCommand(line.substr(1).strip) + output.writeln line, "\n", s.lastOutput else: var expectMatch = true - var pattern = line + var pattern = s.replaceVars(line) if line.startsWith("!"): - pattern = line.substr(1).strip + pattern = pattern.substr(1).strip expectMatch = false - var actualMatch = lastOutput.find(re(pattern)) != -1 + let actualMatch = + s.lastOutput.find(re(pattern, flags = {reStudy})) != -1 if expectMatch == actualMatch: output.writeln "SUCCESS ", line @@ -70,17 +188,33 @@ proc doScenario(script: string, output: PStream): bool = result = false iterator caasTestsRunner*(filter = ""): tuple[test, output: string, - status: bool] = + status: bool, mode: TRunMode] = for scenario in os.walkFiles(TesterDir / "caas/*.txt"): if filter.len > 0 and find(scenario, filter) == -1: continue - var outStream = newStringStream() - let r = doScenario(scenario, outStream) - yield (scenario, outStream.data, r) + for mode in modes: + var outStream = newStringStream() + let r = doScenario(scenario, outStream, mode) + yield (scenario, outStream.data, r, mode) when isMainModule: - var filter = "" - if paramCount() > 0: filter = paramStr(1) - - for t, o, r in caasTestsRunner(filter): - echo t, "\n", o - + var + filter = "" + failures = 0 + verbose = false + + for i in 0..ParamCount() - 1: + let param = string(paramStr(i + 1)) + case param + of "verbose": verbose = true + else: filter = param + + if verbose and len(filter) > 0: + echo "Running only test cases matching filter '$1'" % [filter] + + for test, output, result, mode in caasTestsRunner(filter): + if not result or verbose: + echo test, "\n", output, "-> ", $mode, ":", $result, "\n-----" + if not result: + failures += 1 + + quit(failures) diff --git a/tests/compile/tmacrostmt.nim b/tests/compile/tmacrostmt.nim index 69b49f692..d9c70197d 100644 --- a/tests/compile/tmacrostmt.nim +++ b/tests/compile/tmacrostmt.nim @@ -15,3 +15,12 @@ else: return tkUnknown case_token: inc i + +#bug #488 + +macro foo: stmt = + var exp = newCall("whatwhat", newIntLitNode(1)) + if compiles(getAst(exp)): return exp + else: echo "Does not compute!" + +foo() diff --git a/tests/compile/toverprc.nim b/tests/compile/toverprc.nim index 467dd36c3..96b851fc1 100644 --- a/tests/compile/toverprc.nim +++ b/tests/compile/toverprc.nim @@ -9,6 +9,8 @@ proc parseInt(x: int8): int {.noSideEffect.} = nil proc parseInt(x: TFile): int {.noSideEffect.} = nil proc parseInt(x: char): int {.noSideEffect.} = nil proc parseInt(x: int16): int {.noSideEffect.} = nil + +proc parseInt[T](x: T): int = echo x; 34 type TParseInt = proc (x: string): int {.noSideEffect.} @@ -33,3 +35,5 @@ type proc bar[a,b](f: TFoo[a,b], x: a) = echo(x, " ", f.lorem, f.ipsum) proc bar[a,b](f: TFoo[a,b], x: b) = echo(x, " ", f.lorem, f.ipsum) + +discard parseInt[string]("yay") diff --git a/tests/compile/tsockets.nim b/tests/compile/tsockets.nim index cc7d18b87..6078504f5 100644 --- a/tests/compile/tsockets.nim +++ b/tests/compile/tsockets.nim @@ -4,8 +4,8 @@ s = socket() s.connect("www.google.com", TPort(80)) -var recvData: string = "" -echo(s.recvLine(recvData)) -echo(recvData) +var data: string = "" +s.readLine(data) +echo(data) diff --git a/tests/manyloc/argument_parser/argument_parser.nim b/tests/manyloc/argument_parser/argument_parser.nim new file mode 100644 index 000000000..95c71c08c --- /dev/null +++ b/tests/manyloc/argument_parser/argument_parser.nim @@ -0,0 +1,494 @@ +## Command line parsing module for Nimrod. +## +## `Nimrod `_ provides the `parseopt module +## `_ to parse options from the +## commandline. This module tries to provide functionality to prevent you from +## writing commandline parsing and let you concentrate on providing the best +## possible experience for your users. +## +## Source code for this module can be found at +## https://github.com/gradha/argument_parser. + +import os, strutils, tables, math, parseutils, sequtils, sets, algorithm, + unicode + +const + VERSION_STR* = "0.1.2" ## Module version as a string. + VERSION_INT* = (major: 0, minor: 1, maintenance: 2) ## \ + ## Module version as an integer tuple. + ## + ## Major versions changes mean a break in API backwards compatibility, either + ## through removal of symbols or modification of their purpose. + ## + ## Minor version changes can add procs (and maybe default parameters). Minor + ## odd versions are development/git/unstable versions. Minor even versions + ## are public stable releases. + ## + ## Maintenance version changes mean bugfixes or non API changes. + +# - Types + +type + Tparam_kind* = enum ## Different types of results for parameter parsing. + PK_EMPTY, PK_INT, PK_FLOAT, PK_STRING, PK_BOOL, + PK_BIGGEST_INT, PK_BIGGEST_FLOAT, PK_HELP + + Tparameter_callback* = + proc (parameter: string; value: var Tparsed_parameter): string ## \ + ## Prototype of parameter callbacks + ## + ## A parameter callback is just a custom proc you provide which is invoked + ## after a parameter is parsed passing the basic type validation. The + ## `parameter` parameter is the string which triggered the option. The + ## `value` parameter contains the string passed by the user already parsed + ## into the basic type you specified for it. + ## + ## The callback proc has modification access to the Tparsed_parameter + ## `value` parameter that will be put into Tcommandline_results: you can + ## read it and also modify it, maybe changing its type. In fact, if you + ## need special parsing, most likely you will end up specifying PK_STRING + ## in the parameter input specification so that the parse() proc doesn't + ## *mangle* the string before you can process it yourself. + ## + ## If the callback decides to abort the validation of the parameter, it has + ## to put into result a non zero length string with a message for the user + ## explaining why the validation failed, and maybe offer a hint as to what + ## can be done to pass validation. + + Tparameter_specification* = object ## \ + ## Holds the expectations of a parameter. + ## + ## You create these objects and feed them to the parse() proc, which then + ## uses them to detect parameters and turn them into something uself. + names*: seq[string] ## List of possible parameters to catch for this. + consumes*: Tparam_kind ## Expected type of the parameter (empty for none) + custom_validator*: Tparameter_callback ## Optional custom callback + ## to run after type conversion. + help_text*: string ## Help for this group of parameters. + + Tparsed_parameter* = object ## \ + ## Contains the parsed value from the user. + ## + ## This implements an object variant through the kind field. You can 'case' + ## this field to write a generic proc to deal with parsed parameters, but + ## nothing prevents you from accessing directly the type of field you want + ## if you expect only one kind. + case kind*: Tparam_kind + of PK_EMPTY: nil + of PK_INT: int_val*: int + of PK_BIGGEST_INT: big_int_val*: biggestInt + of PK_FLOAT: float_val*: float + of PK_BIGGEST_FLOAT: big_float_val*: biggestFloat + of PK_STRING: str_val*: string + of PK_BOOL: bool_val*: bool + of PK_HELP: nil + + Tcommandline_results* = object of TObject ## \ + ## Contains the results of the parsing. + ## + ## Usually this is the result of the parse() call, but you can inherit from + ## it to add your own fields for convenience. + ## + ## Note that you always have to access the ``options`` ordered table with + ## the first variant of a parameter name. For instance, if you have an + ## option specified like ``@["-s", "--silent"]`` and the user types + ## ``--silent`` at the commandline, you have to use + ## ``options.hasKey("-s")`` to test for it. This standarizes access through + ## the first name variant for all options to avoid you repeating the test + ## with different keys. + positional_parameters*: seq[Tparsed_parameter] + options*: TOrderedTable[string, Tparsed_parameter] + + +# - Tparam_kind procs + +proc `$`*(value: Tparam_kind): string {.procvar.} = + ## Stringifies the type, used to generate help texts. + case value: + of PK_EMPTY: result = "" + of PK_INT: result = "INT" + of PK_BIGGEST_INT: result = "BIG_INT" + of PK_FLOAT: result = "FLOAT" + of PK_BIGGEST_FLOAT: result = "BIG_FLOAG" + of PK_STRING: result = "STRING" + of PK_BOOL: result = "BOOL" + of PK_HELP: result = "" + +# - Tparameter_specification procs + +proc init*(param: var Tparameter_specification, consumes = PK_EMPTY, + custom_validator: Tparameter_callback = nil, help_text = "", + names: varargs[string]) = + ## Initialization helper with default parameters. + ## + ## You can decide to miss some if you like the defaults, reducing code. You + ## can also use new_parameter_specification() for single assignment + ## variables. + param.names = @names + param.consumes = consumes + param.custom_validator = custom_validator + param.help_text = help_text + +proc new_parameter_specification*(consumes = PK_EMPTY, + custom_validator: Tparameter_callback = nil, help_text = "", + names: varargs[string]): Tparameter_specification = + ## Initialization helper for single assignment variables. + result.init(consumes, custom_validator, help_text, names) + +# - Tparsed_parameter procs + +proc `$`*(data: Tparsed_parameter): string {.procvar.} = + ## Stringifies the value, mostly for debug purposes. + ## + ## The proc will display the value followed by non string type in brackets. + ## The non string types would be PK_INT (i), PK_BIGGEST_INT (I), PK_FLOAT + ## (f), PK_BIGGEST_FLOAT (F), PK_BOOL (b). The string type would be enclosed + ## inside quotes. PK_EMPTY produces the word `nil`, and PK_HELP produces the + ## world `help`. + case data.kind: + of PK_EMPTY: result = "nil" + of PK_INT: result = "$1(i)" % $data.int_val + of PK_BIGGEST_INT: result = "$1(I)" % $data.big_int_val + of PK_FLOAT: result = "$1(f)" % $data.float_val + of PK_BIGGEST_FLOAT: result = "$1(F)" % $data.big_float_val + of PK_STRING: result = "\"" & $data.str_val & "\"" + of PK_BOOL: result = "$1(b)" % $data.bool_val + of PK_HELP: result = "help" + + +template new_parsed_parameter*(tkind: Tparam_kind, expr): Tparsed_parameter = + ## Handy compile time template to build Tparsed_parameter object variants. + ## + ## The problem with object variants is that you first have to initialise them + ## to a kind, then assign values to the correct variable, and it is a little + ## bit annoying. + ## + ## Through this template you specify as the first parameter the kind of the + ## Tparsed_parameter you want to build, and directly the value it will be + ## initialised with. The template figures out at compile time what field to + ## assign the variable to, and thus you reduce code clutter and may use this + ## to initialise single assignments variables in `let` blocks. Example: + ## + ## .. code-block:: nimrod + ## let + ## parsed_param1 = new_parsed_parameter(PK_FLOAT, 3.41) + ## parsed_param2 = new_parsed_parameter(PK_BIGGEST_INT, 2358123 * 23123) + ## # The following line doesn't compile due to + ## # type mismatch: got (string) but expected 'int' + ## #parsed_param3 = new_parsed_parameter(PK_INT, "231") + var result {.gensym.}: Tparsed_parameter + result.kind = tkind + when tkind == PK_EMPTY: nil + elif tkind == PK_INT: result.int_val = expr + elif tkind == PK_BIGGEST_INT: result.big_int_val = expr + elif tkind == PK_FLOAT: result.float_val = expr + elif tkind == PK_BIGGEST_FLOAT: result.big_float_val = expr + elif tkind == PK_STRING: result.str_val = expr + elif tkind == PK_BOOL: result.bool_val = expr + elif tkind == PK_HELP: nil + else: {.error: "unknown kind".} + result + +# - Tcommandline_results procs + +proc init*(param: var Tcommandline_results; + positional_parameters: seq[Tparsed_parameter] = @[]; + options: TOrderedTable[string, Tparsed_parameter] = + initOrderedTable[string, Tparsed_parameter](4)) = + ## Initialization helper with default parameters. + param.positional_parameters = positional_parameters + param.options = options + +proc `$`*(data: Tcommandline_results): string = + ## Stringifies a Tcommandline_results structure for debug output + var dict: seq[string] = @[] + for key, value in data.options: + dict.add("$1: $2" % [escape(key), $value]) + result = "Tcommandline_result{positional_parameters:[$1], options:{$2}}" % [ + join(map(data.positional_parameters, `$`), ", "), join(dict, ", ")] + +# - Parse code + +template raise_or_quit(exception, message: expr): stmt {.immediate.} = + ## Avoids repeating if check based on the default quit_on_failure variable. + ## + ## As a special case, if message has a zero length the call to quit won't + ## generate any messages or errors (used by the mechanism to echo help to the + ## user). + if quit_on_failure: + if len(message) > 0: + quit(message) + else: + quit() + else: + raise newException(exception, message) + +template run_custom_proc(parsed_parameter: Tparsed_parameter, + custom_validator: Tparameter_callback, + parameter: TaintedString) = + ## Runs the custom validator if it is not nil. + ## + ## Pass in the string of the parameter triggering the call. If the + if not custom_validator.isNil: + except: + raise_or_quit(EInvalidValue, ("Couldn't run custom proc for " & + "parameter $1:\n$2" % [escape(parameter), + getCurrentExceptionMsg()])) + let message = custom_validator(parameter, parsed_parameter) + if not message.isNil and message.len > 0: + raise_or_quit(EInvalidValue, ("Failed to validate value for " & + "parameter $1:\n$2" % [escape(parameter), message])) + + +proc parse_parameter(quit_on_failure: bool, param, value: string, + param_kind: Tparam_kind): Tparsed_parameter = + ## Tries to parse a text according to the specified type. + ## + ## Pass the parameter string which requires a value and the text the user + ## passed in for it. It will be parsed according to the param_kind. This proc + ## will raise (EInvalidValue, EOverflow) if something can't be parsed. + result.kind = param_kind + case param_kind: + of PK_INT: + try: result.int_val = value.parseInt + except EOverflow: + raise_or_quit(EOverflow, ("parameter $1 requires an " & + "integer, but $2 is too large to fit into one") % [param, + escape(value)]) + except EInvalidValue: + raise_or_quit(EInvalidValue, ("parameter $1 requires an " & + "integer, but $2 can't be parsed into one") % [param, escape(value)]) + of PK_STRING: + result.str_val = value + of PK_FLOAT: + try: result.float_val = value.parseFloat + except EInvalidValue: + raise_or_quit(EInvalidValue, ("parameter $1 requires a " & + "float, but $2 can't be parsed into one") % [param, escape(value)]) + of PK_BOOL: + try: result.bool_val = value.parseBool + except EInvalidValue: + raise_or_quit(EInvalidValue, ("parameter $1 requires a " & + "boolean, but $2 can't be parsed into one. Valid values are: " & + "y, yes, true, 1, on, n, no, false, 0, off") % [param, escape(value)]) + of PK_BIGGEST_INT: + try: + let parsed_len = parseBiggestInt(value, result.big_int_val) + if value.len != parsed_len or parsed_len < 1: + raise_or_quit(EInvalidValue, ("parameter $1 requires an " & + "integer, but $2 can't be parsed completely into one") % [ + param, escape(value)]) + except EInvalidValue: + raise_or_quit(EInvalidValue, ("parameter $1 requires an " & + "integer, but $2 can't be parsed into one") % [param, escape(value)]) + of PK_BIGGEST_FLOAT: + try: + let parsed_len = parseBiggestFloat(value, result.big_float_val) + if value.len != parsed_len or parsed_len < 1: + raise_or_quit(EInvalidValue, ("parameter $1 requires a " & + "float, but $2 can't be parsed completely into one") % [ + param, escape(value)]) + except EInvalidValue: + raise_or_quit(EInvalidValue, ("parameter $1 requires a " & + "float, but $2 can't be parsed into one") % [param, escape(value)]) + of PK_EMPTY: + nil + of PK_HELP: + nil + + +template build_specification_lookup(): + TOrderedTable[string, ptr Tparameter_specification] = + ## Returns the table used to keep pointers to all of the specifications. + var result {.gensym.}: TOrderedTable[string, ptr Tparameter_specification] + result = initOrderedTable[string, ptr Tparameter_specification]( + nextPowerOfTwo(expected.len)) + for i in 0..expected.len-1: + for param_to_detect in expected[i].names: + if result.hasKey(param_to_detect): + raise_or_quit(EInvalidKey, + "Parameter $1 repeated in input specification" % param_to_detect) + else: + result[param_to_detect] = addr(expected[i]) + result + + +proc echo_help*(expected: seq[Tparameter_specification] = @[], + type_of_positional_parameters = PK_STRING, + bad_prefixes = @["-", "--"], end_of_options = "--") + + +proc parse*(expected: seq[Tparameter_specification] = @[], + type_of_positional_parameters = PK_STRING, args: seq[TaintedString] = nil, + bad_prefixes = @["-", "--"], end_of_options = "--", + quit_on_failure = true): Tcommandline_results = + ## Parses parameters and returns results. + ## + ## The expected array should contain a list of the parameters you want to + ## detect, which can capture additional values. Uncaptured parameters are + ## considered positional parameters for which you can specify a type with + ## type_of_positional_parameters. + ## + ## Before accepting a positional parameter, the list of bad_prefixes is + ## compared against it. If the positional parameter starts with any of them, + ## an error is displayed to the user due to ambiguity. The user can overcome + ## the ambiguity by typing the special string specified by end_of_options. + ## Note that values captured by parameters are not checked against bad + ## prefixes, otherwise it would be a problem to specify the dash as synonim + ## for standard input for many programs. + ## + ## The args sequence should be the list of parameters passed to your program + ## without the program binary (usually OSes provide the path to the binary as + ## the zeroth parameter). If args is nil, the list will be retrieved from the + ## OS. + ## + ## If there is any kind of error and quit_on_failure is true, the quit proc + ## will be called with a user error message. If quit_on_failure is false + ## errors will raise exceptions (usually EInvalidValue or EOverflow) instead + ## for you to catch and handle. + + assert type_of_positional_parameters != PK_EMPTY and + type_of_positional_parameters != PK_HELP + for bad_prefix in bad_prefixes: + assert bad_prefix.len > 0, "Can't pass in a bad prefix of zero length" + var + expected = expected + adding_options = true + result.init() + + # Prepare the input parameter list, maybe get it from the OS if not available. + var args = args + if args == nil: + let total_params = ParamCount() + #echo "Got no explicit args, retrieving from OS. Count: ", total_params + newSeq(args, total_params) + for i in 0..total_params - 1: + #echo ($i) + args[i] = paramStr(i + 1) + + # Generate lookup table for each type of parameter based on strings. + var lookup = build_specification_lookup() + + # Loop through the input arguments detecting their type and doing stuff. + var i = 0 + while i < args.len: + let arg = args[i] + block adding_positional_parameter: + if arg.len > 0 and adding_options: + if arg == end_of_options: + # Looks like we found the end_of_options marker, disable options. + adding_options = false + break adding_positional_parameter + elif lookup.hasKey(arg): + var parsed: Tparsed_parameter + let param = lookup[arg] + + # Insert check here for help, which aborts parsing. + if param.consumes == PK_HELP: + echo_help(expected, type_of_positional_parameters, + bad_prefixes, end_of_options) + raise_or_quit(EInvalidKey, "") + + if param.consumes != PK_EMPTY: + if i + 1 < args.len: + parsed = parse_parameter(quit_on_failure, + arg, args[i + 1], param.consumes) + run_custom_proc(parsed, param.custom_validator, arg) + i += 1 + else: + raise_or_quit(EInvalidValue, ("parameter $1 requires a " & + "value, but none was provided") % [arg]) + result.options[param.names[0]] = parsed + break adding_positional_parameter + else: + for bad_prefix in bad_prefixes: + if arg.startsWith(bad_prefix): + raise_or_quit(EInvalidValue, ("Found ambiguos parameter '$1' " & + "starting with '$2', put '$3' as the previous parameter " & + "if you want to force it as positional parameter.") % [arg, + bad_prefix, end_of_options]) + + # Unprocessed, add the parameter to the list of positional parameters. + result.positional_parameters.add(parse_parameter(quit_on_failure, + $(1 + i), arg, type_of_positional_parameters)) + + i += 1 + + +proc toString(runes: seq[TRune]): string = + result = "" + for rune in runes: result.add(rune.toUTF8) + + +proc ascii_cmp(a, b: string): int = + ## Comparison ignoring non ascii characters, for better switch sorting. + let a = filterIt(toSeq(runes(a)), it.isAlpha()) + # Can't use filterIt twice, github bug #351. + let b = filter(toSeq(runes(b)), proc(x: TRune): bool = x.isAlpha()) + return system.cmp(toString(a), toString(b)) + + +proc build_help*(expected: seq[Tparameter_specification] = @[], + type_of_positional_parameters = PK_STRING, + bad_prefixes = @["-", "--"], end_of_options = "--"): seq[string] = + ## Builds basic help text and returns it as a sequence of strings. + ## + ## Note that this proc doesn't do as much sanity checks as the normal parse() + ## proc, though it's unlikely you will be using one without the other, so if + ## you had a parameter specification problem you would find out soon. + result = @["Usage parameters: "] + + # Generate lookup table for each type of parameter based on strings. + let quit_on_failure = false + var + expected = expected + lookup = build_specification_lookup() + keys = toSeq(lookup.keys()) + + # First generate the joined version of input parameters in a list. + var + seen = initSet[string]() + prefixes: seq[string] = @[] + helps: seq[string] = @[] + for key in keys: + if seen.contains(key): + continue + + # Add the joined string to the list. + let param = lookup[key][] + var param_names = param.names + sort(param_names, ascii_cmp) + var prefix = join(param_names, ", ") + # Don't forget about the type, if the parameter consumes values + if param.consumes != PK_EMPTY and param.consumes != PK_HELP: + prefix &= " " & $param.consumes + prefixes.add(prefix) + helps.add(param.help_text) + # Ignore future elements. + for name in param.names: seen.incl(name) + + # Calculate the biggest width and try to use that + let width = prefixes.map(proc (x: string): int = 3 + len(x)).max + + for line in zip(prefixes, helps): + result.add(line.a & repeatChar(width - line.a.len) & line.b) + + +proc echo_help*(expected: seq[Tparameter_specification] = @[], + type_of_positional_parameters = PK_STRING, + bad_prefixes = @["-", "--"], end_of_options = "--") = + ## Prints out help on the terminal. + ## + ## This is just a wrapper around build_help. Note that calling this proc + ## won't exit your program, you should call quit() yourself. + for line in build_help(expected, + type_of_positional_parameters, bad_prefixes, end_of_options): + echo line + + +when isMainModule: + # Simply tests code embedded in docs. + let + parsed_param1 = new_parsed_parameter(PK_FLOAT, 3.41) + parsed_param2 = new_parsed_parameter(PK_BIGGEST_INT, 2358123 * 23123) + #parsed_param3 = new_parsed_parameter(PK_INT, "231") diff --git a/tests/manyloc/keineschweine/dependencies/genpacket/macro_dsl.nim b/tests/manyloc/keineschweine/dependencies/genpacket/macro_dsl.nim index cbae1334e..c7b577b3d 100644 --- a/tests/manyloc/keineschweine/dependencies/genpacket/macro_dsl.nim +++ b/tests/manyloc/keineschweine/dependencies/genpacket/macro_dsl.nim @@ -33,18 +33,9 @@ proc emptyNode*(): PNimrodNode {.compileTime.} = proc dot*(left, right: PNimrodNode): PNimrodNode {.compileTime.} = result = newNimNode(nnkDotExpr).und(left, right) -proc postfix*(a: PNimrodNode, b: string): PNimrodNode {.compileTime.} = - result = newNimNode(nnkPostfix).und(newIdentNode(!b), a) proc prefix*(a: string, b: PNimrodNode): PNimrodNode {.compileTime.} = result = newNimNode(nnkPrefix).und(newIdentNode(!a), b) -proc infix*(a, b, c: PNimrodNode): PNimrodNode {.compileTime.} = - ## 5.infix("+", 10) ## => 5 + 10 - result = newNimNode(nnkInfix).und(b, a, c) -proc infix*(a: PNimrodNode; b: string; c: PNimrodNode): PNimrodNode {.compileTime.} = - ## Infix operation: infix(5, "+", 10) ## => - result = newNimNode(nnkInfix).und(newIdentNode(b), a, c) - proc quoted2ident*(a: PNimrodNode): PNimrodNode {.compileTime.} = if a.kind != nnkAccQuoted: return a diff --git a/tests/manyloc/keineschweine/enet_server/enet_client.nim b/tests/manyloc/keineschweine/enet_server/enet_client.nim index f4c8732e3..0c903a733 100644 --- a/tests/manyloc/keineschweine/enet_server/enet_client.nim +++ b/tests/manyloc/keineschweine/enet_server/enet_client.nim @@ -145,7 +145,7 @@ proc lobbyInit*() = var pos = vec2f(10, 10) u_alias = gui.newTextEntry( - if s.existsKey("alias"): s["alias"].str else: "alias", + if s.hasKey("alias"): s["alias"].str else: "alias", pos) pos.y += 20 u_passwd = gui.newTextEntry("buzz", pos) diff --git a/tests/manyloc/keineschweine/lib/sg_assets.nim b/tests/manyloc/keineschweine/lib/sg_assets.nim index 766bf5d82..ccd1d9280 100644 --- a/tests/manyloc/keineschweine/lib/sg_assets.nim +++ b/tests/manyloc/keineschweine/lib/sg_assets.nim @@ -227,12 +227,13 @@ template checkFile(path: expr): stmt {.dirty, immediate.} = cacheImpl newSprite, SpriteSheets, PSpriteSheet: result.file = filename - if not(filename =~ re"\S+_(\d+)x(\d+)\.\S\S\S"): + if filename =~ re"\S+_(\d+)x(\d+)\.\S\S\S": + result.framew = strutils.parseInt(matches[0]) + result.frameh = strutils.parseInt(matches[1]) + checkFile("data/gfx"/result.file) + else: errors.add "Bad file: "&filename&" must be in format name_WxH.png" return - result.framew = strutils.parseInt(matches[0]) - result.frameh = strutils.parseInt(matches[1]) - checkFile("data/gfx"/result.file) cacheImpl newSound, SoundCache, PSoundRecord: result.file = filename @@ -288,23 +289,23 @@ proc validateSettings*(settings: PJsonNode, errors: var seq[string]): bool = if settings.kind != JObject: addError("Settings root must be an object") return - if not settings.existsKey("vehicles"): + if not settings.hasKey("vehicles"): addError("Vehicles section missing") - if not settings.existsKey("objects"): + if not settings.hasKey("objects"): errors.add("Objects section is missing") result = false - if not settings.existsKey("level"): + if not settings.hasKey("level"): errors.add("Level settings section is missing") result = false else: let lvl = settings["level"] - if lvl.kind != JObject or not lvl.existsKey("size"): + if lvl.kind != JObject or not lvl.hasKey("size"): errors.add("Invalid level settings") result = false - elif not lvl.existsKey("size") or lvl["size"].kind != JArray or lvl["size"].len != 2: + elif not lvl.hasKey("size") or lvl["size"].kind != JArray or lvl["size"].len != 2: errors.add("Invalid/missing level size") result = false - if not settings.existsKey("items"): + if not settings.hasKey("items"): errors.add("Items section missing") result = false else: @@ -363,7 +364,7 @@ proc loadSettings*(rawJson: string, errors: var seq[string]): bool = nameToVehID[veh.name] = veh.id inc vID vID = 0 - if settings.existsKey("bullets"): + if settings.hasKey("bullets"): for blt in settings["bullets"].items: var bullet = importBullet(blt, errors) bullet.id = bID @@ -408,21 +409,21 @@ proc fetchBullet(name: string): PBulletRecord = return cfg.bullets[nameToBulletID[name]] proc getField(node: PJsonNode, field: string, target: var float) = - if not node.existsKey(field): + if not node.hasKey(field): return if node[field].kind == JFloat: target = node[field].fnum elif node[field].kind == JInt: target = node[field].num.float proc getField(node: PJsonNode, field: string, target: var int) = - if not node.existsKey(field): + if not node.hasKey(field): return if node[field].kind == JInt: target = node[field].num.int elif node[field].kind == JFloat: target = node[field].fnum.int proc getField(node: PJsonNode; field: string; target: var bool) = - if not node.existsKey(field): + if not node.hasKey(field): return case node[field].kind of JBool: @@ -434,7 +435,7 @@ proc getField(node: PJsonNode; field: string; target: var bool) = else: nil template checkKey(node: expr; key: string): stmt = - if not existsKey(node, key): + if not hasKey(node, key): return proc importTrail(data: PJsonNode; errors: var seq[string]): TTrailRecord = @@ -450,17 +451,17 @@ proc importLevel(data: PJsonNode; errors: var seq[string]): PLevelSettings = checkKey(data, "level") var level = data["level"] - if level.existsKey("size") and level["size"].kind == JArray and level["size"].len == 2: + if level.hasKey("size") and level["size"].kind == JArray and level["size"].len == 2: result.size.x = level["size"][0].num.cint result.size.y = level["size"][1].num.cint - if level.existsKey("starfield"): + if level.hasKey("starfield"): for star in level["starfield"].items: result.starfield.add(newSprite(star.str, errors)) proc importPhys(data: PJsonNode): TPhysicsRecord = result.radius = 20.0 result.mass = 10.0 - if data.existsKey("physics") and data["physics"].kind == JObject: + if data.hasKey("physics") and data["physics"].kind == JObject: let phys = data["physics"] phys.getField("radius", result.radius) phys.getField("mass", result.mass) @@ -489,10 +490,10 @@ proc importAnim(data: PJsonNode, errors: var seq[string]): PAnimationRecord = result.delay = 1000.0 result.spriteSheet = nil - if data.existsKey("anim"): + if data.hasKey("anim"): let anim = data["anim"] if anim.kind == JObject: - if anim.existsKey("file"): + if anim.hasKey("file"): result.spriteSheet = newSprite(anim["file"].str, errors) anim.getField "angle", result.angle diff --git a/tests/manyloc/keineschweine/lib/sg_packets.nim b/tests/manyloc/keineschweine/lib/sg_packets.nim index 30e83c147..625436cb6 100644 --- a/tests/manyloc/keineschweine/lib/sg_packets.nim +++ b/tests/manyloc/keineschweine/lib/sg_packets.nim @@ -55,7 +55,8 @@ idPacket(ZoneQuery, 'Q', tuple[pad: char = '\0']) type SpawnKind = enum - SpawnItem = 1'i8, SpawnVehicle, SpawnObject + SpawnDummy, + SpawnItem, SpawnVehicle, SpawnObject forwardPacketT(SpawnKind, int8) defPacket(ScSpawn, tuple[ kind: SpawnKind; id: uint16; record: uint16; amount: uint16]) @@ -64,7 +65,8 @@ defPacket(ScSpawn, tuple[ type TAssetType* = enum - FZoneCfg = 1'i8, FGraphics, FSound + FDummy, + FZoneCfg, FGraphics, FSound forwardPacketT(TAssetType, int8) forwardPacket(MD5Digest, array[0..15, int8]) diff --git a/tests/reject/tcheckedfield1.nim b/tests/reject/tcheckedfield1.nim new file mode 100644 index 000000000..5ade3a13a --- /dev/null +++ b/tests/reject/tcheckedfield1.nim @@ -0,0 +1,60 @@ +discard """ + errormsg: "cannot prove that field 'x.s' is accessible" + line:51 +""" + +import strutils + +{.warning[ProveField]: on.} + +type + TNodeKind = enum + nkBinary, nkTernary, nkStr + PNode = ref TNode not nil + TNode = object + case k: TNodeKind + of nkBinary, nkTernary: a, b: PNode + of nkStr: s: string + + PList = ref object + data: string + next: PList + +proc getData(x: PList not nil) = + echo x.data + +var head: PList + +proc processList() = + var it = head + while it != nil: + getData(it) + it = it.next + +proc toString2(x: PNode): string = + if x.k < nkStr: + toString2(x.a) & " " & toString2(x.b) + else: + x.s + +proc toString(x: PNode): string = + case x.k + of nkTernary, nkBinary: + toString(x.a) & " " & toString(x.b) + of nkStr: + x.s + +proc toString3(x: PNode): string = + if x.k <= nkBinary: + toString3(x.a) & " " & toString3(x.b) + else: + x.s # x.k in {nkStr} --> fact: not (x.k <= nkBinary) + +proc p() = + var x: PNode = PNode(k: nkStr, s: "abc") + + let y = x + if not y.isNil: + echo toString(y), " ", toString2(y) + +p() diff --git a/tests/reject/tdisallowif.nim b/tests/reject/tdisallowif.nim index f7bb7098b..10f54288a 100644 --- a/tests/reject/tdisallowif.nim +++ b/tests/reject/tdisallowif.nim @@ -21,7 +21,7 @@ s[0] = substr(s[0], 0, 2) echo s[0] -if true: +if s[0] != "hi": echo "do it" echo "more branches" else: diff --git a/tests/reject/tneedsdiscard.nim b/tests/reject/tneedsdiscard.nim new file mode 100644 index 000000000..24f5b2eee --- /dev/null +++ b/tests/reject/tneedsdiscard.nim @@ -0,0 +1,12 @@ +discard """ + line: 10 + errormsg: "value returned by statement has to be discarded" +""" + +proc p = + var f: TFile + echo "hi" + + open(f, "arg.txt") + +p() diff --git a/tests/reject/tnicerrorforsymchoice.nim b/tests/reject/tnicerrorforsymchoice.nim new file mode 100644 index 000000000..bc271dcab --- /dev/null +++ b/tests/reject/tnicerrorforsymchoice.nim @@ -0,0 +1,18 @@ +discard """ + line: 18 + errormsg: "type mismatch: got (proc (TScgi) | proc (PAsyncSocket, PStringTable, string))" +""" + +#bug #442 +import scgi, sockets, asyncio, strtabs +proc handleSCGIRequest[TScgi: TScgiState | PAsyncScgiState](s: TScgi) = + nil +proc handleSCGIRequest(client: PAsyncSocket, headers: PStringTable, + input: string) = + nil + +proc test(handle: proc (client: PAsyncSocket, headers: PStringTable, + input: string), b: int) = + nil + +test(handleSCGIRequest) diff --git a/tests/reject/tnotnil1.nim b/tests/reject/tnotnil1.nim new file mode 100644 index 000000000..863fe45f8 --- /dev/null +++ b/tests/reject/tnotnil1.nim @@ -0,0 +1,40 @@ +discard """ + errormsg: "'y' is provably nil" + line:38 +""" + +import strutils + + +type + TObj = object + x, y: int + +type + superstring = string not nil + + +proc q(s: superstring) = + echo s + +proc p2() = + var a: string = "I am not nil" + q(a) # but this should and does not + +p2() + +proc q(x: pointer not nil) = + nil + +proc p() = + var x: pointer + if not x.isNil: + q(x) + + let y = x + if not y.isNil: + q(y) + else: + q(y) + +p() diff --git a/tests/reject/tnotnil2.nim b/tests/reject/tnotnil2.nim new file mode 100644 index 000000000..bd6b8b675 --- /dev/null +++ b/tests/reject/tnotnil2.nim @@ -0,0 +1,24 @@ +discard """ + errormsg: "cannot prove 'y' is not nil" + line:20 +""" + +import strutils + + +type + TObj = object + x, y: int + +proc q(x: pointer not nil) = + nil + +proc p() = + var x: pointer + let y = x + if not y.isNil or y != x: + q(y) + else: + q(y) + +p() diff --git a/tests/reject/tuninit1.nim b/tests/reject/tuninit1.nim new file mode 100644 index 000000000..2a994b187 --- /dev/null +++ b/tests/reject/tuninit1.nim @@ -0,0 +1,36 @@ +discard """ + errormsg: "'y' might not have been initialized" + line:34 +""" + +import strutils + +{.warning[Uninit]:on.} + +proc p = + var x, y, z: int + if stdin.readLine == "true": + x = 34 + + while false: + y = 999 + break + + while true: + if x == 12: break + y = 9999 + + try: + z = parseInt("1233") + except E_Base: + case x + of 34: z = 123 + of 13: z = 34 + else: z = 8 + else: + y = 3444 + x = 3111 + z = 0 + echo x, y, z + +p() diff --git a/tests/run/tpegs.nim b/tests/run/tpegs.nim index e64cd8fef..bdd8db0f8 100644 --- a/tests/run/tpegs.nim +++ b/tests/run/tpegs.nim @@ -77,7 +77,7 @@ type of pkChar, pkGreedyRepChar: ch: char of pkCharChoice, pkGreedyRepSet: charChoice: ref set[char] of pkNonTerminal: nt: PNonTerminal - of pkBackRef..pkBackRefIgnoreStyle: index: range[1..MaxSubpatterns] + of pkBackRef..pkBackRefIgnoreStyle: index: range[0..MaxSubpatterns] else: sons: seq[TNode] PNonTerminal* = ref TNonTerminal @@ -1733,12 +1733,14 @@ when isMainModule: doAssert matches[0] == "a" else: doAssert false - - if match("abcdefg", peg"c {d} ef {g}", matches, 2): - doAssert matches[0] == "d" - doAssert matches[1] == "g" - else: - doAssert false + + block: + var matches: array[0..2, string] + if match("abcdefg", peg"c {d} ef {g}", matches, 2): + doAssert matches[0] == "d" + doAssert matches[1] == "g" + else: + doAssert false for x in findAll("abcdef", peg"{.}", 3): echo x diff --git a/tests/run/tstmtexprs.nim b/tests/run/tstmtexprs.nim index a69acd98b..497a2f6d0 100644 --- a/tests/run/tstmtexprs.nim +++ b/tests/run/tstmtexprs.nim @@ -64,3 +64,8 @@ proc p2(a: int): int = q() echo p(), " ", p2(2) + +proc semiProblem() = + if false: echo "aye"; echo "indeed" + +semiProblem() diff --git a/tests/specials.nim b/tests/specials.nim index 156a289d2..9ced66bbb 100644 --- a/tests/specials.nim +++ b/tests/specials.nim @@ -194,6 +194,8 @@ proc runSpecialTests(r: var TResults, options: string) = for t in os.walkFiles("tests/patterns/t*.nim"): runSingleTest(r, t, options) + for t in ["lib/packages/docutils/highlite"]: + runSingleTest(r, t, options) proc rejectSpecialTests(r: var TResults, options: string) = rejectThreadTests(r, options) diff --git a/tests/tester.nim b/tests/tester.nim index 8c8e31fe8..e10b89761 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -365,8 +365,9 @@ proc outputJSON(reject, compile, run: TResults) = writeFile(jsonFile, s) proc runCaasTests(r: var TResults) = - for test, output, status in caasTestsRunner(): - r.addResult(test, "", output, if status: reSuccess else: reOutputsDiffer) + for test, output, status, mode in caasTestsRunner(): + r.addResult(test, "", output & "-> " & $mode, + if status: reSuccess else: reOutputsDiffer) proc main() = os.putenv "NIMTEST_NO_COLOR", "1" diff --git a/todo.txt b/todo.txt index b59f7502e..35c6a5704 100644 --- a/todo.txt +++ b/todo.txt @@ -1,8 +1,8 @@ version 0.9.4 ============= -- make 'bind' default for templates and introduce 'mixin'; - special rule for ``[]=`` +- make 'bind' default for templates and introduce 'mixin' +- special rule for ``[]=`` - ``=`` should be overloadable; requires specialization for ``=``; general lift mechanism in the compiler is already implemented for 'fields' - mocking support with ``tyProxy`` that does: fallback for ``.`` operator @@ -15,16 +15,15 @@ version 0.9.4 Bugs ==== +- simple closure iterator doesn't work - docgen: sometimes effects are listed twice -- 'result' is not properly cleaned for NRVO -- instantiated generics are listed in error messages +- 'result' is not properly cleaned for NRVO --> use uninit checking instead - sneaking with qualifiedLookup() is really broken! - aporia.nim(968, 5) Error: ambiguous identifier: 'DELETE' -- use a qualifier - blocks can "export" an identifier but the CCG generates {} for them ... - osproc execProcesses can deadlock if all processes fail (as experienced in c++ mode) -- bootstrapping does not work in C++ mode - result = result shr 8 for the "system()" wrapper @@ -32,7 +31,6 @@ version 0.9.x ============= - macros as type pragmas -- implement full 'not nil' checking; range[1..3] needs the same mechanism - implicit deref for parameter matching - lazy overloading resolution: * get rid of ``expr[typ]``, use perhaps ``static[typ]`` instead @@ -142,6 +140,7 @@ Not essential for 1.0.0 - implement closures that support nesting of *procs* > 1 - object constructors: static check for fields if discriminator is known at compile time +- prove array accesses Optimizations diff --git a/tools/nimgrep.nim b/tools/nimgrep.nim index fa14f9e8e..c7893fe78 100644 --- a/tools/nimgrep.nim +++ b/tools/nimgrep.nim @@ -32,6 +32,7 @@ Options: --ignoreCase, -i be case insensitive --ignoreStyle, -y be style insensitive --ext:EX1|EX2|... only search the files with the given extension(s) + --nocolor output will be given without any colours. --verbose be verbose: list every processed file --help, -h shows this help --version, -v shows the version @@ -291,6 +292,7 @@ for kind, key, val in getopt(): of "ignorecase", "i": incl(options, optIgnoreCase) of "ignorestyle", "y": incl(options, optIgnoreStyle) of "ext": extensions = val.split('|') + of "nocolor": useWriteStyled = false of "verbose": incl(options, optVerbose) of "help", "h": writeHelp() of "version", "v": writeVersion() diff --git a/tools/niminst/debcreation.nim b/tools/niminst/debcreation.nim index 1e08e5653..982bfaf3d 100644 --- a/tools/niminst/debcreation.nim +++ b/tools/niminst/debcreation.nim @@ -227,9 +227,9 @@ when isMainModule: #echo(createRules()) - prepDeb("nimrod", "0.8.14", "Dominik Picheta", "morfeusz8@gmail.com", + prepDeb("nimrod", "0.9.2", "Dominik Picheta", "morfeusz8@gmail.com", "The Nimrod compiler", "Compiler for the Nimrod programming language", - @[("bin/nimrod", "gpl2"), ("lib/*", "lgpl")], + @[("bin/nimrod", "MIT"), ("lib/*", "MIT")], @["bin/nimrod"], @["config/*"], @["doc/*"], @["lib/*"], "gcc (>= 4:4.3.2)", "gcc (>= 4:4.3.2)") diff --git a/web/index.txt b/web/index.txt index 260d303c8..e504b65c4 100644 --- a/web/index.txt +++ b/web/index.txt @@ -89,7 +89,7 @@ Nimrod plays nice with others * **The Nimrod Compiler can also generate C++ or Objective C for easier interfacing.** * There are lots of bindings: for example, bindings to GTK2, the Windows API, - the POSIX API, OpenGL, SDL, Cario, Python, Lua, TCL, X11, libzip, PCRE, + the POSIX API, OpenGL, SDL, Cairo, Python, Lua, TCL, X11, libzip, PCRE, libcurl, mySQL and SQLite are included in the standard distribution. * A C to Nimrod conversion utility: New bindings to C libraries are easily generated by ``c2nim``. diff --git a/web/news.txt b/web/news.txt index 5d79d8271..74b33e2bf 100644 --- a/web/news.txt +++ b/web/news.txt @@ -18,17 +18,33 @@ Library Additions Changes affecting backwards compatibility ----------------------------------------- +- The scoping rules for the ``if`` statement changed for better interaction + with the new syntactic construct ``(;)``. +- ``OSError`` family of procedures has been deprecated. Procedures with the same + name but which take different parameters have been introduced. These procs now + require an error code to be passed to them. This error code can be retrieved + using the new ``OSLastError`` proc. + Compiler Additions ------------------ +- The compiler can now warn about "uninitialized" variables. (There are no + real uninitialized variables in Nimrod as they are initialized to binary + zero). Activate via ``{.warning[Uninit]:on.}``. +- The compiler now enforces the ``not nil`` constraint. +- The compiler now supports a ``codegenDecl`` pragma for even more control + over the generated code. + Language Additions ------------------ - Arrays can now be declared with a single integer literal ``N`` instead of a range; the range is then ``0..N-1``. - +- ``macros.dumpTree`` and ``macros.dumpLisp`` have been made ``immediate``, + ``dumpTreeImm`` and ``dumpLispImm`` are now deprecated. +- Added ``requiresInit`` pragma to enforce explicit initialization. 2013-05-20 New website design! diff --git a/web/nimrod.ini b/web/nimrod.ini index 36252deb6..94f196c64 100644 --- a/web/nimrod.ini +++ b/web/nimrod.ini @@ -37,7 +37,7 @@ UNIX. We don't believe this to be a coincidence. - Jeremy S. Anderson.""" [Documentation] doc: "endb;intern;apis;lib;manual;tut1;tut2;nimrodc;overview;filters;trmacros" -doc: "tools;c2nim;niminst;nimgrep;gc;estp" +doc: "tools;c2nim;niminst;nimgrep;gc;estp;idetools" pdf: "manual;lib;tut1;tut2;nimrodc;c2nim;niminst;gc" srcdoc2: "system.nim;impure/graphics;wrappers/sdl" srcdoc2: "core/macros;pure/marshal;core/typeinfo;core/unsigned" @@ -60,7 +60,9 @@ srcdoc2: "pure/collections/intsets;pure/collections/queues;pure/encodings" srcdoc2: "pure/events;pure/collections/sequtils;pure/irc;pure/cookies" srcdoc2: "pure/ftpclient;pure/memfiles;pure/subexes;pure/collections/critbits" srcdoc2: "pure/asyncio;pure/actors;core/locks;pure/oids;pure/endians;pure/uri" -srcdoc2: "pure/nimprof;pure/unittest" +srcdoc2: "pure/nimprof;pure/unittest;packages/docutils/highlite" +srcdoc2: "packages/docutils/rst;packages/docutils/rstast" +srcdoc2: "packages/docutils/rstgen" webdoc: "wrappers/libcurl;pure/md5;wrappers/mysql;wrappers/iup" webdoc: "wrappers/sqlite3;wrappers/postgres;wrappers/tinyc" diff --git a/web/question.txt b/web/question.txt index a54d8605f..89e242e35 100644 --- a/web/question.txt +++ b/web/question.txt @@ -129,8 +129,3 @@ Abbreviation C/C++ Compiler If your C compiler is not in the above list, try using the *generic UNIX C compiler* (``ucc``). If the C compiler needs different command line arguments try the ``--passc`` and ``--passl`` switches. -Unsupported compilers contain serious bugs that keep them from bootstrapping -Nimrod. - - -