diff options
66 files changed, 1443 insertions, 2275 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim index 461854496..9b8218071 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -1049,7 +1049,7 @@ proc discardSons(father: PNode) = father.sons = nil when defined(useNodeIds): - const nodeIdToDebug* = 482228 # 612794 + const nodeIdToDebug* = 310841 # 612794 #612840 # 612905 # 614635 # 614637 # 614641 # 423408 #429107 # 430443 # 441048 # 441090 # 441153 diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index e6b22819f..4698082f1 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -714,11 +714,12 @@ proc genFieldCheck(p: BProc, e: PNode, obj: PRope, field: PSym) = assert(it.sons[0].kind == nkSym) let op = it.sons[0].sym if op.magic == mNot: it = it.sons[1] - assert(it.sons[2].kind == nkSym) + let disc = it.sons[2].skipConv + assert(disc.kind == nkSym) initLoc(test, locNone, it.typ, OnStack) initLocExpr(p, it.sons[1], u) - initLoc(v, locExpr, it.sons[2].typ, OnUnknown) - v.r = ropef("$1.$2", [obj, it.sons[2].sym.loc.r]) + initLoc(v, locExpr, disc.typ, OnUnknown) + v.r = ropef("$1.$2", [obj, disc.sym.loc.r]) genInExprAux(p, it, u, v, test) let id = nodeTableTestOrSet(p.module.dataCache, newStrNode(nkStrLit, field.name.s), gBackendId) @@ -1398,10 +1399,10 @@ proc genSetOp(p: BProc, e: PNode, d: var TLoc, op: TMagic) = of mIncl: var ts = "NI" & $(size * 8) binaryStmtInExcl(p, e, d, - "$1 |=((" & ts & ")(1)<<(($2)%(sizeof(" & ts & ")*8)));$n") + "$1 |= ((" & ts & ")1)<<(($2)%(sizeof(" & ts & ")*8));$n") of mExcl: var ts = "NI" & $(size * 8) - binaryStmtInExcl(p, e, d, "$1 &= ~((" & ts & ")(1) << (($2) % (sizeof(" & + binaryStmtInExcl(p, e, d, "$1 &= ~(((" & ts & ")1) << (($2) % (sizeof(" & ts & ")*8)));$n") of mCard: if size <= 4: unaryExprChar(p, e, d, "#countBits32($1)") diff --git a/compiler/cgen.nim b/compiler/cgen.nim index a5852c735..e2f3b5ab0 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -1050,6 +1050,7 @@ proc getSomeInitName(m: PSym, suffix: string): PRope = assert m.owner.kind == skPackage if {sfSystemModule, sfMainModule} * m.flags == {}: result = m.owner.name.s.mangle.toRope + result.app "_" result.app m.name.s result.app suffix diff --git a/compiler/commands.nim b/compiler/commands.nim index 366019c19..38c8dd294 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -9,10 +9,34 @@ # This module handles the parsing of command line arguments. + +# We do this here before the 'import' statement so 'defined' does not get +# confused with 'TGCMode.gcGenerational' etc. +template bootSwitch(name, expr, userString: expr): expr = + # Helper to build boot constants, for debugging you can 'echo' the else part. + const name = if expr: " " & userString else: "" + +bootSwitch(usedRelease, defined(release), "-d:release") +bootSwitch(usedGnuReadline, defined(useGnuReadline), "-d:useGnuReadline") +bootSwitch(usedNoCaas, defined(noCaas), "-d:noCaas") +bootSwitch(usedBoehm, defined(boehmgc), "--gc:boehm") +bootSwitch(usedMarkAndSweep, defined(gcmarkandsweep), "--gc:markAndSweep") +bootSwitch(usedGenerational, defined(gcgenerational), "--gc:generational") +bootSwitch(usedNoGC, defined(nogc), "--gc:none") + import os, msgs, options, nversion, condsyms, strutils, extccomp, platform, lists, wordrecg, parseutils, babelcmd, idents +# but some have deps to imported modules. Yay. +bootSwitch(usedTinyC, hasTinyCBackend, "-d:tinyc") +bootSwitch(usedAvoidTimeMachine, noTimeMachine, "-d:avoidTimeMachine") +bootSwitch(usedNativeStacktrace, + defined(nativeStackTrace) and nativeStackTraceSupported, + "-d:nativeStackTrace") +bootSwitch(usedFFI, hasFFI, "-d:useFFI") + + proc writeCommandLineUsage*() type @@ -50,30 +74,16 @@ proc writeAdvancedUsage(pass: TCmdLinePass) = CPU[platform.hostCPU].name]) & AdvancedUsage) quit(0) -template bootSwitch(name, expr, userString: expr): expr = - # Helper to build boot constants, for debugging you can 'echo' the else part. - const name = if expr: " " & userString else: "" - -bootSwitch(usedAvoidTimeMachine, noTimeMachine, "-d:avoidTimeMachine") -bootSwitch(usedRelease, defined(release), "-d:release") -bootSwitch(usedTinyC, hasTinyCBackend, "-d:tinyc") -bootSwitch(usedGnuReadline, defined(useGnuReadline), "-d:useGnuReadline") -bootSwitch(usedNativeStacktrace, - defined(nativeStackTrace) and nativeStackTraceSupported, - "-d:nativeStackTrace") -bootSwitch(usedNoCaas, defined(noCaas), "-d:noCaas") -bootSwitch(usedFFI, hasFFI, "-d:useFFI") -bootSwitch(usedBoehm, defined(boehmgc), "--gc:boehm") -bootSwitch(usedMarkAndSweep, defined(gcmarkandsweep), "--gc:markAndSweep") -bootSwitch(usedGenerational, defined(gcgenerational), "--gc:generational") -bootSwitch(usedNoGC, defined(nogc), "--gc:none") - - proc writeVersionInfo(pass: TCmdLinePass) = if pass == passCmd1: msgWriteln(`%`(HelpMessage, [VersionAsString, platform.OS[platform.hostOS].name, CPU[platform.hostCPU].name])) + + const gitHash = gorge("git log -n 1 --format=%H") + if gitHash.strip.len == 40: + msgWriteln("git hash: " & gitHash) + msgWriteln("active boot switches:" & usedRelease & usedAvoidTimeMachine & usedTinyC & usedGnuReadline & usedNativeStacktrace & usedNoCaas & usedFFI & usedBoehm & usedMarkAndSweep & usedGenerational & usedNoGC) diff --git a/compiler/evals.nim b/compiler/evals.nim deleted file mode 100644 index 151adf690..000000000 --- a/compiler/evals.nim +++ /dev/null @@ -1,1502 +0,0 @@ -# -# -# The Nimrod Compiler -# (c) Copyright 2013 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -# This file implements the evaluator for Nimrod code. -# The evaluator is very slow, but simple. Since this -# is used mainly for evaluating macros and some other -# stuff at compile time, performance is not that -# important. - -import - strutils, magicsys, lists, options, ast, astalgo, trees, treetab, nimsets, - msgs, os, condsyms, idents, renderer, types, passes, semfold, transf, - parser, ropes, rodread, idgen, osproc, streams, evaltempl - -when hasFFI: - import evalffi - -type - PStackFrame* = ref TStackFrame - 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 - emConst, ## evaluate for 'const' according to spec - emOptimize, ## evaluate for optimization purposes (same as - ## emConst?) - emStatic ## evaluate for enforced compile time eval - ## ('static' context) - - TSandboxFlag* = enum ## what the evaluation engine should allow - allowCast, ## allow unsafe language feature: 'cast' - allowFFI, ## allow the FFI - allowInfiniteLoops ## allow endless loops - TSandboxFlags* = set[TSandboxFlag] - - TEvalContext* = object of passes.TPassContext - module*: PSym - tos*: PStackFrame # top of stack - lastException*: PNode - callsite: PNode # for 'callsite' magic - mode*: TEvalMode - features: TSandboxFlags - globals*: TIdNodeTable # state of global vars - getType*: proc(n: PNode): PNode {.closure.} - handleIsOperator*: proc(n: PNode): PNode {.closure.} - - PEvalContext* = ref TEvalContext - - TEvalFlag = enum - efNone, efLValue - TEvalFlags = set[TEvalFlag] - -const - evalMaxIterations = 500_000 # max iterations of all loops - evalMaxRecDepth = 10_000 # max recursion depth for evaluation - -# other idea: use a timeout! -> Wether code compiles depends on the machine -# the compiler runs on then! Bad idea! - -proc newStackFrame*(): PStackFrame = - new(result) - result.slots = @[] - -proc newEvalContext*(module: PSym, mode: TEvalMode): PEvalContext = - new(result) - result.module = module - result.mode = mode - result.features = {allowFFI} - initIdNodeTable(result.globals) - -proc pushStackFrame*(c: PEvalContext, t: PStackFrame) {.inline.} = - t.next = c.tos - c.tos = t - -proc popStackFrame*(c: PEvalContext) {.inline.} = - if c.tos != nil: c.tos = c.tos.next - else: InternalError("popStackFrame") - -proc evalMacroCall*(c: PEvalContext, n, nOrig: PNode, sym: PSym): PNode -proc evalAux(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode - -proc raiseCannotEval(c: PEvalContext, info: TLineInfo): PNode = - if defined(debug) and gVerbosity >= 3: writeStackTrace() - result = newNodeI(nkExceptBranch, info) - # creating a nkExceptBranch without sons - # means that it could not be evaluated - -proc stackTraceAux(x: PStackFrame) = - if x != nil: - stackTraceAux(x.next) - var info = if x.call != nil: x.call.info else: UnknownLineInfo() - # we now use the same format as in system/except.nim - var s = toFilename(info) - var line = toLineNumber(info) - if line > 0: - add(s, '(') - add(s, $line) - add(s, ')') - if x.prc != nil: - for k in 1..max(1, 25-s.len): add(s, ' ') - add(s, x.prc.name.s) - MsgWriteln(s) - -proc stackTrace(c: PEvalContext, info: TLineInfo, msg: TMsgKind, arg = "") = - MsgWriteln("stack trace: (most recent call last)") - stackTraceAux(c.tos) - LocalError(info, msg, arg) - -template isSpecial(n: PNode): bool = n.kind == nkExceptBranch -template bailout() {.dirty.} = - if isSpecial(result): return - -template evalX(n, flags) {.dirty.} = - result = evalAux(c, n, flags) - bailout() - -proc myreset(n: PNode) = - when defined(system.reset): - var oldInfo = n.info - reset(n[]) - n.info = oldInfo - -proc evalIf(c: PEvalContext, n: PNode): PNode = - var i = 0 - var length = sonsLen(n) - while (i < length) and (sonsLen(n.sons[i]) >= 2): - evalX(n.sons[i].sons[0], {}) - if result.kind == nkIntLit and result.intVal != 0: - return evalAux(c, n.sons[i].sons[1], {}) - inc(i) - if (i < length) and (sonsLen(n.sons[i]) < 2): - result = evalAux(c, n.sons[i].sons[0], {}) - else: - result = emptyNode - -proc evalCase(c: PEvalContext, n: PNode): PNode = - evalX(n.sons[0], {}) - var res = result - result = emptyNode - for i in countup(1, sonsLen(n) - 1): - if n.sons[i].kind == nkOfBranch: - for j in countup(0, sonsLen(n.sons[i]) - 2): - if overlap(res, n.sons[i].sons[j]): - return evalAux(c, lastSon(n.sons[i]), {}) - else: - result = evalAux(c, lastSon(n.sons[i]), {}) - -var - gWhileCounter: int # Use a counter to prevent endless loops! - # We make this counter global, because otherwise - # nested loops could make the compiler extremely slow. - gNestedEvals: int # count the recursive calls to ``evalAux`` to prevent - # endless recursion - -proc evalWhile(c: PEvalContext, n: PNode): PNode = - while true: - evalX(n.sons[0], {}) - if getOrdValue(result) == 0: - result = emptyNode; break - result = evalAux(c, n.sons[1], {}) - case result.kind - of nkBreakStmt: - if result.sons[0].kind == nkEmpty: - result = emptyNode # consume ``break`` token - # Bugfix (see tmacro2): but break in any case! - break - of nkExceptBranch, nkReturnToken: break - else: nil - dec(gWhileCounter) - if gWhileCounter <= 0: - if allowInfiniteLoops in c.features: - gWhileCounter = 0 - else: - stackTrace(c, n.info, errTooManyIterations) - break - -proc evalBlock(c: PEvalContext, n: PNode): PNode = - result = evalAux(c, n.sons[1], {}) - if result.kind == nkBreakStmt: - if result.sons[0] != nil: - assert(result.sons[0].kind == nkSym) - if n.sons[0].kind != nkEmpty: - assert(n.sons[0].kind == nkSym) - if result.sons[0].sym.id == n.sons[0].sym.id: result = emptyNode - # blocks can only be left with an explicit label now! - #else: - # result = emptyNode # consume ``break`` token - -proc evalFinally(c: PEvalContext, n, exc: PNode): PNode = - var finallyNode = lastSon(n) - if finallyNode.kind == nkFinally: - result = evalAux(c, finallyNode, {}) - if result.kind != nkExceptBranch: result = exc - else: - result = exc - -proc evalTry(c: PEvalContext, n: PNode): PNode = - result = evalAux(c, n.sons[0], {}) - case result.kind - of nkBreakStmt, nkReturnToken: - nil - of nkExceptBranch: - if sonsLen(result) >= 1: - # creating a nkExceptBranch without sons means that it could not be - # evaluated - var exc = result - var i = 1 - var length = sonsLen(n) - while (i < length) and (n.sons[i].kind == nkExceptBranch): - var blen = sonsLen(n.sons[i]) - if blen == 1: - # general except section: - result = evalAux(c, n.sons[i].sons[0], {}) - exc = result - break - else: - for j in countup(0, blen - 2): - assert(n.sons[i].sons[j].kind == nkType) - let a = exc.typ.skipTypes(abstractPtrs) - let b = n.sons[i].sons[j].typ.skipTypes(abstractPtrs) - if a == b: - result = evalAux(c, n.sons[i].sons[blen - 1], {}) - exc = result - break - inc(i) - result = evalFinally(c, n, exc) - else: result = evalFinally(c, n, emptyNode) - -proc getNullValue(typ: PType, info: TLineInfo): PNode -proc getNullValueAux(obj: PNode, result: PNode) = - case obj.kind - of nkRecList: - for i in countup(0, sonsLen(obj) - 1): getNullValueAux(obj.sons[i], result) - of nkRecCase: - getNullValueAux(obj.sons[0], result) - for i in countup(1, sonsLen(obj) - 1): - getNullValueAux(lastSon(obj.sons[i]), result) - of nkSym: - var s = obj.sym - var p = newNodeIT(nkExprColonExpr, result.info, s.typ) - addSon(p, newSymNode(s, result.info)) - addSon(p, getNullValue(s.typ, result.info)) - addSon(result, p) - else: InternalError(result.info, "getNullValueAux") - -proc getNullValue(typ: PType, info: TLineInfo): PNode = - var t = skipTypes(typ, abstractRange-{tyTypeDesc}) - result = emptyNode - case t.kind - of tyBool, tyEnum, tyChar, tyInt..tyInt64: - result = newNodeIT(nkIntLit, info, t) - of tyUInt..tyUInt64: - result = newNodeIT(nkUIntLit, info, t) - of tyFloat..tyFloat128: - result = newNodeIt(nkFloatLit, info, t) - of tyVar, tyPointer, tyPtr, tyRef, tyCString, tySequence, tyString, tyExpr, - tyStmt, tyTypeDesc, tyStatic, tyProc: - result = newNodeIT(nkNilLit, info, t) - of tyObject: - result = newNodeIT(nkPar, info, t) - getNullValueAux(t.n, result) - # initialize inherited fields: - var base = t.sons[0] - while base != nil: - getNullValueAux(skipTypes(base, skipPtrs).n, result) - base = base.sons[0] - of tyArray, tyArrayConstr: - result = newNodeIT(nkBracket, info, t) - for i in countup(0, int(lengthOrd(t)) - 1): - addSon(result, getNullValue(elemType(t), info)) - of tyTuple: - # XXX nkExprColonExpr is out of fashion ... - result = newNodeIT(nkPar, info, t) - for i in countup(0, sonsLen(t) - 1): - var p = newNodeIT(nkExprColonExpr, info, t.sons[i]) - var field = if t.n != nil: t.n.sons[i].sym else: newSym( - skField, getIdent(":tmp" & $i), t.owner, info) - addSon(p, newSymNode(field, info)) - addSon(p, getNullValue(t.sons[i], info)) - addSon(result, p) - of tySet: - result = newNodeIT(nkCurly, info, t) - else: InternalError("getNullValue: " & $t.kind) - -proc evalVarValue(c: PEvalContext, n: PNode): PNode = - result = evalAux(c, n, {}) - if result.kind in {nkType..nkNilLit}: result = result.copyNode - -proc allocSlot(c: PStackFrame; sym: PSym): int = - result = sym.position + ord(sym.kind == skParam) - if result == 0 and sym.kind != skResult: - result = c.slots.len - if result == 0: result = 1 - sym.position = result - setLen(c.slots, max(result+1, c.slots.len)) - -proc setSlot(c: PStackFrame, sym: PSym, val: PNode) = - assert sym.owner == c.prc or sfFromGeneric in sym.flags - let idx = allocSlot(c, sym) - 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.kind == nkVarTuple: - result = evalVarValue(c, a.lastSon) - if result.kind in {nkType..nkNilLit}: - result = result.copyNode - bailout() - 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: - if a.sons[2].kind != nkEmpty: - result = evalVarValue(c, a.sons[2]) - bailout() - else: - 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 - evalX(a.sons[0], {}) - 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, tyStatic, tyStmt, tyTypeDesc} - -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.owner == x.prc: - result = x.slots[sym.position] - assert result != nil - if not aliasNeeded(result, flags): - result = copyTree(result) - return - x = x.next - #internalError(sym.info, "cannot eval " & sym.name.s & " " & $sym.position) - result = raiseCannotEval(nil, sym.info) - #result = emptyNode - -proc evalGlobalVar(c: PEvalContext, s: PSym, flags: TEvalFlags): PNode = - if sfCompileTime in s.flags or c.mode == emRepl or s.kind == skForVar: - result = IdNodeTableGet(c.globals, s) - if result != nil: - if not aliasNeeded(result, flags): - result = copyTree(result) - else: - when hasFFI: - if sfImportc in s.flags and allowFFI in c.features: - result = importcSymbol(s) - IdNodeTablePut(c.globals, s, result) - return result - - result = s.ast - if result == nil or result.kind == nkEmpty: - result = getNullValue(s.typ, s.info) - else: - result = evalAux(c, result, {}) - if isSpecial(result): return - IdNodeTablePut(c.globals, s, result) - else: - result = raiseCannotEval(nil, s.info) - -proc optBody(c: PEvalContext, s: PSym): PNode = - result = s.getBody - -proc evalCall(c: PEvalContext, n: PNode): PNode = - var d = newStackFrame() - d.call = n - var prc = n.sons[0] - let isClosure = prc.kind == nkClosure - setlen(d.slots, sonsLen(n) + ord(isClosure)) - if isClosure: - #debug prc - evalX(prc.sons[1], {efLValue}) - d.slots[sonsLen(n)] = result - result = evalAux(c, prc.sons[0], {}) - else: - result = evalAux(c, prc, {}) - - if isSpecial(result): return - prc = result - # bind the actual params to the local parameter of a new binding - if prc.kind != nkSym: - InternalError(n.info, "evalCall " & n.renderTree) - return - d.prc = prc.sym - if prc.sym.kind notin {skProc, skConverter, skMacro}: - InternalError(n.info, "evalCall") - return - for i in countup(1, sonsLen(n) - 1): - evalX(n.sons[i], {}) - 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 .. <n.len: - newCall.sons[i] = d.slots[i] - return callForeignFunction(newCall) - - pushStackFrame(c, d) - evalX(optBody(c, prc.sym), {}) - if n.typ != nil: result = d.slots[0] - popStackFrame(c) - -proc evalArrayAccess(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode = - evalX(n.sons[0], flags) - var x = result - evalX(n.sons[1], {}) - var idx = getOrdValue(result) - result = emptyNode - case x.kind - of nkPar: - if (idx >= 0) and (idx < sonsLen(x)): - result = x.sons[int(idx)] - if result.kind == nkExprColonExpr: result = result.sons[1] - if not aliasNeeded(result, flags): result = copyTree(result) - else: - stackTrace(c, n.info, errIndexOutOfBounds) - of nkBracket, nkMetaNode: - if (idx >= 0) and (idx < sonsLen(x)): - result = x.sons[int(idx)] - if not aliasNeeded(result, flags): result = copyTree(result) - else: - stackTrace(c, n.info, errIndexOutOfBounds) - of nkStrLit..nkTripleStrLit: - if efLValue in flags: return raiseCannotEval(c, n.info) - result = newNodeIT(nkCharLit, x.info, getSysType(tyChar)) - if (idx >= 0) and (idx < len(x.strVal)): - result.intVal = ord(x.strVal[int(idx) + 0]) - elif idx == len(x.strVal): - nil - else: - stackTrace(c, n.info, errIndexOutOfBounds) - else: stackTrace(c, n.info, errNilAccess) - -proc evalFieldAccess(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode = - # a real field access; proc calls have already been transformed - # XXX: field checks! - evalX(n.sons[0], flags) - var x = result - if x.kind != nkPar: return raiseCannotEval(c, n.info) - # this is performance critical: - var field = n.sons[1].sym - result = x.sons[field.position] - if result.kind == nkExprColonExpr: result = result.sons[1] - if not aliasNeeded(result, flags): result = copyTree(result) - -proc evalAsgn(c: PEvalContext, n: PNode): PNode = - var a = n.sons[0] - if a.kind == nkBracketExpr and a.sons[0].typ.kind in {tyString, tyCString}: - evalX(a.sons[0], {efLValue}) - var x = result - evalX(a.sons[1], {}) - var idx = getOrdValue(result) - - evalX(n.sons[1], {}) - if result.kind notin {nkIntLit, nkCharLit}: return c.raiseCannotEval(n.info) - - if idx >= 0 and idx < len(x.strVal): - x.strVal[int(idx)] = chr(int(result.intVal)) - else: - stackTrace(c, n.info, errIndexOutOfBounds) - else: - evalX(n.sons[0], {efLValue}) - var x = result - evalX(n.sons[1], {}) - 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 i in countup(0, sonsLen(result) - 1): addSon(x, result.sons[i]) - result = emptyNode - assert result.kind == nkEmpty - -proc evalSwap(c: PEvalContext, n: PNode): PNode = - evalX(n.sons[0], {efLValue}) - var x = result - evalX(n.sons[1], {efLValue}) - if x.kind != result.kind: - stackTrace(c, n.info, errCannotInterpretNodeX, $n.kind) - else: - case x.kind - of nkCharLit..nkInt64Lit: swap(x.intVal, result.intVal) - of nkFloatLit..nkFloat64Lit: swap(x.floatVal, result.floatVal) - of nkStrLit..nkTripleStrLit: swap(x.strVal, result.strVal) - of nkIdent: swap(x.ident, result.ident) - of nkSym: swap(x.sym, result.sym) - else: - var tmpn = copyTree(x) - discardSons(x) - for i in countup(0, sonsLen(result) - 1): addSon(x, result.sons[i]) - discardSons(result) - for i in countup(0, sonsLen(tmpn) - 1): addSon(result, tmpn.sons[i]) - result = emptyNode - -proc evalSym(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode = - var s = n.sym - case s.kind - of skProc, skConverter, skMacro, skType: - result = n - #result = s.getBody - of skVar, skLet, skForVar, skTemp, skResult: - if sfGlobal notin s.flags: - result = evalVariable(c.tos, s, flags) - else: - result = evalGlobalVar(c, s, flags) - of skParam: - # XXX what about LValue? - if s.position + 1 <% c.tos.slots.len: - result = c.tos.slots[s.position + 1] - of skConst: result = s.ast - of skEnumField: result = newIntNodeT(s.position, n) - else: result = nil - let mask = if hasFFI and allowFFI in c.features: {sfForward} - else: {sfImportc, sfForward} - if result == nil or mask * s.flags != {}: - result = raiseCannotEval(c, n.info) - -proc evalIncDec(c: PEvalContext, n: PNode, sign: biggestInt): PNode = - evalX(n.sons[1], {efLValue}) - var a = result - evalX(n.sons[2], {}) - var b = result - case a.kind - of nkCharLit..nkInt64Lit: a.intval = a.intVal + sign * getOrdValue(b) - else: return raiseCannotEval(c, n.info) - result = emptyNode - -proc getStrValue(n: PNode): string = - case n.kind - of nkStrLit..nkTripleStrLit: result = n.strVal - else: - InternalError(n.info, "getStrValue") - result = "" - -proc evalEcho(c: PEvalContext, n: PNode): PNode = - for i in countup(1, sonsLen(n) - 1): - evalX(n.sons[i], {}) - Write(stdout, getStrValue(result)) - writeln(stdout, "") - result = emptyNode - -proc evalExit(c: PEvalContext, n: PNode): PNode = - if c.mode in {emRepl, emStatic}: - evalX(n.sons[1], {}) - Message(n.info, hintQuitCalled) - quit(int(getOrdValue(result))) - else: - result = raiseCannotEval(c, n.info) - -proc evalOr(c: PEvalContext, n: PNode): PNode = - evalX(n.sons[1], {}) - if result.intVal == 0: result = evalAux(c, n.sons[2], {}) - -proc evalAnd(c: PEvalContext, n: PNode): PNode = - evalX(n.sons[1], {}) - if result.intVal != 0: result = evalAux(c, n.sons[2], {}) - -proc evalNew(c: PEvalContext, n: PNode): PNode = - #if c.mode == emOptimize: return raiseCannotEval(c, n.info) - - # we ignore the finalizer for now and most likely forever :-) - evalX(n.sons[1], {efLValue}) - var a = result - var t = skipTypes(n.sons[1].typ, abstractVar) - if a.kind == nkEmpty: InternalError(n.info, "first parameter is empty") - myreset(a) - let u = getNullValue(t.sons[0], n.info) - a.kind = u.kind - a.typ = t - shallowCopy(a.sons, u.sons) - result = emptyNode - when false: - a.kind = nkRefTy - a.info = n.info - a.typ = t - a.sons = nil - addSon(a, getNullValue(t.sons[0], n.info)) - result = emptyNode - -proc evalDeref(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode = - evalX(n.sons[0], {efLValue}) - case result.kind - of nkNilLit: stackTrace(c, n.info, errNilAccess) - of nkRefTy: - # XXX efLValue? - result = result.sons[0] - else: - if skipTypes(n.sons[0].typ, abstractInst).kind != tyRef: - result = raiseCannotEval(c, n.info) - -proc evalAddr(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode = - evalX(n.sons[0], {efLValue}) - var a = result - var t = newType(tyPtr, c.module) - addSonSkipIntLit(t, a.typ) - result = newNodeIT(nkRefTy, n.info, t) - addSon(result, a) - -proc evalConv(c: PEvalContext, n: PNode): PNode = - result = evalAux(c, n.sons[1], {efLValue}) - if isSpecial(result): return - if result.typ != nil: - var a = result - result = foldConv(n, a) - if result == nil: - # foldConv() cannot deal with everything that we want to do here: - result = a - -proc evalCast(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode = - if allowCast in c.features: - when hasFFI: - result = evalAux(c, n.sons[1], {efLValue}) - if isSpecial(result): return - InternalAssert result.typ != nil - result = fficast(result, n.typ) - else: - result = evalConv(c, n) - else: - result = raiseCannotEval(c, n.info) - -proc evalCheckedFieldAccess(c: PEvalContext, n: PNode, - flags: TEvalFlags): PNode = - result = evalAux(c, n.sons[0], flags) - -proc evalUpConv(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode = - result = evalAux(c, n.sons[0], flags) - if isSpecial(result): return - var dest = skipTypes(n.typ, abstractPtrs) - var src = skipTypes(result.typ, abstractPtrs) - if inheritanceDiff(src, dest) > 0: - stackTrace(c, n.info, errInvalidConversionFromTypeX, typeToString(src)) - -proc evalRangeChck(c: PEvalContext, n: PNode): PNode = - evalX(n.sons[0], {}) - var x = result - evalX(n.sons[1], {}) - var a = result - evalX(n.sons[2], {}) - var b = result - if leValueConv(a, x) and leValueConv(x, b): - result = x # a <= x and x <= b - result.typ = n.typ - else: - stackTrace(c, n.info, errGenerated, - msgKindToString(errIllegalConvFromXtoY) % [ - typeToString(n.sons[0].typ), typeToString(n.typ)]) - -proc evalConvStrToCStr(c: PEvalContext, n: PNode): PNode = - result = evalAux(c, n.sons[0], {}) - if isSpecial(result): return - result.typ = n.typ - -proc evalConvCStrToStr(c: PEvalContext, n: PNode): PNode = - result = evalAux(c, n.sons[0], {}) - if isSpecial(result): return - result.typ = n.typ - -proc evalRaise(c: PEvalContext, n: PNode): PNode = - if c.mode in {emRepl, emStatic}: - if n.sons[0].kind != nkEmpty: - result = evalAux(c, n.sons[0], {}) - if isSpecial(result): return - var a = result - result = newNodeIT(nkExceptBranch, n.info, a.typ) - addSon(result, a) - c.lastException = result - elif c.lastException != nil: - result = c.lastException - else: - stackTrace(c, n.info, errExceptionAlreadyHandled) - result = newNodeIT(nkExceptBranch, n.info, nil) - addSon(result, ast.emptyNode) - else: - result = raiseCannotEval(c, n.info) - -proc evalReturn(c: PEvalContext, n: PNode): PNode = - if n.sons[0].kind != nkEmpty: - result = evalAsgn(c, n.sons[0]) - if isSpecial(result): return - result = newNodeIT(nkReturnToken, n.info, nil) - -proc evalProc(c: PEvalContext, n: PNode): PNode = - if n.sons[genericParamsPos].kind == nkEmpty: - var s = n.sons[namePos].sym - if (resultPos < sonsLen(n)) and (n.sons[resultPos].kind != nkEmpty): - var v = n.sons[resultPos].sym - result = getNullValue(v.typ, n.info) - if c.tos.slots.len == 0: setLen(c.tos.slots, 1) - c.tos.slots[0] = result - #IdNodeTablePut(c.tos.mapping, v, result) - result = evalAux(c, s.getBody, {}) - if result.kind == nkReturnToken: - result = c.tos.slots[0] - else: - result = evalAux(c, s.getBody, {}) - if result.kind == nkReturnToken: - result = emptyNode - else: - result = emptyNode - -proc evalHigh(c: PEvalContext, n: PNode): PNode = - result = evalAux(c, n.sons[1], {}) - if isSpecial(result): return - case skipTypes(n.sons[1].typ, abstractVar).kind - of tyOpenArray, tySequence, tyVarargs: - result = newIntNodeT(sonsLen(result)-1, n) - of tyString: result = newIntNodeT(len(result.strVal) - 1, n) - else: InternalError(n.info, "evalHigh") - -proc evalOf(c: PEvalContext, n: PNode): PNode = - result = evalAux(c, n.sons[1], {}) - if isSpecial(result): return - result = newIntNodeT(ord(inheritanceDiff(result.typ, n.sons[2].typ) >= 0), n) - -proc evalSetLengthStr(c: PEvalContext, n: PNode): PNode = - result = evalAux(c, n.sons[1], {efLValue}) - if isSpecial(result): return - var a = result - result = evalAux(c, n.sons[2], {}) - if isSpecial(result): return - var b = result - case a.kind - of nkStrLit..nkTripleStrLit: - var newLen = int(getOrdValue(b)) - setlen(a.strVal, newLen) - else: InternalError(n.info, "evalSetLengthStr") - result = emptyNode - -proc evalSetLengthSeq(c: PEvalContext, n: PNode): PNode = - result = evalAux(c, n.sons[1], {efLValue}) - if isSpecial(result): return - var a = result - result = evalAux(c, n.sons[2], {}) - if isSpecial(result): return - var b = result - if a.kind != nkBracket: - InternalError(n.info, "evalSetLengthSeq") - return - var newLen = int(getOrdValue(b)) - var oldLen = sonsLen(a) - setlen(a.sons, newLen) - for i in countup(oldLen, newLen - 1): - a.sons[i] = getNullValue(skipTypes(n.sons[1].typ, abstractVar), n.info) - result = emptyNode - -proc evalNewSeq(c: PEvalContext, n: PNode): PNode = - result = evalAux(c, n.sons[1], {efLValue}) - if isSpecial(result): return - var a = result - result = evalAux(c, n.sons[2], {}) - if isSpecial(result): return - var b = result - var t = skipTypes(n.sons[1].typ, abstractVar) - if a.kind == nkEmpty: InternalError(n.info, "first parameter is empty") - myreset(a) - a.kind = nkBracket - a.info = n.info - a.typ = t - a.sons = nil - var L = int(getOrdValue(b)) - newSeq(a.sons, L) - for i in countup(0, L-1): - a.sons[i] = getNullValue(t.sons[0], n.info) - result = emptyNode - -proc evalIncl(c: PEvalContext, n: PNode): PNode = - result = evalAux(c, n.sons[1], {efLValue}) - if isSpecial(result): return - var a = result - result = evalAux(c, n.sons[2], {}) - if isSpecial(result): return - var b = result - if not inSet(a, b): addSon(a, copyTree(b)) - result = emptyNode - -proc evalExcl(c: PEvalContext, n: PNode): PNode = - result = evalAux(c, n.sons[1], {efLValue}) - if isSpecial(result): return - var a = result - result = evalAux(c, n.sons[2], {}) - if isSpecial(result): return - var b = newNodeIT(nkCurly, n.info, n.sons[1].typ) - addSon(b, result) - var r = diffSets(a, b) - discardSons(a) - for i in countup(0, sonsLen(r) - 1): addSon(a, r.sons[i]) - result = emptyNode - -proc evalAppendStrCh(c: PEvalContext, n: PNode): PNode = - result = evalAux(c, n.sons[1], {efLValue}) - if isSpecial(result): return - var a = result - result = evalAux(c, n.sons[2], {}) - if isSpecial(result): return - var b = result - case a.kind - of nkStrLit..nkTripleStrLit: add(a.strVal, chr(int(getOrdValue(b)))) - else: return raiseCannotEval(c, n.info) - result = emptyNode - -proc evalConStrStr(c: PEvalContext, n: PNode): PNode = - # we cannot use ``evalOp`` for this as we can here have more than 2 arguments - var a = newNodeIT(nkStrLit, n.info, n.typ) - a.strVal = "" - for i in countup(1, sonsLen(n) - 1): - result = evalAux(c, n.sons[i], {}) - if isSpecial(result): return - a.strVal.add(getStrOrChar(result)) - result = a - -proc evalAppendStrStr(c: PEvalContext, n: PNode): PNode = - result = evalAux(c, n.sons[1], {efLValue}) - if isSpecial(result): return - var a = result - result = evalAux(c, n.sons[2], {}) - if isSpecial(result): return - var b = result - case a.kind - of nkStrLit..nkTripleStrLit: a.strVal = a.strVal & getStrOrChar(b) - else: return raiseCannotEval(c, n.info) - result = emptyNode - -proc evalAppendSeqElem(c: PEvalContext, n: PNode): PNode = - result = evalAux(c, n.sons[1], {efLValue}) - if isSpecial(result): return - var a = result - result = evalAux(c, n.sons[2], {}) - if isSpecial(result): return - var b = result - if a.kind == nkBracket: addSon(a, copyTree(b)) - else: return raiseCannotEval(c, n.info) - result = emptyNode - -proc evalRepr(c: PEvalContext, n: PNode): PNode = - result = evalAux(c, n.sons[1], {}) - if isSpecial(result): return - result = newStrNodeT(renderTree(result, {renderNoComments}), n) - -proc isEmpty(n: PNode): bool = - result = n != nil and n.kind == nkEmpty - -proc evalParseExpr(c: PEvalContext, n: PNode): PNode = - var code = evalAux(c, n.sons[1], {}) - var ast = parseString(code.getStrValue, code.info.toFilename, - code.info.line.int) - if sonsLen(ast) != 1: - GlobalError(code.info, errExprExpected, "multiple statements") - result = ast.sons[0] - #result.typ = newType(tyExpr, c.module) - -proc evalParseStmt(c: PEvalContext, n: PNode): PNode = - var code = evalAux(c, n.sons[1], {}) - result = parseString(code.getStrValue, code.info.toFilename, - code.info.line.int) - #result.typ = newType(tyStmt, c.module) - -proc evalTypeTrait*(trait, operand: PNode, context: PSym): PNode = - let typ = operand.typ.skipTypes({tyTypeDesc}) - case trait.sym.name.s.normalize - of "name": - result = newStrNode(nkStrLit, typ.typeToString(preferName)) - result.typ = newType(tyString, context) - result.info = trait.info - of "arity": - result = newIntNode(nkIntLit, typ.n.len-1) - result.typ = newType(tyInt, context) - result.info = trait.info - else: - internalAssert false - -proc expectString(n: PNode) = - if n.kind notin nkStrKinds: - GlobalError(n.info, errStringLiteralExpected) - -proc evalSlurp*(e: PNode, module: PSym): PNode = - expectString(e) - result = newNodeIT(nkStrLit, e.info, getSysType(tyString)) - try: - var filename = e.strVal.FindFile - result.strVal = readFile(filename) - # we produce a fake include statement for every slurped filename, so that - # the module dependencies are accurate: - appendToModule(module, newNode(nkIncludeStmt, e.info, @[ - newStrNode(nkStrLit, filename)])) - except EIO: - result.strVal = "" - LocalError(e.info, errCannotOpenFile, e.strVal) - -proc readOutput(p: PProcess): string = - result = "" - var output = p.outputStream - discard p.waitForExit - while not output.atEnd: - result.add(output.readLine) - -proc evalStaticExec*(cmd, input: PNode): PNode = - expectString(cmd) - var p = startCmd(cmd.strVal) - if input != nil: - expectString(input) - p.inputStream.write(input.strVal) - p.inputStream.close() - result = newStrNode(nkStrLit, p.readOutput) - result.typ = getSysType(tyString) - result.info = cmd.info - -proc evalExpandToAst(c: PEvalContext, original: PNode): PNode = - var - n = original.copyTree - macroCall = n.sons[1] - expandedSym = macroCall.sons[0].sym - - for i in countup(1, macroCall.sonsLen - 1): - macroCall.sons[i] = evalAux(c, macroCall.sons[i], {}) - - case expandedSym.kind - of skTemplate: - let genSymOwner = if c.tos != nil and c.tos.prc != nil: - c.tos.prc - else: - c.module - result = evalTemplate(macroCall, expandedSym, genSymOwner) - of skMacro: - # At this point macroCall.sons[0] is nkSym node. - # To be completely compatible with normal macro invocation, - # we want to replace it with nkIdent node featuring - # the original unmangled macro name. - macroCall.sons[0] = newIdentNode(expandedSym.name, expandedSym.info) - result = evalMacroCall(c, macroCall, original, expandedSym) - else: - InternalError(macroCall.info, - "ExpandToAst: expanded symbol is no macro or template") - result = emptyNode - -proc evalMagicOrCall(c: PEvalContext, n: PNode): PNode = - var m = getMagic(n) - case m - of mNone: result = evalCall(c, n) - of mOf: result = evalOf(c, n) - of mSizeOf: result = raiseCannotEval(c, n.info) - of mHigh: result = evalHigh(c, n) - of mExit: result = evalExit(c, n) - of mNew, mNewFinalize: result = evalNew(c, n) - of mNewSeq: result = evalNewSeq(c, n) - of mSwap: result = evalSwap(c, n) - of mInc: result = evalIncDec(c, n, 1) - of ast.mDec: result = evalIncDec(c, n, - 1) - of mEcho: result = evalEcho(c, n) - of mSetLengthStr: result = evalSetLengthStr(c, n) - of mSetLengthSeq: result = evalSetLengthSeq(c, n) - of mIncl: result = evalIncl(c, n) - of mExcl: result = evalExcl(c, n) - of mAnd: result = evalAnd(c, n) - of mOr: result = evalOr(c, n) - of mAppendStrCh: result = evalAppendStrCh(c, n) - of mAppendStrStr: result = evalAppendStrStr(c, n) - of mAppendSeqElem: result = evalAppendSeqElem(c, n) - of mParseExprToAst: result = evalParseExpr(c, n) - of mParseStmtToAst: result = evalParseStmt(c, n) - of mExpandToAst: result = evalExpandToAst(c, n) - of mTypeTrait: - let operand = evalAux(c, n.sons[1], {}) - result = evalTypeTrait(n[0], operand, c.module) - of mIs: - n.sons[1] = evalAux(c, n.sons[1], {}) - result = c.handleIsOperator(n) - of mSlurp: result = evalSlurp(evalAux(c, n.sons[1], {}), c.module) - of mStaticExec: - let cmd = evalAux(c, n.sons[1], {}) - let input = if n.sonsLen == 3: evalAux(c, n.sons[2], {}) else: nil - result = evalStaticExec(cmd, input) - of mNLen: - result = evalAux(c, n.sons[1], {efLValue}) - if isSpecial(result): return - var a = result - result = newNodeIT(nkIntLit, n.info, n.typ) - case a.kind - of nkEmpty..nkNilLit: nil - else: result.intVal = sonsLen(a) - of mNChild: - result = evalAux(c, n.sons[1], {efLValue}) - if isSpecial(result): return - var a = result - result = evalAux(c, n.sons[2], {efLValue}) - if isSpecial(result): return - var k = getOrdValue(result) - if not (a.kind in {nkEmpty..nkNilLit}) and (k >= 0) and (k < sonsLen(a)): - result = a.sons[int(k)] - if result == nil: result = newNode(nkEmpty) - else: - stackTrace(c, n.info, errIndexOutOfBounds) - result = emptyNode - of mNSetChild: - result = evalAux(c, n.sons[1], {efLValue}) - if isSpecial(result): return - var a = result - result = evalAux(c, n.sons[2], {efLValue}) - if isSpecial(result): return - var b = result - result = evalAux(c, n.sons[3], {efLValue}) - if isSpecial(result): return - var k = getOrdValue(b) - if (k >= 0) and (k < sonsLen(a)) and not (a.kind in {nkEmpty..nkNilLit}): - a.sons[int(k)] = result - else: - stackTrace(c, n.info, errIndexOutOfBounds) - result = emptyNode - of mNAdd: - result = evalAux(c, n.sons[1], {efLValue}) - if isSpecial(result): return - var a = result - result = evalAux(c, n.sons[2], {efLValue}) - if isSpecial(result): return - addSon(a, result) - result = a - of mNAddMultiple: - result = evalAux(c, n.sons[1], {efLValue}) - if isSpecial(result): return - var a = result - result = evalAux(c, n.sons[2], {efLValue}) - if isSpecial(result): return - for i in countup(0, sonsLen(result) - 1): addSon(a, result.sons[i]) - result = a - of mNDel: - result = evalAux(c, n.sons[1], {efLValue}) - if isSpecial(result): return - var a = result - result = evalAux(c, n.sons[2], {efLValue}) - if isSpecial(result): return - var b = result - result = evalAux(c, n.sons[3], {efLValue}) - if isSpecial(result): return - for i in countup(0, int(getOrdValue(result)) - 1): - delSon(a, int(getOrdValue(b))) - result = emptyNode - of mNKind: - result = evalAux(c, n.sons[1], {}) - if isSpecial(result): return - var a = result - result = newNodeIT(nkIntLit, n.info, n.typ) - result.intVal = ord(a.kind) - of mNIntVal: - result = evalAux(c, n.sons[1], {}) - if isSpecial(result): return - var a = result - result = newNodeIT(nkIntLit, n.info, n.typ) - case a.kind - of nkCharLit..nkInt64Lit: result.intVal = a.intVal - else: stackTrace(c, n.info, errFieldXNotFound, "intVal") - of mNFloatVal: - result = evalAux(c, n.sons[1], {}) - if isSpecial(result): return - var a = result - result = newNodeIT(nkFloatLit, n.info, n.typ) - case a.kind - of nkFloatLit..nkFloat64Lit: result.floatVal = a.floatVal - else: stackTrace(c, n.info, errFieldXNotFound, "floatVal") - of mNSymbol: - result = evalAux(c, n.sons[1], {efLValue}) - if isSpecial(result): return - if result.kind != nkSym: stackTrace(c, n.info, errFieldXNotFound, "symbol") - of mNIdent: - result = evalAux(c, n.sons[1], {}) - if isSpecial(result): return - if result.kind != nkIdent: stackTrace(c, n.info, errFieldXNotFound, "ident") - of mNGetType: - var ast = evalAux(c, n.sons[1], {}) - InternalAssert c.getType != nil - result = c.getType(ast) - of mNStrVal: - result = evalAux(c, n.sons[1], {}) - if isSpecial(result): return - var a = result - result = newNodeIT(nkStrLit, n.info, n.typ) - case a.kind - of nkStrLit..nkTripleStrLit: result.strVal = a.strVal - else: stackTrace(c, n.info, errFieldXNotFound, "strVal") - of mNSetIntVal: - result = evalAux(c, n.sons[1], {efLValue}) - if isSpecial(result): return - var a = result - result = evalAux(c, n.sons[2], {}) - if isSpecial(result): return - if a.kind in {nkCharLit..nkInt64Lit} and - result.kind in {nkCharLit..nkInt64Lit}: - a.intVal = result.intVal - else: - stackTrace(c, n.info, errFieldXNotFound, "intVal") - result = emptyNode - of mNSetFloatVal: - result = evalAux(c, n.sons[1], {efLValue}) - if isSpecial(result): return - var a = result - result = evalAux(c, n.sons[2], {}) - if isSpecial(result): return - if a.kind in {nkFloatLit..nkFloat64Lit} and - result.kind in {nkFloatLit..nkFloat64Lit}: - a.floatVal = result.floatVal - else: - stackTrace(c, n.info, errFieldXNotFound, "floatVal") - result = emptyNode - of mNSetSymbol: - result = evalAux(c, n.sons[1], {efLValue}) - if isSpecial(result): return - var a = result - result = evalAux(c, n.sons[2], {efLValue}) - if isSpecial(result): return - if a.kind == nkSym and result.kind == nkSym: - a.sym = result.sym - else: - stackTrace(c, n.info, errFieldXNotFound, "symbol") - result = emptyNode - of mNSetIdent: - result = evalAux(c, n.sons[1], {efLValue}) - if isSpecial(result): return - var a = result - result = evalAux(c, n.sons[2], {efLValue}) - if isSpecial(result): return - if a.kind == nkIdent and result.kind == nkIdent: - a.ident = result.ident - else: - stackTrace(c, n.info, errFieldXNotFound, "ident") - result = emptyNode - of mNSetType: - result = evalAux(c, n.sons[1], {efLValue}) - if isSpecial(result): return - var a = result - result = evalAux(c, n.sons[2], {efLValue}) - if isSpecial(result): return - InternalAssert result.kind == nkSym and result.sym.kind == skType - a.typ = result.sym.typ - result = emptyNode - of mNSetStrVal: - result = evalAux(c, n.sons[1], {efLValue}) - if isSpecial(result): return - var a = result - result = evalAux(c, n.sons[2], {}) - if isSpecial(result): return - - if a.kind in {nkStrLit..nkTripleStrLit} and - result.kind in {nkStrLit..nkTripleStrLit}: - a.strVal = result.strVal - else: stackTrace(c, n.info, errFieldXNotFound, "strVal") - result = emptyNode - of mNNewNimNode: - result = evalAux(c, n.sons[1], {}) - if isSpecial(result): return - var k = getOrdValue(result) - result = evalAux(c, n.sons[2], {efLValue}) - if result.kind == nkExceptBranch: return - var a = result - if k < 0 or k > ord(high(TNodeKind)): - internalError(n.info, "request to create a NimNode with invalid kind") - result = newNodeI(TNodeKind(int(k)), - if a.kind == nkNilLit: n.info else: a.info) - of mNCopyNimNode: - result = evalAux(c, n.sons[1], {efLValue}) - if isSpecial(result): return - result = copyNode(result) - of mNCopyNimTree: - result = evalAux(c, n.sons[1], {efLValue}) - if isSpecial(result): return - result = copyTree(result) - of mNBindSym: - # trivial implementation: - result = n.sons[1] - of mNGenSym: - evalX(n.sons[1], {efLValue}) - let k = getOrdValue(result) - evalX(n.sons[2], {efLValue}) - let b = result - let name = if b.strVal.len == 0: ":tmp" else: b.strVal - if k < 0 or k > ord(high(TSymKind)): - internalError(n.info, "request to create a symbol with invalid kind") - result = newSymNode(newSym(k.TSymKind, name.getIdent, c.module, n.info)) - incl(result.sym.flags, sfGenSym) - of mStrToIdent: - result = evalAux(c, n.sons[1], {}) - if isSpecial(result): return - if not (result.kind in {nkStrLit..nkTripleStrLit}): - stackTrace(c, n.info, errFieldXNotFound, "strVal") - return - var a = result - result = newNodeIT(nkIdent, n.info, n.typ) - result.ident = getIdent(a.strVal) - of mIdentToStr: - result = evalAux(c, n.sons[1], {}) - if isSpecial(result): return - var a = result - result = newNodeIT(nkStrLit, n.info, n.typ) - if a.kind == nkSym: - result.strVal = a.sym.name.s - else: - if a.kind != nkIdent: InternalError(n.info, "no ident node") - result.strVal = a.ident.s - of mEqIdent: - result = evalAux(c, n.sons[1], {}) - if isSpecial(result): return - var a = result - result = evalAux(c, n.sons[2], {}) - if isSpecial(result): return - var b = result - result = newNodeIT(nkIntLit, n.info, n.typ) - if (a.kind == nkIdent) and (b.kind == nkIdent): - if a.ident.id == b.ident.id: result.intVal = 1 - of mEqNimrodNode: - result = evalAux(c, n.sons[1], {efLValue}) - if isSpecial(result): return - var a = result - result = evalAux(c, n.sons[2], {efLValue}) - if isSpecial(result): return - var b = result - result = newNodeIT(nkIntLit, n.info, n.typ) - if (a == b) or - (b.kind in {nkNilLit, nkEmpty}) and (a.kind in {nkNilLit, nkEmpty}): - result.intVal = 1 - of mNLineInfo: - result = evalAux(c, n.sons[1], {}) - if isSpecial(result): return - result = newStrNodeT(result.info.toFileLineCol, n) - of mNHint: - result = evalAux(c, n.sons[1], {}) - if isSpecial(result): return - Message(n.info, hintUser, getStrValue(result)) - result = emptyNode - of mNWarning: - result = evalAux(c, n.sons[1], {}) - if isSpecial(result): return - Message(n.info, warnUser, getStrValue(result)) - result = emptyNode - of mNError: - result = evalAux(c, n.sons[1], {}) - if isSpecial(result): return - stackTrace(c, n.info, errUser, getStrValue(result)) - result = emptyNode - of mConStrStr: - result = evalConStrStr(c, n) - of mRepr: - result = evalRepr(c, n) - of mNewString: - result = evalAux(c, n.sons[1], {}) - if isSpecial(result): return - var a = result - result = newNodeIT(nkStrLit, n.info, n.typ) - result.strVal = newString(int(getOrdValue(a))) - of mNewStringOfCap: - result = evalAux(c, n.sons[1], {}) - if isSpecial(result): return - var a = result - result = newNodeIT(nkStrLit, n.info, n.typ) - result.strVal = newString(0) - of mNCallSite: - if c.callsite != nil: result = c.callsite - else: stackTrace(c, n.info, errFieldXNotFound, "callsite") - else: - result = evalAux(c, n.sons[1], {}) - if isSpecial(result): return - var a = result - var b: PNode = nil - var cc: PNode = nil - if sonsLen(n) > 2: - result = evalAux(c, n.sons[2], {}) - if isSpecial(result): return - b = result - if sonsLen(n) > 3: - result = evalAux(c, n.sons[3], {}) - if isSpecial(result): return - cc = result - if isEmpty(a) or isEmpty(b) or isEmpty(cc): result = emptyNode - else: result = evalOp(m, n, a, b, cc) - -proc evalAux(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode = - result = emptyNode - dec(gNestedEvals) - if gNestedEvals <= 0: stackTrace(c, n.info, errTooManyIterations) - case n.kind - of nkSym: result = evalSym(c, n, flags) - of nkType..nkNilLit, nkTypeOfExpr: - # nkStrLit is VERY common in the traces, so we should avoid - # the 'copyNode' here. - result = n #.copyNode - of nkAsgn, nkFastAsgn: result = evalAsgn(c, n) - of nkCommand..nkHiddenCallConv: - result = evalMagicOrCall(c, n) - of nkDotExpr: result = evalFieldAccess(c, n, flags) - of nkBracketExpr: - result = evalArrayAccess(c, n, flags) - of nkDerefExpr, nkHiddenDeref: result = evalDeref(c, n, flags) - of nkAddr, nkHiddenAddr: result = evalAddr(c, n, flags) - of nkHiddenStdConv, nkHiddenSubConv, nkConv: result = evalConv(c, n) - of nkCurly, nkBracket, nkRange: - # flags need to be passed here for mNAddMultiple :-( - # XXX this is not correct in every case! - var a = copyNode(n) - for i in countup(0, sonsLen(n) - 1): - result = evalAux(c, n.sons[i], flags) - if isSpecial(result): return - addSon(a, result) - result = a - of nkPar, nkClosure: - var a = copyTree(n) - for i in countup(0, sonsLen(n) - 1): - var it = n.sons[i] - if it.kind == nkExprColonExpr: - result = evalAux(c, it.sons[1], flags) - if isSpecial(result): return - a.sons[i].sons[1] = result - else: - result = evalAux(c, it, flags) - if isSpecial(result): return - a.sons[i] = result - result = a - of nkObjConstr: - let t = skipTypes(n.typ, abstractInst) - var a: PNode - if t.kind == tyRef: - result = newNodeIT(nkRefTy, n.info, t) - a = getNullValue(t.sons[0], n.info) - addSon(result, a) - else: - a = getNullValue(t, n.info) - result = a - for i in countup(1, sonsLen(n) - 1): - let it = n.sons[i] - if it.kind == nkExprColonExpr: - let value = evalAux(c, it.sons[1], flags) - if isSpecial(value): return value - a.sons[it.sons[0].sym.position] = value - else: return raiseCannotEval(c, n.info) - of nkWhenStmt, nkIfStmt, nkIfExpr: result = evalIf(c, n) - of nkWhileStmt: result = evalWhile(c, n) - of nkCaseStmt: result = evalCase(c, n) - of nkVarSection, nkLetSection: result = evalVar(c, n) - of nkTryStmt: result = evalTry(c, n) - of nkRaiseStmt: result = evalRaise(c, n) - of nkReturnStmt: result = evalReturn(c, n) - of nkBreakStmt, nkReturnToken: result = n - of nkBlockExpr, nkBlockStmt: result = evalBlock(c, n) - of nkDiscardStmt: result = evalAux(c, n.sons[0], {}) - of nkCheckedFieldExpr: result = evalCheckedFieldAccess(c, n, flags) - of nkObjDownConv: result = evalAux(c, n.sons[0], flags) - of nkObjUpConv: result = evalUpConv(c, n, flags) - of nkChckRangeF, nkChckRange64, nkChckRange: result = evalRangeChck(c, n) - of nkStringToCString: result = evalConvStrToCStr(c, n) - of nkCStringToString: result = evalConvCStrToStr(c, n) - of nkStmtListExpr, nkStmtList: - for i in countup(0, sonsLen(n) - 1): - result = evalAux(c, n.sons[i], flags) - case result.kind - of nkExceptBranch, nkReturnToken, nkBreakStmt: break - else: nil - of nkProcDef, nkMethodDef, nkMacroDef, nkCommentStmt, nkPragma, - nkTypeSection, nkTemplateDef, nkConstSection, nkIteratorDef, - nkConverterDef, nkIncludeStmt, nkImportStmt, nkFromStmt: - nil - of nkMetaNode: - result = copyTree(n.sons[0]) - result.typ = n.typ - of nkPragmaBlock: - result = evalAux(c, n.sons[1], flags) - of nkCast: - result = evalCast(c, n, flags) - of nkIdentDefs, nkYieldStmt, nkAsmStmt, nkForStmt, nkPragmaExpr, - nkLambdaKinds, nkContinueStmt, nkIdent, nkParForStmt, nkBindStmt, - nkClosedSymChoice, nkOpenSymChoice: - result = raiseCannotEval(c, n.info) - of nkRefTy: - result = evalAux(c, n.sons[0], flags) - of nkEmpty: - # nkEmpty occurs once in each trace that I looked at - result = n - else: InternalError(n.info, "evalAux: " & $n.kind) - if result == nil: - InternalError(n.info, "evalAux: returned nil " & $n.kind) - inc(gNestedEvals) - -proc tryEval(c: PEvalContext, n: PNode): PNode = - #internalAssert nfTransf in n.flags - var n = transformExpr(c.module, n) - gWhileCounter = evalMaxIterations - gNestedEvals = evalMaxRecDepth - result = evalAux(c, n, {}) - -proc eval*(c: PEvalContext, n: PNode): PNode = - ## eval never returns nil! This simplifies the code a lot and - ## makes it faster too. - result = tryEval(c, n) - if result.kind == nkExceptBranch: - if sonsLen(result) >= 1: - stackTrace(c, n.info, errUnhandledExceptionX, typeToString(result.typ)) - else: - stackTrace(c, result.info, errCannotInterpretNodeX, renderTree(n)) - -proc evalConstExprAux*(p: PEvalContext, module, prc: PSym, e: PNode): PNode = - var s = newStackFrame() - s.call = e - s.prc = prc - pushStackFrame(p, s) - result = tryEval(p, e) - if result != nil and result.kind == nkExceptBranch: result = nil - popStackFrame(p) - -proc setupMacroParam(x: PNode): PNode = - result = x - if result.kind in {nkHiddenSubConv, nkHiddenStdConv}: result = result.sons[1] - -proc evalMacroCall(c: PEvalContext, n, nOrig: PNode, sym: PSym): PNode = - # XXX GlobalError() is ugly here, but I don't know a better solution for now - inc(evalTemplateCounter) - if evalTemplateCounter > 100: - GlobalError(n.info, errTemplateInstantiationTooNested) - - c.callsite = nOrig - var s = newStackFrame() - s.call = n - s.prc = sym - var L = n.safeLen - if L == 0: L = 1 - setlen(s.slots, L) - # return value: - s.slots[0] = newNodeIT(nkNilLit, n.info, sym.typ.sons[0]) - # setup parameters: - for i in 1 .. < L: s.slots[i] = setupMacroParam(n.sons[i]) - pushStackFrame(c, s) - discard eval(c, optBody(c, sym)) - result = s.slots[0] - popStackFrame(c) - if cyclicTree(result): GlobalError(n.info, errCyclicTree) - dec(evalTemplateCounter) - c.callsite = nil - -proc myOpen(module: PSym): PPassContext = - var c = newEvalContext(module, emRepl) - c.features = {allowCast, allowFFI, allowInfiniteLoops} - pushStackFrame(c, newStackFrame()) - result = c - -var oldErrorCount: int - -proc myProcess(c: PPassContext, n: PNode): PNode = - # don't eval errornous code: - if oldErrorCount == msgs.gErrorCounter: - result = eval(PEvalContext(c), n) - else: - result = n - oldErrorCount = msgs.gErrorCounter - -const evalPass* = makePass(myOpen, nil, myProcess, myProcess) - diff --git a/compiler/lexer.nim b/compiler/lexer.nim index 2bfd8d1eb..0e4dfc2ac 100644 --- a/compiler/lexer.nim +++ b/compiler/lexer.nim @@ -347,7 +347,7 @@ proc getNumber(L: var TLexer): TToken = result.base = base2 while true: case L.buf[pos] - of 'A'..'Z', 'a'..'z', '2'..'9', '.': + of '2'..'9', '.': lexMessage(L, errInvalidNumber, result.literal) inc(pos) of '_': @@ -363,7 +363,7 @@ proc getNumber(L: var TLexer): TToken = result.base = base8 while true: case L.buf[pos] - of 'A'..'Z', 'a'..'z', '8'..'9', '.': + of '8'..'9', '.': lexMessage(L, errInvalidNumber, result.literal) inc(pos) of '_': @@ -377,25 +377,22 @@ proc getNumber(L: var TLexer): TToken = else: break of 'O': lexMessage(L, errInvalidNumber, result.literal) - of 'x', 'X': + of 'x', 'X': result.base = base16 - while true: + while true: case L.buf[pos] - of 'G'..'Z', 'g'..'z': - lexMessage(L, errInvalidNumber, result.literal) - inc(pos) - of '_': - if L.buf[pos+1] notin {'0'..'9', 'a'..'f', 'A'..'F'}: + of '_': + if L.buf[pos+1] notin {'0'..'9', 'a'..'f', 'A'..'F'}: lexMessage(L, errInvalidToken, "_") break inc(pos) - of '0'..'9': + of '0'..'9': xi = `shl`(xi, 4) or (ord(L.buf[pos]) - ord('0')) inc(pos) - of 'a'..'f': + of 'a'..'f': xi = `shl`(xi, 4) or (ord(L.buf[pos]) - ord('a') + 10) inc(pos) - of 'A'..'F': + of 'A'..'F': xi = `shl`(xi, 4) or (ord(L.buf[pos]) - ord('A') + 10) inc(pos) else: break @@ -424,8 +421,14 @@ proc getNumber(L: var TLexer): TToken = if (result.iNumber < low(int32)) or (result.iNumber > high(int32)): if result.tokType == tkIntLit: result.tokType = tkInt64Lit - elif result.tokType in {tkInt8Lit, tkInt16Lit}: - lexMessage(L, errInvalidNumber, result.literal) + elif result.tokType in {tkInt8Lit, tkInt16Lit, tkInt32Lit}: + lexMessage(L, errNumberOutOfRange, result.literal) + elif result.tokType == tkInt8Lit and + (result.iNumber < int8.low or result.iNumber > int8.high): + lexMessage(L, errNumberOutOfRange, result.literal) + elif result.tokType == tkInt16Lit and + (result.iNumber < int16.low or result.iNumber > int16.high): + lexMessage(L, errNumberOutOfRange, result.literal) except EInvalidValue: lexMessage(L, errInvalidNumber, result.literal) except EOverflow, EOutOfRange: @@ -537,6 +540,10 @@ proc getString(L: var TLexer, tok: var TToken, rawMode: bool) = tok.tokType = tkTripleStrLit # long string literal: inc(pos, 2) # skip "" # skip leading newline: + if buf[pos] in {' ', '\t'}: + var newpos = pos+1 + while buf[newpos] in {' ', '\t'}: inc newpos + if buf[newpos] in {CR, LF}: pos = newpos pos = handleCRLF(L, pos) buf = L.buf while true: diff --git a/compiler/modules.nim b/compiler/modules.nim index fb1940741..b102224cd 100644 --- a/compiler/modules.nim +++ b/compiler/modules.nim @@ -115,7 +115,7 @@ proc newModule(fileIdx: int32): PSym = new(result) result.id = - 1 # for better error checking result.kind = skModule - let filename = fileIdx.toFilename + let filename = fileIdx.toFullPath result.name = getIdent(splitFile(filename).name) if not isNimrodIdentifier(result.name.s): rawMessage(errInvalidModuleName, result.name.s) diff --git a/compiler/parser.nim b/compiler/parser.nim index 18de1570a..6ff0c2dfc 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -63,7 +63,7 @@ proc optInd*(p: var TParser, n: PNode) proc indAndComment*(p: var TParser, n: PNode) proc setBaseFlags*(n: PNode, base: TNumericalBase) proc parseSymbol*(p: var TParser, allowNil = false): PNode -proc parseTry(p: var TParser): PNode +proc parseTry(p: var TParser; isExpr: bool): PNode proc parseCase(p: var TParser): PNode # implementation @@ -845,7 +845,7 @@ proc parseIdentColonEquals(p: var TParser, flags: TDeclaredIdentFlags): PNode = addSon(result, parseTypeDesc(p)) else: addSon(result, ast.emptyNode) - if (p.tok.tokType != tkEquals) and not (withBothOptional in flags): + if p.tok.tokType != tkEquals and withBothOptional notin flags: parMessage(p, errColonOrEqualsExpected, p.tok) if p.tok.tokType == tkEquals: getTok(p) @@ -1004,13 +1004,13 @@ proc parseExpr(p: var TParser): PNode = #| expr = (ifExpr #| | whenExpr #| | caseExpr - #| | tryStmt) + #| | tryExpr) #| / simpleExpr case p.tok.tokType: of tkIf: result = parseIfExpr(p, nkIfExpr) of tkWhen: result = parseIfExpr(p, nkWhenExpr) of tkCase: result = parseCase(p) - of tkTry: result = parseTry(p) + of tkTry: result = parseTry(p, isExpr=true) else: result = simpleExpr(p) proc parseEnum(p: var TParser): PNode @@ -1363,22 +1363,25 @@ proc parseCase(p: var TParser): PNode = if wasIndented: p.currInd = oldInd -proc parseTry(p: var TParser): PNode = +proc parseTry(p: var TParser; isExpr: bool): PNode = #| tryStmt = 'try' colcom stmt &(IND{=}? 'except'|'finally') #| (IND{=}? 'except' exprList colcom stmt)* #| (IND{=}? 'finally' colcom stmt)? + #| tryExpr = 'try' colcom stmt &(optInd 'except'|'finally') + #| (optInd 'except' exprList colcom stmt)* + #| (optInd 'finally' colcom stmt)? result = newNodeP(nkTryStmt, p) getTok(p) eat(p, tkColon) skipComment(p, result) addSon(result, parseStmt(p)) var b: PNode = nil - while sameOrNoInd(p): + while sameOrNoInd(p) or isExpr: case p.tok.tokType - of tkExcept: + of tkExcept: b = newNodeP(nkExceptBranch, p) exprList(p, tkColon, b) - of tkFinally: + of tkFinally: b = newNodeP(nkFinally, p) getTokNoInd(p) eat(p, tkColon) @@ -1877,7 +1880,7 @@ proc complexOrSimpleStmt(p: var TParser): PNode = of tkIf: result = parseIfOrWhen(p, nkIfStmt) of tkWhile: result = parseWhile(p) of tkCase: result = parseCase(p) - of tkTry: result = parseTry(p) + of tkTry: result = parseTry(p, isExpr=false) of tkFinally: result = parseExceptBlock(p, nkFinally) of tkExcept: result = parseExceptBlock(p, nkExceptBranch) of tkFor: result = parseFor(p) diff --git a/compiler/patterns.nim b/compiler/patterns.nim index d262790ab..5e21289b5 100644 --- a/compiler/patterns.nim +++ b/compiler/patterns.nim @@ -287,7 +287,7 @@ proc applyRule*(c: PContext, s: PSym, n: PNode): PNode = # constraint not fullfilled: if not ok: return nil - markUsed(n, s) + markUsed(n.info, s) if ctx.subMatch: assert m.len == 3 m.sons[1] = result diff --git a/compiler/pretty.nim b/compiler/pretty.nim index 3a5bfe197..17311f9e6 100644 --- a/compiler/pretty.nim +++ b/compiler/pretty.nim @@ -1,7 +1,7 @@ # # # The Nimrod Compiler -# (c) Copyright 2013 Andreas Rumpf +# (c) Copyright 2014 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -149,8 +149,8 @@ proc checkDef(c: PGen; n: PNode) = if n.kind != nkSym: return checkDef(n, n.sym) -proc checkUse*(n: PNode, s: PSym) = - if n.info.fileIndex < 0: return +proc checkUse*(info: TLineInfo; s: PSym) = + if info.fileIndex < 0: return # we simply convert it to what it looks like in the definition # for consistency @@ -159,10 +159,10 @@ proc checkUse*(n: PNode, s: PSym) = if s.kind in {skType, skGenericParam} and sfAnon in s.flags: return let newName = s.name.s - loadFile(n.info) + loadFile(info) - let line = gSourceFiles[n.info.fileIndex].lines[n.info.line-1] - var first = min(n.info.col.int, line.len) + let line = gSourceFiles[info.fileIndex].lines[info.line-1] + var first = min(info.col.int, line.len) if first < 0: return #inc first, skipIgnoreCase(line, "proc ", first) while first > 0 and line[first-1] in Letters: dec first @@ -179,8 +179,8 @@ proc checkUse*(n: PNode, s: PSym) = if x.match(peg"\s* {\ident} \s* '=' \s* y$1 ('#' .*)?"): x = "" - system.shallowCopy(gSourceFiles[n.info.fileIndex].lines[n.info.line-1], x) - gSourceFiles[n.info.fileIndex].dirty = true + system.shallowCopy(gSourceFiles[info.fileIndex].lines[info.line-1], x) + gSourceFiles[info.fileIndex].dirty = true when false: var cannotRename = initIntSet() @@ -220,53 +220,9 @@ when false: result.add s[i] inc i - proc checkUse(c: PGen; n: PNode) = - if n.info.fileIndex < 0: return - let s = n.sym - # operators stay as they are: - if s.kind in {skResult, skTemp} or s.name.s[0] notin Letters: return - if s.kind in {skType, skGenericParam} and sfAnon in s.flags: return - - if s.id in cannotRename: return - - let newName = if rules.hasKey(s.name.s): rules[s.name.s] - else: beautifyName(s.name.s, n.sym.kind) - - loadFile(n.info) - - let line = gSourceFiles[n.info.fileIndex].lines[n.info.line-1] - var first = min(n.info.col.int, line.len) - if first < 0: return - #inc first, skipIgnoreCase(line, "proc ", first) - while first > 0 and line[first-1] in Letters: dec first - if first < 0: return - if line[first] == '`': inc first - - if {sfImportc, sfExportc} * s.flags != {}: - # careful, we must ensure the resulting name still matches the external - # name: - if newName != s.name.s and newName != s.loc.r.ropeToStr and - lfFullExternalName notin s.loc.flags: - #Message(n.info, errGenerated, - # "cannot rename $# to $# due to external name" % [s.name.s, newName]) - cannotRename.incl(s.id) - return - let last = first+identLen(line, first)-1 - if differ(line, first, last, newName): - # last-first+1 != newName.len or - var x = line.subStr(0, first-1) & newName & line.substr(last+1) - when removeTP: - # the WinAPI module is full of 'TX = X' which after the substitution - # becomes 'X = X'. We remove those lines: - if x.match(peg"\s* {\ident} \s* '=' \s* y$1 ('#' .*)?"): - x = "" - - system.shallowCopy(gSourceFiles[n.info.fileIndex].lines[n.info.line-1], x) - gSourceFiles[n.info.fileIndex].dirty = true - proc check(c: PGen, n: PNode) = case n.kind - of nkSym: checkUse(n, n.sym) + of nkSym: checkUse(n.info, n.sym) of nkBlockStmt, nkBlockExpr, nkBlockType: checkDef(c, n[0]) check(c, n.sons[1]) diff --git a/compiler/renderer.nim b/compiler/renderer.nim index 6b62c48c5..0b1312ccc 100644 --- a/compiler/renderer.nim +++ b/compiler/renderer.nim @@ -146,28 +146,29 @@ proc makeNimString(s: string): string = for i in countup(0, len(s)-1): add(result, toNimChar(s[i])) add(result, '\"') -proc putComment(g: var TSrcGen, s: string) = +proc putComment(g: var TSrcGen, s: string) = + if s.isNil: return var i = 0 var comIndent = 1 var isCode = (len(s) >= 2) and (s[1] != ' ') var ind = g.lineLen var com = "" - while true: + while true: case s[i] - of '\0': - break - of '\x0D': + of '\0': + break + of '\x0D': put(g, tkComment, com) com = "" inc(i) if s[i] == '\x0A': inc(i) optNL(g, ind) - of '\x0A': + of '\x0A': put(g, tkComment, com) com = "" inc(i) optNL(g, ind) - of '#': + of '#': add(com, s[i]) inc(i) comIndent = 0 @@ -175,10 +176,10 @@ proc putComment(g: var TSrcGen, s: string) = add(com, s[i]) inc(i) inc(comIndent) - of ' ', '\x09': + of ' ', '\x09': add(com, s[i]) inc(i) - else: + else: # we may break the comment into a multi-line comment if the line # gets too long: # compute length of the following word: @@ -195,10 +196,10 @@ proc putComment(g: var TSrcGen, s: string) = optNL(g) proc maxLineLength(s: string): int = - result = 0 + if s.isNil: return 0 var i = 0 var lineLen = 0 - while true: + while true: case s[i] of '\0': break @@ -459,7 +460,7 @@ proc lsub(n: PNode): int = of nkBreakStmt: result = lsub(n.sons[0]) + len("break_") of nkContinueStmt: result = lsub(n.sons[0]) + len("continue_") of nkPragma: result = lcomma(n) + 4 - of nkCommentStmt: result = len(n.comment) + of nkCommentStmt: result = if n.comment.isNil: 0 else: len(n.comment) of nkOfBranch: result = lcomma(n, 0, - 2) + lsub(lastSon(n)) + len("of_:_") of nkImportAs: result = lsub(n.sons[0]) + len("_as_") + lsub(n.sons[1]) of nkElifBranch: result = lsons(n) + len("elif_:_") diff --git a/compiler/sem.nim b/compiler/sem.nim index e4ef6473f..8025ef70d 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -305,7 +305,9 @@ proc semAfterMacroCall(c: PContext, n: PNode, s: PSym, proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym, flags: TExprFlags = {}): PNode = - markUsed(n, sym) + pushInfoContext(nOrig.info) + + markUsed(n.info, sym) if sym == c.p.owner: globalError(n.info, errRecursiveDependencyX, sym.name.s) @@ -315,6 +317,7 @@ proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym, result = evalMacroCall(c.module, n, nOrig, sym) if efNoSemCheck notin flags: result = semAfterMacroCall(c, result, sym, flags) + popInfoContext() proc forceBool(c: PContext, n: PNode): PNode = result = fitNode(c, getSysType(tyBool), n) diff --git a/compiler/semcall.nim b/compiler/semcall.nim index 88a7f976c..65a2d7ab8 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -251,7 +251,7 @@ proc inferWithMetatype(c: PContext, formal: PType, proc semResolvedCall(c: PContext, n: PNode, x: TCandidate): PNode = assert x.state == csMatch var finalCallee = x.calleeSym - markUsed(n.sons[0], finalCallee) + markUsed(n.sons[0].info, finalCallee) if finalCallee.ast == nil: internalError(n.info, "calleeSym.ast is nil") # XXX: remove this check! if finalCallee.ast.sons[genericParamsPos].kind != nkEmpty: @@ -283,7 +283,7 @@ proc explicitGenericSym(c: PContext, n: PNode, s: PSym): PNode = var m: TCandidate initCandidate(c, m, s, n) var newInst = generateInstance(c, s, m.bindings, n.info) - markUsed(n, s) + markUsed(n.info, s) result = newSymNode(newInst, n.info) proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode = diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 078e95fbe..7f97124e1 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -12,7 +12,7 @@ proc semTemplateExpr(c: PContext, n: PNode, s: PSym, flags: TExprFlags = {}): PNode = - markUsed(n, s) + markUsed(n.info, s) pushInfoContext(n.info) result = evalTemplate(n, s, getCurrOwner()) if efNoSemCheck notin flags: result = semAfterMacroCall(c, result, s, flags) @@ -78,7 +78,7 @@ proc inlineConst(n: PNode, s: PSym): PNode {.inline.} = proc semSym(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = case s.kind of skConst: - markUsed(n, s) + markUsed(n.info, s) case skipTypes(s.typ, abstractInst-{tyTypeDesc}).kind of tyNil, tyChar, tyInt..tyInt64, tyFloat..tyFloat128, tyTuple, tySet, tyUInt..tyUInt64: @@ -101,7 +101,7 @@ proc semSym(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = of skMacro: result = semMacroExpr(c, n, n, s, flags) of skTemplate: result = semTemplateExpr(c, n, s, flags) of skVar, skLet, skResult, skParam, skForVar: - markUsed(n, s) + markUsed(n.info, s) # if a proc accesses a global variable, it is not side effect free: if sfGlobal in s.flags: incl(c.p.owner.flags, sfSideEffect) @@ -123,13 +123,13 @@ proc semSym(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = n.typ = s.typ return n of skType: - markUsed(n, s) + markUsed(n.info, s) if s.typ.kind == tyStatic and s.typ.n != nil: return s.typ.n result = newSymNode(s, n.info) result.typ = makeTypeDesc(c, s.typ) else: - markUsed(n, s) + markUsed(n.info, s) result = newSymNode(s, n.info) type @@ -253,7 +253,7 @@ proc semConv(c: PContext, n: PNode): PNode = let it = op.sons[i] let status = checkConvertible(c, result.typ, it.typ) if status in {convOK, convNotNeedeed}: - markUsed(n, it.sym) + markUsed(n.info, it.sym) markIndirect(c, it.sym) return it localError(n.info, errUseQualifier, op.sons[0].sym.name.s) @@ -792,6 +792,10 @@ proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode = n.flags.incl nfExprCall result = semOverloadedCallAnalyseEffects(c, n, nOrig, flags) if result == nil: return errorNode(c, n) + elif result.kind notin nkCallKinds: + # the semExpr() in overloadedCallOpr can even break this condition! + # See bug #904 of how to trigger it: + return result #result = afterCallActions(c, result, nOrig, flags) fixAbstractType(c, result) analyseIfAddressTakenInCall(c, result) @@ -971,7 +975,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = var s = qualifiedLookUp(c, n, {checkAmbiguity, checkUndeclared}) if s != nil: - markUsed(n.sons[1], s) + markUsed(n.sons[1].info, s) return semSym(c, n, s, flags) n.sons[0] = semExprWithType(c, n.sons[0], flags+{efDetermineType}) @@ -994,7 +998,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = result = newSymNode(f) result.info = n.info result.typ = ty - markUsed(n, f) + markUsed(n.info, f) return of tyTypeParamsHolders: return readTypeParameter(c, ty, i, n.info) @@ -1026,7 +1030,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = if f != nil: if fieldVisible(c, f): # is the access to a public field or in the same module or in a friend? - markUsed(n.sons[1], f) + markUsed(n.sons[1].info, f) n.sons[0] = makeDeref(n.sons[0]) n.sons[1] = newSymNode(f) # we now have the correct field n.typ = f.typ @@ -1039,7 +1043,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = elif ty.kind == tyTuple and ty.n != nil: f = getSymFromList(ty.n, i) if f != nil: - markUsed(n.sons[1], f) + markUsed(n.sons[1].info, f) n.sons[0] = makeDeref(n.sons[0]) n.sons[1] = newSymNode(f) n.typ = f.typ @@ -1450,7 +1454,7 @@ proc semExpandToAst(c: PContext, n: PNode): PNode = if expandedSym.kind == skError: return n macroCall.sons[0] = newSymNode(expandedSym, macroCall.info) - markUsed(n, expandedSym) + markUsed(n.info, expandedSym) for i in countup(1, macroCall.len-1): macroCall.sons[i] = semExprWithType(c, macroCall[i], {}) @@ -1881,7 +1885,7 @@ proc semBlock(c: PContext, n: PNode): PNode = if sfGenSym notin labl.flags: addDecl(c, labl) n.sons[0] = newSymNode(labl, n.sons[0].info) - suggestSym(n.sons[0], labl) + suggestSym(n.sons[0].info, labl) n.sons[1] = semExpr(c, n.sons[1]) n.typ = n.sons[1].typ if isEmptyType(n.typ): n.kind = nkBlockStmt @@ -2001,7 +2005,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = var s = qualifiedLookUp(c, n.sons[0], mode) if s != nil: if gCmd == cmdPretty and n.sons[0].kind == nkDotExpr: - pretty.checkUse(n.sons[0].sons[1], s) + pretty.checkUse(n.sons[0].sons[1].info, s) case s.kind of skMacro: if sfImmediate notin s.flags: diff --git a/compiler/semfold.nim b/compiler/semfold.nim index 712d2efd2..30e02dcc9 100644 --- a/compiler/semfold.nim +++ b/compiler/semfold.nim @@ -1,7 +1,7 @@ # # # The Nimrod Compiler -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2014 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -537,17 +537,18 @@ proc foldArrayAccess(m: PSym, n: PNode): PNode = var idx = getOrdValue(y) case x.kind of nkPar: - if (idx >= 0) and (idx < sonsLen(x)): + if idx >= 0 and idx < sonsLen(x): result = x.sons[int(idx)] if result.kind == nkExprColonExpr: result = result.sons[1] else: localError(n.info, errIndexOutOfBounds) - of nkBracket: - if (idx >= 0) and (idx < sonsLen(x)): result = x.sons[int(idx)] + of nkBracket: + idx = idx - x.typ.firstOrd + if idx >= 0 and idx < x.len: result = x.sons[int(idx)] else: localError(n.info, errIndexOutOfBounds) - of nkStrLit..nkTripleStrLit: + of nkStrLit..nkTripleStrLit: result = newNodeIT(nkCharLit, x.info, n.typ) - if (idx >= 0) and (idx < len(x.strVal)): + if idx >= 0 and idx < len(x.strVal): result.intVal = ord(x.strVal[int(idx)]) elif idx == len(x.strVal): discard diff --git a/compiler/semgnrc.nim b/compiler/semgnrc.nim index 353017bdd..934434951 100644 --- a/compiler/semgnrc.nim +++ b/compiler/semgnrc.nim @@ -82,7 +82,37 @@ proc lookup(c: PContext, n: PNode, flags: TSemGenericFlags, else: result = semGenericStmtSymbol(c, n, s) # else: leave as nkIdent + +proc newDot(n, b: PNode): PNode = + result = newNodeI(nkDotExpr, n.info) + result.add(n.sons[0]) + result.add(b) + +proc fuzzyLookup(c: PContext, n: PNode, flags: TSemGenericFlags, + ctx: var TIntSet): PNode = + assert n.kind == nkDotExpr + let luf = if withinMixin notin flags: {checkUndeclared} else: {} + var s = qualifiedLookUp(c, n, luf) + if s != nil: + result = semGenericStmtSymbol(c, n, s) + else: + result = n + let n = n[1] + let ident = considerQuotedIdent(n) + var s = searchInScopes(c, ident) + if s != nil and s.kind in routineKinds: + if withinBind in flags: + result = newDot(result, symChoice(c, n, s, scClosed)) + elif s.name.id in ctx: + result = newDot(result, symChoice(c, n, s, scForceOpen)) + else: + let sym = semGenericStmtSymbol(c, n, s) + if sym.kind == nkSym: + result = newDot(result, symChoice(c, n, s, scForceOpen)) + else: + result = newDot(result, sym) + proc semGenericStmt(c: PContext, n: PNode, flags: TSemGenericFlags, ctx: var TIntSet): PNode = result = n @@ -91,10 +121,11 @@ proc semGenericStmt(c: PContext, n: PNode, of nkIdent, nkAccQuoted: result = lookup(c, n, flags, ctx) of nkDotExpr: - let luf = if withinMixin notin flags: {checkUndeclared} else: {} - var s = qualifiedLookUp(c, n, luf) - if s != nil: result = semGenericStmtSymbol(c, n, s) + #let luf = if withinMixin notin flags: {checkUndeclared} else: {} + #var s = qualifiedLookUp(c, n, luf) + #if s != nil: result = semGenericStmtSymbol(c, n, s) # XXX for example: ``result.add`` -- ``add`` needs to be looked up here... + result = fuzzyLookup(c, n, flags, ctx) of nkEmpty, nkSym..nkNilLit: # see tests/compile/tgensymgeneric.nim: # We need to open the gensym'ed symbol again so that the instantiation diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 11399b38b..3cb9691eb 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -33,7 +33,7 @@ proc semBreakOrContinue(c: PContext, n: PNode): PNode = x.info = n.info incl(s.flags, sfUsed) n.sons[0] = x - suggestSym(x, s) + suggestSym(x.info, s) else: localError(n.info, errInvalidControlFlowX, s.name.s) elif (c.p.nestedLoopCounter <= 0) and (c.p.nestedBlockCounter <= 0): @@ -319,7 +319,7 @@ proc semIdentDef(c: PContext, n: PNode, kind: TSymKind): PSym = incl(result.flags, sfGlobal) else: result = semIdentWithPragma(c, kind, n, {}) - suggestSym(n, result) + suggestSym(n.info, result) proc checkNilable(v: PSym) = if sfGlobal in v.flags and {tfNotNil, tfNeedsInit} * v.typ.flags != {}: diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim index 84281dad5..ee8b1ccb8 100644 --- a/compiler/semtempl.nim +++ b/compiler/semtempl.nim @@ -59,7 +59,7 @@ proc symChoice(c: PContext, n: PNode, s: PSym, r: TSymChoiceRule): PNode = # (s.kind notin routineKinds or s.magic != mNone): # for instance 'nextTry' is both in tables.nim and astalgo.nim ... result = newSymNode(s, n.info) - markUsed(n, s) + markUsed(n.info, s) else: # semantic checking requires a type; ``fitNode`` deals with it # appropriately diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 9d38c4619..53fe4e266 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -281,7 +281,7 @@ proc semTypeIdent(c: PContext, n: PNode): PSym = else: result = qualifiedLookUp(c, n, {checkAmbiguity, checkUndeclared}) if result != nil: - markUsed(n, result) + markUsed(n.info, result) if result.kind == skParam and result.typ.kind == tyTypeDesc: # This is a typedesc param. is it already bound? # it's not bound when it's used multiple times in the @@ -443,14 +443,14 @@ proc semCaseBranch(c: PContext, t, branch: PNode, branchIndex: int, elif isRange(b): branch.sons[i] = semCaseBranchRange(c, t, b, covered) else: + # constant sets and arrays are allowed: var r = semConstExpr(c, b) # for ``{}`` we want to trigger the type mismatch in ``fitNode``: - if r.kind != nkCurly or len(r) == 0: + if r.kind notin {nkCurly, nkBracket} or len(r) == 0: checkMinSonsLen(t, 1) branch.sons[i] = skipConv(fitNode(c, t.sons[0].typ, r)) inc(covered) else: - # constant sets have special rules # first element is special and will overwrite: branch.sons[i]: branch.sons[i] = semCaseBranchSetElem(c, t, r[0], covered) # other elements have to be added to ``branch`` @@ -562,7 +562,7 @@ proc semRecordNodeAux(c: PContext, n: PNode, check: var TIntSet, pos: var int, let rec = rectype.sym for i in countup(0, sonsLen(n)-3): var f = semIdentWithPragma(c, skField, n.sons[i], {sfExported}) - suggestSym(n.sons[i], f) + suggestSym(n.sons[i].info, f) f.typ = typ f.position = pos if (rec != nil) and ({sfImportc, sfExportc} * rec.flags != {}) and @@ -827,7 +827,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, result = addImplicitGeneric(newTypeS(tyAnything, c)) of tyGenericParam: - markUsed(genericParams, paramType.sym) + markUsed(info, paramType.sym) if tfWildcard in paramType.flags: paramType.flags.excl tfWildcard paramType.sym.kind = skType @@ -864,7 +864,13 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, var counter = 0 for i in countup(1, n.len - 1): var a = n.sons[i] - if a.kind != nkIdentDefs: illFormedAst(a) + if a.kind != nkIdentDefs: + # for some generic instantiations the passed ':env' parameter + # for closures has already been produced (see bug #898). We simply + # skip this parameter here. It'll then be re-generated in another LL + # pass over this instantiation: + if a.kind == nkSym and sfFromGeneric in a.sym.flags: continue + illFormedAst(a) checkMinSonsLen(a, 3) var typ: PType = nil @@ -1181,7 +1187,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = else: assignType(prev, t) result = prev - markUsed(n, n.sym) + markUsed(n.info, n.sym) else: if n.sym.kind != skError: localError(n.info, errTypeExpected) result = newOrPrevType(tyError, prev, c) diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 551e21d78..e5bf08097 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -62,7 +62,7 @@ type const isNilConversion = isConvertible # maybe 'isIntConv' fits better? -proc markUsed*(n: PNode, s: PSym) +proc markUsed*(info: TLineInfo, s: PSym) proc initCandidateAux(ctx: PContext, c: var TCandidate, callee: PType) {.inline.} = @@ -1058,7 +1058,7 @@ proc userConvMatch(c: PContext, m: var TCandidate, f, a: PType, dest = generateTypeInstance(c, m.bindings, arg, dest) let fdest = typeRel(m, f, dest) if fdest in {isEqual, isGeneric}: - markUsed(arg, c.converters[i]) + markUsed(arg.info, c.converters[i]) var s = newSymNode(c.converters[i]) s.typ = c.converters[i].typ s.info = arg.info @@ -1271,7 +1271,7 @@ proc paramTypesMatch*(m: var TCandidate, f, a: PType, result = nil else: # only one valid interpretation found: - markUsed(arg, arg.sons[best].sym) + markUsed(arg.info, arg.sons[best].sym) result = paramTypesMatchAux(m, f, arg.sons[best].typ, arg.sons[best], argOrig) diff --git a/compiler/suggest.nim b/compiler/suggest.nim index db95c480f..c2bdfc5c3 100644 --- a/compiler/suggest.nim +++ b/compiler/suggest.nim @@ -246,18 +246,18 @@ var usageSym*: PSym lastLineInfo: TLineInfo -proc findUsages(node: PNode, s: PSym) = - if usageSym == nil and isTracked(node.info, s.name.s.len): +proc findUsages(info: TLineInfo; s: PSym) = + if usageSym == nil and isTracked(info, s.name.s.len): usageSym = s suggestWriteln(symToStr(s, isLocal=false, sectionUsage)) elif s == usageSym: - if lastLineInfo != node.info: - suggestWriteln(symToStr(s, isLocal=false, sectionUsage, node.info)) - lastLineInfo = node.info + if lastLineInfo != info: + suggestWriteln(symToStr(s, isLocal=false, sectionUsage, info)) + lastLineInfo = info -proc findDefinition(node: PNode, s: PSym) = - if node.isNil or s.isNil: return - if isTracked(node.info, s.name.s.len): +proc findDefinition(info: TLineInfo; s: PSym) = + if s.isNil: return + if isTracked(info, s.name.s.len): suggestWriteln(symToStr(s, isLocal=false, sectionDef)) suggestQuit() @@ -316,26 +316,26 @@ proc defFromSourceMap*(i: TLineInfo) = defFromLine(gSourceMaps[i.fileIndex].lines[i.line].entries, i.col) -proc suggestSym*(n: PNode, s: PSym) {.inline.} = +proc suggestSym*(info: TLineInfo; s: PSym) {.inline.} = ## misnamed: should be 'symDeclared' if optUsages in gGlobalOptions: - findUsages(n, s) + findUsages(info, s) if optDef in gGlobalOptions: - findDefinition(n, s) - if isServing and not n.isNil: - addToSourceMap(s, n.info) + findDefinition(info, s) + if isServing: + addToSourceMap(s, info) -proc markUsed(n: PNode, s: PSym) = +proc markUsed(info: TLineInfo; s: PSym) = incl(s.flags, sfUsed) if {sfDeprecated, sfError} * s.flags != {}: - if sfDeprecated in s.flags: message(n.info, warnDeprecated, s.name.s) - if sfError in s.flags: localError(n.info, errWrongSymbolX, s.name.s) - suggestSym(n, s) - if gCmd == cmdPretty: checkUse(n, s) + if sfDeprecated in s.flags: message(info, warnDeprecated, s.name.s) + if sfError in s.flags: localError(info, errWrongSymbolX, s.name.s) + suggestSym(info, s) + if gCmd == cmdPretty: checkUse(info, s) proc useSym*(sym: PSym): PNode = result = newSymNode(sym) - markUsed(result, sym) + markUsed(result.info, sym) proc suggestExpr*(c: PContext, node: PNode) = var cp = msgs.inCheckpoint(node.info) diff --git a/compiler/transf.nim b/compiler/transf.nim index fb5e321b6..dece1ac18 100644 --- a/compiler/transf.nim +++ b/compiler/transf.nim @@ -546,7 +546,7 @@ proc flattenTree(root: PNode): PNode = flattenTreeAux(result, root, op) else: result = root - + proc transformCall(c: PTransf, n: PNode): PTransNode = var n = flattenTree(n) var op = getMergeOp(n) @@ -565,6 +565,9 @@ proc transformCall(c: PTransf, n: PNode): PTransNode = inc(j) add(result, a.PTransNode) if len(result) == 2: result = result[1] + elif getMagic(n) == mNBindSym: + # for bindSym(myconst) we MUST NOT perform constant folding: + result = n.PTransNode else: let s = transformSons(c, n).PNode # bugfix: check after 'transformSons' if it's still a method call: diff --git a/compiler/vm.nim b/compiler/vm.nim index 0c2c23987..66595856a 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -138,6 +138,9 @@ proc createStrKeepNode(x: var TFullReg) = template createStr(x) = x.node = newNode(nkStrLit) +template createSet(x) = + x.node = newNode(nkCurly) + proc moveConst(x: var TFullReg, y: TFullReg) = if x.kind != y.kind: myreset(x) @@ -435,7 +438,6 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = if regs[rc].intVal > high(int): stackTrace(c, tos, pc, errIndexOutOfBounds) let idx = regs[rc].intVal.int - # XXX what if the array is not 0-based? -> codegen should insert a sub let src = regs[rb].node if src.kind notin {nkEmpty..nkNilLit} and idx <% src.len: regs[ra].node = src.sons[idx] @@ -501,13 +503,13 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = else: stackTrace(c, tos, pc, errNilAccess) of opcWrDeref: - # a[] = b + # a[] = c; b unused let ra = instr.regA - let rb = instr.regB + let rc = instr.regC case regs[ra].kind - of rkNodeAddr: putIntoNode(regs[ra].nodeAddr[], regs[rb]) - of rkRegisterAddr: regs[ra].regAddr[] = regs[rb] - of rkNode: putIntoNode(regs[ra].node, regs[rb]) + of rkNodeAddr: putIntoNode(regs[ra].nodeAddr[], regs[rc]) + of rkRegisterAddr: regs[ra].regAddr[] = regs[rc] + of rkNode: putIntoNode(regs[ra].node, regs[rc]) else: stackTrace(c, tos, pc, errNilAccess) of opcAddInt: decodeBC(rkInt) @@ -669,14 +671,11 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = of opcLtu: decodeBC(rkInt) regs[ra].intVal = ord(regs[rb].intVal <% regs[rc].intVal) - of opcEqRef: + of opcEqRef, opcEqNimrodNode: decodeBC(rkInt) regs[ra].intVal = ord((regs[rb].node.kind == nkNilLit and regs[rc].node.kind == nkNilLit) or regs[rb].node == regs[rc].node) - of opcEqNimrodNode: - decodeBC(rkInt) - regs[ra].intVal = ord(regs[rb].node == regs[rc].node) of opcXor: decodeBC(rkInt) regs[ra].intVal = ord(regs[rb].intVal != regs[rc].intVal) @@ -722,18 +721,22 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = regs[ra].intVal = ord(containsSets(a, b) and not equalSets(a, b)) of opcMulSet: decodeBC(rkNode) + createSet(regs[ra]) move(regs[ra].node.sons, nimsets.intersectSets(regs[rb].node, regs[rc].node).sons) of opcPlusSet: decodeBC(rkNode) + createSet(regs[ra]) move(regs[ra].node.sons, nimsets.unionSets(regs[rb].node, regs[rc].node).sons) of opcMinusSet: decodeBC(rkNode) + createSet(regs[ra]) move(regs[ra].node.sons, nimsets.diffSets(regs[rb].node, regs[rc].node).sons) of opcSymdiffSet: decodeBC(rkNode) + createSet(regs[ra]) move(regs[ra].node.sons, nimsets.symdiffSets(regs[rb].node, regs[rc].node).sons) of opcConcatStr: @@ -744,11 +747,11 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = regs[ra].node.strVal.add getstr(regs[i]) of opcAddStrCh: decodeB(rkNode) - createStrKeepNode regs[ra] + #createStrKeepNode regs[ra] regs[ra].node.strVal.add(regs[rb].intVal.chr) of opcAddStrStr: decodeB(rkNode) - createStrKeepNode regs[ra] + #createStrKeepNode regs[ra] regs[ra].node.strVal.add(regs[rb].node.strVal) of opcAddSeqElem: decodeB(rkNode) @@ -899,10 +902,10 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = c.exceptionInstr = pc let (newPc, newTos) = cleanUpOnException(c, tos) # -1 because of the following 'inc' - if pc-1 < 0: + if newPc-1 < 0: bailOut(c, tos) return - pc = newPc -1 + pc = newPc-1 if tos != newTos: tos = newTos move(regs, tos.slots) @@ -985,7 +988,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = return TFullReg(kind: rkNone) of opcSetLenStr: decodeB(rkNode) - createStrKeepNode regs[ra] + #createStrKeepNode regs[ra] regs[ra].node.strVal.setLen(regs[rb].intVal.int) of opcOf: decodeBC(rkInt) diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index 305ea7f9e..c1ec637dd 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -9,6 +9,24 @@ ## This module implements the code generator for the VM. +# Important things to remember: +# - The VM does not distinguish between definitions ('var x = y') and +# assignments ('x = y'). For simple data types that fit into a register +# this doesn't matter. However it matters for strings and other complex +# types that use the 'node' field; the reason is that slots are +# re-used in a register based VM. Example: +# +# .. code-block:: nimrod +# let s = a & b # no matter what, create fresh node +# s = a & b # no matter what, keep the node +# +# Also *stores* into non-temporary memory need to perform deep copies: +# a.b = x.y +# We used to generate opcAsgn for the *load* of 'x.y' but this is clearly +# wrong! We need to produce opcAsgn (the copy) for the *store*. This also +# solves the opcLdConst vs opcAsgnConst issue. Of course whether we need +# this copy depends on the involved types. + import unsigned, strutils, ast, astalgo, types, msgs, renderer, vmdef, trees, intsets, rodread, magicsys, options, lowerings @@ -84,21 +102,27 @@ proc gABI(c: PCtx; n: PNode; opc: TOpcode; a, b: TRegister; imm: BiggestInt) = # Takes the `b` register and the immediate `imm`, appies the operation `opc`, # and stores the output value into `a`. # `imm` is signed and must be within [-127, 128] - assert(imm >= -127 and imm <= 128) - let ins = (opc.uint32 or (a.uint32 shl 8'u32) or - (b.uint32 shl 16'u32) or - (imm+byteExcess).uint32 shl 24'u32).TInstr - c.code.add(ins) - c.debug.add(n.info) + if imm >= -127 and imm <= 128: + let ins = (opc.uint32 or (a.uint32 shl 8'u32) or + (b.uint32 shl 16'u32) or + (imm+byteExcess).uint32 shl 24'u32).TInstr + c.code.add(ins) + c.debug.add(n.info) + else: + localError(n.info, errGenerated, + "VM: immediate value does not fit into an int8") proc gABx(c: PCtx; n: PNode; opc: TOpcode; a: TRegister = 0; bx: int) = # Applies `opc` to `bx` and stores it into register `a` # `bx` must be signed and in the range [-32767, 32768] - assert(bx >= -32767 and bx <= 32768) - let ins = (opc.uint32 or a.uint32 shl 8'u32 or - (bx+wordExcess).uint32 shl 16'u32).TInstr - c.code.add(ins) - c.debug.add(n.info) + if bx >= -32767 and bx <= 32768: + let ins = (opc.uint32 or a.uint32 shl 8'u32 or + (bx+wordExcess).uint32 shl 16'u32).TInstr + c.code.add(ins) + c.debug.add(n.info) + else: + localError(n.info, errGenerated, + "VM: immediate value does not fit into an int16") proc xjmp(c: PCtx; n: PNode; opc: TOpcode; a: TRegister = 0): TPosition = #assert opc in {opcJmp, opcFJmp, opcTJmp} @@ -485,11 +509,22 @@ proc genField(n: PNode): TRegister = "too large offset! cannot generate code for: " & s.name.s) result = s.position +proc genIndex(c: PCtx; n: PNode; arr: PType): TRegister = + if arr.skipTypes(abstractInst).kind == tyArray and (let x = firstOrd(arr); + x != 0): + let tmp = c.genx(n) + # freeing the temporary here means we can produce: regA = regA - Imm + c.freeTemp(tmp) + result = c.getTemp(n.typ) + c.gABI(n, opcSubImmInt, result, tmp, x.int) + else: + result = c.genx(n) + proc genAsgnPatch(c: PCtx; le: PNode, value: TRegister) = case le.kind of nkBracketExpr: let dest = c.genx(le.sons[0], {gfAddrOf}) - let idx = c.genx(le.sons[1]) + let idx = c.genIndex(le.sons[1], le.sons[0].typ) c.gABC(le, opcWrArr, dest, idx, value) c.freeTemp(dest) c.freeTemp(idx) @@ -502,12 +537,12 @@ proc genAsgnPatch(c: PCtx; le: PNode, value: TRegister) = c.freeTemp(dest) of nkDerefExpr, nkHiddenDeref: let dest = c.genx(le.sons[0], {gfAddrOf}) - c.gABC(le, opcWrDeref, dest, value) + c.gABC(le, opcWrDeref, dest, 0, value) c.freeTemp(dest) of nkSym: if le.sym.isGlobal: let dest = c.genx(le, {gfAddrOf}) - c.gABC(le, opcWrDeref, dest, value) + c.gABC(le, opcWrDeref, dest, 0, value) c.freeTemp(dest) else: discard @@ -1068,17 +1103,36 @@ proc checkCanEval(c: PCtx; n: PNode) = not s.isOwnedBy(c.prc.sym) and s.owner != c.module: cannotEval(n) +proc isTemp(c: PCtx; dest: TDest): bool = + result = dest >= 0 and c.prc.slots[dest].kind >= slotTempUnknown + +template needsAdditionalCopy(n): expr = + not c.isTemp(dest) and not fitsRegister(n.typ) + +proc preventFalseAlias(c: PCtx; n: PNode; opc: TOpcode; + dest, idx, value: TRegister) = + # opcLdObj et al really means "load address". We sometimes have to create a + # copy in order to not introduce false aliasing: + # mylocal = a.b # needs a copy of the data! + if needsAdditionalCopy(n): + var cc = c.getTemp(n.typ) + c.gABC(n, whichAsgnOpc(n), cc, value) + c.gABC(n, opc, dest, idx, cc) + c.freeTemp(cc) + else: + c.gABC(n, opc, dest, idx, value) + proc genAsgn(c: PCtx; le, ri: PNode; requiresCopy: bool) = case le.kind of nkBracketExpr: let dest = c.genx(le.sons[0], {gfAddrOf}) - let idx = c.genx(le.sons[1]) + let idx = c.genIndex(le.sons[1], le.sons[0].typ) let tmp = c.genx(ri) if le.sons[0].typ.skipTypes(abstractVarRange-{tyTypeDesc}).kind in { tyString, tyCString}: - c.gABC(le, opcWrStrIdx, dest, idx, tmp) + c.preventFalseAlias(le, opcWrStrIdx, dest, idx, tmp) else: - c.gABC(le, opcWrArr, dest, idx, tmp) + c.preventFalseAlias(le, opcWrArr, dest, idx, tmp) c.freeTemp(tmp) of nkDotExpr, nkCheckedFieldExpr: # XXX field checks here @@ -1086,12 +1140,12 @@ proc genAsgn(c: PCtx; le, ri: PNode; requiresCopy: bool) = let dest = c.genx(left.sons[0], {gfAddrOf}) let idx = genField(left.sons[1]) let tmp = c.genx(ri) - c.gABC(left, opcWrObj, dest, idx, tmp) + c.preventFalseAlias(left, opcWrObj, dest, idx, tmp) c.freeTemp(tmp) of nkDerefExpr, nkHiddenDeref: let dest = c.genx(le.sons[0], {gfAddrOf}) let tmp = c.genx(ri) - c.gABC(le, opcWrDeref, dest, tmp) + c.preventFalseAlias(le, opcWrDeref, dest, 0, tmp) c.freeTemp(tmp) of nkSym: let s = le.sym @@ -1100,24 +1154,32 @@ proc genAsgn(c: PCtx; le, ri: PNode; requiresCopy: bool) = withTemp(tmp, le.typ): c.gen(le, tmp, {gfAddrOf}) let val = c.genx(ri) - c.gABC(le, opcWrDeref, tmp, val) + c.preventFalseAlias(le, opcWrDeref, tmp, 0, val) c.freeTemp(val) else: if s.kind == skForVar: c.setSlot s internalAssert s.position > 0 or (s.position == 0 and s.kind in {skParam,skResult}) var dest: TRegister = s.position + ord(s.kind == skParam) - gen(c, ri, dest) + if needsAdditionalCopy(le) and s.kind in {skResult, skVar, skParam}: + var cc = c.getTemp(le.typ) + gen(c, ri, cc) + c.gABC(le, whichAsgnOpc(le), dest, cc) + c.freeTemp(cc) + else: + gen(c, ri, dest) else: let dest = c.genx(le, {gfAddrOf}) genAsgn(c, dest, ri, requiresCopy) proc genLit(c: PCtx; n: PNode; dest: var TDest) = - var opc = opcLdConst + # opcLdConst is now always valid. We produce the necessary copy in the + # assignments now: + #var opc = opcLdConst if dest < 0: dest = c.getTemp(n.typ) - elif c.prc.slots[dest].kind == slotFixedVar: opc = opcAsgnConst + #elif c.prc.slots[dest].kind == slotFixedVar: opc = opcAsgnConst let lit = genLiteral(c, n) - c.gABx(n, opc, dest, lit) + c.gABx(n, opcLdConst, dest, lit) proc genTypeLit(c: PCtx; t: PType; dest: var TDest) = var n = newNode(nkType) @@ -1146,7 +1208,7 @@ proc genGlobalInit(c: PCtx; n: PNode; s: PSym) = let dest = c.getTemp(s.typ) c.gABx(n, opcLdGlobal, dest, s.position) let tmp = c.genx(s.ast) - c.gABC(n, opcWrDeref, dest, tmp) + c.preventFalseAlias(n, opcWrDeref, dest, 0, tmp) c.freeTemp(dest) c.freeTemp(tmp) @@ -1182,12 +1244,15 @@ proc genRdVar(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) = # see tests/t99bott for an example that triggers it: cannotEval(n) +template needsRegLoad(): expr = + gfAddrOf notin flags and fitsRegister(n.typ.skipTypes({tyVar})) + proc genArrAccess2(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode; flags: TGenFlags) = let a = c.genx(n.sons[0], flags) - let b = c.genx(n.sons[1], {}) + let b = c.genIndex(n.sons[1], n.sons[0].typ) if dest < 0: dest = c.getTemp(n.typ) - if gfAddrOf notin flags and fitsRegister(n.typ): + if needsRegLoad(): var cc = c.getTemp(n.typ) c.gABC(n, opc, cc, a, b) c.gABC(n, opcNodeToReg, dest, cc) @@ -1203,7 +1268,7 @@ proc genObjAccess(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) = let a = c.genx(n.sons[0], flags) let b = genField(n.sons[1]) if dest < 0: dest = c.getTemp(n.typ) - if gfAddrOf notin flags and fitsRegister(n.typ.skipTypes({tyVar})): + if needsRegLoad(): var cc = c.getTemp(n.typ) c.gABC(n, opcLdObj, cc, a, b) c.gABC(n, opcNodeToReg, dest, cc) @@ -1303,7 +1368,7 @@ proc genVarSection(c: PCtx; n: PNode) = if a.sons[2].kind != nkEmpty: let tmp = c.genx(a.sons[0], {gfAddrOf}) let val = c.genx(a.sons[2]) - c.gABC(a, opcWrDeref, tmp, val) + c.preventFalseAlias(a, opcWrDeref, tmp, 0, val) c.freeTemp(val) c.freeTemp(tmp) else: @@ -1311,7 +1376,16 @@ proc genVarSection(c: PCtx; n: PNode) = if a.sons[2].kind == nkEmpty: c.gABx(a, ldNullOpcode(s.typ), s.position, c.genType(s.typ)) else: - gen(c, a.sons[2], s.position.TRegister) + if not fitsRegister(s.typ): + c.gABx(a, ldNullOpcode(s.typ), s.position, c.genType(s.typ)) + let le = a.sons[0] + if not fitsRegister(le.typ) and s.kind in {skResult, skVar, skParam}: + var cc = c.getTemp(le.typ) + gen(c, a.sons[2], cc) + c.gABC(le, whichAsgnOpc(le), s.position.TRegister, cc) + c.freeTemp(cc) + else: + gen(c, a.sons[2], s.position.TRegister) else: # assign to a.sons[0]; happens for closures if a.sons[2].kind == nkEmpty: @@ -1339,7 +1413,7 @@ proc genArrayConstr(c: PCtx, n: PNode, dest: var TDest) = c.gABx(n, opcLdNullReg, tmp, c.genType(intType)) for x in n: let a = c.genx(x) - c.gABC(n, whichAsgnOpc(x, opcWrArr), dest, tmp, a) + c.preventFalseAlias(n, whichAsgnOpc(x, opcWrArr), dest, tmp, a) c.gABI(n, opcAddImmInt, tmp, tmp, 1) c.freeTemp(a) c.freeTemp(tmp) @@ -1371,7 +1445,8 @@ proc genObjConstr(c: PCtx, n: PNode, dest: var TDest) = if it.kind == nkExprColonExpr and it.sons[0].kind == nkSym: let idx = genField(it.sons[0]) let tmp = c.genx(it.sons[1]) - c.gABC(it, whichAsgnOpc(it.sons[1], opcWrObj), dest, idx, tmp) + c.preventFalseAlias(it.sons[1], whichAsgnOpc(it.sons[1], opcWrObj), + dest, idx, tmp) c.freeTemp(tmp) else: internalError(n.info, "invalid object constructor") @@ -1385,11 +1460,12 @@ proc genTupleConstr(c: PCtx, n: PNode, dest: var TDest) = if it.kind == nkExprColonExpr: let idx = genField(it.sons[0]) let tmp = c.genx(it.sons[1]) - c.gABC(it, whichAsgnOpc(it.sons[1], opcWrObj), dest, idx, tmp) + c.preventFalseAlias(it.sons[1], whichAsgnOpc(it.sons[1], opcWrObj), + dest, idx, tmp) c.freeTemp(tmp) else: let tmp = c.genx(it) - c.gABC(it, whichAsgnOpc(it, opcWrObj), dest, i.TRegister, tmp) + c.preventFalseAlias(it, whichAsgnOpc(it, opcWrObj), dest, i.TRegister, tmp) c.freeTemp(tmp) proc genProc*(c: PCtx; s: PSym): int @@ -1627,7 +1703,7 @@ proc genProc(c: PCtx; s: PSym): int = c.gABC(body, opcEof, eofInstr.regA) c.optimizeJumps(result) s.offset = c.prc.maxSlots - #if s.name.s == "find": + #if s.name.s == "calc": # echo renderTree(body) # c.echoCode(result) c.prc = oldPrc diff --git a/doc/advopt.txt b/doc/advopt.txt index f5ff90791..08465e457 100644 --- a/doc/advopt.txt +++ b/doc/advopt.txt @@ -2,6 +2,7 @@ Advanced commands: //compileToC, cc compile project with C code generator //compileToCpp, cpp compile project to C++ code //compileToOC, objc compile project to Objective C code + //js compile project to Javascript //rst2html convert a reStructuredText file to HTML //rst2tex convert a reStructuredText file to TeX //jsondoc extract the documentation to a json file diff --git a/doc/backends.txt b/doc/backends.txt new file mode 100644 index 000000000..26576e733 --- /dev/null +++ b/doc/backends.txt @@ -0,0 +1,409 @@ +================================ + Nimrod Backend Integration +================================ + +:Author: Puppet Master +:Version: |nimrodversion| + +.. contents:: + "Heresy grows from idleness." -- Unknown. + + +Introduction +============ + +The `Nimrod Compiler User Guide <nimrodc.html>`_ documents the typical +compiler invocation, using the ``compile`` or ``c`` command to transform a +``.nim`` file into one or more ``.c`` files which are then compiled with the +platform's C compiler into a static binary. However there are other commands +to compile to C++, Objective-C or JavaScript. This document tries to +concentrate in a single place all the backend and interfacing options. + +The Nimrod compiler supports mainly two backend families: the C, C++ and +Objective-C targets and the JavaScript target. `The C like targets`_ creates +source files which can be compiled into a library or a final executable. `The +JavaScript target`_ can generate a ``.js`` file which you reference from an +HTML file or create a `standalone nodejs program <http://nodejs.org>`_. + +On top of generating libraries or standalone applications, Nimrod offers +bidirectional interfacing with the backend targets through generic and +specific pragmas. + + +Backends +======== + +The C like targets +------------------ + +The commands to compile to either C, C++ or Objective-C are: + + //compileToC, cc compile project with C code generator + //compileToCpp, cpp compile project to C++ code + //compileToOC, objc compile project to Objective C code + +The most significant difference between these commands is that if you look +into the ``nimcache`` directory you will find ``.c``, ``.cpp`` or ``.m`` +files, other than that all of them will produce a native binary for your +project. This allows you to take the generated code and place it directly +into a project using any of these languages. Here are some typical command +line invocations:: + + $ nimrod c hallo.nim + $ nimrod cpp hallo.nim + $ nimrod objc hallo.nim + +The compiler commands select the target backend, but if needed you can +`specify additional switches for cross compilation +<nimrodc.html#cross-compilation>`_ to select the target CPU, operative system +or compiler/linker commands. + + +The JavaScript target +--------------------- + +Nimrod can also generate `JavaScript`:idx: code through the ``js`` command. +However, the JavaScript code generator is experimental! + +Nimrod targets JavaScript 1.5 which is supported by any widely used browser. +Since JavaScript does not have a portable means to include another module, +Nimrod just generates a long ``.js`` file. + +Features or modules that the JavaScript platform does not support are not +available. This includes: + +* manual memory management (``alloc``, etc.) +* casting and other unsafe operations (``cast`` operator, ``zeroMem``, etc.) +* file management +* most modules of the Standard library +* proper 64 bit integer arithmetic +* unsigned integer arithmetic + +However, the modules `strutils <strutils.html>`_, `math <math.html>`_, and +`times <times.html>`_ are available! To access the DOM, use the `dom +<dom.html>`_ module that is only available for the JavaScript platform. + +To compile a Nimrod module into a ``.js`` file use the ``js`` command; the +default is a ``.js`` file that is supposed to be referenced in an ``.html`` +file. However, you can also run the code with `nodejs`:idx:, a `software +platform for easily building fast, scalable network applications +<http://nodejs.org>`_:: + + nimrod js -d:nodejs -r examples/hallo.nim + + +Interfacing +=========== + +Nimrod offers bidirectional interfacing with the target backend. This means +that you can call backend code from Nimrod and Nimrod code can be called by +the backend code. Usually the direction of which calls which depends on your +software architecture (is Nimrod your main program or is Nimrod providing a +component?). + + +Nimrod code calling the backend +-------------------------------- + +Nimrod code can interface with the backend through the `Foreign function +interface <manual.html#foreign-function-interface>`_ mainly through the +`importc pragma <manual.html#importc-pragma>`_. The ``importc`` pragma is the +*generic* way of making backend symbols available in Nimrod and is available +in all the target backends (JavaScript too). The C++ or Objective-C backends +have their respective `ImportCpp <nimrodc.html#importcpp-pragma>`_ and +`ImportObjC <nimrodc.html#importobjc-pragma>`_ pragmas to call methods from +classes. + +Whenever you use any of these pragmas you need to integrate native code into +your final binary. In the case of JavaScript this is no problem at all, the +same html file which hosts the generated JavaScript will likely provide other +JavaScript functions which you are importing with ``importc``. + +However, for the C like targets you need to link external code either +statically or dynamically. The preferred way of integrating native code is to +use dynamic linking because it allows you to compile Nimrod programs without +the need for having the related development libraries installed. This is done +through the `dynlib pragma for import +<manual.html#dynlib-pragma-for-import>`_, though more specific control can be +gained using the `dynlib module <dynlib.html>`_. + +The `dynlibOverride <nimrodc.html#dynliboverride>`_ command line switch allows +to avoid dynamic linking if you need to statically link something instead. +Nimrod wrappers designed to statically link source files can use the `compile +pragma <nimrodc.html#compile-pragma>`_ if there are few sources or providing +them along the Nimrod code is easier than using a system library. Libraries +installed on the host system can be linked in with the `PassL pragma +<nimrodc.html#passl-pragma>`_. + +To wrap native code, take a look at the `c2nim tool <c2nim.html>`_ which helps +with the process of scanning and transforming header files into a Nimrod +interface. + +C invocation example +~~~~~~~~~~~~~~~~~~~~ + +Create a ``logic.c`` file with the following content: + +.. code-block:: c + int addTwoIntegers(int a, int b) + { + return a + b; + } + +Create a ``calculator.nim`` file with the following content: + +.. code-block:: nimrod + + {.compile: "logic.c".} + proc addTwoIntegers(a, b: cint): cint {.importc.} + + when isMainModule: + echo addTwoIntegers(3, 7) + +With these two files in place, you can run ``nimrod c -r calculator.nim`` and +the Nimrod compiler will compile the ``logic.c`` file in addition to +``calculator.nim`` and link both into an executable, which outputs ``10`` when +run. Another way to link the C file statically and get the same effect would +be remove the line with the ``compile`` pragma and run the following typical +Unix commands:: + + $ gcc -c logic.c + $ ar rvs mylib.a logic.o + $ nimrod c --passL:mylib.a -r calculator.nim + +Just like in this example we pass the path to the ``mylib.a`` library (and we +could as well pass ``logic.o``) we could be passing switches to link any other +static C library. + + +JavaScript invocation example +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Create a ``host.html`` file with the following content: + +.. code-block:: + + <html><body> + <script type="text/javascript"> + function addTwoIntegers(a, b) + { + return a + b; + } + </script> + <script type="text/javascript" src="calculator.js"></script> + </body></html> + +Create a ``calculator.nim`` file with the following content (or reuse the one +from the previous section): + +.. code-block:: nimrod + + proc addTwoIntegers(a, b: int): int {.importc.} + + when isMainModule: + echo addTwoIntegers(3, 7) + +Compile the Nimrod code to JavaScript with ``nimrod js -o:calculator.js +calculator.nim`` and open ``host.html`` in a browser. If the browser supports +javascript, you should see the value ``10``. In JavaScript the `echo proc +<system.html#echo>`_ will modify the HTML DOM and append the string. Use the +`dom module <dom.html>`_ for specific DOM querying and modification procs. + + +Backend code calling Nimrod +--------------------------- + +Backend code can interface with Nimrod code exposed through the `exportc +pragma <manual.html#exportc-pragma>`_. The ``exportc`` pragma is the *generic* +way of making Nimrod symbols available to the backends. By default the Nimrod +compiler will mangle all the Nimrod symbols to avoid any name collision, so +the most significant thing the ``exportc`` pragma does is maintain the Nimrod +symbol name, or if specified, use an alternative symbol for the backend in +case the symbol rules don't match. + +The JavaScript target doesn't have any further interfacing considerations +since it also has garbage collection, but the C targets require you to +initialize Nimrod's internals, which is done calling a ``NimMain`` function. +Also, C code requires you to specify a forward declaration for functions or +the compiler will asume certain types for the return value and parameters +which will likely make your program crash at runtime. + +The Nimrod compiler can generate a C interface header through the ``--header`` +command line switch. The generated header will contain all the exported +symbols and the ``NimMain`` proc which you need to call before any other +Nimrod code. + + +Nimrod invocation example from C +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Create a ``fib.nim`` file with the following content: + +.. code-block:: nimrod + + proc fib(a: cint): cint {.exportc.} = + if a <= 2: + result = 1 + else: + result = fib(a - 1) + fib(a - 2) + +Create a ``maths.c`` file with the following content: + +.. code-block:: c + + #include "fib.h" + #include <stdio.h> + + int main(void) + { + NimMain(); + for (int f = 0; f < 10; f++) + printf("Fib of %d is %d\n", f, fib(f)); + return 0; + } + +Now you can run the following Unix like commands to first generate C sources +form the Nimrod code, then link them into a static binary along your main C +program:: + + $ nimrod c --noMain --noLinking --header:fib.h fib.nim + $ gcc -o m -Inimcache -Ipath/to/nimrod/lib nimcache/*.c maths.c + +The first command runs the Nimrod compiler with three special options to avoid +generating a ``main()`` function in the generated files, avoid linking the +object files into a final binary, and explicitly generate a header file for C +integration. All the generated files are placed into the ``nimcache`` +directory. That's why the next command compiles the ``maths.c`` source plus +all the ``.c`` files form ``nimcache``. In addition to this path, you also +have to tell the C compiler where to find Nimrod's ``nimbase.h`` header file. + +Instead of depending on the generation of the individual ``.c`` files you can +also ask the Nimrod compiler to generate a statically linked library:: + + $ nimrod c --app:staticLib --noMain --header fib.nim + $ gcc -o m -Inimcache -Ipath/to/nimrod/lib libfib.nim.a maths.c + +The Nimrod compiler will handle linking the source files generated in the +``nimcache`` directory into the ``libfib.nim.a`` static library, which you can +then link into your C program. Note that these commands are generic and will +vary for each system. For instance, on Linux systems you will likely need to +use ``-ldl`` too to link in required dlopen functionality. + + +Nimrod invocation example from JavaScript +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Create a ``mhost.html`` file with the following content: + +.. code-block:: + + <html><body> + <script type="text/javascript" src="fib.js"></script> + <script type="text/javascript"> + alert("Fib for 9 is " + fib(9)); + </script> + </body></html> + +Create a ``fib.nim`` file with the following content (or reuse the one +from the previous section): + +.. code-block:: nimrod + + proc fib(a: cint): cint {.exportc.} = + if a <= 2: + result = 1 + else: + result = fib(a - 1) + fib(a - 2) + +Compile the Nimrod code to JavaScript with ``nimrod js -o:fib.js fib.nim`` and +open ``mhost.html`` in a browser. If the browser supports javascript, you +should see an alert box displaying the text ``Fib for 9 is 34``. As mentioned +earlier, JavaScript doesn't require an initialisation call to ``NimMain`` or +similar function and you can call the exported Nimrod proc directly. + + +Memory management +================= + +In the previous sections the ``NimMain()`` function reared its head. Since +JavaScript already provides automatic memory management, you can freely pass +objects between the two language without problems. In C and derivate languages +you need to be careful about what you do and how you share memory. The +previous examples only dealt with simple scalar values, but passing a Nimrod +string to C, or reading back a C string in Nimrod already requires you to be +aware of who controls what to avoid crashing. + + +Strings and C strings +--------------------- + +The manual mentions that `Nimrod strings are implicitly convertible to +cstrings <manual.html#cstring-type>`_ which makes interaction usually +painless. Most C functions accepting a Nimrod string converted to a +``cstring`` will likely not need to keep this string around and by the time +they return the string won't be needed any more. However, for the rare cases +where a Nimrod string has to be preserved and made available to the C backend +as a ``cstring``, you will need to manually prevent the string data from being +freed with `GC_ref <system.html#GC_ref>`_ and `GC_unref +<system.html#GC_unref>`_. + +A similar thing happens with C code invoking Nimrod code which returns a +``cstring``. Consider the following proc: + +.. code-block:: nimrod + + proc gimme(): cstring {.exportc.} = + result = "Hey there C code! " & $random(100) + +Since Nimrod's garbage collector is not aware of the C code, once the +``gimme`` proc has finished it can reclaim the memory of the ``cstring``. +However, from a practical standpoint, the C code invoking the ``gimme`` +function directly will be able to use it since Nimrod's garbage collector has +not had a chance to run *yet*. This gives you enough time to make a copy for +the C side of the program, as calling any further Nimrod procs *might* trigger +garbage collection making the previously returned string garbage. Or maybe you +are `triggering yourself the collection <gc.html>`_. + + +Custom data types +----------------- + +Just like strings, custom data types that are to be shared between Nimrod and +the backend will need careful consideration of who controlls who. If you want +to hand a Nimrod reference to C code, you will need to use `GC_ref +<system.html#GC_ref>`_ to mark the reference as used, so it does not get +freed. And for the C backend you will need to expose the `GC_unref +<system.html#GC_unref>`_ proc to clean up this memory when it is not required +any more. + +Again, if you are wrapping a library which *mallocs* and *frees* data +structures, you need to expose the appropriate *free* function to Nimrod so +you can clean it up. And of course, once cleaned you should avoid accessing it +from Nimrod (or C for that matter). Typically C data structures have their own +``malloc_structure`` and ``free_structure`` specific functions, so wrapping +these for the Nimrod side should be enough. + + +Thread coordination +------------------- + +When the ``NimMain()`` function is called Nimrod initializes the garbage +collector to the current thread, which is usually the main thread of your +application. If your C code later spawns a different thread and calls Nimrod +code, the garbage collector will fail to work properly and you will crash. + +As long as you don't use the threadvar emulation Nimrod uses native thread +variables, of which you get a fresh version whenever you create a thread. You +can then attach a GC to this thread via + +.. code-block:: nimrod + + setStackBottom(addr(someLocal)) + initGC() + +At the moment this support is still experimental so you need to expose these +functions yourself or submit patches to request a public API. + +It is **not** safe to disable the garbage collector and enable it after the +call from your background thread even if the code you are calling is short +lived. diff --git a/doc/grammar.txt b/doc/grammar.txt index 47ae095f6..a54428678 100644 --- a/doc/grammar.txt +++ b/doc/grammar.txt @@ -86,7 +86,7 @@ distinct = 'distinct' optInd typeDesc expr = (ifExpr | whenExpr | caseExpr - | tryStmt) + | tryExpr) / simpleExpr typeKeyw = 'var' | 'ref' | 'ptr' | 'shared' | 'type' | 'tuple' | 'proc' | 'iterator' | 'distinct' | 'object' | 'enum' @@ -135,6 +135,9 @@ caseStmt = 'case' expr ':'? COMMENT? tryStmt = 'try' colcom stmt &(IND{=}? 'except'|'finally') (IND{=}? 'except' exprList colcom stmt)* (IND{=}? 'finally' colcom stmt)? +tryExpr = 'try' colcom stmt &(optInd 'except'|'finally') + (optInd 'except' exprList colcom stmt)* + (optInd 'finally' colcom stmt)? exceptBlock = 'except' colcom stmt forStmt = 'for' (identWithPragma ^+ comma) 'in' expr colcom stmt blockStmt = 'block' symbol? colcom stmt diff --git a/doc/manual.txt b/doc/manual.txt index e96e50999..32b0541e9 100644 --- a/doc/manual.txt +++ b/doc/manual.txt @@ -263,15 +263,16 @@ String literals can also be delimited by three double quotes ``"""`` ... ``"""``. Literals in this form may run for several lines, may contain ``"`` and do not interpret any escape sequences. -For convenience, when the opening ``"""`` is immediately followed by a newline, -the newline is not included in the string. The ending of the string literal is -defined by the pattern ``"""[^"]``, so this: - +For convenience, when the opening ``"""`` is followed by a newline (there may +be whitespace between the opening ``"""`` and the newline), +the newline (and the preceding whitespace) is not included in the string. The +ending of the string literal is defined by the pattern ``"""[^"]``, so this: + .. code-block:: nimrod """"long string within quotes"""" - + Produces:: - + "long string within quotes" @@ -1089,7 +1090,8 @@ types with no overhead and few abstraction possibilities. The constructor ``()`` can be used to construct tuples. The order of the fields in the constructor must match the order of the tuple's definition. Different tuple-types are *equivalent* if they specify the same fields of the same type in the same -order. +order. The *names* of the fields also have to be identical but this might +change in a future version of the language. The assignment operator for tuples copies each component. The default assignment operator for objects copies each component. Overloading @@ -2226,12 +2228,12 @@ type. If the expression is not of an ordinal type, and no ``else`` part is given, control passes after the ``case`` statement. -To suppress the static error in the ordinal case an ``else`` part with a ``nil`` -statement can be used. +To suppress the static error in the ordinal case an ``else`` part with an +empty ``discard`` statement can be used. As a special semantic extension, an expression in an ``of`` branch of a case -statement may evaluate to a set constructor; the set is then expanded into -a list of its elements: +statement may evaluate to a set or array constructor; the set or array is then +expanded into a list of its elements: .. code-block:: nimrod const @@ -5464,7 +5466,7 @@ and warning message contains a symbol in brackets. This is the message's identifier that can be used to enable or disable it: .. code-block:: Nimrod - {.warning[LineTooLong]: off.} # turn off warning about too long lines + {.hint[LineTooLong]: off.} # turn off the hint about too long lines This is often better than disabling all warnings at once. @@ -5488,7 +5490,10 @@ spelled*: proc printf(formatstr: cstring) {.header: "<stdio.h>", importc: "printf", varargs.} Note that this pragma is somewhat of a misnomer: Other backends will provide -the same feature under the same name. +the same feature under the same name. Also, if you are interfacing with C++ +you can use the `ImportCpp pragma <nimrodc.html#importcpp-pragma>`_ and +interfacing with Objective-C the `ImportObjC pragma +<nimrodc.html#importobjc-pragma>`_. Exportc pragma diff --git a/doc/nimrodc.txt b/doc/nimrodc.txt index fea1037da..428c42f39 100644 --- a/doc/nimrodc.txt +++ b/doc/nimrodc.txt @@ -384,10 +384,11 @@ Example: ImportCpp pragma ---------------- -The ``importcpp`` pragma can be used to import `C++`:idx: methods. The -generated code then uses the C++ method calling syntax: ``obj->method(arg)``. -In addition with the ``header`` and ``emit`` pragmas this allows *sloppy* -interfacing with libraries written in C++: +Similar to the `importc pragma for C <manual.html#importc-pragma>`_, the +``importcpp`` pragma can be used to import `C++`:idx: methods. The generated +code then uses the C++ method calling syntax: ``obj->method(arg)``. In +addition with the ``header`` and ``emit`` pragmas this allows *sloppy* +interfacing with libraries written in C++: .. code-block:: Nimrod # Horrible example of how to interface with a C++ engine ... ;-) @@ -422,11 +423,11 @@ emits C++ code. ImportObjC pragma ----------------- -The ``importobjc`` pragma can be used to import `Objective C`:idx: methods. -The generated code then uses the Objective C method calling -syntax: ``[obj method param1: arg]``. -In addition with the ``header`` and ``emit`` pragmas this allows *sloppy* -interfacing with libraries written in Objective C: +Similar to the `importc pragma for C <manual.html#importc-pragma>`_, the +``importobjc`` pragma can be used to import `Objective C`:idx: methods. The +generated code then uses the Objective C method calling syntax: ``[obj method +param1: arg]``. In addition with the ``header`` and ``emit`` pragmas this +allows *sloppy* interfacing with libraries written in Objective C: .. code-block:: Nimrod # horrible example of how to interface with GNUStep ... @@ -550,8 +551,18 @@ against. For instance, to link statically against Lua this command might work on Linux:: nimrod c --dynlibOverride:lua --passL:liblua.lib program.nim - - + + +Backend language options +======================== + +The typical compiler usage involves using the ``compile`` or ``c`` command to +transform a ``.nim`` file into one or more ``.c`` files which are then +compiled with the platform's C compiler into a static binary. However there +are other commands to compile to C++, Objective-C or Javascript. More details +can be read in the `Nimrod Backend Integration document <backends.html>`_. + + Nimrod documentation tools ========================== @@ -695,35 +706,3 @@ efficient: else: quit(errorStr(p, "expected: console or gui")) of "license": c.license = UnixToNativePath(k.value) else: quit(errorStr(p, "unknown variable: " & k.key)) - - -The JavaScript target -===================== - -Nimrod can also generate `JavaScript`:idx: code. However, the -JavaScript code generator is experimental! - -Nimrod targets JavaScript 1.5 which is supported by any widely used browser. -Since JavaScript does not have a portable means to include another module, -Nimrod just generates a long ``.js`` file. - -Features or modules that the JavaScript platform does not support are not -available. This includes: - -* manual memory management (``alloc``, etc.) -* casting and other unsafe operations (``cast`` operator, ``zeroMem``, etc.) -* file management -* most modules of the Standard library -* proper 64 bit integer arithmetic -* unsigned integer arithmetic - -However, the modules `strutils`:idx:, `math`:idx:, and `times`:idx: are -available! To access the DOM, use the `dom`:idx: module that is only -available for the JavaScript platform. - -To compile a Nimrod module into a ``.js`` file use the ``js`` command; the -default is a ``.js`` file that is supposed to be referenced in an ``.html`` -file. However, you can also run the code with `nodejs`:idx:\: - - nimrod js -d:nodejs -r examples/hallo.nim - diff --git a/doc/trmacros.txt b/doc/trmacros.txt deleted file mode 100644 index d5ad74e6e..000000000 --- a/doc/trmacros.txt +++ /dev/null @@ -1,275 +0,0 @@ -========================================================= - Term rewriting macros for Nimrod -========================================================= - -:Author: Andreas Rumpf - -Term rewriting macros are macros or templates that have not only a *name* but -also a *pattern* that is searched for after the semantic checking phase of -the compiler: This means they provide an easy way to enhance the compilation -pipeline with user defined optimizations: - -.. code-block:: nimrod - template optMul{`*`(a, 2)}(a: int): int = a+a - - let x = 3 - echo x * 2 - -The compiler now rewrites ``x * 2`` as ``x + x``. The code inside the -curlies is the pattern to match against. The operators ``*``, ``**``, -``|``, ``~`` have a special meaning in patterns if they are written in infix -notation, so to match verbatim against ``*`` the ordinary function call syntax -needs to be used. - - -Unfortunately optimizations are hard to get right and even the tiny example -is **wrong**: - -.. code-block:: nimrod - template optMul{`*`(a, 2)}(a: int): int = a+a - - proc f(): int = - echo "side effect!" - result = 55 - - echo f() * 2 - -We cannot duplicate 'a' if it denotes an expression that has a side effect! -Fortunately Nimrod supports side effect analysis: - -.. code-block:: nimrod - template optMul{`*`(a, 2)}(a: int{noSideEffect}): int = a+a - - proc f(): int = - echo "side effect!" - result = 55 - - echo f() * 2 # not optimized ;-) - -So what about ``2 * a``? We should tell the compiler ``*`` is commutative. We -cannot really do that however as the following code only swaps arguments -blindly: - -.. code-block:: nimrod - template mulIsCommutative{`*`(a, b)}(a, b: int): int = b*a - -What optimizers really need to do is a *canonicalization*: - -.. code-block:: nimrod - template canonMul{`*`(a, b)}(a: int{lit}, b: int): int = b*a - -The ``int{lit}`` parameter pattern matches against an expression of -type ``int``, but only if it's a literal. - - - -Parameter constraints -===================== - -The parameter constraint expression can use the operators ``|`` (or), -``&`` (and) and ``~`` (not) and the following predicates: - -=================== ===================================================== -Predicate Meaning -=================== ===================================================== -``atom`` The matching node has no children. -``lit`` The matching node is a literal like "abc", 12. -``sym`` The matching node must be a symbol (a bound - identifier). -``ident`` The matching node must be an identifier (an unbound - identifier). -``call`` The matching AST must be a call/apply expression. -``lvalue`` The matching AST must be an lvalue. -``sideeffect`` The matching AST must have a side effect. -``nosideeffect`` The matching AST must have no side effect. -``param`` A symbol which is a parameter. -``genericparam`` A symbol which is a generic parameter. -``module`` A symbol which is a module. -``type`` A symbol which is a type. -``var`` A symbol which is a variable. -``let`` A symbol which is a ``let`` variable. -``const`` A symbol which is a constant. -``result`` The special ``result`` variable. -``proc`` A symbol which is a proc. -``method`` A symbol which is a method. -``iterator`` A symbol which is an iterator. -``converter`` A symbol which is a converter. -``macro`` A symbol which is a macro. -``template`` A symbol which is a template. -``field`` A symbol which is a field in a tuple or an object. -``enumfield`` A symbol which is a field in an enumeration. -``forvar`` A for loop variable. -``label`` A label (used in ``block`` statements). -``nk*`` The matching AST must have the specified kind. - (Example: ``nkIfStmt`` denotes an ``if`` statement.) -``alias`` States that the marked parameter needs to alias - with *some* other parameter. -``noalias`` States that *every* other parameter must not alias - with the marked parameter. -=================== ===================================================== - -The ``alias`` and ``noalias`` predicates refer not only to the matching AST, -but also to every other bound parameter; syntactially they need to occur after -the ordinary AST predicates: - -.. code-block:: nimrod - template ex{a = b + c}(a: int{noalias}, b, c: int) = - # this transformation is only valid if 'b' and 'c' do not alias 'a': - a = b - inc a, b - - -Pattern operators -================= - -The operators ``*``, ``**``, ``|``, ``~`` have a special meaning in patterns -if they are written in infix notation. - - -The ``|`` operator ------------------- - -The ``|`` operator if used as infix operator creates an ordered choice: - -.. code-block:: nimrod - template t{0|1}(): expr = 3 - let a = 1 - # outputs 3: - echo a - -The matching is performed after the compiler performed some optimizations like -constant folding, so the following does not work: - -.. code-block:: nimrod - template t{0|1}(): expr = 3 - # outputs 1: - echo 1 - -The reason is that the compiler already transformed the 1 into "1" for -the ``echo`` statement. However, a term rewriting macro should not change the -semantics anyway. In fact they can be deactived with the ``--patterns:off`` -command line option or temporarily with the ``patterns`` pragma. - - -The ``{}`` operator -------------------- - -A pattern expression can be bound to a pattern parameter via the ``expr{param}`` -notation: - -.. code-block:: nimrod - template t{(0|1|2){x}}(x: expr): expr = x+1 - let a = 1 - # outputs 2: - echo a - - -The ``~`` operator ------------------- - -The ``~`` operator is the **not** operator in patterns: - -.. code-block:: nimrod - template t{x = (~x){y} and (~x){z}}(x, y, z: bool): stmt = - x = y - if x: x = z - - var - a = false - b = true - c = false - a = b and c - echo a - - -The ``*`` operator ------------------- - -The ``*`` operator can *flatten* a nested binary expression like ``a & b & c`` -to ``&(a, b, c)``: - -.. code-block:: nimrod - var - calls = 0 - - proc `&&`(s: varargs[string]): string = - result = s[0] - for i in 1..len(s)-1: result.add s[i] - inc calls - - template optConc{ `&&` * a }(a: string): expr = &&a - - let space = " " - echo "my" && (space & "awe" && "some " ) && "concat" - - # check that it's been optimized properly: - doAssert calls == 1 - - -The second operator of `*` must be a parameter; it is used to gather all the -arguments. The expression ``"my" && (space & "awe" && "some " ) && "concat"`` -is passed to ``optConc`` in ``a`` as a special list (of kind ``nkArgList``) -which is flattened into a call expression; thus the invocation of ``optConc`` -produces: - -.. code-block:: nimrod - `&&`("my", space & "awe", "some ", "concat") - - -The ``**`` operator -------------------- - -The ``**`` is much like the ``*`` operator, except that it gathers not only -all the arguments, but also the matched operators in reverse polish notation: - -.. code-block:: nimrod - import macros - - type - TMatrix = object - dummy: int - - proc `*`(a, b: TMatrix): TMatrix = nil - proc `+`(a, b: TMatrix): TMatrix = nil - proc `-`(a, b: TMatrix): TMatrix = nil - proc `$`(a: TMatrix): string = result = $a.dummy - proc mat21(): TMatrix = - result.dummy = 21 - - macro optM{ (`+`|`-`|`*`) ** a }(a: TMatrix): expr = - echo treeRepr(a) - result = newCall(bindSym"mat21") - - var x, y, z: TMatrix - - echo x + y * z - x - -This passes the expression ``x + y * z - x`` to the ``optM`` macro as -an ``nnkArgList`` node containing:: - - Arglist - Sym "x" - Sym "y" - Sym "z" - Sym "*" - Sym "+" - Sym "x" - Sym "-" - -(Which is the reverse polish notation of ``x + y * z - x``.) - - -Parameters -========== - -Parameters in a pattern are type checked in the matching process. If a -parameter is of the type ``varargs`` it is treated specially and it can match -0 or more arguments in the AST to be matched against: - -.. code-block:: nimrod - template optWrite{ - write(f, x) - ((write|writeln){w})(f, y) - }(x, y: varargs[expr], f: TFile, w: expr) = - w(f, x, y) - diff --git a/doc/tut1.txt b/doc/tut1.txt index 9874f267b..a2aa835ee 100644 --- a/doc/tut1.txt +++ b/doc/tut1.txt @@ -1,4 +1,4 @@ -======================== +======================== Nimrod Tutorial (Part I) ======================== @@ -1521,17 +1521,13 @@ techniques. Example: .. code-block:: nimrod + proc echoItem(x: int) = echo(x) - type - TCallback = proc (x: int) - - proc echoItem(x: Int) = echo(x) - - proc forEach(callback: TCallback) = + proc forEach(action: proc (x: int)) = const data = [2, 3, 5, 7, 11] for d in items(data): - callback(d) + action(d) forEach(echoItem) diff --git a/lib/impure/db_mysql.nim b/lib/impure/db_mysql.nim index 32cda3e4d..eec4daf00 100644 --- a/lib/impure/db_mysql.nim +++ b/lib/impure/db_mysql.nim @@ -24,6 +24,15 @@ type FReadDb* = object of FDb ## effect that denotes a read operation FWriteDb* = object of FDb ## effect that denotes a write operation +proc sql*(query: string): TSqlQuery {.noSideEffect, inline.} = + ## constructs a TSqlQuery from the string `query`. This is supposed to be + ## used as a raw-string-literal modifier: + ## ``sql"update user set counter = counter + 1"`` + ## + ## If assertions are turned off, it does nothing. If assertions are turned + ## on, later versions will check the string for valid syntax. + result = TSqlQuery(query) + proc dbError(db: TDbConn) {.noreturn.} = ## raises an EDb exception. var e: ref EDb diff --git a/lib/js/dom.nim b/lib/js/dom.nim index d90067176..951d8e835 100644 --- a/lib/js/dom.nim +++ b/lib/js/dom.nim @@ -7,7 +7,8 @@ # distribution, for details about the copyright. # -## Declaration of the Document Object Model for the JavaScript backend. +## Declaration of the Document Object Model for the `JavaScript backend +## <backends.html#the-javascript-target>`_. when not defined(js) and not defined(Nimdoc): {.error: "This module only works on the JavaScript platform".} diff --git a/lib/posix/posix.nim b/lib/posix/posix.nim index cdca826ca..8e66336c2 100644 --- a/lib/posix/posix.nim +++ b/lib/posix/posix.nim @@ -1578,8 +1578,17 @@ var ## Terminates a record (if supported by the protocol). MSG_OOB* {.importc, header: "<sys/socket.h>".}: cint ## Out-of-band data. - MSG_NOSIGNAL* {.importc, header: "<sys/socket.h>".}: cint - ## No SIGPIPE generated when an attempt to send is made on a stream-oriented socket that is no longer connected. + +when defined(macosx): + var + MSG_HAVEMORE* {.importc, header: "<sys/socket.h>".}: cint + MSG_NOSIGNAL* = MSG_HAVEMORE +else: + var + MSG_NOSIGNAL* {.importc, header: "<sys/socket.h>".}: cint + ## No SIGPIPE generated when an attempt to send is made on a stream-oriented socket that is no longer connected. + +var MSG_PEEK* {.importc, header: "<sys/socket.h>".}: cint ## Leave received data in queue. MSG_TRUNC* {.importc, header: "<sys/socket.h>".}: cint diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim index 6292bfc12..d410f8ce1 100644 --- a/lib/pure/asyncdispatch.nim +++ b/lib/pure/asyncdispatch.nim @@ -11,8 +11,9 @@ include "system/inclrtl" import os, oids, tables, strutils, macros -import rawsockets -export TPort +import rawsockets, net + +export TPort, TSocketFlags #{.injectStmt: newGcInvariant().} @@ -40,6 +41,7 @@ type cb: proc () {.closure,gcsafe.} finished: bool error*: ref EBase + stackTrace: string ## For debugging purposes only. PFuture*[T] = ref object of PFutureBase value: T @@ -48,10 +50,23 @@ proc newFuture*[T](): PFuture[T] = ## Creates a new future. new(result) result.finished = false + result.stackTrace = getStackTrace() + +proc checkFinished[T](future: PFuture[T]) = + if future.finished: + echo("<----->") + echo(future.stackTrace) + echo("-----") + when T is string: + echo("Contents: ", future.value.repr) + echo("<----->") + echo("Future already finished, cannot finish twice.") + assert false proc complete*[T](future: PFuture[T], val: T) = ## Completes ``future`` with value ``val``. - assert(not future.finished, "Future already finished, cannot finish twice.") + #assert(not future.finished, "Future already finished, cannot finish twice.") + checkFinished(future) assert(future.error == nil) future.value = val future.finished = true @@ -60,7 +75,8 @@ proc complete*[T](future: PFuture[T], val: T) = proc complete*(future: PFuture[void]) = ## Completes a void ``future``. - assert(not future.finished, "Future already finished, cannot finish twice.") + #assert(not future.finished, "Future already finished, cannot finish twice.") + checkFinished(future) assert(future.error == nil) future.finished = true if future.cb != nil: @@ -68,7 +84,8 @@ proc complete*(future: PFuture[void]) = proc fail*[T](future: PFuture[T], error: ref EBase) = ## Completes ``future`` with ``error``. - assert(not future.finished, "Future already finished, cannot finish twice.") + #assert(not future.finished, "Future already finished, cannot finish twice.") + checkFinished(future) future.finished = true future.error = error if future.cb != nil: @@ -126,6 +143,15 @@ proc failed*(future: PFutureBase): bool = ## Determines whether ``future`` completed with an error. future.error != nil +proc asyncCheck*[T](future: PFuture[T]) = + ## Sets a callback on ``future`` which raises an exception if the future + ## finished with an error. + ## + ## This should be used instead of ``discard`` to discard void futures. + future.callback = + proc () = + if future.failed: raise future.error + when defined(windows) or defined(nimdoc): import winlean, sets, hashes type @@ -344,7 +370,7 @@ when defined(windows) or defined(nimdoc): return retFuture proc recv*(socket: TAsyncFD, size: int, - flags: int = 0): PFuture[string] = + flags = {TSocketFlags.SafeDisconn}): PFuture[string] = ## Reads **up to** ``size`` bytes from ``socket``. Returned future will ## complete once all the data requested is read, a part of the data has been ## read, or the socket has disconnected in which case the future will @@ -364,7 +390,7 @@ when defined(windows) or defined(nimdoc): dataBuf.len = size var bytesReceived: DWord - var flagsio = flags.DWord + var flagsio = flags.toOSFlags().DWord var ol = PCustomOverlapped() GC_ref(ol) ol.data = TCompletionData(sock: socket, cb: @@ -394,7 +420,10 @@ when defined(windows) or defined(nimdoc): dealloc dataBuf.buf dataBuf.buf = nil GC_unref(ol) - retFuture.fail(newException(EOS, osErrorMsg(err))) + if flags.isDisconnectionError(err): + retFuture.complete("") + else: + retFuture.fail(newException(EOS, osErrorMsg(err))) elif ret == 0 and bytesReceived == 0 and dataBuf.buf[0] == '\0': # We have to ensure that the buffer is empty because WSARecv will tell # us immediatelly when it was disconnected, even when there is still @@ -425,7 +454,8 @@ when defined(windows) or defined(nimdoc): # free ``ol``. return retFuture - proc send*(socket: TAsyncFD, data: string): PFuture[void] = + proc send*(socket: TAsyncFD, data: string, + flags = {TSocketFlags.SafeDisconn}): PFuture[void] = ## Sends ``data`` to ``socket``. The returned future will complete once all ## data has been sent. verifyPresence(socket) @@ -435,7 +465,7 @@ when defined(windows) or defined(nimdoc): dataBuf.buf = data # since this is not used in a callback, this is fine dataBuf.len = data.len - var bytesReceived, flags: DWord + var bytesReceived, lowFlags: DWord var ol = PCustomOverlapped() GC_ref(ol) ol.data = TCompletionData(sock: socket, cb: @@ -448,12 +478,15 @@ when defined(windows) or defined(nimdoc): ) let ret = WSASend(socket.TSocketHandle, addr dataBuf, 1, addr bytesReceived, - flags, cast[POverlapped](ol), nil) + lowFlags, cast[POverlapped](ol), nil) if ret == -1: let err = osLastError() if err.int32 != ERROR_IO_PENDING: - retFuture.fail(newException(EOS, osErrorMsg(err))) GC_unref(ol) + if flags.isDisconnectionError(err): + retFuture.complete() + else: + retFuture.fail(newException(EOS, osErrorMsg(err))) else: retFuture.complete() # We don't deallocate ``ol`` here because even though this completed @@ -552,7 +585,18 @@ when defined(windows) or defined(nimdoc): initAll() else: import selectors - from posix import EINTR, EAGAIN, EINPROGRESS, EWOULDBLOCK, MSG_PEEK + when defined(windows): + import winlean + const + EINTR = WSAEINPROGRESS + EINPROGRESS = WSAEINPROGRESS + EWOULDBLOCK = WSAEWOULDBLOCK + EAGAIN = EINPROGRESS + MSG_NOSIGNAL = 0 + else: + from posix import EINTR, EAGAIN, EINPROGRESS, EWOULDBLOCK, MSG_PEEK, + MSG_NOSIGNAL + type TAsyncFD* = distinct cint TCallback = proc (sock: TAsyncFD): bool {.closure,gcsafe.} @@ -686,20 +730,23 @@ else: return retFuture proc recv*(socket: TAsyncFD, size: int, - flags: int = 0): PFuture[string] = + flags = {TSocketFlags.SafeDisconn}): PFuture[string] = var retFuture = newFuture[string]() var readBuffer = newString(size) proc cb(sock: TAsyncFD): bool = result = true - let res = recv(sock.TSocketHandle, addr readBuffer[0], size, - flags.cint) + let res = recv(sock.TSocketHandle, addr readBuffer[0], size.cint, + flags.toOSFlags()) #echo("recv cb res: ", res) if res < 0: let lastError = osLastError() - if lastError.int32 notin {EINTR, EWOULDBLOCK, EAGAIN}: - retFuture.fail(newException(EOS, osErrorMsg(lastError))) + if lastError.int32 notin {EINTR, EWOULDBLOCK, EAGAIN}: + if flags.isDisconnectionError(lastError): + retFuture.complete("") + else: + retFuture.fail(newException(EOS, osErrorMsg(lastError))) else: result = false # We still want this callback to be called. elif res == 0: @@ -708,11 +755,13 @@ else: else: readBuffer.setLen(res) retFuture.complete(readBuffer) - + # TODO: The following causes a massive slowdown. + #if not cb(socket): addRead(socket, cb) return retFuture - proc send*(socket: TAsyncFD, data: string): PFuture[void] = + proc send*(socket: TAsyncFD, data: string, + flags = {TSocketFlags.SafeDisconn}): PFuture[void] = var retFuture = newFuture[void]() var written = 0 @@ -721,11 +770,15 @@ else: result = true let netSize = data.len-written var d = data.cstring - let res = send(sock.TSocketHandle, addr d[written], netSize, 0.cint) + let res = send(sock.TSocketHandle, addr d[written], netSize.cint, + MSG_NOSIGNAL) if res < 0: let lastError = osLastError() if lastError.int32 notin {EINTR, EWOULDBLOCK, EAGAIN}: - retFuture.fail(newException(EOS, osErrorMsg(lastError))) + if flags.isDisconnectionError(lastError): + retFuture.complete() + else: + retFuture.fail(newException(EOS, osErrorMsg(lastError))) else: result = false # We still want this callback to be called. else: @@ -734,6 +787,8 @@ else: result = false # We still have data to send. else: retFuture.complete() + # TODO: The following causes crashes. + #if not cb(socket): addWrite(socket, cb) return retFuture @@ -779,6 +834,7 @@ proc accept*(socket: TAsyncFD): PFuture[TAsyncFD] = template createCb*(retFutureSym, iteratorNameSym, name: expr): stmt {.immediate.} = var nameIterVar = iteratorNameSym + #{.push stackTrace: off.} proc cb {.closure,gcsafe.} = try: if not nameIterVar.finished: @@ -791,7 +847,7 @@ template createCb*(retFutureSym, iteratorNameSym, except: retFutureSym.fail(getCurrentException()) cb() - + #{.pop.} proc generateExceptionCheck(futSym, exceptBranch, rootReceiver: PNimrodNode): PNimrodNode {.compileTime.} = if exceptBranch == nil: @@ -1005,8 +1061,6 @@ macro async*(prc: stmt): stmt {.immediate.} = result[4].del(i) if subtypeIsVoid: # Add discardable pragma. - if prc.kind == nnkProcDef: # TODO: This is a workaround for #1287 - result[4].add(newIdentNode("discardable")) if returnType.kind == nnkEmpty: # Add PFuture[void] result[3][0] = parseExpr("PFuture[void]") @@ -1042,7 +1096,7 @@ proc recvLine*(socket: TAsyncFD): PFuture[string] {.async.} = if c.len == 0: return "" if c == "\r": - c = await recv(socket, 1, MSG_PEEK) + c = await recv(socket, 1, {TSocketFlags.SafeDisconn, TSocketFlags.Peek}) if c.len > 0 and c == "\L": discard await recv(socket, 1) addNLIfEmpty() diff --git a/lib/pure/asynchttpserver.nim b/lib/pure/asynchttpserver.nim index 005c56ebc..ee6658fd1 100644 --- a/lib/pure/asynchttpserver.nim +++ b/lib/pure/asynchttpserver.nim @@ -20,7 +20,7 @@ type protocol*: tuple[orig: string, major, minor: int] url*: TURL hostname*: string ## The hostname of the client that made the request. - body*: string # TODO + body*: string PAsyncHttpServer* = ref object socket: PAsyncSocket @@ -51,10 +51,15 @@ proc `==`*(protocol: tuple[orig: string, major, minor: int], proc newAsyncHttpServer*(): PAsyncHttpServer = new result -proc sendHeaders*(req: TRequest, headers: PStringTable) {.async.} = - ## Sends the specified headers to the requesting client. +proc addHeaders(msg: var string, headers: PStringTable) = for k, v in headers: - await req.client.send(k & ": " & v & "\c\L") + msg.add(k & ": " & v & "\c\L") + +proc sendHeaders*(req: TRequest, headers: PStringTable): PFuture[void] = + ## Sends the specified headers to the requesting client. + var msg = "" + addHeaders(msg, headers) + return req.client.send(msg) proc respond*(req: TRequest, code: THttpCode, content: string, headers: PStringTable = newStringTable()) {.async.} = @@ -64,9 +69,9 @@ proc respond*(req: TRequest, code: THttpCode, ## This procedure will **not** close the client socket. var customHeaders = headers customHeaders["Content-Length"] = $content.len - await req.client.send("HTTP/1.1 " & $code & "\c\L") - await sendHeaders(req, headers) - await req.client.send("\c\L" & content) + var msg = "HTTP/1.1 " & $code & "\c\L" + msg.addHeaders(customHeaders) + await req.client.send(msg & "\c\L" & content) proc newRequest(): TRequest = result.headers = newStringTable(modeCaseInsensitive) @@ -78,7 +83,7 @@ proc parseHeader(line: string): tuple[key, value: string] = i += line.skipWhiteSpace(i) i += line.parseUntil(result.value, {'\c', '\L'}, i) -proc parseProtocol(protocol: string): tuple[orig: string, major, minor: int] = +proc parseProtocol(protocol: string): tuple[orig: string, major, minor: int] = var i = protocol.skipIgnoreCase("HTTP/") if i != 5: raise newException(EInvalidValue, "Invalid request protocol. Got: " & @@ -88,70 +93,95 @@ proc parseProtocol(protocol: string): tuple[orig: string, major, minor: int] = i.inc # Skip . i.inc protocol.parseInt(result.minor, i) +proc sendStatus(client: PAsyncSocket, status: string): PFuture[void] = + client.send("HTTP/1.1 " & status & "\c\L") + proc processClient(client: PAsyncSocket, address: string, callback: proc (request: TRequest): PFuture[void]) {.async.} = - # GET /path HTTP/1.1 - # Header: val - # \n - var request = newRequest() - request.hostname = address - assert client != nil - request.client = client - - # First line - GET /path HTTP/1.1 - let line = await client.recvLine() # TODO: Timeouts. - if line == "": - client.close() - return - let lineParts = line.split(' ') - if lineParts.len != 3: - request.respond(Http400, "Invalid request. Got: " & line) - client.close() - return - - let reqMethod = lineParts[0] - let path = lineParts[1] - let protocol = lineParts[2] - - # Headers - var i = 0 while true: - i = 0 - let headerLine = await client.recvLine() - if headerLine == "": - client.close(); return - if headerLine == "\c\L": break - # TODO: Compiler crash - #let (key, value) = parseHeader(headerLine) - let kv = parseHeader(headerLine) - request.headers[kv.key] = kv.value - - request.reqMethod = reqMethod - request.url = parseUrl(path) - try: - request.protocol = protocol.parseProtocol() - except EInvalidValue: - request.respond(Http400, "Invalid request protocol. Got: " & protocol) - return - - case reqMethod.normalize - of "get", "post", "head", "put", "delete", "trace", "options", "connect", "patch": - await callback(request) - else: - request.respond(Http400, "Invalid request method. Got: " & reqMethod) - - # Persistent connections - if (request.protocol == HttpVer11 and - request.headers["connection"].normalize != "close") or - (request.protocol == HttpVer10 and - request.headers["connection"].normalize == "keep-alive"): - # In HTTP 1.1 we assume that connection is persistent. Unless connection - # header states otherwise. - # In HTTP 1.0 we assume that the connection should not be persistent. - # Unless the connection header states otherwise. - await processClient(client, address, callback) - else: - request.client.close() + # GET /path HTTP/1.1 + # Header: val + # \n + var request = newRequest() + request.hostname = address + assert client != nil + request.client = client + + # First line - GET /path HTTP/1.1 + let line = await client.recvLine() # TODO: Timeouts. + if line == "": + client.close() + return + let lineParts = line.split(' ') + if lineParts.len != 3: + await request.respond(Http400, "Invalid request. Got: " & line) + continue + + let reqMethod = lineParts[0] + let path = lineParts[1] + let protocol = lineParts[2] + + # Headers + var i = 0 + while true: + i = 0 + let headerLine = await client.recvLine() + if headerLine == "": + client.close(); return + if headerLine == "\c\L": break + # TODO: Compiler crash + #let (key, value) = parseHeader(headerLine) + let kv = parseHeader(headerLine) + request.headers[kv.key] = kv.value + + request.reqMethod = reqMethod + request.url = parseUrl(path) + try: + request.protocol = protocol.parseProtocol() + except EInvalidValue: + asyncCheck request.respond(Http400, "Invalid request protocol. Got: " & + protocol) + continue + + if reqMethod.normalize == "post": + # Check for Expect header + if request.headers.hasKey("Expect"): + if request.headers["Expect"].toLower == "100-continue": + await client.sendStatus("100 Continue") + else: + await client.sendStatus("417 Expectation Failed") + + # Read the body + # - Check for Content-length header + if request.headers.hasKey("Content-Length"): + var contentLength = 0 + if parseInt(request.headers["Content-Length"], contentLength) == 0: + await request.respond(Http400, "Bad Request. Invalid Content-Length.") + else: + request.body = await client.recv(contentLength) + assert request.body.len == contentLength + else: + await request.respond(Http400, "Bad Request. No Content-Length.") + continue + + case reqMethod.normalize + of "get", "post", "head", "put", "delete", "trace", "options", "connect", "patch": + await callback(request) + else: + await request.respond(Http400, "Invalid request method. Got: " & reqMethod) + + # Persistent connections + if (request.protocol == HttpVer11 and + request.headers["connection"].normalize != "close") or + (request.protocol == HttpVer10 and + request.headers["connection"].normalize == "keep-alive"): + # In HTTP 1.1 we assume that connection is persistent. Unless connection + # header states otherwise. + # In HTTP 1.0 we assume that the connection should not be persistent. + # Unless the connection header states otherwise. + else: + request.client.close() + break proc serve*(server: PAsyncHttpServer, port: TPort, callback: proc (request: TRequest): PFuture[void], @@ -168,7 +198,7 @@ proc serve*(server: PAsyncHttpServer, port: TPort, # TODO: Causes compiler crash. #var (address, client) = await server.socket.acceptAddr() var fut = await server.socket.acceptAddr() - processClient(fut.client, fut.address, callback) + asyncCheck processClient(fut.client, fut.address, callback) proc close*(server: PAsyncHttpServer) = ## Terminates the async http server instance. @@ -183,5 +213,5 @@ when isMainModule: "Content-type": "text/plain; charset=utf-8"} await req.respond(Http200, "Hello World", headers.newStringTable()) - server.serve(TPort(5555), cb) + asyncCheck server.serve(TPort(5555), cb) runForever() diff --git a/lib/pure/asyncnet.nim b/lib/pure/asyncnet.nim index d16c85c58..374ac77e3 100644 --- a/lib/pure/asyncnet.nim +++ b/lib/pure/asyncnet.nim @@ -80,7 +80,8 @@ proc connect*(socket: PAsyncSocket, address: string, port: TPort, ## or an error occurs. result = connect(socket.fd.TAsyncFD, address, port, af) -proc readIntoBuf(socket: PAsyncSocket, flags: int): PFuture[int] {.async.} = +proc readIntoBuf(socket: PAsyncSocket, + flags: set[TSocketFlags]): PFuture[int] {.async.} = var data = await recv(socket.fd.TAsyncFD, BufferSize, flags) if data.len != 0: copyMem(addr socket.buffer[0], addr data[0], data.len) @@ -89,7 +90,7 @@ proc readIntoBuf(socket: PAsyncSocket, flags: int): PFuture[int] {.async.} = result = data.len proc recv*(socket: PAsyncSocket, size: int, - flags: int = 0): PFuture[string] {.async.} = + flags = {TSocketFlags.SafeDisconn}): PFuture[string] {.async.} = ## Reads ``size`` bytes from ``socket``. Returned future will complete once ## all of the requested data is read. If socket is disconnected during the ## recv operation then the future may complete with only a part of the @@ -100,7 +101,7 @@ proc recv*(socket: PAsyncSocket, size: int, let originalBufPos = socket.currPos if socket.bufLen == 0: - let res = await socket.readIntoBuf(flags and (not MSG_PEEK)) + let res = await socket.readIntoBuf(flags - {TSocketFlags.Peek}) if res == 0: result.setLen(0) return @@ -108,32 +109,31 @@ proc recv*(socket: PAsyncSocket, size: int, var read = 0 while read < size: if socket.currPos >= socket.bufLen: - if (flags and MSG_PEEK) == MSG_PEEK: + if TSocketFlags.Peek in flags: # We don't want to get another buffer if we're peeking. - result.setLen(read) - return - let res = await socket.readIntoBuf(flags and (not MSG_PEEK)) + break + let res = await socket.readIntoBuf(flags - {TSocketFlags.Peek}) if res == 0: - result.setLen(read) - return + break let chunk = min(socket.bufLen-socket.currPos, size-read) copyMem(addr(result[read]), addr(socket.buffer[socket.currPos]), chunk) read.inc(chunk) socket.currPos.inc(chunk) - if (flags and MSG_PEEK) == MSG_PEEK: + if TSocketFlags.Peek in flags: # Restore old buffer cursor position. socket.currPos = originalBufPos result.setLen(read) else: result = await recv(socket.fd.TAsyncFD, size, flags) -proc send*(socket: PAsyncSocket, data: string): PFuture[void] = +proc send*(socket: PAsyncSocket, data: string, + flags = {TSocketFlags.SafeDisconn}): PFuture[void] = ## Sends ``data`` to ``socket``. The returned future will complete once all ## data has been sent. assert socket != nil - result = send(socket.fd.TAsyncFD, data) + result = send(socket.fd.TAsyncFD, data, flags) proc acceptAddr*(socket: PAsyncSocket): PFuture[tuple[address: string, client: PAsyncSocket]] = @@ -168,7 +168,8 @@ proc accept*(socket: PAsyncSocket): PFuture[PAsyncSocket] = retFut.complete(future.read.client) return retFut -proc recvLine*(socket: PAsyncSocket): PFuture[string] {.async.} = +proc recvLine*(socket: PAsyncSocket, + flags = {TSocketFlags.SafeDisconn}): PFuture[string] {.async.} = ## Reads a line of data from ``socket``. Returned future will complete once ## a full line is read or an error occurs. ## @@ -181,28 +182,60 @@ proc recvLine*(socket: PAsyncSocket): PFuture[string] {.async.} = ## If the socket is disconnected in the middle of a line (before ``\r\L`` ## is read) then line will be set to ``""``. ## The partial line **will be lost**. - + ## + ## **Warning**: The ``Peek`` flag is not yet implemented. template addNLIfEmpty(): stmt = if result.len == 0: result.add("\c\L") + assert TSocketFlags.Peek notin flags ## TODO: + if socket.isBuffered: + result = "" + if socket.bufLen == 0: + let res = await socket.readIntoBuf(flags) + if res == 0: + return + + var lastR = false + while true: + if socket.currPos >= socket.bufLen: + let res = await socket.readIntoBuf(flags) + if res == 0: + result = "" + break - result = "" - var c = "" - while true: - c = await recv(socket, 1) - if c.len == 0: - return "" - if c == "\r": - c = await recv(socket, 1, MSG_PEEK) - if c.len > 0 and c == "\L": - let dummy = await recv(socket, 1) - assert dummy == "\L" - addNLIfEmpty() - return - elif c == "\L": - addNLIfEmpty() - return - add(result.string, c) + case socket.buffer[socket.currPos] + of '\r': + lastR = true + addNLIfEmpty() + of '\L': + addNLIfEmpty() + socket.currPos.inc() + return + else: + if lastR: + socket.currPos.inc() + return + else: + result.add socket.buffer[socket.currPos] + socket.currPos.inc() + else: + result = "" + var c = "" + while true: + c = await recv(socket, 1, flags) + if c.len == 0: + return "" + if c == "\r": + c = await recv(socket, 1, flags + {TSocketFlags.Peek}) + if c.len > 0 and c == "\L": + let dummy = await recv(socket, 1, flags) + assert dummy == "\L" + addNLIfEmpty() + return + elif c == "\L": + addNLIfEmpty() + return + add(result.string, c) proc bindAddr*(socket: PAsyncSocket, port = TPort(0), address = "") = ## Binds ``address``:``port`` to the socket. @@ -241,7 +274,7 @@ when isMainModule: break else: echo("Got line: ", line) - main() + asyncCheck main() elif test == LowClient: var sock = newAsyncSocket() var f = connect(sock, "irc.freenode.net", TPort(6667)) diff --git a/lib/pure/collections/sequtils.nim b/lib/pure/collections/sequtils.nim index f5db9d3fa..e760c5e02 100644 --- a/lib/pure/collections/sequtils.nim +++ b/lib/pure/collections/sequtils.nim @@ -47,19 +47,15 @@ proc concat*[T](seqs: varargs[seq[T]]): seq[T] = result[i] = itm inc(i) -proc distnct*[T](seq1: seq[T]): seq[T] = +proc deduplicate*[T](seq1: seq[T]): seq[T] = ## Returns a new sequence without duplicates. ## - ## This proc is `misspelled` on purpose to avoid a clash with the keyword - ## ``distinct`` used to `define a derived type incompatible with its base - ## type <manual.html#distinct-type>`_. Example: - ## ## .. code-block:: nimrod ## let ## dup1 = @[1, 1, 3, 4, 2, 2, 8, 1, 4] ## dup2 = @["a", "a", "c", "d", "d"] - ## unique1 = distnct(dup1) - ## unique2 = distnct(dup2) + ## unique1 = deduplicate(dup1) + ## unique2 = deduplicate(dup2) ## assert unique1 == @[1, 3, 4, 2, 8] ## assert unique2 == @["a", "c", "d"] result = @[] @@ -182,6 +178,24 @@ proc filter*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): seq[T] = ## assert f2 == @["yellow"] accumulateResult(filter(seq1, pred)) +proc keepIf*[T](seq1: var seq[T], pred: proc(item: T): bool {.closure.}) = + ## Keeps the items in the passed sequence if they fulfilled the predicate. + ## Same as the ``filter`` proc, but modifies the sequence directly. + ## + ## Example: + ## + ## .. code-block:: nimrod + ## var floats = @[13.0, 12.5, 5.8, 2.0, 6.1, 9.9, 10.1] + ## filter(floats, proc(x: float): bool = x > 10) + ## assert floats == @[13.0, 12.5, 10.1] + var pos = 0 + for i in 0 .. <len(seq1): + if pred(seq1[i]): + if pos != i: + seq1[pos] = seq1[i] + inc(pos) + setLen(seq1, pos) + proc delete*[T](s: var seq[T], first=0, last=0) = ## Deletes in `s` the items at position `first` .. `last`. This modifies ## `s` itself, it does not return a copy. @@ -252,6 +266,27 @@ template filterIt*(seq1, pred: expr): expr {.immediate.} = if pred: result.add(it) result +template keepItIf*(varSeq, pred: expr) = + ## Convenience template around the ``keepIf`` proc to reduce typing. + ## + ## Unlike the `proc` version, the predicate needs to be an expression using + ## the ``it`` variable for testing, like: ``keepItIf("abcxyz", it == 'x')``. + ## Example: + ## + ## .. code-block:: nimrod + ## var candidates = @["foo", "bar", "baz", "foobar"] + ## keepItIf(candidates, it.len == 3 and it[0] == 'b') + ## assert candidates == @["bar", "baz"] + var pos = 0 + for i in 0 .. <len(varSeq): + let it {.inject.} = varSeq[i] + if pred: + if pos != i: + varSeq[pos] = varSeq[i] + inc(pos) + setLen(varSeq, pos) + + template toSeq*(iter: expr): expr {.immediate.} = ## Transforms any iterator into a sequence. ## @@ -387,8 +422,8 @@ when isMainModule: let dup1 = @[1, 1, 3, 4, 2, 2, 8, 1, 4] dup2 = @["a", "a", "c", "d", "d"] - unique1 = distnct(dup1) - unique2 = distnct(dup2) + unique1 = deduplicate(dup1) + unique2 = deduplicate(dup2) assert unique1 == @[1, 3, 4, 2, 8] assert unique2 == @["a", "c", "d"] @@ -418,6 +453,11 @@ when isMainModule: echo($n) # echoes 4, 8, 4 in separate lines + block: # keepIf test + var floats = @[13.0, 12.5, 5.8, 2.0, 6.1, 9.9, 10.1] + keepIf(floats, proc(x: float): bool = x > 10) + assert floats == @[13.0, 12.5, 10.1] + block: # filterIt test let temperatures = @[-272.15, -2.0, 24.5, 44.31, 99.9, -113.44] @@ -426,6 +466,11 @@ when isMainModule: assert acceptable == @[-2.0, 24.5, 44.31] assert notAcceptable == @[-272.15, 99.9, -113.44] + block: # keepItIf test + var candidates = @["foo", "bar", "baz", "foobar"] + keepItIf(candidates, it.len == 3 and it[0] == 'b') + assert candidates == @["bar", "baz"] + block: # toSeq test let numeric = @[1, 2, 3, 4, 5, 6, 7, 8, 9] diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim index be06a7b8e..9bacc80d6 100644 --- a/lib/pure/httpclient.nim +++ b/lib/pure/httpclient.nim @@ -654,7 +654,7 @@ when isMainModule: resp = await client.request("http://nimrod-lang.org/download.html") echo("Got response: ", resp.status) - main() + asyncCheck main() runForever() else: diff --git a/lib/pure/math.nim b/lib/pure/math.nim index 78ea02cbf..2f7a696b9 100644 --- a/lib/pure/math.nim +++ b/lib/pure/math.nim @@ -10,7 +10,8 @@ ## Constructive mathematics is naturally typed. -- Simon Thompson ## ## Basic math routines for Nimrod. -## This module is available for the JavaScript target. +## This module is available for the `JavaScript target +## <backends.html#the-javascript-target>`_. include "system/inclrtl" diff --git a/lib/pure/net.nim b/lib/pure/net.nim index e34c88327..ddc2bbe2d 100644 --- a/lib/pure/net.nim +++ b/lib/pure/net.nim @@ -350,6 +350,30 @@ type ETimeout* = object of ESynch + TSocketFlags* {.pure.} = enum + Peek, + SafeDisconn ## Ensures disconnection exceptions (ECONNRESET, EPIPE etc) are not thrown. + +proc isDisconnectionError*(flags: set[TSocketFlags], + lastError: TOSErrorCode): bool = + ## Determines whether ``lastError`` is a disconnection error. Only does this + ## if flags contains ``SafeDisconn``. + when useWinVersion: + TSocketFlags.SafeDisconn in flags and + lastError.int32 in {WSAECONNRESET, WSAECONNABORTED, WSAENETRESET, + WSAEDISCON} + else: + TSocketFlags.SafeDisconn in flags and + lastError.int32 in {ECONNRESET, EPIPE, ENETRESET} + +proc toOSFlags*(socketFlags: set[TSocketFlags]): cint = + ## Converts the flags into the underlying OS representation. + for f in socketFlags: + case f + of TSocketFlags.Peek: + result = result or MSG_PEEK + of TSocketFlags.SafeDisconn: continue + proc createSocket(fd: TSocketHandle, isBuff: bool): PSocket = assert fd != osInvalidSocket new(result) @@ -470,7 +494,8 @@ when defined(ssl): if SSLSetFd(socket.sslHandle, socket.fd) != 1: SSLError() -proc socketError*(socket: PSocket, err: int = -1, async = false) = +proc socketError*(socket: PSocket, err: int = -1, async = false, + lastError = (-1).TOSErrorCode) = ## Raises an EOS error based on the error code returned by ``SSLGetError`` ## (for SSL sockets) and ``osLastError`` otherwise. ## @@ -500,17 +525,17 @@ proc socketError*(socket: PSocket, err: int = -1, async = false) = else: SSLError("Unknown Error") if err == -1 and not (when defined(ssl): socket.isSSL else: false): - let lastError = osLastError() + let lastE = if lastError.int == -1: osLastError() else: lastError if async: when useWinVersion: - if lastError.int32 == WSAEWOULDBLOCK: + if lastE.int32 == WSAEWOULDBLOCK: return - else: osError(lastError) + else: osError(lastE) else: - if lastError.int32 == EAGAIN or lastError.int32 == EWOULDBLOCK: + if lastE.int32 == EAGAIN or lastE.int32 == EWOULDBLOCK: return - else: osError(lastError) - else: osError(lastError) + else: osError(lastE) + else: osError(lastE) proc listen*(socket: PSocket, backlog = SOMAXCONN) {.tags: [FReadIO].} = ## Marks ``socket`` as accepting connections. @@ -881,7 +906,8 @@ proc recv*(socket: PSocket, data: pointer, size: int, timeout: int): int {. result = read -proc recv*(socket: PSocket, data: var string, size: int, timeout = -1): int = +proc recv*(socket: PSocket, data: var string, size: int, timeout = -1, + flags = {TSocketFlags.SafeDisconn}): int = ## Higher-level version of ``recv``. ## ## When 0 is returned the socket's connection has been closed. @@ -893,11 +919,15 @@ proc recv*(socket: PSocket, data: var string, size: int, timeout = -1): int = ## within the time specified an ETimeout exception will be raised. ## ## **Note**: ``data`` must be initialised. + ## + ## **Warning**: Only the ``SafeDisconn`` flag is currently supported. data.setLen(size) result = recv(socket, cstring(data), size, timeout) if result < 0: data.setLen(0) - socket.socketError(result) + let lastError = osLastError() + if flags.isDisconnectionError(lastError): return + socket.socketError(result, lastError = lastError) data.setLen(result) proc peekChar(socket: PSocket, c: var char): int {.tags: [FReadIO].} = @@ -920,7 +950,8 @@ proc peekChar(socket: PSocket, c: var char): int {.tags: [FReadIO].} = return result = recv(socket.fd, addr(c), 1, MSG_PEEK) -proc readLine*(socket: PSocket, line: var TaintedString, timeout = -1) {. +proc readLine*(socket: PSocket, line: var TaintedString, timeout = -1, + flags = {TSocketFlags.SafeDisconn}) {. tags: [FReadIO, FTime].} = ## Reads a line of data from ``socket``. ## @@ -934,11 +965,18 @@ proc readLine*(socket: PSocket, line: var TaintedString, timeout = -1) {. ## ## A timeout can be specified in miliseconds, if data is not received within ## the specified time an ETimeout exception will be raised. + ## + ## **Warning**: Only the ``SafeDisconn`` flag is currently supported. template addNLIfEmpty(): stmt = if line.len == 0: line.add("\c\L") + template raiseSockError(): stmt {.dirty, immediate.} = + let lastError = osLastError() + if flags.isDisconnectionError(lastError): setLen(line.string, 0); return + socket.socketError(n, lastError = lastError) + var waited = 0.0 setLen(line.string, 0) @@ -946,14 +984,14 @@ proc readLine*(socket: PSocket, 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: socket.socketError() - elif n == 0: return + if n < 0: raiseSockError() + elif n == 0: setLen(line.string, 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: socket.socketError() + elif n <= 0: raiseSockError() addNLIfEmpty() return elif c == '\L': @@ -1021,11 +1059,14 @@ proc send*(socket: PSocket, data: pointer, size: int): int {. const MSG_NOSIGNAL = 0 result = send(socket.fd, data, size, int32(MSG_NOSIGNAL)) -proc send*(socket: PSocket, data: string) {.tags: [FWriteIO].} = +proc send*(socket: PSocket, data: string, + flags = {TSocketFlags.SafeDisconn}) {.tags: [FWriteIO].} = ## sends data to a socket. let sent = send(socket, cstring(data), data.len) if sent < 0: - socketError(socket) + let lastError = osLastError() + if flags.isDisconnectionError(lastError): return + socketError(socket, lastError = lastError) if sent != data.len: raise newException(EOS, "Could not send all data.") diff --git a/lib/pure/os.nim b/lib/pure/os.nim index e2fc62d77..0b4538abc 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -1769,16 +1769,16 @@ else: FileId = TIno type - FileInfo = object + FileInfo* = object ## Contains information associated with a file object. - id: tuple[device: DeviceId, file: FileId] # Device and file id. - kind: TPathComponent # Kind of file object - directory, symlink, etc. - size: BiggestInt # Size of file. - permissions: set[TFilePermission] # File permissions - linkCount: BiggestInt # Number of hard links the file object has. - lastAccessTime: TTime # Time file was last accessed. - lastWriteTime: TTime # Time file was last modified/written to. - creationTime: TTime # Time file was created. Not supported on all systems! + id*: tuple[device: DeviceId, file: FileId] # Device and file id. + kind*: TPathComponent # Kind of file object - directory, symlink, etc. + size*: BiggestInt # Size of file. + permissions*: set[TFilePermission] # File permissions + linkCount*: BiggestInt # Number of hard links the file object has. + lastAccessTime*: TTime # Time file was last accessed. + lastWriteTime*: TTime # Time file was last modified/written to. + creationTime*: TTime # Time file was created. Not supported on all systems! template rawToFormalFileInfo(rawInfo, formalInfo): expr = ## Transforms the native file info structure into the one nimrod uses. diff --git a/lib/pure/rawsockets.nim b/lib/pure/rawsockets.nim index 94189fd89..d96741846 100644 --- a/lib/pure/rawsockets.nim +++ b/lib/pure/rawsockets.nim @@ -21,11 +21,12 @@ const useWinVersion = defined(Windows) or defined(nimdoc) when useWinVersion: import winlean - export WSAEWOULDBLOCK + export WSAEWOULDBLOCK, WSAECONNRESET, WSAECONNABORTED, WSAENETRESET, + WSAEDISCON else: import posix export fcntl, F_GETFL, O_NONBLOCK, F_SETFL, EAGAIN, EWOULDBLOCK, MSG_NOSIGNAL, - EINTR, EINPROGRESS + EINTR, EINPROGRESS, ECONNRESET, EPIPE, ENETRESET export TSocketHandle, TSockaddr_in, TAddrinfo, INADDR_ANY, TSockAddr, TSockLen, inet_ntoa, recv, `==`, connect, send, accept, recvfrom, sendto diff --git a/lib/pure/selectors.nim b/lib/pure/selectors.nim index 3af5f699c..bd53c2dbf 100644 --- a/lib/pure/selectors.nim +++ b/lib/pure/selectors.nim @@ -163,7 +163,7 @@ elif defined(linux): proc newSelector*(): PSelector = new result result.epollFD = epoll_create(64) - result.events = cast[array[64, epoll_event]](alloc0(sizeof(epoll_event)*64)) + #result.events = cast[array[64, epoll_event]](alloc0(sizeof(epoll_event)*64)) result.fds = initTable[TSocketHandle, PSelectorKey]() if result.epollFD < 0: OSError(OSLastError()) diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index bd6814dcc..e642f6a99 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -10,6 +10,8 @@ ## This module contains various string utility routines. ## See the module `re <re.html>`_ for regular expression support. ## See the module `pegs <pegs.html>`_ for PEG support. +## This module is available for the `JavaScript target +## <backends.html#the-javascript-target>`_. import parseutils diff --git a/lib/pure/times.nim b/lib/pure/times.nim index fdff06b2a..498511899 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -9,7 +9,8 @@ ## This module contains routines and types for dealing with time. -## This module is available for the JavaScript target. +## This module is available for the `JavaScript target +## <backends.html#the-javascript-target>`_. {.push debugger:off.} # the user does not want to trace a part # of the standard library! diff --git a/lib/system.nim b/lib/system.nim index 3e5308f90..3c42385b7 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -46,6 +46,7 @@ const on* = true ## alias for ``true`` off* = false ## alias for ``false`` +{.push warning[GcMem]: off.} {.push hints: off.} type @@ -431,12 +432,12 @@ proc pred*[T](x: Ordinal[T], y = 1): T {.magic: "Pred", noSideEffect.} ## an ordinal type. If such a value does not exist, ``EOutOfRange`` is raised ## or a compile time error occurs. -proc inc*[T](x: var Ordinal[T], y = 1) {.magic: "Inc", noSideEffect.} +proc inc*[T: Ordinal|uint|uint64](x: var T, y = 1) {.magic: "Inc", noSideEffect.} ## increments the ordinal ``x`` by ``y``. If such a value does not ## exist, ``EOutOfRange`` is raised or a compile time error occurs. This is a ## short notation for: ``x = succ(x, y)``. -proc dec*[T](x: var Ordinal[T], y = 1) {.magic: "Dec", noSideEffect.} +proc dec*[T: Ordinal|uint|uint64](x: var T, y = 1) {.magic: "Dec", noSideEffect.} ## decrements the ordinal ``x`` by ``y``. If such a value does not ## exist, ``EOutOfRange`` is raised or a compile time error occurs. This is a ## short notation for: ``x = pred(x, y)``. @@ -2663,6 +2664,8 @@ when hostOS != "standalone": proc `[]`*[Idx, T](a: array[Idx, T], x: TSlice[int]): seq[T] = ## slice operation for arrays. Negative indexes are **not** supported ## because the array might have negative bounds. + when low(a) < 0: + {.error: "Slicing for arrays with negative indices is unsupported.".} var L = x.b - x.a + 1 newSeq(result, L) for i in 0.. <L: result[i] = a[i + x.a] @@ -2670,6 +2673,8 @@ proc `[]`*[Idx, T](a: array[Idx, T], x: TSlice[int]): seq[T] = proc `[]=`*[Idx, T](a: var array[Idx, T], x: TSlice[int], b: openArray[T]) = ## slice assignment for arrays. Negative indexes are **not** supported ## because the array might have negative bounds. + when low(a) < 0: + {.error: "Slicing for arrays with negative indices is unsupported.".} var L = x.b - x.a + 1 if L == b.len: for i in 0 .. <L: a[i+x.a] = b[i] @@ -2745,13 +2750,13 @@ proc staticExec*(command: string, input = ""): string {. ## inside a pragma like `passC <nimrodc.html#passc-pragma>`_ or `passL ## <nimrodc.html#passl-pragma>`_. -proc `+=`*[T: TOrdinal](x: var T, y: T) {.magic: "Inc", noSideEffect.} +proc `+=`*[T: TOrdinal|uint|uint64](x: var T, y: T) {.magic: "Inc", noSideEffect.} ## Increments an ordinal -proc `-=`*[T: TOrdinal](x: var T, y: T) {.magic: "Dec", noSideEffect.} +proc `-=`*[T: TOrdinal|uint|uint64](x: var T, y: T) {.magic: "Dec", noSideEffect.} ## Decrements an ordinal -proc `*=`*[T: TOrdinal](x: var T, y: T) {.inline, noSideEffect.} = +proc `*=`*[T: TOrdinal|uint|uint64](x: var T, y: T) {.inline, noSideEffect.} = ## Binary `*=` operator for ordinals x = x * y @@ -2990,6 +2995,8 @@ proc deepCopy*[T](x: T): T {.magic: "DeepCopy", noSideEffect.} = ## for the implementation of ``spawn``. discard +{.pop.} #{.push warning[GcMem]: off.} + when not defined(booting): type semistatic*[T] = static[T] | T diff --git a/tests/async/tasyncawait.nim b/tests/async/tasyncawait.nim index da4952677..2d65db4bd 100644 --- a/tests/async/tasyncawait.nim +++ b/tests/async/tasyncawait.nim @@ -61,11 +61,11 @@ proc createServer(port: TPort) {.async.} = discard server.TSocketHandle.listen() while true: var client = await accept(server) - readMessages(client) + asyncCheck readMessages(client) # TODO: Test: readMessages(disp, await disp.accept(server)) -createServer(TPort(10335)) -launchSwarm(TPort(10335)) +asyncCheck createServer(TPort(10335)) +asyncCheck launchSwarm(TPort(10335)) while true: poll() if clientCount == swarmSize: break diff --git a/tests/async/tasyncdiscard.nim b/tests/async/tasyncdiscard.nim index 48d8a8c4d..966851acc 100644 --- a/tests/async/tasyncdiscard.nim +++ b/tests/async/tasyncdiscard.nim @@ -36,4 +36,4 @@ proc main {.async.} = discard await g() echo 6 -main() +asyncCheck main() diff --git a/tests/async/tnestedpfuturetypeparam.nim b/tests/async/tnestedpfuturetypeparam.nim index d0d87e567..1db442170 100644 --- a/tests/async/tnestedpfuturetypeparam.nim +++ b/tests/async/tnestedpfuturetypeparam.nim @@ -5,4 +5,4 @@ proc main {.async.} = await newAsyncSocket().connect("www.google.com", TPort(80)) let x = await f() -main() +asyncCheck main() diff --git a/tests/casestmt/tcase_arrayconstr.nim b/tests/casestmt/tcase_arrayconstr.nim new file mode 100644 index 000000000..cd7156600 --- /dev/null +++ b/tests/casestmt/tcase_arrayconstr.nim @@ -0,0 +1,19 @@ +discard """ + output: '''Not found! +Found!''' +""" + +const + md_extension = [".md", ".markdown"] + +proc test(ext: string) = + case ext + of ".txt", md_extension: + echo "Found!" + else: + echo "Not found!" + +test(".something") +# ensure it's not evaluated at compile-time: +var foo = ".markdown" +test(foo) diff --git a/tests/exprs/tstmtexprs.nim b/tests/exprs/tstmtexprs.nim index ed0066287..d6b827b6d 100644 --- a/tests/exprs/tstmtexprs.nim +++ b/tests/exprs/tstmtexprs.nim @@ -4,7 +4,8 @@ discard """ 1244 6 abcdefghijklmnopqrstuvwxyz -145 23''' +145 23 +3''' """ import strutils @@ -92,3 +93,32 @@ proc parseResponse(): PJsonNode = #bug #992 var se = @[1,2] let b = (se[1] = 1; 1) + + +# bug #1161 + +type + PFooBase = ref object of PObject + field: int + + PFoo[T] = ref object of PFooBase + field2: T + +var testIf = + if true: + 2 + else: + 3 + +var testCase = + case 8 + of 8: 9 + else: 10 + +var testTry = + try: + PFoo[string](field: 3, field2: "asfasf") + except: + PFooBase(field: 5) + +echo(testTry.field) diff --git a/tests/generics/mdotlookup.nim b/tests/generics/mdotlookup.nim new file mode 100644 index 000000000..7a5e0ccbf --- /dev/null +++ b/tests/generics/mdotlookup.nim @@ -0,0 +1,8 @@ +proc baz(o: any): int = 5 # if bar is exported, it works + +type MyObj = object + x: int + +proc foo*(b: any) = + var o: MyObj + echo b.baz, " ", o.x.baz, " ", b.baz() diff --git a/tests/generics/tdotlookup.nim b/tests/generics/tdotlookup.nim new file mode 100644 index 000000000..b886cd8c9 --- /dev/null +++ b/tests/generics/tdotlookup.nim @@ -0,0 +1,7 @@ +discard """ + output: '''5 5 5''' +""" + +import mdotlookup + +foo(7) diff --git a/tests/macros/tbindsym.nim b/tests/macros/tbindsym.nim new file mode 100644 index 000000000..e1e3b5112 --- /dev/null +++ b/tests/macros/tbindsym.nim @@ -0,0 +1,25 @@ +discard """ + output: '''TFoo +TBar''' +""" + +# bug #1319 + +import macros + +type + TTextKind = enum + TFoo, TBar + +macro test: stmt = + var x = @[TFoo, TBar] + result = newStmtList() + for i in x: + result.add newCall(newIdentNode("echo"), + case i + of TFoo: + bindSym("TFoo") + of TBar: + bindSym("TBar")) + +test() diff --git a/tests/macros/tbug1149.nim b/tests/macros/tbug1149.nim deleted file mode 100644 index 5c4cb8530..000000000 --- a/tests/macros/tbug1149.nim +++ /dev/null @@ -1,20 +0,0 @@ -discard """ -msg: '''a -s -d -f''' -""" - -type - Foo = object - s: char - -iterator test2(f: string): Foo = - for i in f: - yield Foo(s: i) - -macro test(): stmt = - for i in test2("asdf"): - echo i.s - -test() diff --git a/tests/macros/tbugs.nim b/tests/macros/tbugs.nim new file mode 100644 index 000000000..3db851dd1 --- /dev/null +++ b/tests/macros/tbugs.nim @@ -0,0 +1,90 @@ +discard """ +msg: '''a +s +d +f +TTaa +TTaa +TTaa +TTaa +true +true +nil''' + +output: '''test +2''' +""" + +type + Foo = object + s: char + +iterator test2(f: string): Foo = + for i in f: + yield Foo(s: i) + +macro test(): stmt = + for i in test2("asdf"): + echo i.s + +test() + + +# bug 1297 + +import macros + +type TType = tuple[s: string] + +macro echotest(): stmt = + var t: TType + t.s = "" + t.s.add("test") + result = newCall(newIdentNode("echo"), newStrLitNode(t.s)) + +echotest() + +# bug #1103 + +type + Td = tuple + a:string + b:int + +proc get_data(d: Td) : string {.compileTime.} = + result = d.a # Works if a literal string is used here. + # Bugs if line A or B is active. Works with C + result &= "aa" # A + #result.add("aa") # B + #result = result & "aa" # C + +macro m(s:static[Td]) : stmt = + echo get_data(s) + echo get_data(s) + result = newEmptyNode() + +const s=("TT", 3) +m(s) +m(s) + +# bug #933 + +proc nilcheck(): PNimrodNode {.compileTime.} = + echo(result == nil) # true + echo(result.isNil) # true + echo(repr(result)) # nil + +macro testnilcheck(): stmt = + result = newNimNode(nnkStmtList) + discard nilcheck() + +testnilcheck() + +# bug #1323 + +proc calc(): array[1, int] = + result[0].inc() + result[0].inc() + +const c = calc() +echo c[0] diff --git a/tests/metatype/tautoproc.nim b/tests/metatype/tautoproc.nim new file mode 100644 index 000000000..9e8ff0bcb --- /dev/null +++ b/tests/metatype/tautoproc.nim @@ -0,0 +1,16 @@ +# bug #898 + +proc measureTime(e: auto) = + discard + +proc generate(a: int): void = + discard + +proc runExample = + var builder: int = 0 + + measureTime: + builder.generate() + +measureTime: + discard diff --git a/tests/objects/tobjconstr2.nim b/tests/objects/tobjconstr2.nim index cb47e146d..8ef7004e4 100644 --- a/tests/objects/tobjconstr2.nim +++ b/tests/objects/tobjconstr2.nim @@ -20,3 +20,15 @@ type var a = Bar(y: 100, x: 200) # works var b = Bar(x: 100, y: 200) # used to fail + +# bug 1275 + +type + Graphic = object of TObject + case kind: range[0..1] + of 0: + radius: float + of 1: + size: tuple[w, h: float] + +var d = Graphic(kind: 1, size: (12.9, 6.9)) diff --git a/tests/seq/tsequtils.nim b/tests/seq/tsequtils.nim index 7bc15ef9c..3a7eeeffa 100644 --- a/tests/seq/tsequtils.nim +++ b/tests/seq/tsequtils.nim @@ -50,6 +50,6 @@ var concatseq = concat(seq1,seq2) echo "Concat: ", $$(concatseq) var seq3 = @[1,2,3,4,5,5,5,7] -var discntseq = distnct(seq3) -echo "Distnct: ", $$(discntseq) +var dedupseq = deduplicate(seq3) +echo "Deduplicate: ", $$(dedupseq) diff --git a/tests/vm/tarrayboundeval.nim b/tests/vm/tarrayboundeval.nim index 9b33a2415..07aac4c4e 100644 --- a/tests/vm/tarrayboundeval.nim +++ b/tests/vm/tarrayboundeval.nim @@ -1,6 +1,7 @@ discard """ output: '''7 -8 8''' +8 8 +-2''' """ #bug 1063 @@ -21,3 +22,10 @@ type internal: array[int((KeyMax + 31)/32), cuint] echo myconst, " ", int((KeyMax + 31) / 32) + +#bug 1304 or something: + +const constArray: array [-3..2, int] = [-3, -2, -1, 0, 1, 2] + +echo constArray[-2] + diff --git a/tests/vm/teval1.nim b/tests/vm/teval1.nim index a02f26592..cdb4ad8e2 100644 --- a/tests/vm/teval1.nim +++ b/tests/vm/teval1.nim @@ -16,4 +16,9 @@ const echo "##", x, "##" +# bug #1310 +static: + var i, j: set[int8] = {} + var k = i + j + diff --git a/todo.txt b/todo.txt index d9d91b1ec..16c05186a 100644 --- a/todo.txt +++ b/todo.txt @@ -1,19 +1,21 @@ version 0.9.6 ============= +- scopes are still broken for generic instantiation! +- integrate the new LL into the devel branch +- start experimental branch - overloading of '='; general lift mechanism - Concurrency ----------- +- 'gcsafe' inferrence needs to be fixed - the disjoint checker needs to deal with 'a = spawn f(); g = spawn f()' - implement 'deepCopy' builtin - implement 'foo[1..4] = spawn(f[4..7])' - support for exception propagation - Minor: The copying of the 'ref Promise' into the thead local storage only happens to work due to the write barrier's implementation -- 'gcsafe' inferrence needs to be fixed - implement lock levels --> first without the more complex race avoidance - document the new 'spawn' and 'parallel' statements @@ -32,6 +34,7 @@ Misc - type API for macros; make 'spawn' a macro - markAndSweepGC should expose an API for fibers - prevent 'alloc(TypeWithGCedMemory)' +- some table related tests are wrong (memory usage checks) Bugs diff --git a/web/babelpkglist.nim b/web/babelpkglist.nim index 378d4ce30..8745c9f99 100644 --- a/web/babelpkglist.nim +++ b/web/babelpkglist.nim @@ -28,13 +28,19 @@ proc processContent(content: string) = officialCount = 0 unofficialList = "" unofficialCount = 0 + let + endings = {'.', '!'} for pkg in jsonArr: assert pkg.kind == JObject let pkgWeb = if pkg.hasKey("web"): pkg["web"].str else: pkg["url"].str - let listItem = li(a(href=pkgWeb, pkg["name"].str), " ", pkg["description"].str) + let + desc = pkg["description"].str + # Review array index access when #1291 is solved. + dot = if desc.high > 0 and desc[<desc.high] in endings: "" else: "." + listItem = li(a(href=pkgWeb, pkg["name"].str), " ", desc & dot) if pkg["url"].str.startsWith("git://github.com/nimrod-code") or "official" in pkg["tags"].elems: officialCount.inc diff --git a/web/news.txt b/web/news.txt index 74a48493a..aa91a83c4 100644 --- a/web/news.txt +++ b/web/news.txt @@ -12,6 +12,8 @@ News - ``spawn`` now uses an elaborate self-adapting thread pool and as such has been moved into its own module. So to use it, you now have to import ``threadpool``. + - The symbol binding rules in generics changed: ``bar`` in ``foo.bar`` is + now considered for implicit early binding. - ``c2nim`` moved into its own repository and is now a Babel package. - ``pas2nim`` moved into its own repository and is now a Babel package. @@ -21,6 +23,7 @@ News - Added module ``cpuinfo``. - Added module ``threadpool``. + - ``sequtils.distnct`` has been renamed to ``sequtils.deduplicate``. 2014-04-21 Version 0.9.4 released diff --git a/web/nimrod.ini b/web/nimrod.ini index eb9ffa6b4..7b0a91c19 100644 --- a/web/nimrod.ini +++ b/web/nimrod.ini @@ -36,8 +36,8 @@ 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;niminst;nimgrep;gc;estp;idetools;docgen;koch" +doc: "endb;intern;apis;lib;manual;tut1;tut2;nimrodc;overview;filters" +doc: "tools;c2nim;niminst;nimgrep;gc;estp;idetools;docgen;koch;backends.txt" pdf: "manual;lib;tut1;tut2;nimrodc;niminst;gc" srcdoc2: "system.nim;impure/graphics;wrappers/sdl" srcdoc2: "core/macros;pure/marshal;core/typeinfo;core/unsigned" |