From cb9110c43d4ae9c29a0a1e0d54f7735712d4ba62 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Thu, 3 Jan 2019 18:56:05 +0100 Subject: [PATCH 001/202] --define:nimQuirky exception handling for Nim; in preparation of a blog post --- compiler/ccgstmts.nim | 58 +++++++++++++++++++++++++----------------- compiler/lineinfos.nim | 2 +- compiler/pragmas.nim | 6 +++-- compiler/semcall.nim | 1 + compiler/vm.nim | 18 ++++++++----- lib/system.nim | 22 ++++++++++++++++ lib/system/excpt.nim | 2 ++ 7 files changed, 77 insertions(+), 32 deletions(-) diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index 6c33b302d207..a077331c43f7 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -167,7 +167,7 @@ proc blockLeaveActions(p: BProc, howManyTrys, howManyExcepts: int) = let tryStmt = p.nestedTryStmts.pop if not p.module.compileToCpp or optNoCppExceptions in p.config.globalOptions: # Pop safe points generated by try - if not tryStmt.inExcept: + if not tryStmt.inExcept and not isDefined(p.config, "nimQuirky"): linefmt(p, cpsStmts, "#popSafePoint();$n") # Pop this try-stmt of the list of nested trys @@ -382,7 +382,7 @@ proc genReturnStmt(p: BProc, t: PNode) = blockLeaveActions(p, howManyTrys = p.nestedTryStmts.len, howManyExcepts = p.inExceptBlockLen) - if (p.finallySafePoints.len > 0): + if (p.finallySafePoints.len > 0) and not isDefined(p.config, "nimQuirky"): # If we're in a finally block, and we came here by exception # consume it before we return. var safePoint = p.finallySafePoints[p.finallySafePoints.len-1] @@ -919,29 +919,38 @@ proc genTry(p: BProc, t: PNode, d: var TLoc) = # if not isEmptyType(t.typ) and d.k == locNone: getTemp(p, t.typ, d) - p.module.includeHeader("") + let quirkyExceptions = isDefined(p.config, "nimQuirky") + if not quirkyExceptions: + p.module.includeHeader("") genLineDir(p, t) - var safePoint = getTempName(p.module) discard cgsym(p.module, "Exception") - linefmt(p, cpsLocals, "#TSafePoint $1;$n", safePoint) - linefmt(p, cpsStmts, "#pushSafePoint(&$1);$n", safePoint) - if isDefined(p.config, "nimStdSetjmp"): - linefmt(p, cpsStmts, "$1.status = setjmp($1.context);$n", safePoint) - elif isDefined(p.config, "nimSigSetjmp"): - linefmt(p, cpsStmts, "$1.status = sigsetjmp($1.context, 0);$n", safePoint) - elif isDefined(p.config, "nimRawSetjmp"): - linefmt(p, cpsStmts, "$1.status = _setjmp($1.context);$n", safePoint) - else: - linefmt(p, cpsStmts, "$1.status = setjmp($1.context);$n", safePoint) - startBlock(p, "if ($1.status == 0) {$n", [safePoint]) + var safePoint: Rope + if not quirkyExceptions: + safePoint = getTempName(p.module) + linefmt(p, cpsLocals, "#TSafePoint $1;$n", safePoint) + linefmt(p, cpsStmts, "#pushSafePoint(&$1);$n", safePoint) + if isDefined(p.config, "nimStdSetjmp"): + linefmt(p, cpsStmts, "$1.status = setjmp($1.context);$n", safePoint) + elif isDefined(p.config, "nimSigSetjmp"): + linefmt(p, cpsStmts, "$1.status = sigsetjmp($1.context, 0);$n", safePoint) + elif isDefined(p.config, "nimRawSetjmp"): + linefmt(p, cpsStmts, "$1.status = _setjmp($1.context);$n", safePoint) + else: + linefmt(p, cpsStmts, "$1.status = setjmp($1.context);$n", safePoint) + startBlock(p, "if ($1.status == 0) {$n", [safePoint]) var length = sonsLen(t) add(p.nestedTryStmts, (t, false)) expr(p, t.sons[0], d) - linefmt(p, cpsStmts, "#popSafePoint();$n") - endBlock(p) - startBlock(p, "else {$n") - linefmt(p, cpsStmts, "#popSafePoint();$n") - genRestoreFrameAfterException(p) + if not quirkyExceptions: + linefmt(p, cpsStmts, "#popSafePoint();$n") + endBlock(p) + startBlock(p, "else {$n") + linefmt(p, cpsStmts, "#popSafePoint();$n") + genRestoreFrameAfterException(p) + elif 1 < length and t.sons[1].kind == nkExceptBranch: + startBlock(p, "if (#getCurrentException()) {$n") + else: + startBlock(p) p.nestedTryStmts[^1].inExcept = true var i = 1 while (i < length) and (t.sons[i].kind == nkExceptBranch): @@ -952,7 +961,8 @@ proc genTry(p: BProc, t: PNode, d: var TLoc) = # general except section: if i > 1: lineF(p, cpsStmts, "else", []) startBlock(p) - linefmt(p, cpsStmts, "$1.status = 0;$n", safePoint) + if not quirkyExceptions: + linefmt(p, cpsStmts, "$1.status = 0;$n", safePoint) expr(p, t.sons[i].sons[0], d) linefmt(p, cpsStmts, "#popCurrentException();$n") endBlock(p) @@ -968,7 +978,8 @@ proc genTry(p: BProc, t: PNode, d: var TLoc) = [genTypeInfo(p.module, t[i][j].typ, t[i][j].info)]) if i > 1: line(p, cpsStmts, "else ") startBlock(p, "if ($1) {$n", [orExpr]) - linefmt(p, cpsStmts, "$1.status = 0;$n", safePoint) + if not quirkyExceptions: + linefmt(p, cpsStmts, "$1.status = 0;$n", safePoint) expr(p, t.sons[i].sons[blen-1], d) linefmt(p, cpsStmts, "#popCurrentException();$n") endBlock(p) @@ -979,7 +990,8 @@ proc genTry(p: BProc, t: PNode, d: var TLoc) = p.finallySafePoints.add(safePoint) genSimpleBlock(p, t.sons[i].sons[0]) discard pop(p.finallySafePoints) - linefmt(p, cpsStmts, "if ($1.status != 0) #reraiseException();$n", safePoint) + if not quirkyExceptions: + linefmt(p, cpsStmts, "if ($1.status != 0) #reraiseException();$n", safePoint) proc genAsmOrEmitStmt(p: BProc, t: PNode, isAsmStmt=false): Rope = var res = "" diff --git a/compiler/lineinfos.nim b/compiler/lineinfos.nim index b1ecf779eea5..e8bdb1dca14b 100644 --- a/compiler/lineinfos.nim +++ b/compiler/lineinfos.nim @@ -224,7 +224,7 @@ type proc `==`*(a, b: FileIndex): bool {.borrow.} -proc raiseRecoverableError*(msg: string) {.noinline, noreturn.} = +proc raiseRecoverableError*(msg: string) {.noinline.} = raise newException(ERecoverableError, msg) const diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index 58f64f7b039a..7db25a7d1800 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -864,7 +864,9 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, incl(sym.flags, sfSideEffect) of wNoreturn: noVal(c, it) - incl(sym.flags, sfNoReturn) + # Disable the 'noreturn' annotation when in the "Quirky Exceptions" mode! + if not isDefined(c.config, "nimQuirky"): + incl(sym.flags, sfNoReturn) if sym.typ[0] != nil: localError(c.config, sym.ast[paramsPos][0].info, ".noreturn with return type not allowed") @@ -1119,7 +1121,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, else: sym.flags.incl sfUsed of wLiftLocals: discard else: invalidPragma(c, it) - elif sym == nil or (sym != nil and sym.kind in {skVar, skLet, skParam, + elif sym == nil or (sym != nil and sym.kind in {skVar, skLet, skParam, skField, skProc, skFunc, skConverter, skMethod, skType}): n.sons[i] = semCustomPragma(c, it) elif sym != nil: diff --git a/compiler/semcall.nim b/compiler/semcall.nim index 7e0ea54904ce..5d77d8325b47 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -239,6 +239,7 @@ proc notFoundError*(c: PContext, n: PNode, errors: CandidateErrors) = if c.config.m.errorOutputs == {}: # fail fast: globalError(c.config, n.info, "type mismatch") + return if errors.len == 0: localError(c.config, n.info, "expression '$1' cannot be called" % n[0].renderTree) return diff --git a/compiler/vm.nim b/compiler/vm.nim index c8784c3e7dcd..6986359567c2 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -80,7 +80,7 @@ proc stackTraceAux(c: PCtx; x: PStackFrame; pc: int; recursionLimit=100) = add(s, x.prc.name.s) msgWriteln(c.config, s) -proc stackTrace(c: PCtx, tos: PStackFrame, pc: int, +proc stackTraceB(c: PCtx, tos: PStackFrame, pc: int, msg: string, lineInfo: TLineInfo) = msgWriteln(c.config, "stack trace: (most recent call last)") stackTraceAux(c, tos, pc) @@ -88,8 +88,14 @@ proc stackTrace(c: PCtx, tos: PStackFrame, pc: int, if c.mode == emRepl: globalError(c.config, lineInfo, msg) else: localError(c.config, lineInfo, msg) -proc stackTrace(c: PCtx, tos: PStackFrame, pc: int, msg: string) = - stackTrace(c, tos, pc, msg, c.debug[pc]) +template stackTrace(c: PCtx, tos: PStackFrame, pc: int, + msg: string, lineInfo: TLineInfo) = + stackTraceB(c, tos, pc, msg, lineInfo) + return + +template stackTrace(c: PCtx, tos: PStackFrame, pc: int, msg: string) = + stackTraceB(c, tos, pc, msg, c.debug[pc]) + return proc bailOut(c: PCtx; tos: PStackFrame) = stackTrace(c, tos, c.exceptionInstr, "unhandled exception: " & @@ -950,13 +956,13 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = decodeBC(rkInt) let a = regs[rb].node let b = regs[rc].node - if a.kind == nkSym and a.sym.kind in skProcKinds and + if a.kind == nkSym and a.sym.kind in skProcKinds and b.kind == nkSym and b.sym.kind in skProcKinds: regs[ra].intVal = if sfFromGeneric in a.sym.flags and a.sym.owner == b.sym: 1 else: 0 - else: - stackTrace(c, tos, pc, "node is not a proc symbol") + else: + stackTrace(c, tos, pc, "node is not a proc symbol") of opcEcho: let rb = instr.regB if rb == 1: diff --git a/lib/system.nim b/lib/system.nim index b9f86f54933c..38f45ff7a442 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -2968,6 +2968,28 @@ when not declared(sysFatal): proc sysFatal(exceptn: typedesc, message, arg: string) {.inline.} = rawoutput(message) panic(arg) + elif defined(nimQuirky): + proc name(t: typedesc): string {.magic: "TypeTrait".} + proc sysFatal(exceptn: typedesc, message: string) {.inline, noReturn.} = + var buf = newStringOfCap(200) + add(buf, "Error: unhandled exception: ") + add(buf, message) + add(buf, " [") + add(buf, name exceptn) + add(buf, "]") + echo buf + quit 1 + + proc sysFatal(exceptn: typedesc, message, arg: string) {.inline, noReturn.} = + var buf = newStringOfCap(200) + add(buf, "Error: unhandled exception: ") + add(buf, message) + add(buf, arg) + add(buf, " [") + add(buf, name exceptn) + add(buf, "]") + echo buf + quit 1 else: proc sysFatal(exceptn: typedesc, message: string) {.inline, noReturn.} = var e: ref exceptn diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim index 84a1da3438f8..3c0e42c6e5f7 100644 --- a/lib/system/excpt.nim +++ b/lib/system/excpt.nim @@ -357,6 +357,8 @@ proc raiseExceptionAux(e: ref Exception) = raiseCounter.inc # skip zero at overflow e.raiseId = raiseCounter {.emit: "`e`->raise();".} + elif defined(nimQuirky): + if currException == nil: currException = e else: if excHandler != nil: if not excHandler.hasRaiseAction or excHandler.raiseAction(e): From 6bae78f365caa6d9afc2c83973ae97d415697079 Mon Sep 17 00:00:00 2001 From: deansher Date: Mon, 28 Jan 2019 08:11:59 -0500 Subject: [PATCH 002/202] First pass at compile-time/runtime terminology changes. --- doc/manual.rst | 334 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 220 insertions(+), 114 deletions(-) diff --git a/doc/manual.rst b/doc/manual.rst index f8a5887916d9..20aa320a7996 100644 --- a/doc/manual.rst +++ b/doc/manual.rst @@ -46,7 +46,7 @@ and ``a ^* b`` is short for ``(a (b a)*)?``. Example:: arrayConstructor = '[' expr ^* ',' ']' -Other parts of Nim - like scoping rules or runtime semantics are only +Other parts of Nim - like scoping rules or execution semantics are only described in the, more easily comprehensible, informal manner for now. @@ -55,7 +55,7 @@ described in the, more easily comprehensible, informal manner for now. Definitions =========== -A Nim program specifies a computation that acts on a memory consisting of +Nim code specifies a computation that acts on a memory consisting of components called `locations`:idx:. A variable is basically a name for a location. Each variable and location is of a certain `type`:idx:. The variable's type is called `static type`:idx:, the location's type is called @@ -71,21 +71,37 @@ identifier is declared unless overloading resolution rules suggest otherwise. An expression specifies a computation that produces a value or location. Expressions that produce locations are called `l-values`:idx:. An l-value can denote either a location or the value the location contains, depending on -the context. Expressions whose values can be determined statically are called -`constant expressions`:idx:; they are never l-values. - -A `static error`:idx: is an error that the implementation detects before -program execution. Unless explicitly classified, an error is a static error. - -A `checked runtime error`:idx: is an error that the implementation detects -and reports at runtime. The method for reporting such errors is via *raising -exceptions* or *dying with a fatal error*. However, the implementation -provides a means to disable these runtime checks. See the section pragmas_ -for details. - -Whether a checked runtime error results in an exception or in a fatal error at -runtime is implementation specific. Thus the following program is always -invalid: +the context. + +A Nim `program`:idx: consists of one or more text `source files`:idx: containing +Nim code. It is processed by a Nim `compiler`:idx: into an `executable`:idx:. +The nature of this executable depends on the compiler implementation; it may, +for example, be a native binary or JavaScript source code. A Nim program can +contain Nim code that will be executed at `compile time`:idx:, such as constant +expressions, macro definitions, and Nim procedures used by macro definitions. +Typically, the bulk of a Nim program's code is compiled into the executable and +executed at `runtime`:idx:. Only a subset of the Nim language is supported at +compile time, but it is a substantial subset. See `Restrictions on +Compile-Time Execution `_ for details. + +The compiler parses Nim source code into an internal data structure called the +`abstract syntax tree`:idx: (`AST`:idx). Then, before executing the code or +processing it into the executable, it transforms the AST through `semantic +analysis`:idx:. This adds considerable semantic information to the AST, such as +expression types, identifier meanings, and in some cases expression values. An +error detected during semantic analysis is called a `static error`:idx:. Errors +mentioned in this manual are static errors when not otherwise specified. + +An error detected during code execution (whether at compile time or at runtime) +is a `checked execution error`:idx:. The method for reporting such errors is via +*raising exceptions* or *dying with a fatal error*. However, the implementation +provides a means to disable these `execution-time checks`:idx:. See the section +pragmas_ for details. + +Whether a checked execution error results in an exception or in a fatal error +is implementation specific. Thus the following program is invalid; even +though it purports to catch the `IndexError` from an out-of-bounds array access, +the compiler may instead choose to allow execution to die with a fatal error. .. code-block:: nim var a: array[0..1, char] @@ -95,11 +111,28 @@ invalid: except IndexError: echo "invalid index" -An `unchecked runtime error`:idx: is an error that is not guaranteed to be +An `unchecked execution error`:idx: is an error that is not guaranteed to be detected, and can cause the subsequent behavior of the computation to -be arbitrary. Unchecked runtime errors cannot occur if only `safe`:idx: -language features are used. - +be arbitrary. Unchecked execution errors cannot occur if only `safe`:idx: +language features are used and if no execution-time checks are disabled. + +A `constant expression`:idx: is an expression whose value can be computed during +semantic analysis of the code in which it appears. It is never an l-value and +never has side effects. Constant expressions are not limited to the capabilities +of semantic analysis, such as constant folding; they can use the substantial +subset of the Nim language that is supported at compile time. Since constant +expressions can be used as an input to semantic analysis (such as for defining +array bounds), this flexibility requires the compiler to interleave semantic +analysis and compile-time code execution. + +It is mostly accurate to picture semantic analysis proceeding top to bottom and +left to right in the source code, with compile-time code execution interleaved +when necessary to compute values that are required for subsequent semantic +analysis. We saw just above that this interleaving is necessary for handling +constant expressions. We will see much later in this document that macro +invocation not only requires this interleaving, but also creates a situation +where semantic analyis does not entirely proceed top to bottom and left to +right. Lexical Analysis @@ -673,10 +706,90 @@ Rationale: Consistency with overloaded assignment or assignment-like operations, ``a = b`` can be read as ``performSomeCopy(a, b)``. +Constants and Constant Expressions +================================== + +A `constant`:idx: is a symbol that is bound to the value of a `constant +expression`. This is an expression whose value can be computed during +semantic analysis of the code in which it appears. However, constant +expressions are not limited to the capabilities of semantic analysis; they +can use the substantial subset of the Nim language that is supported for +compile-time execution. Compile-time execution is interleaved with semantic +analysis as necessary. A constant's value cannot change after it is first +computed. + +A constant expression must be composed of the following elements: + +* literals +* previously declared constants and compile-time variables +* previously declared macros and templates +* previously declared procedures that have no side effects beyond + possibly modifying compile-time variables +* operators representing such procedures +* code blocks that can internally use all Nim features supported at + compile time (the next section below), but that cannot + refer to any external values beyond those listed above + +Constant expressions must adhere to the restrictions on compile-time +execution described in `Restrictions on +Compile-Time Execution `_. + +For example, the following code echoes the beginning of the Fibonacci +series **at compile time**. This is a demonstration of flexibility in +defining constants, not a recommended style for solving this problem! + +.. code-block:: nim + :test: "nim c $1" + import strformat + + var fib_n {.compileTime.}: int + var fib_prev {.compileTime.}: int + var fib_prev_prev {.compileTime.}: int + + proc next_fib(): int = + result = if fib_n < 2: + fib_n + else: + fib_prev_prev + fib_prev + inc(fib_n) + fib_prev_prev = fib_prev + fib_prev = result + + const f0 = next_fib() + const f1 = next_fib() + + const display_fib = block: + const f2 = next_fib() + var result = fmt"Fibonacci sequence: {f0}, {f1}, {f2}, " + for i in 0..10: + if i > 0: + add(result, ", ") + add(result, $next_fib()) + result + + static: + echo display_fib + + +Restrictions on Compile-Time Execution +====================================== + +Nim code that will be executed at compile-time cannot use the following +language features: + +* methods +* closure iterators +* ``cast`` +* ``ptr``s or ``ref``s +* the FFI + +Some or all of these restrictions are likely to be lifted over time. + + Types ===== -All expressions have a type which is known at compile time. Nim +All expressions have a type which is known during semantic analysis. Nim is statically typed. One can declare new types, which is in essence defining an identifier that can be used to denote this custom type. @@ -700,9 +813,9 @@ Ordinal types have the following characteristics: the operation of functions as ``inc``, ``ord``, ``dec`` on ordinal types to be defined. - Ordinal values have a smallest possible value. Trying to count further - down than the smallest value gives a checked runtime or static error. + down than the smallest value gives a checked execution or static error. - Ordinal values have a largest possible value. Trying to count further - than the largest value gives a checked runtime or static error. + than the largest value gives a checked execution or static error. Integers, bool, characters and enumeration types (and subranges of these types) belong to ordinal types. For reasons of simplicity of implementation @@ -810,7 +923,7 @@ lowest and highest value of the type: to 5. ``PositiveFloat`` defines a subrange of all positive floating point values. NaN does not belong to any subrange of floating point types. Assigning any other value to a variable of type ``Subrange`` is a -checked runtime error (or static error if it can be statically +checked execution error (or static error if it can be statically determined). Assignments from the base type to one of its subrange types (and vice versa) are allowed. @@ -854,7 +967,7 @@ The IEEE standard defines five types of floating-point exceptions: * Inexact: operation produces a result that cannot be represented with infinite precision, for example, 2.0 / 3.0, log(1.1) and 0.1 in input. -The IEEE exceptions are either ignored at runtime or mapped to the +The IEEE exceptions are either ignored during execution or mapped to the Nim exceptions: `FloatInvalidOpError`:idx:, `FloatDivByZeroError`:idx:, `FloatOverflowError`:idx:, `FloatUnderflowError`:idx:, and `FloatInexactError`:idx:. @@ -881,8 +994,9 @@ The only operations that are affected by the ``floatChecks`` pragma are the ``+``, ``-``, ``*``, ``/`` operators for floating point types. An implementation should always use the maximum precision available to evaluate -floating pointer values at compile time; this means expressions like -``0.09'f32 + 0.01'f32 == 0.09'f64 + 0.01'f64`` are true. +floating pointer values during semantic analysis; this means expressions like +``0.09'f32 + 0.01'f32 == 0.09'f64 + 0.01'f64`` that are evaluating during +constant folding are true. Boolean type @@ -1110,8 +1224,8 @@ tuples, objects and sets belong to the structured types. Array and sequence types ------------------------ Arrays are a homogeneous type, meaning that each element in the array -has the same type. Arrays always have a fixed length which is specified at -compile time (except for open arrays). They can be indexed by any ordinal type. +has the same type. Arrays always have a fixed length which is known during +semantic analysis (except for open arrays). They can be indexed by any ordinal type. A parameter ``A`` may be an *open array*, in which case it is indexed by integers from 0 to ``len(A)-1``. An array expression may be constructed by the array constructor ``[]``. The element type of this array expression is @@ -1119,7 +1233,7 @@ inferred from the type of the first element. All other elements need to be implicitly convertable to this type. Sequences are similar to arrays but of dynamic length which may change -during runtime (like strings). Sequences are implemented as growable arrays, +during execution (like strings). Sequences are implemented as growable arrays, allocating pieces of memory as items are added. A sequence ``S`` is always indexed by integers from 0 to ``len(S)-1`` and its bounds are checked. Sequences can be constructed by the array constructor ``[]`` in conjunction @@ -1153,7 +1267,7 @@ operator, and remove (and get) the last element of a sequence with the The notation ``x[i]`` can be used to access the i-th element of ``x``. -Arrays are always bounds checked (at compile-time or at runtime). These +Arrays are always bounds checked (statically or during execution). These checks can be disabled via pragmas or invoking the compiler with the ``--boundChecks:off`` command line switch. @@ -1260,7 +1374,7 @@ is currently not checked. **Future directions**: GC'ed memory should be allowed in unchecked arrays and there should be an explicit annotation of how the GC is to determine the -runtime size of the array. +execution-time size of the array. @@ -1317,7 +1431,7 @@ can also be defined with indentation instead of ``[]``: age: natural # and an age Objects provide many features that tuples do not. Object provide inheritance -and information hiding. Objects have access to their type at runtime, so that +and information hiding. Objects have access to their type during execution, so that the ``of`` operator can be used to determine the object's type. The ``of`` operator is similar to the ``instanceof`` operator in Java. @@ -1415,7 +1529,7 @@ safety its address cannot be taken and assignments to it are restricted: The new value must not lead to a change of the active object branch. For an object branch switch ``system.reset`` has to be used. Also, when the fields of a particular branch are specified during object construction, the correct value -for the discriminator must be supplied at compile-time. +for the discriminator must be available for semantic analysis. Package level objects --------------------- @@ -1429,7 +1543,7 @@ contexts (``var/ref/ptr IncompleteObject``) in general since the compiler does not yet know the size of the object. To complete an incomplete object the ``package`` pragma has to be used. ``package`` implies ``byref``. -As long as a type ``T`` is incomplete ``sizeof(T)`` or "runtime type +As long as a type ``T`` is incomplete ``sizeof(T)`` or "execution-time type information" for ``T`` is not available. @@ -1859,7 +1973,7 @@ that don't. Distinct types provide a means to introduce a new string type username: string db.query("SELECT FROM users WHERE name = '$1'" % username) - # Error at compile time: `query` expects an SQL string! + # Static error: `query` expects an SQL string! It is an essential property of abstract types that they **do not** imply a @@ -2246,11 +2360,8 @@ Overloading resolution ====================== In a call ``p(args)`` the routine ``p`` that matches best is selected. If -multiple routines match equally well, the ambiguity is reported at compiletime. - -Every arg in args needs to match. There are multiple different categories how an -argument can match. Let ``f`` be the formal parameter's type and ``a`` the type -of the argument. +multiple routines match equally well, the ambiguity is reported during +semantic analysis. 1. Exact match: ``a`` and ``f`` are of the same type. 2. Literal match: ``a`` is an integer literal of value ``v`` @@ -2627,53 +2738,36 @@ identifier ``_`` can be used to ignore some parts of the tuple: Const section ------------- -`Constants`:idx: are symbols which are bound to a value. The constant's value -cannot change. The compiler must be able to evaluate the expression in a -constant declaration at compile time. +A const section declares constants whose values are constant expressions: -Nim contains a sophisticated compile-time evaluator, so procedures which -have no side-effect can be used in constant expressions too: - -.. code-block:: nim +.. code-block:: import strutils const + roundPi = 3.1415 constEval = contains("abc", 'b') # computed at compile time! +Once declared, a constant's symbol can be used as a constant expression. -The rules for compile-time computability are: - -1. Literals are compile-time computable. -2. Type conversions are compile-time computable. -3. Procedure calls of the form ``p(X)`` are compile-time computable if - ``p`` is a proc without side-effects (see the `noSideEffect pragma - <#pragmas-nosideeffect-pragma>`_ for details) and if ``X`` is a - (possibly empty) list of compile-time computable arguments. - - -Constants cannot be of type ``ptr``, ``ref`` or ``var``, nor can -they contain such a type. - +See `Constants and Constant Expressions <#constants-and-constant-expressions>`_ +for details. Static statement/expression --------------------------- -A static statement/expression can be used to enforce compile -time evaluation explicitly. Enforced compile time evaluation can even evaluate -code that has side effects: +A static statement/expression explicitly requires compile-time execution. +Even some code that has side effects is permitted in a static block: .. code-block:: static: echo "echo at compile time" -It's a static error if the compiler cannot perform the evaluation at compile +There are limitations on what Nim code can be executed at compile time; +see `Restrictions on Compile-Time Execution +<#restrictions-on-compile-time-execution>`_ for details. +It's a static error if the compiler cannot execute the block at compile time. -The current implementation poses some restrictions for compile time -evaluation: Code which contains ``cast`` or makes use of the foreign function -interface cannot be evaluated at compile time. Later versions of Nim will -support the FFI at compile time. - If statement ------------ @@ -2747,9 +2841,9 @@ empty ``discard`` statement should be used. For non ordinal types it is not possible to list every possible value and so these always require an ``else`` part. -As case statements perform compile-time exhaustiveness checks, the value in -every ``of`` branch must be known at compile time. This fact is also exploited -to generate more performant code. +Because case statements are checked for exhaustiveness during semantic analysis, +the value in every ``of`` branch must be computable during analysis. +This restriction also allows the compiler to generate more performant code. As a special semantic extension, an expression in an ``of`` branch of a case statement may evaluate to a set or array constructor; the set or array is then @@ -2814,10 +2908,10 @@ Example: .. code-block:: nim proc someProcThatMayRunInCompileTime(): bool = when nimvm: - # This code runs in compile time + # This code executes at compile time result = true else: - # This code runs in runtime + # This code executes at runtime result = false const ctValue = someProcThatMayRunInCompileTime() let rtValue = someProcThatMayRunInCompileTime() @@ -3557,7 +3651,7 @@ returned value is an l-value and can be modified by the caller: writeAccessToG() = 6 assert g == 6 -It is a compile time error if the implicitly introduced pointer could be +It is a static error if the implicitly introduced pointer could be used to access a location beyond its lifetime: .. code-block:: nim @@ -3673,7 +3767,7 @@ Invocation of a multi-method cannot be ambiguous: collide 2 is preferred over collide 1 because the resolution works from left to right. In the example ``Unit, Thing`` is preferred over ``Thing, Unit``. -**Note**: Compile time evaluation is not (yet) supported for methods. +**Note**: Compile time execution is not (yet) supported for methods. Inhibit dynamic method resolution via procCall @@ -3794,7 +3888,7 @@ In contrast to that, a `closure iterator`:idx: can be passed around more freely: Closure iterators have other restrictions than inline iterators: 1. ``yield`` in a closure iterator can not occur in a ``try`` statement. -2. For now, a closure iterator cannot be evaluated at compile time. +2. For now, a closure iterator cannot be executed at compile time. 3. ``return`` is allowed in a closure iterator (but rarely useful) and ends iteration. 4. Neither inline nor closure iterators can be recursive. @@ -4113,7 +4207,7 @@ The exception tree is defined in the `system `_ module. Every exception inherits from ``system.Exception``. Exceptions that indicate programming bugs inherit from ``system.Defect`` (which is a subtype of ``Exception``) and are stricly speaking not catchable as they can also be mapped to an operation -that terminates the whole process. Exceptions that indicate any other runtime error +that terminates the whole process. Exceptions that indicate any other execution error that can be caught inherit from ``system.CatchableError`` (which is a subtype of ``Exception``). @@ -4351,7 +4445,7 @@ a `type variable`:idx:. Is operator ----------- -The ``is`` operator checks for type equivalence at compile time. It is +The ``is`` operator checks for type equivalence during semantic analysis. It is therefore very useful for type specialization within generic code: .. code-block:: nim @@ -5357,6 +5451,14 @@ invocation would have been replaced by its result in the source code. This can be used to implement `domain specific languages`:idx:. +Macro invocation leads to a case where semantic analyis does **not** entirely +proceed top to bottom and left to right. The compiler must + +* perform semantic analysis through the end of the macro invocation, +* execute the macro body, +* replace the AST of the macro invocation with the AST returned by the macro, +* and finally repeat semantic analysis of that region of the code. + While macros enable advanced compile-time code transformations, they cannot change Nim's syntax. However, this is no real restriction because Nim's syntax is flexible enough anyway. @@ -5639,7 +5741,8 @@ static[T] **Note**: static[T] is still in development. -As their name suggests, static parameters must be known at compile-time: +As their name suggests, static parameters must be computable during +semantic analysis: .. code-block:: nim @@ -5651,7 +5754,8 @@ As their name suggests, static parameters must be known at compile-time: # regex, stored in a global variable precompiledRegex(paramStr(1)) # Error, command-line options - # are not known at compile-time + # are not computable during + # semantic analysis For the purposes of code generation, all static params are treated as @@ -5665,7 +5769,7 @@ Static params can also appear in the signatures of generic types: type Matrix[M,N: static int; T: Number] = array[0..(M*N - 1), T] # Note how `Number` is just a type constraint here, while - # `static int` requires us to supply a compile-time int value + # `static int` requires us to supply an int value AffineTransform2D[T] = Matrix[3, 3, T] AffineTransform3D[T] = Matrix[4, 4, T] @@ -5673,13 +5777,12 @@ Static params can also appear in the signatures of generic types: var m1: AffineTransform3D[float] # OK var m2: AffineTransform2D[string] # Error, `string` is not a `Number` -Please note that ``static T`` is just a syntactic convenience for the -underlying generic type ``static[T]``. The type param can be omitted -to obtain the type class of all values known at compile-time. A more -specific type class can be created by instantiating ``static`` with -another type class. +Please note that ``static T`` is just a syntactic convenience for the underlying +generic type ``static[T]``. The type param can be omitted to obtain the type +class of all values computable during semantic analysis. A more specific type +class can be created by instantiating ``static`` with another type class. -You can force the evaluation of a certain expression at compile-time by +You can force the evaluation of a certain expression during semantic analysis by coercing it to a corresponding ``static`` type: .. code-block:: nim @@ -6766,11 +6869,11 @@ pragma block can be used: compileTime pragma ------------------ -The ``compileTime`` pragma is used to mark a proc or variable to be used at -compile time only. No code will be generated for it. Compile time procs are -useful as helpers for macros. Since version 0.12.0 of the language, a proc -that uses ``system.NimNode`` within its parameter types is implicitly declared -``compileTime``: +The ``compileTime`` pragma is used to mark a proc or variable to be used only +during compile-time execution. No code will be generated for it. Compile time +procs are useful as helpers for macros. Since version 0.12.0 of the language, a +proc that uses ``system.NimNode`` within its parameter types is implicitly +declared ``compileTime``: .. code-block:: nim proc astHelper(n: NimNode): NimNode = @@ -6858,7 +6961,7 @@ structure: pure pragma ----------- An object type can be marked with the ``pure`` pragma so that its type -field which is used for runtime type identification is omitted. This used to be +field which is used for execution-time type identification is omitted. This used to be necessary for binary compatibility with other compiled languages. An enum type can be marked as ``pure``. Then access of its fields always @@ -6884,7 +6987,7 @@ though. The ``error`` pragma can also be used to annotate a symbol (like an iterator or proc). The *usage* of the symbol then -triggers a compile-time error. This is especially useful to rule out that some +triggers a static error. This is especially useful to rule out that some operation is valid due to overloading and type conversions: .. code-block:: nim @@ -7006,7 +7109,7 @@ extension the pragma is simply ignored. unroll pragma ------------- The ``unroll`` pragma can be used to tell the compiler that it should unroll -a `for`:idx: or `while`:idx: loop for runtime efficiency: +a `for`:idx: or `while`:idx: loop for execution efficiency: .. code-block:: nim proc searchChar(s: string, c: char): int = @@ -7039,7 +7142,7 @@ others may be added later). =============== =============== ============================================ pragma allowed values description =============== =============== ============================================ -checks on|off Turns the code generation for all runtime +checks on|off Turns the code generation for all execution checks on or off. boundChecks on|off Turns the code generation for array bound checks on or off. @@ -7066,7 +7169,7 @@ Example: .. code-block:: nim {.checks: off, optimization: speed.} - # compile without runtime checks and optimize for speed + # compile without execution-time checks and optimize for speed push and pop pragmas @@ -7076,7 +7179,7 @@ but are used to override the settings temporarily. Example: .. code-block:: nim {.push checks: off.} - # compile this section without runtime checks as it is + # compile this section without execution-time checks as it is # speed critical # ... some code ... {.pop.} # restore old settings @@ -7321,7 +7424,8 @@ compiler like you would using the commandline switch ``--passC``: {.passC: "-Wall -Werror".} Note that you can use ``gorge`` from the `system module `_ to -embed parameters from an external command at compile time: +embed parameters from an external command that will be executed +during semantic analysis: .. code-block:: Nim {.passC: gorge("pkg-config --cflags sdl").} @@ -7335,7 +7439,8 @@ like you would using the commandline switch ``--passL``: {.passL: "-lSDLmain -lSDL".} Note that you can use ``gorge`` from the `system module `_ to -embed parameters from an external command at compile time: +embed parameters from an external command that will be executed +during semantic analysis: .. code-block:: Nim {.passL: gorge("pkg-config --libs sdl").} @@ -7946,7 +8051,7 @@ interoperability with C. Combining packed pragma with inheritance is not defined, and it should not be used with GC'ed memory (ref's). **Future directions**: Using GC'ed memory in packed pragma will result in -compile-time error. Usage with inheritance should be defined and documented. +a static error. Usage with inheritance should be defined and documented. Dynlib pragma for import @@ -8229,8 +8334,8 @@ Example: The parallel statement is the preferred mechanism to introduce parallelism in a Nim program. A subset of the Nim language is valid within a -``parallel`` section. This subset is checked to be free of data races at -compile time. A sophisticated `disjoint checker`:idx: ensures that no data +``parallel`` section. This subset is checked to be free of data races during +semantic analysis. A sophisticated `disjoint checker`:idx: ensures that no data races are possible even though shared memory is extensively supported! The subset is in fact the full language with the following @@ -8263,8 +8368,8 @@ pragmas: 1) A `guard`:idx: annotation is introduced to prevent data races. 2) Every access of a guarded memory location needs to happen in an appropriate `locks`:idx: statement. -3) Locks and routines can be annotated with `lock levels`:idx: to prevent - deadlocks at compile time. +3) Locks and routines can be annotated with `lock levels`:idx: to allow + potential deadlocks to be detected during static analysis. Guards and the locks section @@ -8296,9 +8401,9 @@ Top level accesses to ``gdata`` are always allowed so that it can be initialized conveniently. It is *assumed* (but not enforced) that every top level statement is executed before any concurrent action happens. -The ``locks`` section deliberately looks ugly because it has no runtime +The ``locks`` section deliberately looks ugly because it has no execution-time semantics and should not be used directly! It should only be used in templates -that also implement some form of locking at runtime: +that also implement some form of locking during execution: .. code-block:: nim template lock(a: TLock; body: untyped) = @@ -8384,9 +8489,10 @@ This means the following compiles (for now) even though it really should not: Lock levels ----------- -Lock levels are used to enforce a global locking order in order to prevent -deadlocks at compile-time. A lock level is an constant integer in the range -0..1_000. Lock level 0 means that no lock is acquired at all. +Lock levels are used to enforce a global locking order in order to detect +potential deadlocks during semantic analysis. A lock level is an constant +integer in the range 0..1_000. Lock level 0 means that no lock is acquired at +all. If a section of code holds a lock of level ``M`` than it can also acquire any lock of level ``N < M``. Another lock of level ``M`` cannot be acquired. Locks @@ -8416,7 +8522,7 @@ single ``locks`` section: Here is how a typical multilock statement can be implemented in Nim. Note how -the runtime check is required to ensure a global ordering for two locks ``a`` +the execution check is required to ensure a global ordering for two locks ``a`` and ``b`` of the same lock level: .. code-block:: nim From 03a628dddcb5676492cc2a5a34523b6176c97bd7 Mon Sep 17 00:00:00 2001 From: deansher Date: Tue, 29 Jan 2019 05:33:54 -0500 Subject: [PATCH 003/202] Solid edit pass on changes and successful rst2html; still need to review HTML output. --- doc/manual.rst | 121 ++++++++++++++++++++++++------------------------- 1 file changed, 60 insertions(+), 61 deletions(-) diff --git a/doc/manual.rst b/doc/manual.rst index 20aa320a7996..953f89e7ff47 100644 --- a/doc/manual.rst +++ b/doc/manual.rst @@ -76,21 +76,21 @@ the context. A Nim `program`:idx: consists of one or more text `source files`:idx: containing Nim code. It is processed by a Nim `compiler`:idx: into an `executable`:idx:. The nature of this executable depends on the compiler implementation; it may, -for example, be a native binary or JavaScript source code. A Nim program can -contain Nim code that will be executed at `compile time`:idx:, such as constant -expressions, macro definitions, and Nim procedures used by macro definitions. -Typically, the bulk of a Nim program's code is compiled into the executable and -executed at `runtime`:idx:. Only a subset of the Nim language is supported at -compile time, but it is a substantial subset. See `Restrictions on +for example, be a native binary or JavaScript source code. In addition to the +code that is compiled into the executable and executed at `runtime`:idx:, +a Nim program can contain Nim code that will be executed at `compile time`:idx:. +This can include constant expressions, macro definitions, and Nim procedures +used by macro definitions. Most of the Nim language is supported at +compile time, but there are some restrictions -- see `Restrictions on Compile-Time Execution `_ for details. The compiler parses Nim source code into an internal data structure called the `abstract syntax tree`:idx: (`AST`:idx). Then, before executing the code or processing it into the executable, it transforms the AST through `semantic -analysis`:idx:. This adds considerable semantic information to the AST, such as -expression types, identifier meanings, and in some cases expression values. An -error detected during semantic analysis is called a `static error`:idx:. Errors -mentioned in this manual are static errors when not otherwise specified. +analysis`:idx:. This adds semantic information such as expression types, +identifier meanings, and in some cases expression values. An error detected +during semantic analysis is called a `static error`:idx:. Errors described in +this manual are static errors when not otherwise specified. An error detected during code execution (whether at compile time or at runtime) is a `checked execution error`:idx:. The method for reporting such errors is via @@ -98,10 +98,10 @@ is a `checked execution error`:idx:. The method for reporting such errors is via provides a means to disable these `execution-time checks`:idx:. See the section pragmas_ for details. -Whether a checked execution error results in an exception or in a fatal error -is implementation specific. Thus the following program is invalid; even -though it purports to catch the `IndexError` from an out-of-bounds array access, -the compiler may instead choose to allow execution to die with a fatal error. +Whether a checked execution error results in an exception or in a fatal error is +implementation specific. Thus the following program is invalid; even though the +code purports to catch the `IndexError` from an out-of-bounds array access, the +compiler may instead choose to allow execution to die with a fatal error. .. code-block:: nim var a: array[0..1, char] @@ -119,8 +119,8 @@ language features are used and if no execution-time checks are disabled. A `constant expression`:idx: is an expression whose value can be computed during semantic analysis of the code in which it appears. It is never an l-value and never has side effects. Constant expressions are not limited to the capabilities -of semantic analysis, such as constant folding; they can use the substantial -subset of the Nim language that is supported at compile time. Since constant +of semantic analysis, such as constant folding; they can use all Nim language +features that are supported for compile-time execution. Since constant expressions can be used as an input to semantic analysis (such as for defining array bounds), this flexibility requires the compiler to interleave semantic analysis and compile-time code execution. @@ -713,30 +713,32 @@ A `constant`:idx: is a symbol that is bound to the value of a `constant expression`. This is an expression whose value can be computed during semantic analysis of the code in which it appears. However, constant expressions are not limited to the capabilities of semantic analysis; they -can use the substantial subset of the Nim language that is supported for +can use all Nim language features that are supported for compile-time execution. Compile-time execution is interleaved with semantic analysis as necessary. A constant's value cannot change after it is first computed. -A constant expression must be composed of the following elements: +Constant expressions can only depend on the following values and operations +that are either built into the language or available during compilation of +the constant expression: * literals +* built-in operators * previously declared constants and compile-time variables * previously declared macros and templates * previously declared procedures that have no side effects beyond possibly modifying compile-time variables -* operators representing such procedures -* code blocks that can internally use all Nim features supported at - compile time (the next section below), but that cannot - refer to any external values beyond those listed above -Constant expressions must adhere to the restrictions on compile-time -execution described in `Restrictions on -Compile-Time Execution `_. +A constant expression can contain code blocks that may internally use all Nim +features supported at compile time (as detailed in the next section below), +but that cannot refer to any external values beyond those listed above. -For example, the following code echoes the beginning of the Fibonacci -series **at compile time**. This is a demonstration of flexibility in -defining constants, not a recommended style for solving this problem! +The ability to access and modify compile-time variables adds flexibility to +constant expressions that may be surprising to those coming from other +statically typed languages. For example, the following code echoes the beginning +of the Fibonacci series **at compile time**. (This is a demonstration of +flexibility in defining constants, not a recommended style for solving this +problem!) .. code-block:: nim :test: "nim c $1" @@ -760,11 +762,9 @@ defining constants, not a recommended style for solving this problem! const display_fib = block: const f2 = next_fib() - var result = fmt"Fibonacci sequence: {f0}, {f1}, {f2}, " - for i in 0..10: - if i > 0: - add(result, ", ") - add(result, $next_fib()) + var result = fmt"Fibonacci sequence: {f0}, {f1}, {f2}" + for i in 3..12: + add(result, fmt", {next_fib()}") result static: @@ -779,8 +779,8 @@ language features: * methods * closure iterators -* ``cast`` -* ``ptr``s or ``ref``s +* the ``cast`` operator +* reference (pointer) types * the FFI Some or all of these restrictions are likely to be lifted over time. @@ -923,8 +923,8 @@ lowest and highest value of the type: to 5. ``PositiveFloat`` defines a subrange of all positive floating point values. NaN does not belong to any subrange of floating point types. Assigning any other value to a variable of type ``Subrange`` is a -checked execution error (or static error if it can be statically -determined). Assignments from the base type to one of its subrange types +checked execution error (or static error if it can be determined during +semantic analysis). Assignments from the base type to one of its subrange types (and vice versa) are allowed. A subrange type has the same size as its base type (``int`` in the @@ -1223,9 +1223,9 @@ tuples, objects and sets belong to the structured types. Array and sequence types ------------------------ -Arrays are a homogeneous type, meaning that each element in the array -has the same type. Arrays always have a fixed length which is known during -semantic analysis (except for open arrays). They can be indexed by any ordinal type. +Arrays are a homogeneous type, meaning that each element in the array has the +same type. Arrays always have a fixed length specified as a constant expression +(except for open arrays). They can be indexed by any ordinal type. A parameter ``A`` may be an *open array*, in which case it is indexed by integers from 0 to ``len(A)-1``. An array expression may be constructed by the array constructor ``[]``. The element type of this array expression is @@ -1430,10 +1430,10 @@ can also be defined with indentation instead of ``[]``: name: string # a person consists of a name age: natural # and an age -Objects provide many features that tuples do not. Object provide inheritance -and information hiding. Objects have access to their type during execution, so that -the ``of`` operator can be used to determine the object's type. The ``of`` operator -is similar to the ``instanceof`` operator in Java. +Objects provide many features that tuples do not. Object provide inheritance and +information hiding. Objects have access to their type during execution, so that +the ``of`` operator can be used to determine the object's type. The ``of`` +operator is similar to the ``instanceof`` operator in Java. .. code-block:: nim type @@ -1528,8 +1528,8 @@ In the example the ``kind`` field is called the `discriminator`:idx:\: For safety its address cannot be taken and assignments to it are restricted: The new value must not lead to a change of the active object branch. For an object branch switch ``system.reset`` has to be used. Also, when the fields of a -particular branch are specified during object construction, the correct value -for the discriminator must be available for semantic analysis. +particular branch are specified during object construction, the corresponding +discriminator value must be specified as a constant expression. Package level objects --------------------- @@ -2842,7 +2842,7 @@ For non ordinal types it is not possible to list every possible value and so these always require an ``else`` part. Because case statements are checked for exhaustiveness during semantic analysis, -the value in every ``of`` branch must be computable during analysis. +the value in every ``of`` branch must be a constant expression. This restriction also allows the compiler to generate more performant code. As a special semantic extension, an expression in an ``of`` branch of a case @@ -4207,8 +4207,8 @@ The exception tree is defined in the `system `_ module. Every exception inherits from ``system.Exception``. Exceptions that indicate programming bugs inherit from ``system.Defect`` (which is a subtype of ``Exception``) and are stricly speaking not catchable as they can also be mapped to an operation -that terminates the whole process. Exceptions that indicate any other execution error -that can be caught inherit from ``system.CatchableError`` +that terminates the whole process. Exceptions that indicate any other execution +error that can be caught inherit from ``system.CatchableError`` (which is a subtype of ``Exception``). @@ -4445,8 +4445,9 @@ a `type variable`:idx:. Is operator ----------- -The ``is`` operator checks for type equivalence during semantic analysis. It is -therefore very useful for type specialization within generic code: +The ``is`` operator is evaluated during semantic analysis to check for type +equivalence. It is therefore very useful for type specialization within generic +code: .. code-block:: nim type @@ -5741,8 +5742,7 @@ static[T] **Note**: static[T] is still in development. -As their name suggests, static parameters must be computable during -semantic analysis: +As their name suggests, static parameters must be constant expressions: .. code-block:: nim @@ -5754,8 +5754,7 @@ semantic analysis: # regex, stored in a global variable precompiledRegex(paramStr(1)) # Error, command-line options - # are not computable during - # semantic analysis + # are not constant expressions For the purposes of code generation, all static params are treated as @@ -5779,11 +5778,11 @@ Static params can also appear in the signatures of generic types: Please note that ``static T`` is just a syntactic convenience for the underlying generic type ``static[T]``. The type param can be omitted to obtain the type -class of all values computable during semantic analysis. A more specific type -class can be created by instantiating ``static`` with another type class. +class of all constant expressions. A more specific type class can be created by +instantiating ``static`` with another type class. -You can force the evaluation of a certain expression during semantic analysis by -coercing it to a corresponding ``static`` type: +You can force an expression to be evaluated at compile time as a constant +expression by coercing it to a corresponding ``static`` type: .. code-block:: nim import math @@ -6960,8 +6959,8 @@ structure: pure pragma ----------- -An object type can be marked with the ``pure`` pragma so that its type -field which is used for execution-time type identification is omitted. This used to be +An object type can be marked with the ``pure`` pragma so that its type field +which is used for execution-time type identification is omitted. This used to be necessary for binary compatibility with other compiled languages. An enum type can be marked as ``pure``. Then access of its fields always From 07a0a61875f4f5c7ac74acfd0c7dd4e4eb333dce Mon Sep 17 00:00:00 2001 From: Araq Date: Tue, 29 Jan 2019 14:31:43 +0100 Subject: [PATCH 004/202] fixes #9149 [backport] --- lib/system.nim | 10 ++++++---- tests/overload/tconverter_to_string.nim | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 tests/overload/tconverter_to_string.nim diff --git a/lib/system.nim b/lib/system.nim index 0241b92e08dd..4951961caccf 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -4307,15 +4307,17 @@ proc `==`*(x, y: cstring): bool {.magic: "EqCString", noSideEffect, when defined(nimNoNilSeqs2): when not compileOption("nilseqs"): when defined(nimHasUserErrors): - proc `==`*(x: string; y: type(nil)): bool {. + # bug #9149; ensure that 'type(nil)' does not match *too* well by using 'type(nil) | type(nil)'. + # Eventually (in 0.20?) we will be able to remove this hack completely. + proc `==`*(x: string; y: type(nil) | type(nil)): bool {. error: "'nil' is now invalid for 'string'; compile with --nilseqs:on for a migration period".} = discard - proc `==`*(x: type(nil); y: string): bool {. + proc `==`*(x: type(nil) | type(nil); y: string): bool {. error: "'nil' is now invalid for 'string'; compile with --nilseqs:on for a migration period".} = discard else: - proc `==`*(x: string; y: type(nil)): bool {.error.} = discard - proc `==`*(x: type(nil); y: string): bool {.error.} = discard + proc `==`*(x: string; y: type(nil) | type(nil)): bool {.error.} = discard + proc `==`*(x: type(nil) | type(nil); y: string): bool {.error.} = discard template closureScope*(body: untyped): untyped = ## Useful when creating a closure in a loop to capture local loop variables by diff --git a/tests/overload/tconverter_to_string.nim b/tests/overload/tconverter_to_string.nim new file mode 100644 index 000000000000..1960372d8ae9 --- /dev/null +++ b/tests/overload/tconverter_to_string.nim @@ -0,0 +1,22 @@ +discard """ + output: '''123 +c is not nil''' +""" + +# bug #9149 + +type + Container = ref object + data: int + +converter containerToString*(x: Container): string = $x.data + +var c = Container(data: 123) +var str = string c +echo str + +if c == nil: # this line can compile on v0.18, but not on 0.19 + echo "c is nil" + +if not c.isNil: + echo "c is not nil" From 15422a3e5a24d6c10d1f713cff7e04289bf7a232 Mon Sep 17 00:00:00 2001 From: Emery Hemingway Date: Tue, 29 Jan 2019 15:08:43 +0100 Subject: [PATCH 005/202] Genode fixes (#10491) Readline pasthru, add linker to config, do not pass -lm to linker. --- config/nim.cfg | 5 ++++- lib/impure/rdstdin.nim | 9 +++++++++ lib/pure/fenv.nim | 2 +- lib/pure/math.nim | 2 +- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/config/nim.cfg b/config/nim.cfg index 2a118c5cf3bb..c3e0823b6561 100644 --- a/config/nim.cfg +++ b/config/nim.cfg @@ -290,13 +290,16 @@ tcc.options.always = "-w" # Configuration for the Genode toolchain @if genode: + noCppExceptions # avoid std C++ + tlsEmulation:on # no TLS segment register magic gcc.path = "/usr/local/genode-gcc/bin" - gcc.cpp.options.always = "-D__GENODE__ -fno-stack-protector" @if i386 or amd64: gcc.exe = "genode-x86-gcc" gcc.cpp.exe = "genode-x86-g++" + gcc.cpp.linkerexe = "genode-x86-ld" @elif arm: gcc.exe = "genode-arm-gcc" gcc.cpp.exe = "genode-arm-g++" + gcc.cpp.linkerexe = "genode-arm-ld" @end @end diff --git a/lib/impure/rdstdin.nim b/lib/impure/rdstdin.nim index 54bab82f0bc4..ac38addbac9b 100644 --- a/lib/impure/rdstdin.nim +++ b/lib/impure/rdstdin.nim @@ -73,6 +73,15 @@ when defined(Windows): discard readConsoleInputW(hStdin, irInputRecord, 1, dwEventsRead) return result +elif defined(genode): + proc readLineFromStdin*(prompt: string): TaintedString {. + tags: [ReadIOEffect, WriteIOEffect].} = + stdin.readLine() + + proc readLineFromStdin*(prompt: string, line: var TaintedString): bool {. + tags: [ReadIOEffect, WriteIOEffect].} = + stdin.readLine(line) + else: import linenoise, termios diff --git a/lib/pure/fenv.nim b/lib/pure/fenv.nim index 0725973cadcb..ab47da08eab2 100644 --- a/lib/pure/fenv.nim +++ b/lib/pure/fenv.nim @@ -12,7 +12,7 @@ {.deadCodeElim: on.} # dce option deprecated -when defined(Posix): +when defined(Posix) and not defined(genode): {.passl: "-lm".} var diff --git a/lib/pure/math.nim b/lib/pure/math.nim index 460be1cd0e57..526ddbbb2e9c 100644 --- a/lib/pure/math.nim +++ b/lib/pure/math.nim @@ -95,7 +95,7 @@ proc fac*(n: int): int = {.push checks:off, line_dir:off, stack_trace:off.} -when defined(Posix): +when defined(Posix) and not defined(genode): {.passl: "-lm".} const From dee8e6e98ae868b8d933a718250c8e471bc125ea Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Tue, 29 Jan 2019 15:12:16 +0100 Subject: [PATCH 006/202] gc: destructors is beginning to work (#10483) * kochdocs.nim: code cleanup * docgen: nicer indentation * parser.nim: code cleanup * fixes #10458 * make tests green again * make =destroy mixins * gc:destructors: produced C code is almost working * --gc:destructors simple program compiles (but leaks memory) * gc:destructors make examples compile in C++ mode * destructors: string implementation bugfixes * strs.nim: minor code cleanup * destructors: builtin seqs are beginning to work * remove debugging helpers --- compiler/ast.nim | 1 - compiler/ccgexprs.nim | 6 ++-- compiler/cgen.nim | 6 ++++ compiler/destroyer.nim | 14 +++++++-- compiler/parser.nim | 48 +++++++++++++---------------- compiler/semasgn.nim | 5 +++ compiler/semstmts.nim | 3 +- compiler/semtypes.nim | 17 +++++----- compiler/semtypinst.nim | 37 ++++++++++++++-------- compiler/sigmatch.nim | 5 +++ lib/core/seqs.nim | 10 ++++-- lib/core/strs.nim | 17 +++++----- lib/system.nim | 36 +++++++++++----------- lib/system/excpt.nim | 21 +++++++------ lib/system/gc_regions.nim | 20 +++++++++--- lib/system/helpers2.nim | 4 +-- tests/misc/tinvalidarrayaccess.nim | 2 +- tests/misc/tinvalidarrayaccess2.nim | 2 +- tests/parser/tprecedence.nim | 9 ++++++ 19 files changed, 162 insertions(+), 101 deletions(-) diff --git a/compiler/ast.nim b/compiler/ast.nim index 24891d6d3ec1..fc470b7a8954 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -1352,7 +1352,6 @@ proc copySym*(s: PSym): PSym = result = newSym(s.kind, s.name, s.owner, s.info, s.options) #result.ast = nil # BUGFIX; was: s.ast which made problems result.typ = s.typ - result.id = getID() when debugIds: registerId(result) result.flags = s.flags result.magic = s.magic diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index ed62550040b6..5bcbcda1c2b3 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -460,7 +460,7 @@ proc binaryStmtAddr(p: BProc, e: PNode, d: var TLoc, frmt: string) = if d.k != locNone: internalError(p.config, e.info, "binaryStmtAddr") initLocExpr(p, e.sons[1], a) initLocExpr(p, e.sons[2], b) - lineCg(p, cpsStmts, frmt, addrLoc(p.config, a), rdLoc(b)) + lineCg(p, cpsStmts, frmt, byRefLoc(p, a), rdLoc(b)) proc unaryStmt(p: BProc, e: PNode, d: var TLoc, frmt: string) = var a: TLoc @@ -1028,7 +1028,7 @@ proc gcUsage(conf: ConfigRef; n: PNode) = proc strLoc(p: BProc; d: TLoc): Rope = if p.config.selectedGc == gcDestructors: - result = addrLoc(p.config, d) + result = byRefLoc(p, d) else: result = rdLoc(d) @@ -1110,7 +1110,7 @@ proc genStrAppend(p: BProc, e: PNode, d: var TLoc) = strLoc(p, dest), rdLoc(a))) if p.config.selectedGC == gcDestructors: linefmt(p, cpsStmts, "#prepareAdd($1, $2$3);$n", - addrLoc(p.config, dest), lens, rope(L)) + byRefLoc(p, dest), lens, rope(L)) else: initLoc(call, locCall, e, OnHeap) call.r = ropecg(p.module, "#resizeString($1, $2$3)", [rdLoc(dest), lens, rope(L)]) diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 2d9814621d95..d020b1bd7dde 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -264,6 +264,12 @@ proc addrLoc(conf: ConfigRef; a: TLoc): Rope = if lfIndirect notin a.flags and mapType(conf, a.t) != ctArray: result = "(&" & result & ")" +proc byRefLoc(p: BProc; a: TLoc): Rope = + result = a.r + if lfIndirect notin a.flags and mapType(p.config, a.t) != ctArray and not + p.module.compileToCpp: + result = "(&" & result & ")" + proc rdCharLoc(a: TLoc): Rope = # read a location that may need a char-cast: result = rdLoc(a) diff --git a/compiler/destroyer.nim b/compiler/destroyer.nim index e21d532ea985..22ace36349b4 100644 --- a/compiler/destroyer.nim +++ b/compiler/destroyer.nim @@ -244,7 +244,10 @@ proc patchHead(n: PNode) = proc patchHead(s: PSym) = if sfFromGeneric in s.flags: - patchHead(s.ast[bodyPos]) + # do not patch the builtin type bound operators for seqs: + let dest = s.typ.sons[1].skipTypes(abstractVar) + if dest.kind != tySequence: + patchHead(s.ast[bodyPos]) proc checkForErrorPragma(c: Con; t: PType; ri: PNode; opname: string) = var m = "'" & opname & "' is not available for type <" & typeToString(t) & ">" @@ -267,7 +270,8 @@ template genOp(opr, opname, ri) = globalError(c.graph.config, dest.info, "internal error: '" & opname & "' operator not found for type " & typeToString(t)) elif op.ast[genericParamsPos].kind != nkEmpty: - globalError(c.graph.config, dest.info, "internal error: '" & opname & "' operator is generic") + globalError(c.graph.config, dest.info, "internal error: '" & opname & + "' operator is generic") patchHead op if sfError in op.flags: checkForErrorPragma(c, t, ri, opname) let addrExp = newNodeIT(nkHiddenAddr, dest.info, makePtrType(c, dest.typ)) @@ -275,6 +279,12 @@ template genOp(opr, opname, ri) = result = newTree(nkCall, newSymNode(op), addrExp) proc genSink(c: Con; t: PType; dest, ri: PNode): PNode = + when false: + if t.kind != tyString: + echo "this one ", c.graph.config$dest.info, " for ", typeToString(t, preferDesc) + debug t.sink.typ.sons[2] + echo t.sink.id, " owner ", t.id + quit 1 let t = t.skipTypes({tyGenericInst, tyAlias, tySink}) genOp(if t.sink != nil: t.sink else: t.assignment, "=sink", ri) diff --git a/compiler/parser.nim b/compiler/parser.nim index c9626c527c2c..01a3ce4d001a 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -724,6 +724,14 @@ const tkTypeClasses = {tkRef, tkPtr, tkVar, tkStatic, tkType, tkEnum, tkTuple, tkObject, tkProc} +proc commandExpr(p: var TParser; r: PNode; mode: TPrimaryMode): PNode = + result = newNodeP(nkCommand, p) + addSon(result, r) + var isFirstParam = true + # progress NOT guaranteed + p.hasProgress = false + addSon result, commandParam(p, isFirstParam, mode) + proc primarySuffix(p: var TParser, r: PNode, baseIndent: int, mode: TPrimaryMode): PNode = #| primarySuffix = '(' (exprColonEqExpr comma?)* ')' doBlocks? @@ -734,8 +742,6 @@ proc primarySuffix(p: var TParser, r: PNode, #| | &( '`'|IDENT|literal|'cast'|'addr'|'type') expr # command syntax result = r - template somePar() = - if p.tok.strongSpaceA > 0: break # progress guaranteed while p.tok.indent < 0 or (p.tok.tokType == tkDot and p.tok.indent >= baseIndent): @@ -749,6 +755,8 @@ proc primarySuffix(p: var TParser, r: PNode, result = newNodeP(nkCommand, p) result.addSon r result.addSon primary(p, pmNormal) + else: + result = commandExpr(p, result, mode) break result = namedParams(p, result, nkCall, tkParRi) if result.len > 1 and result.sons[1].kind == nkExprColonExpr: @@ -759,39 +767,27 @@ proc primarySuffix(p: var TParser, r: PNode, result = parseGStrLit(p, result) of tkBracketLe: # progress guaranteed - somePar() + if p.tok.strongSpaceA > 0: + result = commandExpr(p, result, mode) + break result = namedParams(p, result, nkBracketExpr, tkBracketRi) of tkCurlyLe: # progress guaranteed - somePar() + if p.tok.strongSpaceA > 0: + result = commandExpr(p, result, mode) + break result = namedParams(p, result, nkCurlyExpr, tkCurlyRi) of tkSymbol, tkAccent, tkIntLit..tkCharLit, tkNil, tkCast, tkOpr, tkDotDot, tkTypeClasses - {tkRef, tkPtr}: - # XXX: In type sections we allow the free application of the - # command syntax, with the exception of expressions such as - # `foo ref` or `foo ptr`. Unfortunately, these two are also - # used as infix operators for the memory regions feature and - # the current parsing rules don't play well here. + # XXX: In type sections we allow the free application of the + # command syntax, with the exception of expressions such as + # `foo ref` or `foo ptr`. Unfortunately, these two are also + # used as infix operators for the memory regions feature and + # the current parsing rules don't play well here. if p.inPragma == 0 and (isUnary(p) or p.tok.tokType notin {tkOpr, tkDotDot}): # actually parsing {.push hints:off.} as {.push(hints:off).} is a sweet # solution, but pragmas.nim can't handle that - let a = result - result = newNodeP(nkCommand, p) - addSon(result, a) - var isFirstParam = true - when true: - # progress NOT guaranteed - p.hasProgress = false - addSon result, commandParam(p, isFirstParam, mode) - if not p.hasProgress: break - else: - while p.tok.tokType != tkEof: - let x = parseExpr(p) - addSon(result, x) - if p.tok.tokType != tkComma: break - getTok(p) - optInd(p, x) - result = postExprBlocks(p, result) + result = commandExpr(p, result, mode) break else: break diff --git a/compiler/semasgn.nim b/compiler/semasgn.nim index 9f1ef313b71e..41b0879e663e 100644 --- a/compiler/semasgn.nim +++ b/compiler/semasgn.nim @@ -316,6 +316,11 @@ proc liftBody(g: ModuleGraph; typ: PType; kind: TTypeAttachedOp; info: TLineInfo): PSym = if typ.kind == tyDistinct: return liftBodyDistinctType(g, typ, kind, info) + when false: + var typ = typ + if c.config.selectedGC == gcDestructors and typ.kind == tySequence: + # use the canonical type to access the =sink and =destroy etc. + typ = c.graph.sysTypes[tySequence] var a: TLiftCtx a.info = info diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 5e9d5d9c5389..f1778e816176 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -168,7 +168,7 @@ proc semIf(c: PContext, n: PNode; flags: TExprFlags): PNode = else: illFormedAst(it, c.config) if isEmptyType(typ) or typ.kind in {tyNil, tyExpr} or (not hasElse and efInTypeof notin flags): - for it in n: + for it in n: it.sons[^1] = discardCheck(c, it.sons[^1], flags) result.kind = nkIfStmt # propagate any enforced VoidContext: @@ -1563,6 +1563,7 @@ proc semOverride(c: PContext, s: PSym, n: PNode) = if obj.kind in {tyObject, tyDistinct, tySequence, tyString} and sameType(obj, objB): # attach these ops to the canonical tySequence obj = canonType(c, obj) + #echo "ATTACHING TO ", obj.id, " ", s.name.s, " ", cast[int](obj) let opr = if s.name.s == "=": addr(obj.assignment) else: addr(obj.sink) if opr[].isNil: opr[] = s diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index fbf36383472f..7447463238fb 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -1159,7 +1159,7 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, # compiler only checks for 'nil': if skipTypes(r, {tyGenericInst, tyAlias, tySink}).kind != tyVoid: if kind notin {skMacro, skTemplate} and r.kind in {tyStmt, tyExpr}: - localError(c.config, n.sons[0].info, "return type '" & typeToString(r) & + localError(c.config, n.sons[0].info, "return type '" & typeToString(r) & "' is only valid for macros and templates") # 'auto' as a return type does not imply a generic: elif r.kind == tyAnything: @@ -1577,11 +1577,16 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = assert s != nil assert prev == nil result = copyType(s, s.owner, keepId=false) - # XXX figure out why this has children already... + # Remove the 'T' parameter from tySequence: result.sons.setLen 0 result.n = nil result.flags = {tfHasAsgn} semContainerArg(c, n, "seq", result) + if result.len > 0: + var base = result[0] + if base.kind in {tyGenericInst, tyAlias, tySink}: base = lastSon(base) + if base.kind != tyGenericParam: + c.typesWithOps.add((result, result)) else: result = semContainer(c, n, tySequence, "seq", prev) if c.config.selectedGc == gcDestructors: @@ -1714,11 +1719,9 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = result = newOrPrevType(tyError, prev, c) n.typ = result dec c.inTypeContext - if c.inTypeContext == 0: instAllTypeBoundOp(c, n.info) - -when false: - proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = - result = semTypeNodeInner(c, n, prev) + if c.inTypeContext == 0: + #if $n == "var seq[StackTraceEntry]": + # echo "begin ", n instAllTypeBoundOp(c, n.info) proc setMagicType(conf: ConfigRef; m: PSym, kind: TTypeKind, size: int) = diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index 027ffd4aa857..ebe822cdfca3 100644 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -297,12 +297,6 @@ proc instCopyType*(cl: var TReplTypeVars, t: PType): PType = #result.destructor = nil result.sink = nil -template typeBound(c, newty, oldty, field, info) = - let opr = newty.field - if opr != nil and sfFromGeneric notin opr.flags: - # '=' needs to be instantiated for generics when the type is constructed: - newty.field = c.instTypeBoundOp(c, opr, oldty, info, attachedAsgn, 1) - proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType = # tyGenericInvocation[A, tyGenericInvocation[A, B]] # is difficult to handle: @@ -317,7 +311,10 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType = else: result = searchInstTypes(t) - if result != nil and eqFlags*result.flags == eqFlags*t.flags: return + if result != nil and eqFlags*result.flags == eqFlags*t.flags: + when defined(reportCacheHits): + echo "Generic instantiation cached ", typeToString(result), " for ", typeToString(t) + return for i in countup(1, sonsLen(t) - 1): var x = t.sons[i] if x.kind in {tyGenericParam}: @@ -332,7 +329,11 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType = if header != t: # search again after first pass: result = searchInstTypes(header) - if result != nil and eqFlags*result.flags == eqFlags*t.flags: return + if result != nil and eqFlags*result.flags == eqFlags*t.flags: + when defined(reportCacheHits): + echo "Generic instantiation cached ", typeToString(result), " for ", + typeToString(t), " header ", typeToString(header) + return else: header = instCopyType(cl, t) @@ -384,7 +385,7 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType = rawAddSon(result, newbody) checkPartialConstructedType(cl.c.config, cl.info, newbody) let dc = newbody.deepCopy - if cl.allowMetaTypes == false: + if not cl.allowMetaTypes: if dc != nil and sfFromGeneric notin newbody.deepCopy.flags: # 'deepCopy' needs to be instantiated for # generics *when the type is constructed*: @@ -402,6 +403,11 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType = discard else: newbody.lastSon.typeInst = result + # DESTROY: adding object|opt for opt[topttree.Tree] + # sigmatch: Formal opt[=destroy.T] real opt[topttree.Tree] + # adding myseq for myseq[system.int] + # sigmatch: Formal myseq[=destroy.T] real myseq[system.int] + #echo "DESTROY: adding ", typeToString(newbody), " for ", typeToString(result, preferDesc) cl.c.typesWithOps.add((newbody, result)) let mm = skipTypes(bbody, abstractPtrs) if tfFromGeneric notin mm.flags: @@ -432,7 +438,7 @@ proc eraseVoidParams*(t: PType) = inc pos setLen t.sons, pos setLen t.n.sons, pos - return + break proc skipIntLiteralParams*(t: PType) = for i in 0 ..< t.sonsLen: @@ -561,9 +567,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType = for i in countup(0, sonsLen(result) - 1): if result.sons[i] != nil: if result.sons[i].kind == tyGenericBody: - localError( - cl.c.config, - t.sym.info, + localError(cl.c.config, t.sym.info, "cannot instantiate '" & typeToString(result.sons[i], preferDesc) & "' inside of type definition: '" & @@ -603,6 +607,13 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType = result.size = -1 result.n = replaceObjBranches(cl, result.n) +template typeBound(c, newty, oldty, field, info) = + let opr = newty.field + if opr != nil and sfFromGeneric notin opr.flags: + # '=' needs to be instantiated for generics when the type is constructed: + #echo "DESTROY: instantiating ", astToStr(field), " for ", typeToString(oldty) + newty.field = c.instTypeBoundOp(c, opr, oldty, info, attachedAsgn, 1) + proc instAllTypeBoundOp*(c: PContext, info: TLineInfo) = var i = 0 while i < c.typesWithOps.len: diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index fa4ab3703f12..3eaac06e5105 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -2505,6 +2505,11 @@ proc instTypeBoundOp*(c: PContext; dc: PSym; t: PType; info: TLineInfo; if f.kind in {tyRef, tyPtr}: f = f.lastSon else: if f.kind == tyVar: f = f.lastSon + #if c.config.selectedGC == gcDestructors and f.kind == tySequence: + # use the canonical type to access the =sink and =destroy etc. + # f = c.graph.sysTypes[tySequence] + #echo "YUP_---------Formal ", typeToString(f, preferDesc), " real ", typeToString(t, preferDesc), " ", f.id, " ", t.id + if typeRel(m, f, t) == isNone: localError(c.config, info, "cannot instantiate: '" & dc.name.s & "'") else: diff --git a/lib/core/seqs.nim b/lib/core/seqs.nim index 977b23b26a6c..1a81b89ea0b0 100644 --- a/lib/core/seqs.nim +++ b/lib/core/seqs.nim @@ -15,7 +15,7 @@ proc supportsCopyMem(t: typedesc): bool {.magic: "TypeTrait".} ## Default seq implementation used by Nim's core. type - NimSeqPayload {.core.}[T] = object + NimSeqPayload[T] = object cap: int region: Allocator data: UncheckedArray[T] @@ -40,6 +40,7 @@ proc `=destroy`[T](s: var seq[T]) = var x = cast[ptr NimSeqV2[T]](addr s) var p = x.p if p != nil: + mixin `=destroy` when not supportsCopyMem(T): for i in 0.. 0: # also copy the \0 terminator: copyMem(unsafeAddr dest.p.data[dest.len], unsafeAddr src.p.data[0], src.len+1) + inc dest.len, src.len proc appendChar(dest: var NimStringV2; c: char) {.compilerproc, inline.} = dest.p.data[dest.len] = c @@ -166,7 +164,6 @@ proc mnewString(len: int): NimStringV2 {.compilerProc.} = proc setLengthStrV2(s: var NimStringV2, newLen: int) {.compilerRtl.} = if newLen > s.len: prepareAdd(s, newLen - s.len) - else: - s.len = newLen - # this also only works because the destructor - # looks at s.p and not s.len + s.len = newLen + # this also only works because the destructor + # looks at s.p and not s.len diff --git a/lib/system.nim b/lib/system.nim index 4951961caccf..a7cf251f646f 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -3049,6 +3049,19 @@ else: if x < 0: -x else: x {.pop.} +when defined(nimNewRoof): + iterator `..<`*[T](a, b: T): T = + var i = T(a) + while i < b: + yield i + inc i +else: + iterator `..<`*[S, T](a: S, b: T): T = + var i = T(a) + while i < b: + yield i + inc i + when not defined(JS): proc likelyProc(val: bool): bool {.importc: "likely", nodecl, nosideeffect.} proc unlikelyProc(val: bool): bool {.importc: "unlikely", nodecl, nosideeffect.} @@ -3144,7 +3157,7 @@ when not defined(JS): #and not defined(nimscript): # ----------------- IO Part ------------------------------------------------ type CFile {.importc: "FILE", header: "", - final, incompletestruct.} = object + incompletestruct.} = object File* = ptr CFile ## The type representing a file handle. FileMode* = enum ## The file mode when opening a file. @@ -3392,6 +3405,10 @@ when not defined(JS): #and not defined(nimscript): ## returns the OS file handle of the file ``f``. This is only useful for ## platform specific programming. + when defined(gcDestructors) and not defined(nimscript): + include "core/strs" + include "core/seqs" + when declared(newSeq): proc cstringArrayToSeq*(a: cstringArray, len: Natural): seq[string] = ## converts a ``cstringArray`` to a ``seq[string]``. `a` is supposed to be @@ -3483,10 +3500,6 @@ when not defined(JS): #and not defined(nimscript): when defined(memtracker): include "system/memtracker" - when defined(gcDestructors): - include "core/strs" - include "core/seqs" - when hostOS == "standalone": include "system/embedded" else: @@ -3716,19 +3729,6 @@ template `..<`*(a, b: untyped): untyped = ## a shortcut for 'a .. (when b is BackwardsIndex: succ(b) else: pred(b))'. a .. (when b is BackwardsIndex: succ(b) else: pred(b)) -when defined(nimNewRoof): - iterator `..<`*[T](a, b: T): T = - var i = T(a) - while i < b: - yield i - inc i -else: - iterator `..<`*[S, T](a: S, b: T): T = - var i = T(a) - while i < b: - yield i - inc i - template spliceImpl(s, a, L, b: untyped): untyped = # make room for additional elements or cut: var shift = b.len - max(0,L) # ignore negative slice size diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim index cc0c1f54bd43..f2f82c3b8d91 100644 --- a/lib/system/excpt.nim +++ b/lib/system/excpt.nim @@ -220,11 +220,12 @@ proc auxWriteStackTrace(f: PFrame; s: var seq[StackTraceEntry]) = inc(i) it = it.prev var last = i-1 - if s.len == 0: - s = newSeq[StackTraceEntry](i) - else: - last = s.len + i - 1 - s.setLen(last+1) + when true: # not defined(gcDestructors): + if s.len == 0: + s = newSeq[StackTraceEntry](i) + else: + last = s.len + i - 1 + s.setLen(last+1) it = f while it != nil: s[last] = StackTraceEntry(procname: it.procname, @@ -440,11 +441,13 @@ proc getStackTrace(e: ref Exception): string = else: result = "" -when not defined(gcDestructors): - proc getStackTraceEntries*(e: ref Exception): seq[StackTraceEntry] = - ## Returns the attached stack trace to the exception ``e`` as - ## a ``seq``. This is not yet available for the JS backend. +proc getStackTraceEntries*(e: ref Exception): seq[StackTraceEntry] = + ## Returns the attached stack trace to the exception ``e`` as + ## a ``seq``. This is not yet available for the JS backend. + when not defined(gcDestructors): shallowCopy(result, e.trace) + else: + result = move(e.trace) const nimCallDepthLimit {.intdefine.} = 2000 diff --git a/lib/system/gc_regions.nim b/lib/system/gc_regions.nim index 59f68918fd4a..797eeeebf5b5 100644 --- a/lib/system/gc_regions.nim +++ b/lib/system/gc_regions.nim @@ -195,6 +195,19 @@ proc runFinalizers(c: Chunk) = (cast[Finalizer](it.typ.finalizer))(it+!sizeof(ObjHeader)) it = it.nextFinal +proc runFinalizers(c: Chunk; newbump: pointer) = + var it = c.head + var prev: ptr ObjHeader = nil + while it != nil: + let nxt = it.nextFinal + if it >= newbump: + if it.typ != nil and it.typ.finalizer != nil: + (cast[Finalizer](it.typ.finalizer))(it+!sizeof(ObjHeader)) + elif prev != nil: + prev.nextFinal = nil + prev = it + it = nxt + proc dealloc(r: var MemRegion; p: pointer; size: int) = let it = cast[ptr ObjHeader](p-!sizeof(ObjHeader)) if it.typ != nil and it.typ.finalizer != nil: @@ -237,16 +250,15 @@ template computeRemaining(r): untyped = proc setObstackPtr*(r: var MemRegion; sp: StackPtr) = # free everything after 'sp': - if sp.current.next != nil: + if sp.current != nil and sp.current.next != nil: deallocAll(r, sp.current.next) sp.current.next = nil when false: # better leak this memory than be sorry: for i in 0..high(r.freeLists): r.freeLists[i] = nil r.holes = nil - #else: - # deallocAll(r, r.head) - # r.head = nil + if r.tail != nil: runFinalizers(r.tail, sp.bump) + r.bump = sp.bump r.tail = sp.current r.remaining = sp.remaining diff --git a/lib/system/helpers2.nim b/lib/system/helpers2.nim index c67a2c2785ca..8bd69ad71199 100644 --- a/lib/system/helpers2.nim +++ b/lib/system/helpers2.nim @@ -1,7 +1,7 @@ # imported by other modules, unlike helpers.nim which is included template formatErrorIndexBound*[T](i, a, b: T): string = - "index out of bounds: (a:" & $a & ") <= (i:" & $i & ") <= (b:" & $b & ") " + "index out of bounds: (a: " & $a & ") <= (i: " & $i & ") <= (b: " & $b & ") " template formatErrorIndexBound*[T](i, n: T): string = - "index out of bounds: (i:" & $i & ") <= (n:" & $n & ") " + "index out of bounds: (i: " & $i & ") <= (n: " & $n & ") " diff --git a/tests/misc/tinvalidarrayaccess.nim b/tests/misc/tinvalidarrayaccess.nim index 57ad38b85e67..ab44d98e889b 100644 --- a/tests/misc/tinvalidarrayaccess.nim +++ b/tests/misc/tinvalidarrayaccess.nim @@ -1,5 +1,5 @@ discard """ - errormsg: "index out of bounds: (a:0) <= (i:2) <= (b:1) " + errormsg: "index out of bounds: (a: 0) <= (i: 2) <= (b: 1) " line: 18 """ diff --git a/tests/misc/tinvalidarrayaccess2.nim b/tests/misc/tinvalidarrayaccess2.nim index 86d34945773c..a791dc4e7ff9 100644 --- a/tests/misc/tinvalidarrayaccess2.nim +++ b/tests/misc/tinvalidarrayaccess2.nim @@ -1,5 +1,5 @@ discard """ - errormsg: "index out of bounds: (a:0) <= (i:3) <= (b:1) " + errormsg: "index out of bounds: (a: 0) <= (i: 3) <= (b: 1) " line: 9 """ diff --git a/tests/parser/tprecedence.nim b/tests/parser/tprecedence.nim index aff7c6aca483..3e1c03dd1815 100644 --- a/tests/parser/tprecedence.nim +++ b/tests/parser/tprecedence.nim @@ -40,3 +40,12 @@ proc getX(x: MyObject): lent MyField {.inline.} = let a = MyObject() echo a.getX.b.len + + +# bug #10458 +template t(x: untyped): untyped = "x" + +let + aaa = t 2 + 4 + ccc = t (1, 1) + 6 + ddd = t [0, 1, 2] + 5 From b2a5195e92f8852a47ca70136b103bc374b73393 Mon Sep 17 00:00:00 2001 From: c-blake Date: Tue, 29 Jan 2019 15:25:08 -0500 Subject: [PATCH 007/202] Add a standard Rusage type definition and wait4, getrusage declarations (#10484) --- changelog.md | 2 ++ lib/posix/posix.nim | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/changelog.md b/changelog.md index 85e06112bb7b..9382a194f98d 100644 --- a/changelog.md +++ b/changelog.md @@ -124,6 +124,8 @@ proc enumToString*(enums: openArray[enum]): string = - Added `xmltree.toXmlAttributes`. +- Added `Rusage`, `getrusage`, `wait4` to posix interface. + ### Library changes diff --git a/lib/posix/posix.nim b/lib/posix/posix.nim index 175f6a61dd1c..800188b8fc81 100644 --- a/lib/posix/posix.nim +++ b/lib/posix/posix.nim @@ -663,6 +663,26 @@ proc waitid*(a1: cint, a2: Id, a3: var SigInfo, a4: cint): cint {. proc waitpid*(a1: Pid, a2: var cint, a3: cint): Pid {. importc, header: "".} +type Rusage* {.importc: "struct rusage", header: "", + bycopy.} = object + ru_utime*, ru_stime*: Timeval # User and system time + ru_maxrss*, ru_ixrss*, ru_idrss*, ru_isrss*, # memory sizes + ru_minflt*, ru_majflt*, ru_nswap*, # paging activity + ru_inblock*, ru_oublock*, ru_msgsnd*, ru_msgrcv*, # IO activity + ru_nsignals*, ru_nvcsw*, ru_nivcsw*: clong # switching activity + +proc wait4*(pid: Pid, status: ptr cint, options: cint, rusage: ptr Rusage): Pid + {.importc, header: "".} + +const + RUSAGE_SELF* = cint(0) + RUSAGE_CHILDREN* = cint(-1) + RUSAGE_THREAD* = cint(1) # This one is less std; Linux, BSD agree though. + +# This can only fail if `who` is invalid or `rusage` ptr is invalid. +proc getrusage*(who: cint, rusage: ptr Rusage): cint + {.importc, header: "", discardable.} + proc bsd_signal*(a1: cint, a2: proc (x: pointer) {.noconv.}) {. importc, header: "".} proc kill*(a1: Pid, a2: cint): cint {.importc, header: "".} From 7295468516efedb18fcd28ee34e2c7830395dfe9 Mon Sep 17 00:00:00 2001 From: deansher Date: Tue, 29 Jan 2019 19:43:01 -0500 Subject: [PATCH 008/202] Finished this terminology update. --- doc/manual.rst | 88 +++++++++++++++++++++++++++----------------------- 1 file changed, 48 insertions(+), 40 deletions(-) diff --git a/doc/manual.rst b/doc/manual.rst index 953f89e7ff47..b9d5499b22dc 100644 --- a/doc/manual.rst +++ b/doc/manual.rst @@ -46,8 +46,8 @@ and ``a ^* b`` is short for ``(a (b a)*)?``. Example:: arrayConstructor = '[' expr ^* ',' ']' -Other parts of Nim - like scoping rules or execution semantics are only -described in the, more easily comprehensible, informal manner for now. +Other parts of Nim, like scoping rules or execution semantics, are +described informally. @@ -76,17 +76,19 @@ the context. A Nim `program`:idx: consists of one or more text `source files`:idx: containing Nim code. It is processed by a Nim `compiler`:idx: into an `executable`:idx:. The nature of this executable depends on the compiler implementation; it may, -for example, be a native binary or JavaScript source code. In addition to the -code that is compiled into the executable and executed at `runtime`:idx:, -a Nim program can contain Nim code that will be executed at `compile time`:idx:. -This can include constant expressions, macro definitions, and Nim procedures -used by macro definitions. Most of the Nim language is supported at -compile time, but there are some restrictions -- see `Restrictions on -Compile-Time Execution `_ for details. +for example, be a native binary or JavaScript source code. + +In a typical Nim program, most of the code is compiled into the executable for +execution at `runtime`:idx:. However, some of the code may be executed at +`compile time`:idx:. This can include constant expressions, macro definitions, +and Nim procedures used by macro definitions. Most of the Nim language is +supported at compile time, but there are some restrictions -- see `Restrictions +on Compile-Time Execution <#restrictions-on-compileminustime-execution>`_ for +details. The compiler parses Nim source code into an internal data structure called the -`abstract syntax tree`:idx: (`AST`:idx). Then, before executing the code or -processing it into the executable, it transforms the AST through `semantic +`abstract syntax tree`:idx: (`AST`:idx:). Then, before executing the code or +compiling it into the executable, it transforms the AST through `semantic analysis`:idx:. This adds semantic information such as expression types, identifier meanings, and in some cases expression values. An error detected during semantic analysis is called a `static error`:idx:. Errors described in @@ -128,11 +130,9 @@ analysis and compile-time code execution. It is mostly accurate to picture semantic analysis proceeding top to bottom and left to right in the source code, with compile-time code execution interleaved when necessary to compute values that are required for subsequent semantic -analysis. We saw just above that this interleaving is necessary for handling -constant expressions. We will see much later in this document that macro -invocation not only requires this interleaving, but also creates a situation -where semantic analyis does not entirely proceed top to bottom and left to -right. +analysis. We will see much later in this document that macro invocation not only +requires this interleaving, but also creates a situation where semantic analyis +does not entirely proceed top to bottom and left to right. Lexical Analysis @@ -709,8 +709,8 @@ Rationale: Consistency with overloaded assignment or assignment-like operations, Constants and Constant Expressions ================================== -A `constant`:idx: is a symbol that is bound to the value of a `constant -expression`. This is an expression whose value can be computed during +A `constant`:idx: is a symbol that is bound to the value of a constant +expression. This is an expression whose value can be computed during semantic analysis of the code in which it appears. However, constant expressions are not limited to the capabilities of semantic analysis; they can use all Nim language features that are supported for @@ -718,9 +718,9 @@ compile-time execution. Compile-time execution is interleaved with semantic analysis as necessary. A constant's value cannot change after it is first computed. -Constant expressions can only depend on the following values and operations -that are either built into the language or available during compilation of -the constant expression: +Constant expressions are restricted to depend only on the following categories +of values and operations, because these are either built into the language or +declared and evaluated before semantic analysis of the constant expression: * literals * built-in operators @@ -730,8 +730,12 @@ the constant expression: possibly modifying compile-time variables A constant expression can contain code blocks that may internally use all Nim -features supported at compile time (as detailed in the next section below), -but that cannot refer to any external values beyond those listed above. +features supported at compile time (as detailed in the next section below). +Within such a code block, it is possible to declare variables and then later +read and update them, or declare variables and pass them to procedures that +modify them. However, the code in such a block must still adhere to the +retrictions listed above for referencing values and operations outside the +block. The ability to access and modify compile-time variables adds flexibility to constant expressions that may be surprising to those coming from other @@ -774,7 +778,7 @@ problem!) Restrictions on Compile-Time Execution ====================================== -Nim code that will be executed at compile-time cannot use the following +Nim code that will be executed at compile time cannot use the following language features: * methods @@ -910,8 +914,8 @@ For further details, see `Convertible relation Subrange types -------------- A subrange type is a range of values from an ordinal or floating point type (the base -type). To define a subrange type, one must specify it's limiting values: the -lowest and highest value of the type: +type). To define a subrange type, one must specify its limiting values -- the +lowest and highest value of the type. For example: .. code-block:: nim type @@ -1543,8 +1547,8 @@ contexts (``var/ref/ptr IncompleteObject``) in general since the compiler does not yet know the size of the object. To complete an incomplete object the ``package`` pragma has to be used. ``package`` implies ``byref``. -As long as a type ``T`` is incomplete ``sizeof(T)`` or "execution-time type -information" for ``T`` is not available. +As long as a type ``T`` is incomplete, neither ``sizeof(T)`` nor execution-time +type information for ``T`` is available. Example: @@ -2363,6 +2367,10 @@ In a call ``p(args)`` the routine ``p`` that matches best is selected. If multiple routines match equally well, the ambiguity is reported during semantic analysis. +Every arg in args needs to match. There are multiple different categories how an +argument can match. Let ``f`` be the formal parameter's type and ``a`` the type +of the argument. + 1. Exact match: ``a`` and ``f`` are of the same type. 2. Literal match: ``a`` is an integer literal of value ``v`` and ``f`` is a signed or unsigned integer type and ``v`` is in ``f``'s @@ -2764,7 +2772,7 @@ Even some code that has side effects is permitted in a static block: There are limitations on what Nim code can be executed at compile time; see `Restrictions on Compile-Time Execution -<#restrictions-on-compile-time-execution>`_ for details. +<#restrictions-on-compileminustime-execution>`_ for details. It's a static error if the compiler cannot execute the block at compile time. @@ -3767,7 +3775,7 @@ Invocation of a multi-method cannot be ambiguous: collide 2 is preferred over collide 1 because the resolution works from left to right. In the example ``Unit, Thing`` is preferred over ``Thing, Unit``. -**Note**: Compile time execution is not (yet) supported for methods. +**Note**: Compile-time execution is not (yet) supported for methods. Inhibit dynamic method resolution via procCall @@ -5443,7 +5451,7 @@ chance to convert it into a sequence. Macros ====== -A macro is a special function that is executed at compile-time. +A macro is a special function that is executed at compile time. Normally the input for a macro is an abstract syntax tree (AST) of the code that is passed to it. The macro can then do transformations on it and return the transformed AST. The @@ -5452,8 +5460,8 @@ invocation would have been replaced by its result in the source code. This can be used to implement `domain specific languages`:idx:. -Macro invocation leads to a case where semantic analyis does **not** entirely -proceed top to bottom and left to right. The compiler must +Macro invocation is a case where semantic analyis does **not** entirely proceed +top to bottom and left to right. The compiler must * perform semantic analysis through the end of the macro invocation, * execute the macro body, @@ -6869,7 +6877,7 @@ pragma block can be used: compileTime pragma ------------------ The ``compileTime`` pragma is used to mark a proc or variable to be used only -during compile-time execution. No code will be generated for it. Compile time +during compile-time execution. No code will be generated for it. Compile-time procs are useful as helpers for macros. Since version 0.12.0 of the language, a proc that uses ``system.NimNode`` within its parameter types is implicitly declared ``compileTime``: @@ -8331,11 +8339,11 @@ Example: echo formatFloat(pi(5000)) -The parallel statement is the preferred mechanism to introduce parallelism -in a Nim program. A subset of the Nim language is valid within a -``parallel`` section. This subset is checked to be free of data races during -semantic analysis. A sophisticated `disjoint checker`:idx: ensures that no data -races are possible even though shared memory is extensively supported! +The parallel statement is the preferred mechanism to introduce parallelism in a +Nim program. A subset of the Nim language is valid within a ``parallel`` +section. This subset is checked during semantic analysis to be free of data +races. A sophisticated `disjoint checker`:idx: ensures that no data races are +possible even though shared memory is extensively supported! The subset is in fact the full language with the following restrictions / changes: @@ -8368,7 +8376,7 @@ pragmas: 2) Every access of a guarded memory location needs to happen in an appropriate `locks`:idx: statement. 3) Locks and routines can be annotated with `lock levels`:idx: to allow - potential deadlocks to be detected during static analysis. + potential deadlocks to be detected during semantic analysis. Guards and the locks section From e98bd3acebb21b14894e7f12c0b7f9e79a3f4c9c Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Wed, 30 Jan 2019 00:26:33 -0800 Subject: [PATCH 009/202] fix #10488 GC memory leak regression (#10498) * fix #10488 GC memory leak regression * re-enable gch.stack.bottom.repr but only inside when defined(logGC) --- lib/system/gc.nim | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/system/gc.nim b/lib/system/gc.nim index 416827f21f3d..018197c1eb98 100644 --- a/lib/system/gc.nim +++ b/lib/system/gc.nim @@ -864,7 +864,10 @@ when not defined(useNimRtl): for stack in items(gch.stack): result.add "[GC] stack " & stack.bottom.repr & "[GC] max stack size " & cast[pointer](stack.maxStackSize).repr & "\n" else: - result.add "[GC] stack bottom: " & gch.stack.bottom.repr + # this caused memory leaks, see #10488 ; find a way without `repr` + # maybe using a local copy of strutils.toHex or snprintf + when defined(logGC): + result.add "[GC] stack bottom: " & gch.stack.bottom.repr result.add "[GC] max stack size: " & $gch.stat.maxStackSize & "\n" {.pop.} # profiler: off, stackTrace: off From 9031c8fb0faeae35748fa311c22d7b0ee817d356 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Tue, 29 Jan 2019 11:51:04 +0100 Subject: [PATCH 010/202] Make sure the test snippet directory exists --- compiler/docgen.nim | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/compiler/docgen.nim b/compiler/docgen.nim index a7f7d77b5c7f..90b6d0426fbd 100644 --- a/compiler/docgen.nim +++ b/compiler/docgen.nim @@ -149,14 +149,16 @@ proc newDocumentor*(filename: AbsoluteFile; cache: IdentCache; conf: ConfigRef, if filename.len == 0: inc(d.id) let nameOnly = splitFile(d.filename).name - let subdir = getNimcacheDir(conf) / RelativeDir(nameOnly) - createDir(subdir) - outp = subdir / RelativeFile(nameOnly & "_snippet_" & $d.id & ".nim") + outp = getNimcacheDir(conf) / RelativeDir(nameOnly) / + RelativeFile(nameOnly & "_snippet_" & $d.id & ".nim") elif isAbsolute(filename): - outp = AbsoluteFile filename + outp = AbsoluteFile(filename) else: # Nim's convention: every path is relative to the file it was written in: - outp = splitFile(d.filename).dir.AbsoluteDir / RelativeFile(filename) + let nameOnly = splitFile(d.filename).name + outp = AbsoluteDir(nameOnly) / RelativeFile(filename) + # Make sure the destination directory exists + createDir(outp.splitFile.dir) # Include the current file if we're parsing a nim file let importStmt = if d.isPureRst: "" else: "import \"$1\"\n" % [d.filename] writeFile(outp, importStmt & content) From 49710b9d14291389529cdc627c21a25a99ff238d Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Tue, 29 Jan 2019 11:52:19 +0100 Subject: [PATCH 011/202] The `file` directive is relative to the file it is in --- compiler/docgen.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/docgen.nim b/compiler/docgen.nim index 90b6d0426fbd..b70561a1d0b5 100644 --- a/compiler/docgen.nim +++ b/compiler/docgen.nim @@ -246,7 +246,7 @@ proc genComment(d: PDoc, n: PNode): string = result = "" var dummyHasToc: bool if n.comment.len > 0: - renderRstToOut(d[], parseRst(n.comment, toFilename(d.conf, n.info), + renderRstToOut(d[], parseRst(n.comment, toFullPath(d.conf, n.info), toLinenumber(n.info), toColumn(n.info), dummyHasToc, d.options, d.conf), result) From fa7b75782361fb31e74117a7e84ebe8fcda922c0 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Tue, 29 Jan 2019 13:04:21 +0100 Subject: [PATCH 012/202] Remove unused variable --- lib/system.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/system.nim b/lib/system.nim index a7cf251f646f..b9be52308db0 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -4407,7 +4407,7 @@ template doAssertRaises*(exception: typedesc, code: untyped): typed = wrong = true except exception: discard - except Exception as exc: + except Exception: raiseAssert(astToStr(exception) & " wasn't raised, another error was raised instead by:\n"& astToStr(code)) From 7118e1ca01e1892208836411b3e4801f06b028c3 Mon Sep 17 00:00:00 2001 From: Mandeep Singh Date: Wed, 30 Jan 2019 10:35:45 -0500 Subject: [PATCH 013/202] Example for json.pretty (#10466) --- lib/pure/json.nim | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/lib/pure/json.nim b/lib/pure/json.nim index ffb8f4f351e6..4a3d5b43230e 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -130,9 +130,9 @@ ## { "name": "Susan", "age": herAge } ## ] ## -## var j2 = %* {"name": "Isaac", "books": ["Robot Dreams"]} -## j2["details"] = %* {"age":35, "pi":3.1415} -## echo j2 +## var j2 = %* {"name": "Isaac", "books": ["Robot Dreams"]} +## j2["details"] = %* {"age":35, "pi":3.1415} +## echo j2 runnableExamples: ## Note: for JObject, key ordering is preserved, unlike in some languages, @@ -708,6 +708,25 @@ proc toPretty(result: var string, node: JsonNode, indent = 2, ml = true, proc pretty*(node: JsonNode, indent = 2): string = ## Returns a JSON Representation of `node`, with indentation and ## on multiple lines. + ## + ## Similar to prettyprint in Python. + ## + ## **Examples:** + ## + ## .. code-block:: Nim + ## let j = %* {"name": "Isaac", "books": ["Robot Dreams"], + ## "details": {"age":35, "pi":3.1415}} + ## echo pretty(j) + ## # { + ## # "name": "Isaac", + ## # "books": [ + ## # "Robot Dreams" + ## # ], + ## # "details": { + ## # "age": 35, + ## # "pi": 3.1415 + ## # } + ## # } result = "" toPretty(result, node, indent) From 9ac0cbdd51e838bbe4b040ac2df67fa050e6ac07 Mon Sep 17 00:00:00 2001 From: Miran Date: Wed, 30 Jan 2019 17:31:06 +0100 Subject: [PATCH 014/202] fixes #2796 (#10500) [backport] --- lib/pure/oids.nim | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/lib/pure/oids.nim b/lib/pure/oids.nim index d6369b5f9f9e..3aee3941dd2b 100644 --- a/lib/pure/oids.nim +++ b/lib/pure/oids.nim @@ -60,23 +60,21 @@ proc `$`*(oid: Oid): string = result = newString(24) oidToString(oid, result) +proc rand(): cint {.importc: "rand", header: "", nodecl.} +proc srand(seed: cint) {.importc: "srand", header: "", nodecl.} + +var t = getTime().toUnix.int32 +srand(t) + var - incr: int - fuzz: int32 + incr: int = rand() + fuzz: int32 = rand() proc genOid*(): Oid = ## generates a new OID. - proc rand(): cint {.importc: "rand", header: "", nodecl.} - proc srand(seed: cint) {.importc: "srand", header: "", nodecl.} - - var t = getTime().toUnix.int32 - + t = getTime().toUnix.int32 var i = int32(atomicInc(incr)) - if fuzz == 0: - # racy, but fine semantically: - srand(t) - fuzz = rand() bigEndian32(addr result.time, addr(t)) result.fuzz = fuzz bigEndian32(addr result.count, addr(i)) From 0c2c2dca2a8a3bcdb9729021d1f4734b8db9efbd Mon Sep 17 00:00:00 2001 From: Miran Date: Wed, 30 Jan 2019 17:35:09 +0100 Subject: [PATCH 015/202] better docs: os (#10492) * better docs: os * remove broken test on osx --- lib/pure/includes/osenv.nim | 42 +- lib/pure/includes/oserr.nim | 30 +- lib/pure/includes/osseps.nim | 32 +- lib/pure/os.nim | 1213 +++++++++++++++++++++++++--------- 4 files changed, 968 insertions(+), 349 deletions(-) diff --git a/lib/pure/includes/osenv.nim b/lib/pure/includes/osenv.nim index 945555540faa..f9c076158d3f 100644 --- a/lib/pure/includes/osenv.nim +++ b/lib/pure/includes/osenv.nim @@ -102,9 +102,18 @@ proc findEnvVar(key: string): int = proc getEnv*(key: string, default = ""): TaintedString {.tags: [ReadEnvEffect].} = ## Returns the value of the `environment variable`:idx: named `key`. ## - ## If the variable does not exist, "" is returned. To distinguish - ## whether a variable exists or it's value is just "", call - ## `existsEnv(key)`. + ## If the variable does not exist, `""` is returned. To distinguish + ## whether a variable exists or it's value is just `""`, call + ## `existsEnv(key) proc <#existsEnv,string>`_. + ## + ## See also: + ## * `existsEnv proc <#existsEnv,string>`_ + ## * `putEnv proc <#putEnv,string,string>`_ + ## * `envPairs iterator <#envPairs.i>`_ + runnableExamples: + assert getEnv("unknownEnv") == "" + assert getEnv("unknownEnv", "doesn't exist") == "doesn't exist" + when nimvm: discard "built into the compiler" else: @@ -119,6 +128,14 @@ proc getEnv*(key: string, default = ""): TaintedString {.tags: [ReadEnvEffect].} proc existsEnv*(key: string): bool {.tags: [ReadEnvEffect].} = ## Checks whether the environment variable named `key` exists. ## Returns true if it exists, false otherwise. + ## + ## See also: + ## * `getEnv proc <#getEnv,string,string>`_ + ## * `putEnv proc <#putEnv,string,string>`_ + ## * `envPairs iterator <#envPairs.i>`_ + runnableExamples: + assert not existsEnv("unknownEnv") + when nimvm: discard "built into the compiler" else: @@ -127,7 +144,12 @@ proc existsEnv*(key: string): bool {.tags: [ReadEnvEffect].} = proc putEnv*(key, val: string) {.tags: [WriteEnvEffect].} = ## Sets the value of the `environment variable`:idx: named `key` to `val`. - ## If an error occurs, `EInvalidEnvVar` is raised. + ## If an error occurs, `OSError` is raised. + ## + ## See also: + ## * `getEnv proc <#getEnv,string,string>`_ + ## * `existsEnv proc <#existsEnv,string>`_ + ## * `envPairs iterator <#envPairs.i>`_ # Note: by storing the string in the environment sequence, # we guarantee that we don't free the memory before the program @@ -154,9 +176,15 @@ proc putEnv*(key, val: string) {.tags: [WriteEnvEffect].} = raiseOSError(osLastError()) iterator envPairs*(): tuple[key, value: TaintedString] {.tags: [ReadEnvEffect].} = - ## Iterate over all `environments variables`:idx:. In the first component - ## of the tuple is the name of the current variable stored, in the second - ## its value. + ## Iterate over all `environments variables`:idx:. + ## + ## In the first component of the tuple is the name of the current variable stored, + ## in the second its value. + ## + ## See also: + ## * `getEnv proc <#getEnv,string,string>`_ + ## * `existsEnv proc <#existsEnv,string>`_ + ## * `putEnv proc <#putEnv,string,string>`_ getEnvVarsC() for i in 0..high(environment): var p = find(environment[i], '=') diff --git a/lib/pure/includes/oserr.nim b/lib/pure/includes/oserr.nim index db7d84c1e06c..25e221d3bcbe 100644 --- a/lib/pure/includes/oserr.nim +++ b/lib/pure/includes/oserr.nim @@ -18,7 +18,7 @@ proc `$`*(err: OSErrorCode): string {.borrow.} proc osErrorMsg*(errorCode: OSErrorCode): string = ## Converts an OS error code into a human readable string. ## - ## The error code can be retrieved using the ``osLastError`` proc. + ## The error code can be retrieved using the `osLastError proc <#osLastError>`_. ## ## If conversion fails, or ``errorCode`` is ``0`` then ``""`` will be ## returned. @@ -26,6 +26,16 @@ proc osErrorMsg*(errorCode: OSErrorCode): string = ## On Windows, the ``-d:useWinAnsi`` compilation flag can be used to ## make this procedure use the non-unicode Win API calls to retrieve the ## message. + ## + ## See also: + ## * `raiseOSError proc <#raiseOSError,OSErrorCode,string>`_ + ## * `osLastError proc <#osLastError>`_ + runnableExamples: + when defined(posix): + assert osErrorMsg(OSErrorCode(0)) == "" + assert osErrorMsg(OSErrorCode(1)) == "Operation not permitted" + assert osErrorMsg(OSErrorCode(2)) == "No such file or directory" + result = "" when defined(nimscript): discard @@ -48,13 +58,21 @@ proc osErrorMsg*(errorCode: OSErrorCode): string = result = $c_strerror(errorCode.int32) proc raiseOSError*(errorCode: OSErrorCode; additionalInfo = "") {.noinline.} = - ## Raises an ``OSError`` exception. The ``errorCode`` will determine the - ## message, ``osErrorMsg`` will be used to get this message. + ## Raises an `OSError exception `_. + ## + ## The ``errorCode`` will determine the + ## message, `osErrorMsg proc <#osErrorMsg,OSErrorCode>`_ will be used + ## to get this message. ## - ## The error code can be retrieved using the ``osLastError`` proc. + ## The error code can be retrieved using the `osLastError proc + ## <#osLastError>`_. ## ## If the error code is ``0`` or an error message could not be retrieved, ## the message ``unknown OS error`` will be used. + ## + ## See also: + ## * `osErrorMsg proc <#osErrorMsg,OSErrorCode>`_ + ## * `osLastError proc <#osLastError>`_ var e: ref OSError; new(e) e.errorCode = errorCode.int32 e.msg = osErrorMsg(errorCode) @@ -80,6 +98,10 @@ proc osLastError*(): OSErrorCode {.sideEffect.} = ## On Windows some OS calls can reset the error code to ``0`` causing this ## procedure to return ``0``. It is therefore advised to call this procedure ## immediately after an OS call fails. On POSIX systems this is not a problem. + ## + ## See also: + ## * `osErrorMsg proc <#osErrorMsg,OSErrorCode>`_ + ## * `raiseOSError proc <#raiseOSError,OSErrorCode,string>`_ when defined(nimscript): discard elif defined(windows): diff --git a/lib/pure/includes/osseps.nim b/lib/pure/includes/osseps.nim index 944ad123ef21..859722f6a9cf 100644 --- a/lib/pure/includes/osseps.nim +++ b/lib/pure/includes/osseps.nim @@ -7,44 +7,44 @@ const when defined(Nimdoc): # only for proper documentation: const CurDir* = '.' - ## The constant string used by the operating system to refer to the + ## The constant character used by the operating system to refer to the ## current directory. ## - ## For example: '.' for POSIX or ':' for the classic Macintosh. + ## For example: `'.'` for POSIX or `':'` for the classic Macintosh. ParDir* = ".." ## The constant string used by the operating system to refer to the ## parent directory. ## - ## For example: ".." for POSIX or "::" for the classic Macintosh. + ## For example: `".."` for POSIX or `"::"` for the classic Macintosh. DirSep* = '/' ## The character used by the operating system to separate pathname - ## components, for example, '/' for POSIX or ':' for the classic - ## Macintosh. + ## components, for example: `'/'` for POSIX, `':'` for the classic + ## Macintosh, and `'\\'` on Windows. AltSep* = '/' ## An alternative character used by the operating system to separate - ## pathname components, or the same as `DirSep` if only one separator - ## character exists. This is set to '/' on Windows systems - ## where `DirSep` is a backslash. + ## pathname components, or the same as `DirSep <#DirSep>`_ if only one separator + ## character exists. This is set to `'/'` on Windows systems + ## where `DirSep <#DirSep>`_ is a backslash (`'\\'`). PathSep* = ':' ## The character conventionally used by the operating system to separate - ## search patch components (as in PATH), such as ':' for POSIX - ## or ';' for Windows. + ## search patch components (as in PATH), such as `':'` for POSIX + ## or `';'` for Windows. FileSystemCaseSensitive* = true - ## true if the file system is case sensitive, false otherwise. Used by - ## `cmpPaths` to compare filenames properly. + ## True if the file system is case sensitive, false otherwise. Used by + ## `cmpPaths proc <#cmpPaths,string,string>`_ to compare filenames properly. ExeExt* = "" ## The file extension of native executables. For example: - ## "" for POSIX, "exe" on Windows. + ## `""` for POSIX, `"exe"` on Windows (without a dot). ScriptExt* = "" - ## The file extension of a script file. For example: "" for POSIX, - ## "bat" on Windows. + ## The file extension of a script file. For example: `""` for POSIX, + ## `"bat"` on Windows. DynlibFormat* = "lib$1.so" ## The format string to turn a filename into a `DLL`:idx: file (also @@ -127,4 +127,4 @@ else: # UNIX-like operating system const ExtSep* = '.' ## The character which separates the base filename from the extension; - ## for example, the '.' in ``os.nim``. + ## for example, the `'.'` in ``os.nim``. diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 181bc5728d3a..53bf880b648c 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -10,6 +10,36 @@ ## This module contains basic operating system facilities like ## retrieving environment variables, reading command line arguments, ## working with directories, running shell commands, etc. +## +## .. code-block:: +## import os +## +## let myFile = "/path/to/my/file.nim" +## +## let splittedPath = splitPath(myFile) +## assert splittedPath.head == "/path/to/my" +## assert splittedPath.tail == "file.nim" +## +## assert parentDir(myFile) == "/path/to/my" +## +## let splittedFile = splitFile(myFile) +## assert splittedFile.dir == "/path/to/my" +## assert splittedFile.name == "file" +## assert splittedFile.ext == ".nim" +## +## assert myFile.changeFileExt("c") == "/path/to/my/file.c" +## +## +## **See also:** +## * `osproc module `_ for process communication beyond +## `execShellCmd proc <#execShellCmd,string>`_ +## * `parseopt module `_ for command-line parser beyond +## `parseCmdLine proc <#parseCmdLine,string>`_ +## * `distros module `_ +## * `dynlib module `_ +## * `streams module `_ + + {.deadCodeElim: on.} # dce option deprecated {.push debugger: off.} @@ -39,24 +69,24 @@ else: {.pragma: noNimScript.} type - ReadEnvEffect* = object of ReadIOEffect ## effect that denotes a read - ## from an environment variable - WriteEnvEffect* = object of WriteIOEffect ## effect that denotes a write - ## to an environment variable + ReadEnvEffect* = object of ReadIOEffect ## Effect that denotes a read + ## from an environment variable. + WriteEnvEffect* = object of WriteIOEffect ## Effect that denotes a write + ## to an environment variable. - ReadDirEffect* = object of ReadIOEffect ## effect that denotes a read + ReadDirEffect* = object of ReadIOEffect ## Effect that denotes a read ## operation from the directory - ## structure - WriteDirEffect* = object of WriteIOEffect ## effect that denotes a write + ## structure. + WriteDirEffect* = object of WriteIOEffect ## Effect that denotes a write ## operation to - ## the directory structure + ## the directory structure. OSErrorCode* = distinct int32 ## Specifies an OS Error Code. include "includes/osseps" proc normalizePathEnd(path: var string, trailingSep = false) = - ## ensures ``path`` has exactly 0 or 1 trailing `DirSep`, depending on + ## Ensures ``path`` has exactly 0 or 1 trailing `DirSep`, depending on ## ``trailingSep``, and taking care of edge cases: it preservers whether ## a path is absolute or relative, and makes sure trailing sep is `DirSep`, ## not `AltSep`. @@ -83,27 +113,24 @@ proc joinPath*(head, tail: string): string {. noSideEffect, rtl, extern: "nos$1".} = ## Joins two directory names to one. ## - ## For example on Unix: - ## - ## .. code-block:: nim - ## joinPath("usr", "lib") - ## - ## results in: + ## If `head` is the empty string, `tail` is returned. If `tail` is the empty + ## string, `head` is returned with a trailing path separator. If `tail` starts + ## with a path separator it will be removed when concatenated to `head`. Other + ## path separators not located on boundaries won't be modified. ## - ## .. code-block:: nim - ## "usr/lib" - ## - ## If head is the empty string, tail is returned. If tail is the empty - ## string, head is returned with a trailing path separator. If tail starts - ## with a path separator it will be removed when concatenated to head. Other - ## path separators not located on boundaries won't be modified. More - ## examples on Unix: - ## - ## .. code-block:: nim - ## assert joinPath("usr", "") == "usr/" - ## assert joinPath("", "lib") == "lib" - ## assert joinPath("", "/lib") == "/lib" - ## assert joinPath("usr/", "/lib") == "usr/lib" + ## See also: + ## * `joinPath(varargs) proc <#joinPath,varargs[string]>`_ + ## * `/ proc <#/,string,string>`_ + ## * `splitPath proc <#splitPath,string>`_ + runnableExamples: + when defined(posix): + assert joinPath("usr", "lib") == "usr/lib" + assert joinPath("usr", "") == "usr/" + assert joinPath("", "lib") == "lib" + assert joinPath("", "/lib") == "/lib" + assert joinPath("usr/", "/lib") == "usr/lib" + assert joinPath("usr/lib", "../bin") == "usr/bin" + result = newStringOfCap(head.len + tail.len) var state = 0 addNormalizePath(head, result, state, DirSep) @@ -127,9 +154,23 @@ proc joinPath*(head, tail: string): string {. proc joinPath*(parts: varargs[string]): string {.noSideEffect, rtl, extern: "nos$1OpenArray".} = - ## The same as `joinPath(head, tail)`, but works with any number of - ## directory parts. You need to pass at least one element or the proc + ## The same as `joinPath(head, tail) proc <#joinPath,string,string>`_, + ## but works with any number of directory parts. + ## + ## You need to pass at least one element or the proc ## will assert in debug builds and crash on release builds. + ## + ## See also: + ## * `joinPath(head, tail) proc <#joinPath,string,string>`_ + ## * `/ proc <#/,string,string>`_ + ## * `/../ proc <#/../,string,string>`_ + ## * `splitPath proc <#splitPath,string>`_ + runnableExamples: + when defined(posix): + assert joinPath("a") == "a" + assert joinPath("a", "b", "c") == "a/b/c" + assert joinPath("usr/lib", "../../var", "log") == "var/log" + var estimatedLen = 0 for p in parts: estimatedLen += p.len result = newStringOfCap(estimatedLen) @@ -138,30 +179,41 @@ proc joinPath*(parts: varargs[string]): string {.noSideEffect, addNormalizePath(parts[i], result, state, DirSep) proc `/`*(head, tail: string): string {.noSideEffect.} = - ## The same as ``joinPath(head, tail)`` + ## The same as `joinPath(head, tail) proc <#joinPath,string,string>`_. ## - ## Here are some examples for Unix: - ## - ## .. code-block:: nim - ## assert "usr" / "" == "usr/" - ## assert "" / "lib" == "lib" - ## assert "" / "/lib" == "/lib" - ## assert "usr/" / "/lib" == "usr/lib" + ## See also: + ## * `/../ proc <#/../,string,string>`_ + ## * `joinPath(head, tail) proc <#joinPath,string,string>`_ + ## * `joinPath(varargs) proc <#joinPath,varargs[string]>`_ + ## * `splitPath proc <#splitPath,string>`_ + runnableExamples: + when defined(posix): + assert "usr" / "" == "usr/" + assert "" / "lib" == "lib" + assert "" / "/lib" == "/lib" + assert "usr/" / "/lib" == "usr/lib" + assert "usr" / "lib" / "../bin" == "usr/bin" + return joinPath(head, tail) proc splitPath*(path: string): tuple[head, tail: string] {. noSideEffect, rtl, extern: "nos$1".} = - ## Splits a directory into (head, tail), so that + ## Splits a directory into `(head, tail)` tuple, so that ## ``head / tail == path`` (except for edge cases like "/usr"). ## - ## Examples: - ## - ## .. code-block:: nim - ## splitPath("usr/local/bin") -> ("usr/local", "bin") - ## splitPath("usr/local/bin/") -> ("usr/local/bin", "") - ## splitPath("bin") -> ("", "bin") - ## splitPath("/bin") -> ("", "bin") - ## splitPath("") -> ("", "") + ## See also: + ## * `joinPath(head, tail) proc <#joinPath,string,string>`_ + ## * `joinPath(varargs) proc <#joinPath,varargs[string]>`_ + ## * `/ proc <#/,string,string>`_ + ## * `/../ proc <#/../,string,string>`_ + ## * `relativePath proc <#relativePath,string,string>`_ + runnableExamples: + assert splitPath("usr/local/bin") == ("usr/local", "bin") + assert splitPath("usr/local/bin/") == ("usr/local/bin", "") + assert splitPath("bin") == ("", "bin") + assert splitPath("/bin") == ("", "bin") + assert splitPath("") == ("", "") + var sepPos = -1 for i in countdown(len(path)-1, 0): if path[i] in {DirSep, AltSep}: @@ -182,16 +234,21 @@ else: proc relativePath*(path, base: string; sep = DirSep): string {. noSideEffect, rtl, extern: "nos$1", raises: [].} = ## Converts `path` to a path relative to `base`. - ## The `sep` is used for the path normalizations, this can be useful to - ## ensure the relative path only contains '/' so that it can be used for - ## URL constructions. + ## + ## The `sep` (default: `DirSep <#DirSep>`_) is used for the path normalizations, + ## this can be useful to ensure the relative path only contains `'/'` + ## so that it can be used for URL constructions. + ## + ## See also: + ## * `splitPath proc <#splitPath,string>`_ + ## * `parentDir proc <#parentDir,string>`_ + ## * `tailDir proc <#tailDir,string>`_ runnableExamples: - doAssert relativePath("/Users/me/bar/z.nim", "/Users/other/bad", '/') == "../../me/bar/z.nim" - doAssert relativePath("/Users/me/bar/z.nim", "/Users/other", '/') == "../me/bar/z.nim" - doAssert relativePath("/Users///me/bar//z.nim", "//Users/", '/') == "me/bar/z.nim" - doAssert relativePath("/Users/me/bar/z.nim", "/Users/me", '/') == "bar/z.nim" - doAssert relativePath("", "/users/moo", '/') == "" - + assert relativePath("/Users/me/bar/z.nim", "/Users/other/bad", '/') == "../../me/bar/z.nim" + assert relativePath("/Users/me/bar/z.nim", "/Users/other", '/') == "../me/bar/z.nim" + assert relativePath("/Users///me/bar//z.nim", "//Users/", '/') == "me/bar/z.nim" + assert relativePath("/Users/me/bar/z.nim", "/Users/me", '/') == "bar/z.nim" + assert relativePath("", "/users/moo", '/') == "" # Todo: If on Windows, path and base do not agree on the drive letter, # return `path` as is. @@ -252,12 +309,21 @@ proc parentDir*(path: string): string {. ## ## This is the same as ``splitPath(path).head`` when ``path`` doesn't end ## in a dir separator. - ## The remainder can be obtained with ``lastPathPart(path)`` + ## The remainder can be obtained with `lastPathPart(path) proc + ## <#lastPathPart,string>`_. + ## + ## See also: + ## * `relativePath proc <#relativePath,string,string>`_ + ## * `splitPath proc <#splitPath,string>`_ + ## * `tailDir proc <#tailDir,string>`_ + ## * `parentDirs iterator <#parentDirs.i,string>`_ runnableExamples: - doAssert parentDir("") == "" + assert parentDir("") == "" when defined(posix): - doAssert parentDir("/usr/local/bin") == "/usr/local" - doAssert parentDir("foo/bar/") == "foo" + assert parentDir("/usr/local/bin") == "/usr/local" + assert parentDir("foo/bar/") == "foo" + assert parentDir("./foo") == "." + assert parentDir("/foo") == "" let sepPos = parentDirPos(path) if sepPos >= 0: @@ -267,11 +333,18 @@ proc parentDir*(path: string): string {. proc tailDir*(path: string): string {. noSideEffect, rtl, extern: "nos$1".} = - ## Returns the tail part of `path`.. + ## Returns the tail part of `path`. ## - ## | Example: ``tailDir("/usr/local/bin") == "local/bin"``. - ## | Example: ``tailDir("usr/local/bin/") == "local/bin"``. - ## | Example: ``tailDir("bin") == ""``. + ## See also: + ## * `relativePath proc <#relativePath,string,string>`_ + ## * `splitPath proc <#splitPath,string>`_ + ## * `parentDir proc <#parentDir,string>`_ + runnableExamples: + assert tailDir("/bin") == "bin" + assert tailDir("bin") == "" + assert tailDir("/usr/local/bin") == "usr/local/bin" + assert tailDir("usr/local/bin") == "local/bin" + var q = 1 if len(path) >= 1 and path[len(path)-1] in {DirSep, AltSep}: q = 2 for i in 0..len(path)-q: @@ -281,18 +354,53 @@ proc tailDir*(path: string): string {. proc isRootDir*(path: string): bool {. noSideEffect, rtl, extern: "nos$1".} = - ## Checks whether a given `path` is a root directory + ## Checks whether a given `path` is a root directory. + runnableExamples: + assert isRootDir("") + assert isRootDir(".") + assert isRootDir("/") + assert isRootDir("a") + assert not isRootDir("/a") + assert not isRootDir("a/b/c") + result = parentDirPos(path) < 0 iterator parentDirs*(path: string, fromRoot=false, inclusive=true): string = - ## Walks over all parent directories of a given `path` + ## Walks over all parent directories of a given `path`. ## - ## If `fromRoot` is set, the traversal will start from the file system root - ## diretory. If `inclusive` is set, the original argument will be included + ## If `fromRoot` is true (default: false), the traversal will start from + ## the file system root diretory. + ## If `inclusive` is true (default), the original argument will be included ## in the traversal. ## - ## Relative paths won't be expanded by this proc. Instead, it will traverse + ## Relative paths won't be expanded by this iterator. Instead, it will traverse ## only the directories appearing in the relative path. + ## + ## See also: + ## * `parentDir proc <#parentDir,string>`_ + ## + ## **Examples:** + ## + ## .. code-block:: + ## let g = "a/b/c" + ## + ## for p in g.parentDirs: + ## echo p + ## # a/b/c + ## # a/b + ## # a + ## + ## for p in g.parentDirs(fromRoot=true): + ## echo p + ## # a/ + ## # a/b/ + ## # a/b/c + ## + ## for p in g.parentDirs(inclusive=false): + ## echo p + ## # a/b + ## # a + if not fromRoot: var current = path if inclusive: yield path @@ -310,8 +418,16 @@ iterator parentDirs*(path: string, fromRoot=false, inclusive=true): string = if inclusive: yield path proc `/../`*(head, tail: string): string {.noSideEffect.} = - ## The same as ``parentDir(head) / tail`` unless there is no parent + ## The same as ``parentDir(head) / tail``, unless there is no parent ## directory. Then ``head / tail`` is performed instead. + ## + ## See also: + ## * `/ proc <#/,string,string>`_ + ## * `parentDir proc <#parentDir,string>`_ + runnableExamples: + assert "a/b/c" /../ "d/e" == "a/b/d/e" + assert "a" /../ "d/e" == "a/d/e" + let sepPos = parentDirPos(head) if sepPos >= 0: result = substr(head, 0, sepPos-1) / tail @@ -323,8 +439,21 @@ proc normExt(ext: string): string = else: result = ExtSep & ext proc searchExtPos*(path: string): int = - ## Returns index of the '.' char in `path` if it signifies the beginning + ## Returns index of the `'.'` char in `path` if it signifies the beginning ## of extension. Returns -1 otherwise. + ## + ## See also: + ## * `splitFile proc <#splitFile,string>`_ + ## * `extractFilename proc <#extractFilename,string>`_ + ## * `lastPathPart proc <#lastPathPart,string>`_ + ## * `changeFileExt proc <#changeFileExt,string,string>`_ + ## * `addFileExt proc <#addFileExt,string,string>`_ + runnableExamples: + assert searchExtPos("a/b/c") == -1 + assert searchExtPos("c.nim") == 1 + assert searchExtPos("a/b/c.nim") == 5 + assert searchExtPos("a.b.c.nim") == 5 + # BUGFIX: do not search until 0! .DS_Store is no file extension! result = -1 for i in countdown(len(path)-1, 1): @@ -336,21 +465,35 @@ proc searchExtPos*(path: string): int = proc splitFile*(path: string): tuple[dir, name, ext: string] {. noSideEffect, rtl, extern: "nos$1".} = - ## Splits a filename into (dir, name, extension). - ## `dir` does not end in `DirSep`. - ## `extension` includes the leading dot. - ## - ## Example: + ## Splits a filename into `(dir, name, extension)` tuple. ## - ## .. code-block:: nim - ## var (dir, name, ext) = splitFile("usr/local/nimc.html") - ## assert dir == "usr/local" - ## assert name == "nimc" - ## assert ext == ".html" + ## `dir` does not end in `DirSep <#DirSep>`_. + ## `extension` includes the leading dot. ## ## If `path` has no extension, `ext` is the empty string. ## If `path` has no directory component, `dir` is the empty string. ## If `path` has no filename component, `name` and `ext` are empty strings. + ## + ## See also: + ## * `searchExtPos proc <#searchExtPos,string>`_ + ## * `extractFilename proc <#extractFilename,string>`_ + ## * `lastPathPart proc <#lastPathPart,string>`_ + ## * `changeFileExt proc <#changeFileExt,string,string>`_ + ## * `addFileExt proc <#addFileExt,string,string>`_ + runnableExamples: + var (dir, name, ext) = splitFile("usr/local/nimc.html") + assert dir == "usr/local" + assert name == "nimc" + assert ext == ".html" + (dir, name, ext) = splitFile("/usr/local/os") + assert dir == "/usr/local" + assert name == "os" + assert ext == "" + (dir, name, ext) = splitFile("/usr/local/") + assert dir == "/usr/local" + assert name == "" + assert ext == "" + if path.len == 0: result = ("", "", "") elif path[^1] in {DirSep, AltSep}: @@ -380,12 +523,22 @@ proc splitFile*(path: string): tuple[dir, name, ext: string] {. proc extractFilename*(path: string): string {. noSideEffect, rtl, extern: "nos$1".} = - ## Extracts the filename of a given `path`. This is the same as - ## ``name & ext`` from ``splitFile(path)``. See also ``lastPathPart``. + ## Extracts the filename of a given `path`. + ## + ## This is the same as ``name & ext`` from `splitFile(path) proc + ## <#splitFile,string>`_. + ## + ## See also: + ## * `searchExtPos proc <#searchExtPos,string>`_ + ## * `splitFile proc <#splitFile,string>`_ + ## * `lastPathPart proc <#lastPathPart,string>`_ + ## * `changeFileExt proc <#changeFileExt,string,string>`_ + ## * `addFileExt proc <#addFileExt,string,string>`_ runnableExamples: - when defined(posix): - doAssert extractFilename("foo/bar/") == "" - doAssert extractFilename("foo/bar") == "bar" + assert extractFilename("foo/bar/") == "" + assert extractFilename("foo/bar") == "bar" + assert extractFilename("foo/bar.baz") == "bar.baz" + if path.len == 0 or path[path.len-1] in {DirSep, AltSep}: result = "" else: @@ -393,11 +546,19 @@ proc extractFilename*(path: string): string {. proc lastPathPart*(path: string): string {. noSideEffect, rtl, extern: "nos$1".} = - ## like ``extractFilename``, but ignores trailing dir separator; aka: `baseName`:idx: - ## in some other languages. + ## Like `extractFilename proc <#extractFilename,string>`_, but ignores + ## trailing dir separator; aka: `baseName`:idx: in some other languages. + ## + ## See also: + ## * `searchExtPos proc <#searchExtPos,string>`_ + ## * `splitFile proc <#splitFile,string>`_ + ## * `extractFilename proc <#extractFilename,string>`_ + ## * `changeFileExt proc <#changeFileExt,string,string>`_ + ## * `addFileExt proc <#addFileExt,string,string>`_ runnableExamples: - when defined(posix): - doAssert lastPathPart("foo/bar/") == "bar" + assert lastPathPart("foo/bar/") == "bar" + assert lastPathPart("foo/bar") == "bar" + let path = path.normalizePathEnd(trailingSep = false) result = extractFilename(path) @@ -407,9 +568,22 @@ proc changeFileExt*(filename, ext: string): string {. ## ## If the `filename` has no extension, `ext` will be added. ## If `ext` == "" then any extension is removed. - ## `Ext` should be given without the leading '.', because some + ## + ## `Ext` should be given without the leading `'.'`, because some ## filesystems may use a different character. (Although I know ## of none such beast.) + ## + ## See also: + ## * `searchExtPos proc <#searchExtPos,string>`_ + ## * `splitFile proc <#splitFile,string>`_ + ## * `extractFilename proc <#extractFilename,string>`_ + ## * `lastPathPart proc <#lastPathPart,string>`_ + ## * `addFileExt proc <#addFileExt,string,string>`_ + runnableExamples: + assert changeFileExt("foo.bar", "baz") == "foo.baz" + assert changeFileExt("foo.bar", "") == "foo" + assert changeFileExt("foo", "baz") == "foo.baz" + var extPos = searchExtPos(filename) if extPos < 0: result = filename & normExt(ext) else: result = substr(filename, 0, extPos-1) & normExt(ext) @@ -419,9 +593,21 @@ proc addFileExt*(filename, ext: string): string {. ## Adds the file extension `ext` to `filename`, unless ## `filename` already has an extension. ## - ## `Ext` should be given without the leading '.', because some + ## `Ext` should be given without the leading `'.'`, because some ## filesystems may use a different character. ## (Although I know of none such beast.) + ## + ## See also: + ## * `searchExtPos proc <#searchExtPos,string>`_ + ## * `splitFile proc <#splitFile,string>`_ + ## * `extractFilename proc <#extractFilename,string>`_ + ## * `lastPathPart proc <#lastPathPart,string>`_ + ## * `changeFileExt proc <#changeFileExt,string,string>`_ + runnableExamples: + assert addFileExt("foo.bar", "baz") == "foo.bar" + assert addFileExt("foo.bar", "") == "foo.bar" + assert addFileExt("foo", "baz") == "foo.baz" + var extPos = searchExtPos(filename) if extPos < 0: result = filename & normExt(ext) else: result = filename @@ -438,9 +624,9 @@ proc cmpPaths*(pathA, pathB: string): int {. ## | > 0 iff pathA > pathB runnableExamples: when defined(macosx): - doAssert cmpPaths("foo", "Foo") == 0 + assert cmpPaths("foo", "Foo") == 0 elif defined(posix): - doAssert cmpPaths("foo", "Foo") > 0 + assert cmpPaths("foo", "Foo") > 0 let a = normalizePath(pathA) let b = normalizePath(pathB) @@ -458,11 +644,12 @@ proc isAbsolute*(path: string): bool {.rtl, noSideEffect, extern: "nos$1".} = ## ## On Windows, network paths are considered absolute too. runnableExamples: - doAssert(not "".isAbsolute) - doAssert(not ".".isAbsolute) + assert not "".isAbsolute + assert not ".".isAbsolute when defined(posix): - doAssert "/".isAbsolute - doAssert(not "a/".isAbsolute) + assert "/".isAbsolute + assert not "a/".isAbsolute + assert "/a/".isAbsolute if len(path) == 0: return false @@ -483,7 +670,7 @@ proc unixToNativePath*(path: string, drive=""): string {. ## Converts an UNIX-like path to a native one. ## ## On an UNIX system this does nothing. Else it converts - ## '/', '.', '..' to the appropriate things. + ## `'/'`, `'.'`, `'..'` to the appropriate things. ## ## On systems with a concept of "drives", `drive` is used to determine ## which drive label to use during absolute path conversion. @@ -542,8 +729,18 @@ proc getHomeDir*(): string {.rtl, extern: "nos$1", tags: [ReadEnvEffect, ReadIOEffect].} = ## Returns the home directory of the current user. ## - ## This proc is wrapped by the expandTilde proc for the convenience of - ## processing paths coming from user configuration files. + ## This proc is wrapped by the `expandTilde proc <#expandTilde,string>`_ + ## for the convenience of processing paths coming from user configuration files. + ## + ## See also: + ## * `getConfigDir proc <#getConfigDir>`_ + ## * `getTempDir proc <#getTempDir>`_ + ## * `expandTilde proc <#expandTilde,string>`_ + ## * `getCurrentDir proc <#getCurrentDir>`_ + ## * `setCurrentDir proc <#setCurrentDir,string>`_ + runnableExamples: + assert getHomeDir() == expandTilde("~") + when defined(windows): return string(getEnv("USERPROFILE")) & "\\" else: return string(getEnv("HOME")) & "/" @@ -552,12 +749,19 @@ proc getConfigDir*(): string {.rtl, extern: "nos$1", ## Returns the config directory of the current user for applications. ## ## On non-Windows OSs, this proc conforms to the XDG Base Directory - ## spec. Thus, this proc returns the value of the XDG_CONFIG_HOME environment - ## variable if it is set, and returns the default configuration directory, - ## "~/.config/", otherwise. + ## spec. Thus, this proc returns the value of the `XDG_CONFIG_HOME` environment + ## variable if it is set, otherwise it returns the default configuration + ## directory ("~/.config/"). ## ## An OS-dependent trailing slash is always present at the end of the - ## returned string; `\` on Windows and `/` on all other OSs. + ## returned string: `\\` on Windows and `/` on all other OSs. + ## + ## See also: + ## * `getHomeDir proc <#getHomeDir>`_ + ## * `getTempDir proc <#getTempDir>`_ + ## * `expandTilde proc <#expandTilde,string>`_ + ## * `getCurrentDir proc <#getCurrentDir>`_ + ## * `setCurrentDir proc <#setCurrentDir,string>`_ when defined(windows): result = getEnv("APPDATA").string else: @@ -573,6 +777,13 @@ proc getTempDir*(): string {.rtl, extern: "nos$1", ## returns ``getHomeDir()``, and on other Unix based systems it can cause ## security problems too. That said, you can override this implementation ## by adding ``-d:tempDir=mytempname`` to your compiler invokation. + ## + ## See also: + ## * `getHomeDir proc <#getHomeDir>`_ + ## * `getConfigDir proc <#getConfigDir>`_ + ## * `expandTilde proc <#expandTilde,string>`_ + ## * `getCurrentDir proc <#getCurrentDir>`_ + ## * `setCurrentDir proc <#setCurrentDir,string>`_ when defined(tempDir): const tempDir {.strdefine.}: string = nil return tempDir @@ -583,12 +794,22 @@ proc getTempDir*(): string {.rtl, extern: "nos$1", proc expandTilde*(path: string): string {. tags: [ReadEnvEffect, ReadIOEffect].} = ## Expands ``~`` or a path starting with ``~/`` to a full path, replacing - ## ``~`` with ``getHomeDir()`` (otherwise returns ``path`` unmodified). + ## ``~`` with `getHomeDir() <#getHomeDir>`_ (otherwise returns ``path`` unmodified). ## ## Windows: this is still supported despite Windows platform not having this ## convention; also, both ``~/`` and ``~\`` are handled. + ## + ## See also: + ## * `getHomeDir proc <#getHomeDir>`_ + ## * `getConfigDir proc <#getConfigDir>`_ + ## * `getTempDir proc <#getTempDir>`_ + ## * `getCurrentDir proc <#getCurrentDir>`_ + ## * `setCurrentDir proc <#setCurrentDir,string>`_ runnableExamples: - doAssert expandTilde("~" / "appname.cfg") == getHomeDir() / "appname.cfg" + assert expandTilde("~" / "appname.cfg") == getHomeDir() / "appname.cfg" + assert expandTilde("~/foo/bar") == getHomeDir() / "foo/bar" + assert expandTilde("/foo/bar") == "/foo/bar" + if len(path) == 0 or path[0] != '~': result = path elif len(path) == 1: @@ -602,11 +823,13 @@ proc expandTilde*(path: string): string {. # TODO: consider whether quoteShellPosix, quoteShellWindows, quoteShell, quoteShellCommand # belong in `strutils` instead; they are not specific to paths proc quoteShellWindows*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} = - ## Quote s, so it can be safely passed to Windows API. - ## Based on Python's subprocess.list2cmdline - ## See http://msdn.microsoft.com/en-us/library/17w5ykft.aspx - let needQuote = {' ', '\t'} in s or s.len == 0 + ## Quote `s`, so it can be safely passed to Windows API. + ## + ## Based on Python's `subprocess.list2cmdline`. + ## See `this link `_ + ## for more details. + let needQuote = {' ', '\t'} in s or s.len == 0 result = "" var backslashBuff = "" if needQuote: @@ -631,7 +854,7 @@ proc quoteShellWindows*(s: string): string {.noSideEffect, rtl, extern: "nosp$1" proc quoteShellPosix*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} = ## Quote ``s``, so it can be safely passed to POSIX shell. - ## Based on Python's pipes.quote + ## Based on Python's `pipes.quote`. const safeUnixChars = {'%', '+', '-', '.', '/', '_', ':', '=', '@', '0'..'9', 'A'..'Z', 'a'..'z'} if s.len == 0: @@ -647,18 +870,23 @@ proc quoteShellPosix*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} when defined(windows) or defined(posix) or defined(nintendoswitch): proc quoteShell*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} = ## Quote ``s``, so it can be safely passed to shell. + ## + ## When on Windows, it calls `quoteShellWindows proc + ## <#quoteShellWindows,string>`_. Otherwise, calls `quoteShellPosix proc + ## <#quoteShellPosix,string>`_. when defined(windows): return quoteShellWindows(s) else: return quoteShellPosix(s) proc quoteShellCommand*(args: openArray[string]): string = - ## Concatenates and quotes shell arguments `args` + ## Concatenates and quotes shell arguments `args`. runnableExamples: when defined(posix): assert quoteShellCommand(["aaa", "", "c d"]) == "aaa '' 'c d'" when defined(windows): assert quoteShellCommand(["aaa", "", "c d"]) == "aaa \"\" \"c d\"" + # can't use `map` pending https://github.com/nim-lang/Nim/issues/8303 for i in 0.. 0: result.add " " @@ -705,8 +933,12 @@ when defined(windows) and not weirdTarget: proc existsFile*(filename: string): bool {.rtl, extern: "nos$1", tags: [ReadDirEffect], noNimScript.} = ## Returns true if `filename` exists and is a regular file or symlink. - ## (directories, device files, named pipes and sockets return false) - ## This proc is not available for NimScript. + ## + ## Directories, device files, named pipes and sockets return false. + ## + ## See also: + ## * `existsDir proc <#existsDir,string>`_ + ## * `symlinkExists proc <#symlinkExists,string>`_ when defined(windows): when useWinUnicode: wrapUnary(a, getFileAttributesW, filename) @@ -722,6 +954,10 @@ proc existsDir*(dir: string): bool {.rtl, extern: "nos$1", tags: [ReadDirEffect] noNimScript.} = ## Returns true iff the directory `dir` exists. If `dir` is a file, false ## is returned. Follows symlinks. + ## + ## See also: + ## * `existsFile proc <#existsFile,string>`_ + ## * `symlinkExists proc <#symlinkExists,string>`_ when defined(windows): when useWinUnicode: wrapUnary(a, getFileAttributesW, dir) @@ -738,6 +974,10 @@ proc symlinkExists*(link: string): bool {.rtl, extern: "nos$1", noNimScript.} = ## Returns true iff the symlink `link` exists. Will return true ## regardless of whether the link points to a directory or file. + ## + ## See also: + ## * `existsFile proc <#existsFile,string>`_ + ## * `existsDir proc <#existsDir,string>`_ when defined(windows): when useWinUnicode: wrapUnary(a, getFileAttributesW, link) @@ -750,11 +990,19 @@ proc symlinkExists*(link: string): bool {.rtl, extern: "nos$1", return lstat(link, res) >= 0'i32 and S_ISLNK(res.st_mode) proc fileExists*(filename: string): bool {.inline, noNimScript.} = - ## Synonym for existsFile + ## Alias for `existsFile proc <#existsFile,string>`_. + ## + ## See also: + ## * `existsDir proc <#existsDir,string>`_ + ## * `symlinkExists proc <#symlinkExists,string>`_ existsFile(filename) proc dirExists*(dir: string): bool {.inline, noNimScript.} = - ## Synonym for existsDir + ## Alias for `existsDir proc <#existsDir,string>`_. + ## + ## See also: + ## * `existsFile proc <#existsFile,string>`_ + ## * `symlinkExists proc <#symlinkExists,string>`_ existsDir(dir) when not defined(windows) and not weirdTarget: @@ -764,19 +1012,23 @@ when not defined(windows) and not weirdTarget: else: result = S_ISLNK(rawInfo.st_mode) const - ExeExts* = when defined(windows): ["exe", "cmd", "bat"] else: [""] ## \ - ## platform specific file extension for executables. On Windows - ## ``["exe", "cmd", "bat"]``, on Posix ``[""]``. + ExeExts* = ## Platform specific file extension for executables. + ## On Windows ``["exe", "cmd", "bat"]``, on Posix ``[""]``. + when defined(windows): ["exe", "cmd", "bat"] else: [""] proc findExe*(exe: string, followSymlinks: bool = true; extensions: openarray[string]=ExeExts): string {. tags: [ReadDirEffect, ReadEnvEffect, ReadIOEffect], noNimScript.} = ## Searches for `exe` in the current working directory and then ## in directories listed in the ``PATH`` environment variable. - ## Returns "" if the `exe` cannot be found. `exe` + ## + ## Returns `""` if the `exe` cannot be found. `exe` ## is added the `ExeExts <#ExeExts>`_ file extensions if it has none. + ## ## If the system supports symlinks it also resolves them until it - ## meets the actual file. This behavior can be disabled if desired. + ## meets the actual file. This behavior can be disabled if desired + ## by setting `followSymlinks = false`. + if exe.len == 0: return template checkCurrentDir() = for ext in extensions: @@ -824,6 +1076,11 @@ when weirdTarget: proc getLastModificationTime*(file: string): times.Time {.rtl, extern: "nos$1", noNimScript.} = ## Returns the `file`'s last modification time. + ## + ## See also: + ## * `getLastAccessTime proc <#getLastAccessTime,string>`_ + ## * `getCreationTime proc <#getCreationTime,string>`_ + ## * `fileNewer proc <#fileNewer,string,string>`_ when defined(posix): var res: Stat if stat(file, res) < 0'i32: raiseOSError(osLastError()) @@ -837,6 +1094,11 @@ proc getLastModificationTime*(file: string): times.Time {.rtl, extern: "nos$1", proc getLastAccessTime*(file: string): times.Time {.rtl, extern: "nos$1", noNimScript.} = ## Returns the `file`'s last read or write access time. + ## + ## See also: + ## * `getLastModificationTime proc <#getLastModificationTime,string>`_ + ## * `getCreationTime proc <#getCreationTime,string>`_ + ## * `fileNewer proc <#fileNewer,string,string>`_ when defined(posix): var res: Stat if stat(file, res) < 0'i32: raiseOSError(osLastError()) @@ -854,6 +1116,11 @@ proc getCreationTime*(file: string): times.Time {.rtl, extern: "nos$1", noNimScr ## **Note:** Under POSIX OS's, the returned time may actually be the time at ## which the file's attribute's were last modified. See ## `here `_ for details. + ## + ## See also: + ## * `getLastModificationTime proc <#getLastModificationTime,string>`_ + ## * `getLastAccessTime proc <#getLastAccessTime,string>`_ + ## * `fileNewer proc <#fileNewer,string,string>`_ when defined(posix): var res: Stat if stat(file, res) < 0'i32: raiseOSError(osLastError()) @@ -868,6 +1135,11 @@ proc getCreationTime*(file: string): times.Time {.rtl, extern: "nos$1", noNimScr proc fileNewer*(a, b: string): bool {.rtl, extern: "nos$1", noNimScript.} = ## Returns true if the file `a` is newer than file `b`, i.e. if `a`'s ## modification time is later than `b`'s. + ## + ## See also: + ## * `getLastModificationTime proc <#getLastModificationTime,string>`_ + ## * `getLastAccessTime proc <#getLastAccessTime,string>`_ + ## * `getCreationTime proc <#getCreationTime,string>`_ when defined(posix): # If we don't have access to nanosecond resolution, use '>=' when not StatHasNanoseconds: @@ -879,6 +1151,12 @@ proc fileNewer*(a, b: string): bool {.rtl, extern: "nos$1", noNimScript.} = proc getCurrentDir*(): string {.rtl, extern: "nos$1", tags: [], noNimScript.} = ## Returns the `current working directory`:idx:. + ## + ## See also: + ## * `getHomeDir proc <#getHomeDir>`_ + ## * `getConfigDir proc <#getConfigDir>`_ + ## * `getTempDir proc <#getTempDir>`_ + ## * `setCurrentDir proc <#setCurrentDir,string>`_ when defined(windows): var bufsize = MAX_PATH.int32 when useWinUnicode: @@ -922,8 +1200,14 @@ proc getCurrentDir*(): string {.rtl, extern: "nos$1", tags: [], noNimScript.} = raiseOSError(osLastError()) proc setCurrentDir*(newDir: string) {.inline, tags: [], noNimScript.} = - ## Sets the `current working directory`:idx:; `OSError` is raised if - ## `newDir` cannot been set. + ## Sets the `current working directory`:idx:; `OSError` + ## is raised if `newDir` cannot been set. + ## + ## See also: + ## * `getHomeDir proc <#getHomeDir>`_ + ## * `getConfigDir proc <#getConfigDir>`_ + ## * `getTempDir proc <#getTempDir>`_ + ## * `getCurrentDir proc <#getCurrentDir>`_ when defined(Windows): when useWinUnicode: if setCurrentDirectoryW(newWideCString(newDir)) == 0'i32: @@ -935,10 +1219,16 @@ proc setCurrentDir*(newDir: string) {.inline, tags: [], noNimScript.} = when not weirdTarget: proc absolutePath*(path: string, root = getCurrentDir()): string {.noNimScript.} = - ## Returns the absolute path of `path`, rooted at `root` (which must be absolute) - ## if `path` is absolute, return it, ignoring `root` + ## Returns the absolute path of `path`, rooted at `root` (which must be absolute; + ## default: current directory). + ## If `path` is absolute, return it, ignoring `root`. + ## + ## See also: + ## * `normalizedPath proc <#normalizedPath,string>`_ + ## * `normalizePath proc <#normalizePath,string>`_ runnableExamples: - doAssert absolutePath("a") == getCurrentDir() / "a" + assert absolutePath("a") == getCurrentDir() / "a" + if isAbsolute(path): path else: if not root.isAbsolute: @@ -950,11 +1240,21 @@ proc normalizePath*(path: var string) {.rtl, extern: "nos$1", tags: [], noNimScr ## ## Consecutive directory separators are collapsed, including an initial double slash. ## - ## On relative paths, double dot (..) sequences are collapsed if possible. + ## On relative paths, double dot (`..`) sequences are collapsed if possible. ## On absolute paths they are always collapsed. ## ## Warning: URL-encoded and Unicode attempts at directory traversal are not detected. ## Triple dot is not handled. + ## + ## See also: + ## * `absolutePath proc <#absolutePath,string>`_ + ## * `normalizedPath proc <#normalizedPath,string>`_ for a version which returns + ## a new string + runnableExamples: + var a = "a///b//..//c///d" + a.normalizePath() + assert a == "a/c/d" + path = pathnorm.normalizePath(path) when false: let isAbs = isAbsolute(path) @@ -984,7 +1284,13 @@ proc normalizePath*(path: var string) {.rtl, extern: "nos$1", tags: [], noNimScr path = "." proc normalizedPath*(path: string): string {.rtl, extern: "nos$1", tags: [], noNimScript.} = - ## Returns a normalized path for the current OS. See `<#normalizePath>`_ + ## Returns a normalized path for the current OS. + ## + ## See also: + ## * `absolutePath proc <#absolutePath,string>`_ + ## * `normalizePath proc <#normalizePath,string>`_ for the in-place version + runnableExamples: + assert normalizedPath("a///b//..//c///d") == "a/c/d" result = pathnorm.normalizePath(path) when defined(Windows) and not weirdTarget: @@ -1010,11 +1316,16 @@ when defined(Windows) and not weirdTarget: proc sameFile*(path1, path2: string): bool {.rtl, extern: "nos$1", tags: [ReadDirEffect], noNimScript.} = ## Returns true if both pathname arguments refer to the same physical - ## file or directory. Raises an exception if any of the files does not + ## file or directory. + ## + ## Raises `OSError` if any of the files does not ## exist or information about it can not be obtained. ## ## This proc will return true if given two alternative hard-linked or ## sym-linked paths to the same file or directory. + ## + ## See also: + ## * `sameFileContent proc <#sameFileContent,string,string>`_ when defined(Windows): var success = true var f1 = openHandle(path1) @@ -1051,6 +1362,9 @@ proc sameFileContent*(path1, path2: string): bool {.rtl, extern: "nos$1", tags: [ReadIOEffect], noNimScript.} = ## Returns true if both pathname arguments refer to files with identical ## binary content. + ## + ## See also: + ## * `sameFile proc <#sameFile,string,string>`_ const bufSize = 8192 # 8K buffer var @@ -1079,7 +1393,12 @@ proc sameFileContent*(path1, path2: string): bool {.rtl, extern: "nos$1", close(b) type - FilePermission* = enum ## file access permission; modelled after UNIX + FilePermission* = enum ## File access permission, modelled after UNIX. + ## + ## See also: + ## * `getFilePermissions <#getFilePermissions,string>`_ + ## * `setFilePermissions <#setFilePermissions,string,set[FilePermission]>`_ + ## * `FileInfo object <#FileInfo>`_ fpUserExec, ## execute access for the file owner fpUserWrite, ## write access for the file owner fpUserRead, ## read access for the file owner @@ -1092,9 +1411,15 @@ type proc getFilePermissions*(filename: string): set[FilePermission] {. rtl, extern: "nos$1", tags: [ReadDirEffect], noNimScript.} = - ## retrieves file permissions for `filename`. `OSError` is raised in case of - ## an error. On Windows, only the ``readonly`` flag is checked, every other + ## Retrieves file permissions for `filename`. + ## + ## `OSError` is raised in case of an error. + ## On Windows, only the ``readonly`` flag is checked, every other ## permission is available in any case. + ## + ## See also: + ## * `setFilePermissions proc <#setFilePermissions,string,set[FilePermission]>`_ + ## * `FilePermission enum <#FilePermission>`_ when defined(posix): var a: Stat if stat(filename, a) < 0'i32: raiseOSError(osLastError()) @@ -1124,9 +1449,15 @@ proc getFilePermissions*(filename: string): set[FilePermission] {. proc setFilePermissions*(filename: string, permissions: set[FilePermission]) {. rtl, extern: "nos$1", tags: [WriteDirEffect], noNimScript.} = - ## sets the file permissions for `filename`. `OSError` is raised in case of - ## an error. On Windows, only the ``readonly`` flag is changed, depending on - ## ``fpUserWrite``. + ## Sets the file permissions for `filename`. + ## + ## `OSError` is raised in case of an error. + ## On Windows, only the ``readonly`` flag is changed, depending on + ## ``fpUserWrite`` permission. + ## + ## See also: + ## * `getFilePermissions <#getFilePermissions,string>`_ + ## * `FilePermission enum <#FilePermission>`_ when defined(posix): var p = 0'i32 if fpUserRead in permissions: p = p or S_IRUSR @@ -1162,14 +1493,29 @@ proc copyFile*(source, dest: string) {.rtl, extern: "nos$1", tags: [ReadIOEffect, WriteIOEffect], noNimScript.} = ## Copies a file from `source` to `dest`. ## - ## If this fails, `OSError` is raised. On the Windows platform this proc will - ## copy the source file's attributes into dest. On other platforms you need - ## to use `getFilePermissions() <#getFilePermissions>`_ and - ## `setFilePermissions() <#setFilePermissions>`_ to copy them by hand (or use - ## the convenience `copyFileWithPermissions() <#copyFileWithPermissions>`_ - ## proc), otherwise `dest` will inherit the default permissions of a newly - ## created file for the user. If `dest` already exists, the file attributes + ## If this fails, `OSError` is raised. + ## + ## On the Windows platform this proc will + ## copy the source file's attributes into dest. + ## + ## On other platforms you need + ## to use `getFilePermissions <#getFilePermissions,string>`_ and + ## `setFilePermissions <#setFilePermissions,string,set[FilePermission]>`_ procs + ## to copy them by hand (or use the convenience `copyFileWithPermissions + ## proc <#copyFileWithPermissions,string,string>`_), + ## otherwise `dest` will inherit the default permissions of a newly + ## created file for the user. + ## + ## If `dest` already exists, the file attributes ## will be preserved and the content overwritten. + ## + ## See also: + ## * `copyDir proc <#copyDir,string,string>`_ + ## * `copyFileWithPermissions proc <#copyFileWithPermissions,string,string>`_ + ## * `tryRemoveFile proc <#tryRemoveFile,string>`_ + ## * `removeFile proc <#removeFile,string>`_ + ## * `moveFile proc <#moveFile,string,string>`_ + when defined(Windows): when useWinUnicode: let s = newWideCString(source) @@ -1221,9 +1567,18 @@ when defined(Windows) and not weirdTarget: setFileAttributesA(file, attrs) proc tryRemoveFile*(file: string): bool {.rtl, extern: "nos$1", tags: [WriteDirEffect], noNimScript.} = - ## Removes the `file`. If this fails, returns `false`. This does not fail + ## Removes the `file`. + ## + ## If this fails, returns `false`. This does not fail ## if the file never existed in the first place. + ## ## On Windows, ignores the read-only attribute. + ## + ## See also: + ## * `copyFile proc <#copyFile,string,string>`_ + ## * `copyFileWithPermissions proc <#copyFileWithPermissions,string,string>`_ + ## * `removeFile proc <#removeFile,string>`_ + ## * `moveFile proc <#moveFile,string,string>`_ result = true when defined(Windows): when useWinUnicode: @@ -1244,9 +1599,19 @@ proc tryRemoveFile*(file: string): bool {.rtl, extern: "nos$1", tags: [WriteDirE result = false proc removeFile*(file: string) {.rtl, extern: "nos$1", tags: [WriteDirEffect], noNimScript.} = - ## Removes the `file`. If this fails, `OSError` is raised. This does not fail + ## Removes the `file`. + ## + ## If this fails, `OSError` is raised. This does not fail ## if the file never existed in the first place. + ## ## On Windows, ignores the read-only attribute. + ## + ## See also: + ## * `removeDir proc <#removeDir,string>`_ + ## * `copyFile proc <#copyFile,string,string>`_ + ## * `copyFileWithPermissions proc <#copyFileWithPermissions,string,string>`_ + ## * `tryRemoveFile proc <#tryRemoveFile,string>`_ + ## * `moveFile proc <#moveFile,string,string>`_ if not tryRemoveFile(file): when defined(Windows): raiseOSError(osLastError()) @@ -1254,9 +1619,11 @@ proc removeFile*(file: string) {.rtl, extern: "nos$1", tags: [WriteDirEffect], n raiseOSError(osLastError(), $strerror(errno)) proc tryMoveFSObject(source, dest: string): bool {.noNimScript.} = - ## Moves a file or directory from `source` to `dest`. Returns false in case - ## of `EXDEV` error. In case of other errors `OSError` is raised. Returns - ## true in case of success. + ## Moves a file or directory from `source` to `dest`. + ## + ## Returns false in case of `EXDEV` error. + ## In case of other errors `OSError` is raised. + ## Returns true in case of success. when defined(Windows): when useWinUnicode: let s = newWideCString(source) @@ -1275,8 +1642,19 @@ proc tryMoveFSObject(source, dest: string): bool {.noNimScript.} = proc moveFile*(source, dest: string) {.rtl, extern: "nos$1", tags: [ReadIOEffect, WriteIOEffect], noNimScript.} = - ## Moves a file from `source` to `dest`. If this fails, `OSError` is raised. - ## Can be used to `rename files`:idx: + ## Moves a file from `source` to `dest`. + ## + ## If this fails, `OSError` is raised. + ## + ## Can be used to `rename files`:idx:. + ## + ## See also: + ## * `moveDir proc <#moveDir,string,string>`_ + ## * `copyFile proc <#copyFile,string,string>`_ + ## * `copyFileWithPermissions proc <#copyFileWithPermissions,string,string>`_ + ## * `removeFile proc <#removeFile,string>`_ + ## * `tryRemoveFile proc <#tryRemoveFile,string>`_ + if not tryMoveFSObject(source, dest): when not defined(windows): # Fallback to copy & del @@ -1288,7 +1666,7 @@ proc moveFile*(source, dest: string) {.rtl, extern: "nos$1", raise proc exitStatusLikeShell*(status: cint): cint = - ## converts exit code from `c_system` into a shell exit code + ## Converts exit code from `c_system` into a shell exit code. when defined(posix) and not weirdTarget: if WIFSIGNALED(status): # like the shell! @@ -1304,9 +1682,16 @@ proc execShellCmd*(command: string): int {.rtl, extern: "nos$1", ## ## Command has the form 'program args' where args are the command ## line arguments given to program. The proc returns the error code - ## of the shell when it has finished. The proc does not return until - ## the process has finished. To execute a program without having a - ## shell involved, use `osproc.execProcess`. + ## of the shell when it has finished (zero if there is no error). + ## The proc does not return until the process has finished. + ## + ## To execute a program without having a shell involved, use `osproc.execProcess proc + ## `_. + ## + ## **Examples:** + ## + ## .. code-block:: + ## discard execShellCmd("ls -la") result = exitStatusLikeShell(c_system(command)) # Templates for filtering directories and files @@ -1368,32 +1753,51 @@ template walkCommon(pattern: string, filter) = iterator walkPattern*(pattern: string): string {.tags: [ReadDirEffect], noNimScript.} = ## Iterate over all the files and directories that match the `pattern`. - ## On POSIX this uses the `glob`:idx: call. ## - ## `pattern` is OS dependent, but at least the "\*.ext" + ## On POSIX this uses the `glob`:idx: call. + ## `pattern` is OS dependent, but at least the `"\*.ext"` ## notation is supported. + ## + ## See also: + ## * `walkFiles iterator <#walkFiles.i,string>`_ + ## * `walkDirs iterator <#walkDirs.i,string>`_ + ## * `walkDir iterator <#walkDir.i,string>`_ + ## * `walkDirRec iterator <#walkDirRec.i,string>`_ walkCommon(pattern, defaultWalkFilter) iterator walkFiles*(pattern: string): string {.tags: [ReadDirEffect], noNimScript.} = - ## Iterate over all the files that match the `pattern`. On POSIX this uses - ## the `glob`:idx: call. + ## Iterate over all the files that match the `pattern`. ## - ## `pattern` is OS dependent, but at least the "\*.ext" + ## On POSIX this uses the `glob`:idx: call. + ## `pattern` is OS dependent, but at least the `"\*.ext"` ## notation is supported. + ## + ## See also: + ## * `walkPattern iterator <#walkPattern.i,string>`_ + ## * `walkDirs iterator <#walkDirs.i,string>`_ + ## * `walkDir iterator <#walkDir.i,string>`_ + ## * `walkDirRec iterator <#walkDirRec.i,string>`_ walkCommon(pattern, isFile) iterator walkDirs*(pattern: string): string {.tags: [ReadDirEffect], noNimScript.} = ## Iterate over all the directories that match the `pattern`. - ## On POSIX this uses the `glob`:idx: call. ## - ## `pattern` is OS dependent, but at least the "\*.ext" + ## On POSIX this uses the `glob`:idx: call. + ## `pattern` is OS dependent, but at least the `"\*.ext"` ## notation is supported. + ## + ## See also: + ## * `walkPattern iterator <#walkPattern.i,string>`_ + ## * `walkFiles iterator <#walkFiles.i,string>`_ + ## * `walkDir iterator <#walkDir.i,string>`_ + ## * `walkDirRec iterator <#walkDirRec.i,string>`_ walkCommon(pattern, isDir) proc expandFilename*(filename: string): string {.rtl, extern: "nos$1", tags: [ReadDirEffect], noNimScript.} = - ## Returns the full (`absolute`:idx:) path of an existing file `filename`, - ## raises OSError in case of an error. Follows symlinks. + ## Returns the full (`absolute`:idx:) path of an existing file `filename`. + ## + ## Raises `OSError` in case of an error. Follows symlinks. when defined(windows): var bufsize = MAX_PATH.int32 when useWinUnicode: @@ -1440,13 +1844,19 @@ proc expandFilename*(filename: string): string {.rtl, extern: "nos$1", type PathComponent* = enum ## Enumeration specifying a path component. + ## + ## See also: + ## * `walkDirRec iterator <#walkDirRec.i,string>`_ + ## * `FileInfo object <#FileInfo>`_ pcFile, ## path refers to a file pcLinkToFile, ## path refers to a symbolic link to a file pcDir, ## path refers to a directory pcLinkToDir ## path refers to a symbolic link to a directory proc getCurrentCompilerExe*(): string {.compileTime.} = discard - ## `getAppFilename` at CT; can be used to retrive the currently executing + ## This is `getAppFilename() <#getAppFilename>`_ at compile time. + ## + ## Can be used to retrive the currently executing ## Nim compiler from a Nim or nimscript program, or the nimble binary ## inside a nimble program (likewise with other binaries built from ## compiler API). @@ -1467,10 +1877,11 @@ proc staticWalkDir(dir: string; relative: bool): seq[ iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path: string] {. tags: [ReadDirEffect].} = - ## walks over the directory `dir` and yields for each directory or file in - ## `dir`. The component type and full path for each item is returned. - ## Walking is not recursive. If ``relative`` is true the resulting path is - ## shortened to be relative to ``dir``. + ## Walks over the directory `dir` and yields for each directory or file in + ## `dir`. The component type and full path for each item are returned. + ## + ## Walking is not recursive. If ``relative`` is true (default: false) + ## the resulting path is shortened to be relative to ``dir``. ## Example: This directory structure:: ## dirA / dirB / fileB1.txt ## / dirC @@ -1483,11 +1894,18 @@ iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path: ## for kind, path in walkDir("dirA"): ## echo(path) ## - ## produces this output (but not necessarily in this order!):: + ## produce this output (but not necessarily in this order!):: ## dirA/dirB ## dirA/dirC ## dirA/fileA1.txt ## dirA/fileA2.txt + ## + ## See also: + ## * `walkPattern iterator <#walkPattern.i,string>`_ + ## * `walkFiles iterator <#walkFiles.i,string>`_ + ## * `walkDirs iterator <#walkDirs.i,string>`_ + ## * `walkDirRec iterator <#walkDirRec.i,string>`_ + when nimvm: for k, v in items(staticWalkDir(dir, relative)): yield (k, v) @@ -1554,19 +1972,20 @@ iterator walkDirRec*(dir: string, relative = false): string {.tags: [ReadDirEffect].} = ## Recursively walks over the directory `dir` and yields for each file ## or directory in `dir`. - ## If ``relative`` is true the resulting path is + ## + ## If ``relative`` is true (default: false) the resulting path is ## shortened to be relative to ``dir``, otherwise the full path is returned. ## ## **Warning**: ## Modifying the directory structure while the iterator ## is traversing may result in undefined behavior! ## - ## Walking is recursive. `filters` controls the behaviour of the iterator: + ## Walking is recursive. `followFilter` controls the behaviour of the iterator: ## ## --------------------- --------------------------------------------- ## yieldFilter meaning ## --------------------- --------------------------------------------- - ## ``pcFile`` yield real files + ## ``pcFile`` yield real files (default) ## ``pcLinkToFile`` yield symbolic links to files ## ``pcDir`` yield real directories ## ``pcLinkToDir`` yield symbolic links to directories @@ -1575,10 +1994,17 @@ iterator walkDirRec*(dir: string, ## --------------------- --------------------------------------------- ## followFilter meaning ## --------------------- --------------------------------------------- - ## ``pcDir`` follow real directories + ## ``pcDir`` follow real directories (default) ## ``pcLinkToDir`` follow symbolic links to directories ## --------------------- --------------------------------------------- ## + ## + ## See also: + ## * `walkPattern iterator <#walkPattern.i,string>`_ + ## * `walkFiles iterator <#walkFiles.i,string>`_ + ## * `walkDirs iterator <#walkDirs.i,string>`_ + ## * `walkDir iterator <#walkDir.i,string>`_ + var stack = @[""] while stack.len > 0: let d = stack.pop() @@ -1609,6 +2035,15 @@ proc removeDir*(dir: string) {.rtl, extern: "nos$1", tags: [ ## ## If this fails, `OSError` is raised. This does not fail if the directory never ## existed in the first place. + ## + ## See also: + ## * `tryRemoveFile proc <#tryRemoveFile,string>`_ + ## * `removeFile proc <#removeFile,string>`_ + ## * `existsOrCreateDir proc <#existsOrCreateDir,string>`_ + ## * `createDir proc <#createDir,string>`_ + ## * `copyDir proc <#copyDir,string,string>`_ + ## * `copyDirWithPermissions proc <#copyDirWithPermissions,string,string>`_ + ## * `moveDir proc <#moveDir,string,string>`_ for kind, path in walkDir(dir): case kind of pcFile, pcLinkToFile, pcLinkToDir: removeFile(path) @@ -1666,6 +2101,13 @@ proc existsOrCreateDir*(dir: string): bool {.rtl, extern: "nos$1", ## Does not create parent directories (fails if parent does not exist). ## Returns `true` if the directory already exists, and `false` ## otherwise. + ## + ## See also: + ## * `removeDir proc <#removeDir,string>`_ + ## * `createDir proc <#createDir,string>`_ + ## * `copyDir proc <#copyDir,string,string>`_ + ## * `copyDirWithPermissions proc <#copyDirWithPermissions,string,string>`_ + ## * `moveDir proc <#moveDir,string,string>`_ result = not rawCreateDir(dir) if result: # path already exists - need to check that it is indeed a directory @@ -1677,9 +2119,17 @@ proc createDir*(dir: string) {.rtl, extern: "nos$1", ## Creates the `directory`:idx: `dir`. ## ## The directory may contain several subdirectories that do not exist yet. - ## The full path is created. If this fails, `OSError` is raised. It does **not** - ## fail if the directory already exists because for most usages this does not - ## indicate an error. + ## The full path is created. If this fails, `OSError` is raised. + ## + ## It does **not** fail if the directory already exists because for + ## most usages this does not indicate an error. + ## + ## See also: + ## * `removeDir proc <#removeDir,string>`_ + ## * `existsOrCreateDir proc <#existsOrCreateDir,string>`_ + ## * `copyDir proc <#copyDir,string,string>`_ + ## * `copyDirWithPermissions proc <#copyDirWithPermissions,string,string>`_ + ## * `moveDir proc <#moveDir,string,string>`_ var omitNext = false when doslikeFileSystem: omitNext = isAbsolute(dir) @@ -1699,11 +2149,24 @@ proc copyDir*(source, dest: string) {.rtl, extern: "nos$1", tags: [WriteIOEffect, ReadIOEffect], benign, noNimScript.} = ## Copies a directory from `source` to `dest`. ## - ## If this fails, `OSError` is raised. On the Windows platform this proc will - ## copy the attributes from `source` into `dest`. On other platforms created - ## files and directories will inherit the default permissions of a newly - ## created file/directory for the user. To preserve attributes recursively on - ## these platforms use `copyDirWithPermissions() <#copyDirWithPermissions>`_. + ## If this fails, `OSError` is raised. + ## + ## On the Windows platform this proc will copy the attributes from + ## `source` into `dest`. + ## + ## On other platforms created files and directories will inherit the + ## default permissions of a newly created file/directory for the user. + ## Use `copyDirWithPermissions proc <#copyDirWithPermissions,string,string>`_ + ## to preserve attributes recursively on these platforms. + ## + ## See also: + ## * `copyDirWithPermissions proc <#copyDirWithPermissions,string,string>`_ + ## * `copyFile proc <#copyFile,string,string>`_ + ## * `copyFileWithPermissions proc <#copyFileWithPermissions,string,string>`_ + ## * `removeDir proc <#removeDir,string>`_ + ## * `existsOrCreateDir proc <#existsOrCreateDir,string>`_ + ## * `createDir proc <#createDir,string>`_ + ## * `moveDir proc <#moveDir,string,string>`_ createDir(dest) for kind, path in walkDir(source): var noSource = splitPath(path).tail @@ -1714,6 +2177,24 @@ proc copyDir*(source, dest: string) {.rtl, extern: "nos$1", copyDir(path, dest / noSource) else: discard +proc moveDir*(source, dest: string) {.tags: [ReadIOEffect, WriteIOEffect], noNimScript.} = + ## Moves a directory from `source` to `dest`. + ## + ## If this fails, `OSError` is raised. + ## + ## See also: + ## * `moveFile proc <#moveFile,string,string>`_ + ## * `copyDir proc <#copyDir,string,string>`_ + ## * `copyDirWithPermissions proc <#copyDirWithPermissions,string,string>`_ + ## * `removeDir proc <#removeDir,string>`_ + ## * `existsOrCreateDir proc <#existsOrCreateDir,string>`_ + ## * `createDir proc <#createDir,string>`_ + if not tryMoveFSObject(source, dest): + when not defined(windows): + # Fallback to copy & del + copyDir(source, dest) + removeDir(source) + proc createSymlink*(src, dest: string) {.noNimScript.} = ## Create a symbolic link at `dest` which points to the item specified ## by `src`. On most operating systems, will fail if a link already exists. @@ -1721,6 +2202,11 @@ proc createSymlink*(src, dest: string) {.noNimScript.} = ## **Warning**: ## Some OS's (such as Microsoft Windows) restrict the creation ## of symlinks to root users (administrators). + ## + ## See also: + ## * `createHardlink proc <#createHardlink,string,string>`_ + ## * `expandSymlink proc <#expandSymlink,string>`_ + when defined(Windows): # 2 is the SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE. This allows # anyone with developer mode on to create a link @@ -1743,6 +2229,9 @@ proc createHardlink*(src, dest: string) {.noNimScript.} = ## ## **Warning**: Some OS's restrict the creation of hard links to ## root users (administrators). + ## + ## See also: + ## * `createSymlink proc <#createSymlink,string,string>`_ when defined(Windows): when useWinUnicode: var wSrc = newWideCString(src) @@ -1756,13 +2245,128 @@ proc createHardlink*(src, dest: string) {.noNimScript.} = if link(src, dest) != 0: raiseOSError(osLastError()) +proc copyFileWithPermissions*(source, dest: string, + ignorePermissionErrors = true) {.noNimScript.} = + ## Copies a file from `source` to `dest` preserving file permissions. + ## + ## This is a wrapper proc around `copyFile <#copyFile,string,string>`_, + ## `getFilePermissions <#getFilePermissions,string>`_ and + ## `setFilePermissions<#setFilePermissions,string,set[FilePermission]>`_ + ## procs on non-Windows platforms. + ## + ## On Windows this proc is just a wrapper for `copyFile proc + ## <#copyFile,string,string>`_ since that proc already copies attributes. + ## + ## On non-Windows systems permissions are copied after the file itself has + ## been copied, which won't happen atomically and could lead to a race + ## condition. If `ignorePermissionErrors` is true (default), errors while + ## reading/setting file attributes will be ignored, otherwise will raise + ## `OSError`. + ## + ## See also: + ## * `copyFile proc <#copyFile,string,string>`_ + ## * `copyDir proc <#copyDir,string,string>`_ + ## * `tryRemoveFile proc <#tryRemoveFile,string>`_ + ## * `removeFile proc <#removeFile,string>`_ + ## * `moveFile proc <#moveFile,string,string>`_ + ## * `copyDirWithPermissions proc <#copyDirWithPermissions,string,string>`_ + copyFile(source, dest) + when not defined(Windows): + try: + setFilePermissions(dest, getFilePermissions(source)) + except: + if not ignorePermissionErrors: + raise + +proc copyDirWithPermissions*(source, dest: string, + ignorePermissionErrors = true) {.rtl, extern: "nos$1", + tags: [WriteIOEffect, ReadIOEffect], benign, noNimScript.} = + ## Copies a directory from `source` to `dest` preserving file permissions. + ## + ## If this fails, `OSError` is raised. This is a wrapper proc around `copyDir + ## <#copyDir,string,string>`_ and `copyFileWithPermissions + ## <#copyFileWithPermissions,string,string>`_ procs + ## on non-Windows platforms. + ## + ## On Windows this proc is just a wrapper for `copyDir proc + ## <#copyDir,string,string>`_ since that proc already copies attributes. + ## + ## On non-Windows systems permissions are copied after the file or directory + ## itself has been copied, which won't happen atomically and could lead to a + ## race condition. If `ignorePermissionErrors` is true (default), errors while + ## reading/setting file attributes will be ignored, otherwise will raise + ## `OSError`. + ## + ## See also: + ## * `copyDir proc <#copyDir,string,string>`_ + ## * `copyFile proc <#copyFile,string,string>`_ + ## * `copyFileWithPermissions proc <#copyFileWithPermissions,string,string>`_ + ## * `removeDir proc <#removeDir,string>`_ + ## * `moveDir proc <#moveDir,string,string>`_ + ## * `existsOrCreateDir proc <#existsOrCreateDir,string>`_ + ## * `createDir proc <#createDir,string>`_ + createDir(dest) + when not defined(Windows): + try: + setFilePermissions(dest, getFilePermissions(source)) + except: + if not ignorePermissionErrors: + raise + for kind, path in walkDir(source): + var noSource = splitPath(path).tail + case kind + of pcFile: + copyFileWithPermissions(path, dest / noSource, ignorePermissionErrors) + of pcDir: + copyDirWithPermissions(path, dest / noSource, ignorePermissionErrors) + else: discard + +proc inclFilePermissions*(filename: string, + permissions: set[FilePermission]) {. + rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect], noNimScript.} = + ## A convenience proc for: + ## + ## .. code-block:: nim + ## setFilePermissions(filename, getFilePermissions(filename)+permissions) + setFilePermissions(filename, getFilePermissions(filename)+permissions) + +proc exclFilePermissions*(filename: string, + permissions: set[FilePermission]) {. + rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect], noNimScript.} = + ## A convenience proc for: + ## + ## .. code-block:: nim + ## setFilePermissions(filename, getFilePermissions(filename)-permissions) + setFilePermissions(filename, getFilePermissions(filename)-permissions) + +proc expandSymlink*(symlinkPath: string): string {.noNimScript.} = + ## Returns a string representing the path to which the symbolic link points. + ## + ## On Windows this is a noop, ``symlinkPath`` is simply returned. + ## + ## See also: + ## * `createSymlink proc <#createSymlink,string,string>`_ + when defined(windows): + result = symlinkPath + else: + result = newString(256) + var len = readlink(symlinkPath, result, 256) + if len < 0: + raiseOSError(osLastError()) + if len > 256: + result = newString(len+1) + len = readlink(symlinkPath, result, len) + setLen(result, len) + proc parseCmdLine*(c: string): seq[string] {. noSideEffect, rtl, extern: "nos$1".} = - ## Splits a `command line`:idx: into several components; - ## This proc is only occasionally useful, better use the `parseopt` module. + ## Splits a `command line`:idx: into several components. + ## + ## **Note**: This proc is only occasionally useful, better use the + ## `parseopt module `_. ## - ## On Windows, it uses the following parsing rules - ## (see http://msdn.microsoft.com/en-us/library/17w5ykft.aspx ): + ## On Windows, it uses the `following parsing rules + ## `_: ## ## * Arguments are delimited by white space, which is either a space or a tab. ## * The caret character (^) is not recognized as an escape character or @@ -1787,6 +2391,13 @@ proc parseCmdLine*(c: string): seq[string] {. ## On Posix systems, it uses the following parsing rules: ## Components are separated by whitespace unless the whitespace ## occurs within ``"`` or ``'`` quotes. + ## + ## See also: + ## * `parseopt module `_ + ## * `paramCount proc <#paramCount>`_ + ## * `paramStr proc <#paramStr,int>`_ + ## * `commandLineParams proc <#commandLineParams>`_ + result = @[] var i = 0 var a = "" @@ -1844,102 +2455,6 @@ proc parseCmdLine*(c: string): seq[string] {. inc(i) add(result, a) -proc copyFileWithPermissions*(source, dest: string, - ignorePermissionErrors = true) {.noNimScript.} = - ## Copies a file from `source` to `dest` preserving file permissions. - ## - ## This is a wrapper proc around `copyFile() <#copyFile>`_, - ## `getFilePermissions() <#getFilePermissions>`_ and `setFilePermissions() - ## <#setFilePermissions>`_ on non Windows platform. On Windows this proc is - ## just a wrapper for `copyFile() <#copyFile>`_ since that proc already - ## copies attributes. - ## - ## On non Windows systems permissions are copied after the file itself has - ## been copied, which won't happen atomically and could lead to a race - ## condition. If `ignorePermissionErrors` is true, errors while - ## reading/setting file attributes will be ignored, otherwise will raise - ## `OSError`. - copyFile(source, dest) - when not defined(Windows): - try: - setFilePermissions(dest, getFilePermissions(source)) - except: - if not ignorePermissionErrors: - raise - -proc copyDirWithPermissions*(source, dest: string, - ignorePermissionErrors = true) {.rtl, extern: "nos$1", - tags: [WriteIOEffect, ReadIOEffect], benign, noNimScript.} = - ## Copies a directory from `source` to `dest` preserving file permissions. - ## - ## If this fails, `OSError` is raised. This is a wrapper proc around `copyDir() - ## <#copyDir>`_ and `copyFileWithPermissions() <#copyFileWithPermissions>`_ - ## on non Windows platforms. On Windows this proc is just a wrapper for - ## `copyDir() <#copyDir>`_ since that proc already copies attributes. - ## - ## On non Windows systems permissions are copied after the file or directory - ## itself has been copied, which won't happen atomically and could lead to a - ## race condition. If `ignorePermissionErrors` is true, errors while - ## reading/setting file attributes will be ignored, otherwise will raise - ## `OSError`. - createDir(dest) - when not defined(Windows): - try: - setFilePermissions(dest, getFilePermissions(source)) - except: - if not ignorePermissionErrors: - raise - for kind, path in walkDir(source): - var noSource = splitPath(path).tail - case kind - of pcFile: - copyFileWithPermissions(path, dest / noSource, ignorePermissionErrors) - of pcDir: - copyDirWithPermissions(path, dest / noSource, ignorePermissionErrors) - else: discard - -proc inclFilePermissions*(filename: string, - permissions: set[FilePermission]) {. - rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect], noNimScript.} = - ## a convenience procedure for: - ## - ## .. code-block:: nim - ## setFilePermissions(filename, getFilePermissions(filename)+permissions) - setFilePermissions(filename, getFilePermissions(filename)+permissions) - -proc exclFilePermissions*(filename: string, - permissions: set[FilePermission]) {. - rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect], noNimScript.} = - ## a convenience procedure for: - ## - ## .. code-block:: nim - ## setFilePermissions(filename, getFilePermissions(filename)-permissions) - setFilePermissions(filename, getFilePermissions(filename)-permissions) - -proc moveDir*(source, dest: string) {.tags: [ReadIOEffect, WriteIOEffect], noNimScript.} = - ## Moves a directory from `source` to `dest`. If this fails, `OSError` is raised. - if not tryMoveFSObject(source, dest): - when not defined(windows): - # Fallback to copy & del - copyDir(source, dest) - removeDir(source) - -proc expandSymlink*(symlinkPath: string): string {.noNimScript.} = - ## Returns a string representing the path to which the symbolic link points. - ## - ## On Windows this is a noop, ``symlinkPath`` is simply returned. - when defined(windows): - result = symlinkPath - else: - result = newString(256) - var len = readlink(symlinkPath, result, 256) - if len < 0: - raiseOSError(osLastError()) - if len > 256: - result = newString(len+1) - len = readlink(symlinkPath, result, len) - setLen(result, len) - when defined(nimdoc): # Common forward declaration docstring block for parameter retrieval procs. proc paramCount*(): int {.tags: [ReadIOEffect].} = @@ -1948,14 +2463,21 @@ when defined(nimdoc): ## ## Unlike `argc`:idx: in C, if your binary was called without parameters this ## will return zero. - ## You can query each individual paramater with `paramStr() <#paramStr>`_ - ## or retrieve all of them in one go with `commandLineParams() + ## You can query each individual paramater with `paramStr proc <#paramStr,int>`_ + ## or retrieve all of them in one go with `commandLineParams proc ## <#commandLineParams>`_. ## - ## **Availability**: When generating a dynamic library (see --app:lib) on + ## **Availability**: When generating a dynamic library (see `--app:lib`) on ## Posix this proc is not defined. - ## Test for availability using `declared() `_. - ## Example: + ## Test for availability using `declared() `_. + ## + ## See also: + ## * `parseopt module `_ + ## * `parseCmdLine proc <#parseCmdLine,string>`_ + ## * `paramStr proc <#paramStr,int>`_ + ## * `commandLineParams proc <#commandLineParams>`_ + ## + ## **Examples:** ## ## .. code-block:: nim ## when declared(paramCount): @@ -1976,10 +2498,18 @@ when defined(nimdoc): ## contents (usually the name of the invoked executable). You should avoid ## this and call `getAppFilename() <#getAppFilename>`_ instead. ## - ## **Availability**: When generating a dynamic library (see --app:lib) on + ## **Availability**: When generating a dynamic library (see `--app:lib`) on ## Posix this proc is not defined. - ## Test for availability using `declared() `_. - ## Example: + ## Test for availability using `declared() `_. + ## + ## See also: + ## * `parseopt module `_ + ## * `parseCmdLine proc <#parseCmdLine,string>`_ + ## * `paramCount proc <#paramCount>`_ + ## * `commandLineParams proc <#commandLineParams>`_ + ## * `getAppFilename proc <#getAppFilename>`_ + ## + ## **Examples:** ## ## .. code-block:: nim ## when declared(paramStr): @@ -2052,8 +2582,17 @@ when declared(paramCount) or defined(nimdoc): ## ## **Availability**: On Posix there is no portable way to get the command ## line from a DLL and thus the proc isn't defined in this environment. You - ## can test for its availability with `declared() `_. - ## Example: + ## can test for its availability with `declared() + ## `_. + ## + ## See also: + ## * `parseopt module `_ + ## * `parseCmdLine proc <#parseCmdLine,string>`_ + ## * `paramCount proc <#paramCount>`_ + ## * `paramStr proc <#paramStr,int>`_ + ## * `getAppFilename proc <#getAppFilename>`_ + ## + ## **Examples:** ## ## .. code-block:: nim ## when declared(commandLineParams): @@ -2151,10 +2690,12 @@ when defined(haiku): result = "" proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect], noNimScript.} = - ## Returns the filename of the application's executable. See also - ## `getCurrentCompilerExe`. + ## Returns the filename of the application's executable. + ## This proc will resolve symlinks. ## - ## This procedure will resolve symlinks. + ## See also: + ## * `getAppDir proc <#getAppDir>`_ + ## * `getCurrentCompilerExe proc <#getCurrentCompilerExe>`_ # Linux: /proc//exe # Solaris: @@ -2213,10 +2754,13 @@ proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect], noN proc getAppDir*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect], noNimScript.} = ## Returns the directory of the application's executable. + ## + ## See also: + ## * `getAppFilename proc <#getAppFilename>`_ result = splitFile(getAppFilename()).dir proc sleep*(milsecs: int) {.rtl, extern: "nos$1", tags: [TimeEffect], noNimScript.} = - ## sleeps `milsecs` milliseconds. + ## Sleeps `milsecs` milliseconds. when defined(windows): winlean.sleep(int32(milsecs)) else: @@ -2227,7 +2771,7 @@ proc sleep*(milsecs: int) {.rtl, extern: "nos$1", tags: [TimeEffect], noNimScrip proc getFileSize*(file: string): BiggestInt {.rtl, extern: "nos$1", tags: [ReadIOEffect], noNimScript.} = - ## returns the file size of `file` (in bytes). An ``OSError`` exception is + ## Returns the file size of `file` (in bytes). ``OSError`` is ## raised in case of an error. when defined(windows): var a: WIN32_FIND_DATA @@ -2254,14 +2798,19 @@ else: type FileInfo* = object ## Contains information associated with a file object. - id*: tuple[device: DeviceId, file: FileId] # Device and file id. - kind*: PathComponent # Kind of file object - directory, symlink, etc. - size*: BiggestInt # Size of file. - permissions*: set[FilePermission] # File permissions - linkCount*: BiggestInt # Number of hard links the file object has. - lastAccessTime*: times.Time # Time file was last accessed. - lastWriteTime*: times.Time # Time file was last modified/written to. - creationTime*: times.Time # Time file was created. Not supported on all systems! + ## + ## See also: + ## * `getFileInfo(handle) proc <#getFileInfo,FileHandle>`_ + ## * `getFileInfo(file) proc <#getFileInfo,File>`_ + ## * `getFileInfo(path) proc <#getFileInfo,string>`_ + id*: tuple[device: DeviceId, file: FileId] ## Device and file id. + kind*: PathComponent ## Kind of file object - directory, symlink, etc. + size*: BiggestInt ## Size of file. + permissions*: set[FilePermission] ## File permissions + linkCount*: BiggestInt ## Number of hard links the file object has. + lastAccessTime*: times.Time ## Time file was last accessed. + lastWriteTime*: times.Time ## Time file was last modified/written to. + creationTime*: times.Time ## Time file was created. Not supported on all systems! template rawToFormalFileInfo(rawInfo, path, formalInfo): untyped = ## Transforms the native file info structure into the one nim uses. @@ -2333,7 +2882,12 @@ proc getFileInfo*(handle: FileHandle): FileInfo {.noNimScript.} = ## handle. ## ## If the information cannot be retrieved, such as when the file handle - ## is invalid, an error will be thrown. + ## is invalid, `OSError` is raised. + ## + ## See also: + ## * `getFileInfo(file) proc <#getFileInfo,File>`_ + ## * `getFileInfo(path) proc <#getFileInfo,string>`_ + # Done: ID, Kind, Size, Permissions, Link Count when defined(Windows): var rawInfo: BY_HANDLE_FILE_INFORMATION @@ -2350,6 +2904,11 @@ proc getFileInfo*(handle: FileHandle): FileInfo {.noNimScript.} = rawToFormalFileInfo(rawInfo, "", result) proc getFileInfo*(file: File): FileInfo {.noNimScript.} = + ## Retrieves file information for the file object. + ## + ## See also: + ## * `getFileInfo(handle) proc <#getFileInfo,FileHandle>`_ + ## * `getFileInfo(path) proc <#getFileInfo,string>`_ if file.isNil: raise newException(IOError, "File is nil") result = getFileInfo(file.getFileHandle()) @@ -2358,16 +2917,20 @@ proc getFileInfo*(path: string, followSymlink = true): FileInfo {.noNimScript.} ## Retrieves file information for the file object pointed to by `path`. ## ## Due to intrinsic differences between operating systems, the information - ## contained by the returned `FileInfo` structure will be slightly different - ## across platforms, and in some cases, incomplete or inaccurate. + ## contained by the returned `FileInfo object <#FileInfo>`_ will be slightly + ## different across platforms, and in some cases, incomplete or inaccurate. ## - ## When `followSymlink` is true, symlinks are followed and the information - ## retrieved is information related to the symlink's target. Otherwise, - ## information on the symlink itself is retrieved. + ## When `followSymlink` is true (default), symlinks are followed and the + ## information retrieved is information related to the symlink's target. + ## Otherwise, information on the symlink itself is retrieved. ## ## If the information cannot be retrieved, such as when the path doesn't ## exist, or when permission restrictions prevent the program from retrieving - ## file information, an error will be thrown. + ## file information, `OSError` is raised. + ## + ## See also: + ## * `getFileInfo(handle) proc <#getFileInfo,FileHandle>`_ + ## * `getFileInfo(file) proc <#getFileInfo,File>`_ when defined(Windows): var handle = openHandle(path, followSymlink) @@ -2389,21 +2952,23 @@ proc getFileInfo*(path: string, followSymlink = true): FileInfo {.noNimScript.} rawToFormalFileInfo(rawInfo, path, result) proc isHidden*(path: string): bool {.noNimScript.} = - ## Determines whether ``path`` is hidden or not, using this - ## reference https://en.wikipedia.org/wiki/Hidden_file_and_hidden_directory + ## Determines whether ``path`` is hidden or not, using `this + ## reference `_. ## ## On Windows: returns true if it exists and its "hidden" attribute is set. ## ## On posix: returns true if ``lastPathPart(path)`` starts with ``.`` and is - ## not ``.`` or ``..``. Note: paths are not normalized to determine `isHidden`. + ## not ``.`` or ``..``. + ## + ## **Note**: paths are not normalized to determine `isHidden`. runnableExamples: when defined(posix): - doAssert ".foo".isHidden - doAssert: not ".foo/bar".isHidden - doAssert: not ".".isHidden - doAssert: not "..".isHidden - doAssert: not "".isHidden - doAssert ".foo/".isHidden + assert ".foo".isHidden + assert not ".foo/bar".isHidden + assert not ".".isHidden + assert not "..".isHidden + assert not "".isHidden + assert ".foo/".isHidden when defined(Windows): when useWinUnicode: @@ -2417,7 +2982,10 @@ proc isHidden*(path: string): bool {.noNimScript.} = result = len(fileName) >= 2 and fileName[0] == '.' and fileName != ".." proc getCurrentProcessId*(): int {.noNimScript.} = - ## return current process ID. See also ``osproc.processID(p: Process)``. + ## Return current process ID. + ## + ## See also: + ## * `osproc.processID(p: Process) `_ when defined(windows): proc GetCurrentProcessId(): DWORD {.stdcall, dynlib: "kernel32", importc: "GetCurrentProcessId".} @@ -2444,6 +3012,7 @@ proc setLastModificationTime*(file: string, t: times.Time) {.noNimScript.} = discard h.closeHandle if res == 0'i32: raiseOSError(osLastError()) + when isMainModule: assert quoteShellWindows("aaa") == "aaa" assert quoteShellWindows("aaa\"") == "aaa\\\"" From 0a628f36f077f4ae1162e17549203ba30a8bf1f2 Mon Sep 17 00:00:00 2001 From: Araq Date: Wed, 30 Jan 2019 18:17:40 +0100 Subject: [PATCH 016/202] destructors: do not produce strong backrefs in closure environments so that refcounting works --- compiler/lambdalifting.nim | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/compiler/lambdalifting.nim b/compiler/lambdalifting.nim index ddde1be31143..1c12688a33ff 100644 --- a/compiler/lambdalifting.nim +++ b/compiler/lambdalifting.nim @@ -320,17 +320,30 @@ proc getEnvTypeForOwner(c: var DetectionPass; owner: PSym; rawAddSon(result, obj) c.ownerToType[owner.id] = result +proc getEnvTypeForOwnerUp(c: var DetectionPass; owner: PSym; + info: TLineInfo): PType = + var r = c.getEnvTypeForOwner(owner, info) + result = newType(tyPtr, owner) + rawAddSon(result, r.base) + proc createUpField(c: var DetectionPass; dest, dep: PSym; info: TLineInfo) = let refObj = c.getEnvTypeForOwner(dest, info) # getHiddenParam(dest).typ let obj = refObj.lastSon - let fieldType = c.getEnvTypeForOwner(dep, info) #getHiddenParam(dep).typ + # The assumption here is that gcDestructors means we cannot deal + # with cycles properly, so it's better to produce a weak ref (=ptr) here. + # This seems to be generally correct but since it's a bit risky it's only + # enabled for gcDestructors. + let fieldType = if c.graph.config.selectedGc == gcDestructors: + c.getEnvTypeForOwnerUp(dep, info) #getHiddenParam(dep).typ + else: + c.getEnvTypeForOwner(dest, info) if refObj == fieldType: localError(c.graph.config, dep.info, "internal error: invalid up reference computed") let upIdent = getIdent(c.graph.cache, upName) let upField = lookupInRecord(obj.n, upIdent) if upField != nil: - if upField.typ != fieldType: + if upField.typ.base != fieldType.base: localError(c.graph.config, dep.info, "internal error: up references do not agree") else: let result = newSym(skField, upIdent, obj.owner, obj.owner.info) @@ -555,7 +568,7 @@ proc rawClosureCreation(owner: PSym; let upField = lookupInRecord(env.typ.lastSon.n, getIdent(d.graph.cache, upName)) if upField != nil: let up = getUpViaParam(d.graph, owner) - if up != nil and upField.typ == up.typ: + if up != nil and upField.typ.base == up.typ.base: result.add(newAsgnStmt(rawIndirectAccess(env, upField, env.info), up, env.info)) #elif oldenv != nil and oldenv.typ == upField.typ: @@ -586,7 +599,7 @@ proc closureCreationForIter(iter: PNode; let upField = lookupInRecord(v.typ.lastSon.n, getIdent(d.graph.cache, upName)) if upField != nil: let u = setupEnvVar(owner, d, c) - if u.typ == upField.typ: + if u.typ.base == upField.typ.base: result.add(newAsgnStmt(rawIndirectAccess(vnode, upField, iter.info), u, iter.info)) else: From 2ce9845fe4bd2196fd08a2bca0b3c259ed8838d2 Mon Sep 17 00:00:00 2001 From: Araq Date: Wed, 30 Jan 2019 19:48:21 +0100 Subject: [PATCH 017/202] fixes silly typo causing tons of async regressions --- compiler/lambdalifting.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/lambdalifting.nim b/compiler/lambdalifting.nim index 1c12688a33ff..ba67f0d4e77e 100644 --- a/compiler/lambdalifting.nim +++ b/compiler/lambdalifting.nim @@ -336,7 +336,7 @@ proc createUpField(c: var DetectionPass; dest, dep: PSym; info: TLineInfo) = let fieldType = if c.graph.config.selectedGc == gcDestructors: c.getEnvTypeForOwnerUp(dep, info) #getHiddenParam(dep).typ else: - c.getEnvTypeForOwner(dest, info) + c.getEnvTypeForOwner(dep, info) if refObj == fieldType: localError(c.graph.config, dep.info, "internal error: invalid up reference computed") From 47037ebee32792f5cc0ef292b7f2fc7d4c734936 Mon Sep 17 00:00:00 2001 From: Miran Date: Wed, 30 Jan 2019 19:49:09 +0100 Subject: [PATCH 018/202] fixes #9471 (#10502) --- doc/regexprs.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/regexprs.txt b/doc/regexprs.txt index 5c6d37e898f7..83dbd2eeb04a 100644 --- a/doc/regexprs.txt +++ b/doc/regexprs.txt @@ -80,13 +80,13 @@ meta character meaning ``|`` start of alternative branch ``(`` start subpattern ``)`` end subpattern -``?`` extends the meaning of ``(`` - also 0 or 1 quantifier - also quantifier minimizer -``*`` 0 or more quantifier -``+`` 1 or more quantifier - also "possessive quantifier" ``{`` start min/max quantifier +``?`` extends the meaning of ``(`` + | also 0 or 1 quantifier (equal to ``{0,1}``) + | also quantifier minimizer +``*`` 0 or more quantifier (equal to ``{0,}``) +``+`` 1 or more quantifier (equal to ``{1,}``) + | also "possessive quantifier" ============== ============================================================ From 3d6f2b77b8494265bda01f82746b427c2574da30 Mon Sep 17 00:00:00 2001 From: Miran Date: Wed, 30 Jan 2019 19:50:20 +0100 Subject: [PATCH 019/202] fix #9725 (#10503) --- doc/manual.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/manual.rst b/doc/manual.rst index f8a5887916d9..f17cab106824 100644 --- a/doc/manual.rst +++ b/doc/manual.rst @@ -251,6 +251,10 @@ Another advantage is that it frees the programmer from remembering the exact spelling of an identifier. The exception with respect to the first letter allows common code like ``var foo: Foo`` to be parsed unambiguously. +Note that this rule also applies to keywords, meaning that ``notin`` is +the same as ``notIn`` and ``not_in`` (all-lowercase version (``notin``, ``isnot``) +is the preferred way of writing keywords). + Historically, Nim was a fully `style-insensitive`:idx: language. This meant that it was not case-sensitive and underscores were ignored and there was not even a distinction between ``foo`` and ``Foo``. From acac107fe7fbef2b55cef3264e9e5e10e1f69ea3 Mon Sep 17 00:00:00 2001 From: alaviss Date: Thu, 31 Jan 2019 01:52:21 +0700 Subject: [PATCH 020/202] times: use clock_gettime() for epochTime() (#10499) * times: use clock_gettime() for epochTime() Fixes #10494 * times: remove gettimeofday() As FreeBSD and OSX support CLOCK_REALTIME, there's no reason to keep this code around. --- lib/pure/times.nim | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/lib/pure/times.nim b/lib/pure/times.nim index 0104a97c160a..e2d0b873983d 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -224,9 +224,6 @@ elif defined(posix): cpuClockId {.importc: "CLOCK_THREAD_CPUTIME_ID", header: "".}: Clockid - proc gettimeofday(tp: var Timeval, unused: pointer = nil) - {.importc: "gettimeofday", header: "".} - when not defined(freebsd) and not defined(netbsd) and not defined(openbsd): var timezone {.importc, header: "".}: int when not defined(valgrind_workaround_10121): @@ -1110,12 +1107,6 @@ proc getTime*(): Time {.tags: [TimeEffect], benign.} = let nanos = convert(Milliseconds, Nanoseconds, millis mod convert(Seconds, Milliseconds, 1).int) result = initTime(seconds, nanos) - # I'm not entirely certain if freebsd needs to use `gettimeofday`. - elif defined(macosx) or defined(freebsd): - var a: Timeval - gettimeofday(a) - result = initTime(a.tv_sec.int64, - convert(Microseconds, Nanoseconds, a.tv_usec.int)) elif defined(posix): var ts: Timespec discard clock_gettime(realTimeClockId, ts) @@ -2449,9 +2440,10 @@ when not defined(JS): ## ## ``getTime`` should generally be prefered over this proc. when defined(posix): - var a: Timeval - gettimeofday(a) - result = toBiggestFloat(a.tv_sec.int64) + toFloat(a.tv_usec)*0.00_0001 + var ts: Timespec + discard clock_gettime(realTimeClockId, ts) + result = toBiggestFloat(ts.tv_sec.int64) + + toBiggestFloat(ts.tv_nsec.int64) / 1_000_000_000 elif defined(windows): var f: winlean.FILETIME getSystemTimeAsFileTime(f) From e16310525c6749ceaefe7c75295038081a1d1e09 Mon Sep 17 00:00:00 2001 From: deansher Date: Wed, 30 Jan 2019 16:22:08 -0500 Subject: [PATCH 021/202] Incorporated Araq's initial feedback. --- doc/manual.rst | 99 ++++++++++++++++++++++++-------------------------- 1 file changed, 47 insertions(+), 52 deletions(-) diff --git a/doc/manual.rst b/doc/manual.rst index b9d5499b22dc..09265d3c8d74 100644 --- a/doc/manual.rst +++ b/doc/manual.rst @@ -46,7 +46,7 @@ and ``a ^* b`` is short for ``(a (b a)*)?``. Example:: arrayConstructor = '[' expr ^* ',' ']' -Other parts of Nim, like scoping rules or execution semantics, are +Other parts of Nim, like scoping rules or runtime semantics, are described informally. @@ -78,13 +78,14 @@ Nim code. It is processed by a Nim `compiler`:idx: into an `executable`:idx:. The nature of this executable depends on the compiler implementation; it may, for example, be a native binary or JavaScript source code. -In a typical Nim program, most of the code is compiled into the executable for -execution at `runtime`:idx:. However, some of the code may be executed at +In a typical Nim program, most of the code is compiled into the executable. +However, some of the code may be executed at `compile time`:idx:. This can include constant expressions, macro definitions, and Nim procedures used by macro definitions. Most of the Nim language is supported at compile time, but there are some restrictions -- see `Restrictions on Compile-Time Execution <#restrictions-on-compileminustime-execution>`_ for -details. +details. We use the term `runtime`:idx: to cover both compile-time execution +and code execution in the executable. The compiler parses Nim source code into an internal data structure called the `abstract syntax tree`:idx: (`AST`:idx:). Then, before executing the code or @@ -94,16 +95,16 @@ identifier meanings, and in some cases expression values. An error detected during semantic analysis is called a `static error`:idx:. Errors described in this manual are static errors when not otherwise specified. -An error detected during code execution (whether at compile time or at runtime) -is a `checked execution error`:idx:. The method for reporting such errors is via +A `checked runtime error`:idx: is an error that the implementation detects +and reports at runtime. The method for reporting such errors is via *raising exceptions* or *dying with a fatal error*. However, the implementation -provides a means to disable these `execution-time checks`:idx:. See the section +provides a means to disable these `runtime checks`:idx:. See the section pragmas_ for details. -Whether a checked execution error results in an exception or in a fatal error is +Whether a checked runtime error results in an exception or in a fatal error is implementation specific. Thus the following program is invalid; even though the code purports to catch the `IndexError` from an out-of-bounds array access, the -compiler may instead choose to allow execution to die with a fatal error. +compiler may instead choose to allow the program to die with a fatal error. .. code-block:: nim var a: array[0..1, char] @@ -113,10 +114,10 @@ compiler may instead choose to allow execution to die with a fatal error. except IndexError: echo "invalid index" -An `unchecked execution error`:idx: is an error that is not guaranteed to be +An `unchecked runtime error`:idx: is an error that is not guaranteed to be detected, and can cause the subsequent behavior of the computation to -be arbitrary. Unchecked execution errors cannot occur if only `safe`:idx: -language features are used and if no execution-time checks are disabled. +be arbitrary. Unchecked runtime errors cannot occur if only `safe`:idx: +language features are used and if no runtime checks are disabled. A `constant expression`:idx: is an expression whose value can be computed during semantic analysis of the code in which it appears. It is never an l-value and @@ -710,17 +711,10 @@ Constants and Constant Expressions ================================== A `constant`:idx: is a symbol that is bound to the value of a constant -expression. This is an expression whose value can be computed during -semantic analysis of the code in which it appears. However, constant -expressions are not limited to the capabilities of semantic analysis; they -can use all Nim language features that are supported for -compile-time execution. Compile-time execution is interleaved with semantic -analysis as necessary. A constant's value cannot change after it is first -computed. - -Constant expressions are restricted to depend only on the following categories -of values and operations, because these are either built into the language or -declared and evaluated before semantic analysis of the constant expression: +expression. Constant expressions are restricted to depend only on the following +categories of values and operations, because these are either built into the +language or declared and evaluated before semantic analysis of the constant +expression: * literals * built-in operators @@ -817,9 +811,9 @@ Ordinal types have the following characteristics: the operation of functions as ``inc``, ``ord``, ``dec`` on ordinal types to be defined. - Ordinal values have a smallest possible value. Trying to count further - down than the smallest value gives a checked execution or static error. + down than the smallest value gives a checked runtime or static error. - Ordinal values have a largest possible value. Trying to count further - than the largest value gives a checked execution or static error. + than the largest value gives a checked runtime or static error. Integers, bool, characters and enumeration types (and subranges of these types) belong to ordinal types. For reasons of simplicity of implementation @@ -927,7 +921,7 @@ lowest and highest value of the type. For example: to 5. ``PositiveFloat`` defines a subrange of all positive floating point values. NaN does not belong to any subrange of floating point types. Assigning any other value to a variable of type ``Subrange`` is a -checked execution error (or static error if it can be determined during +checked runtime error (or static error if it can be determined during semantic analysis). Assignments from the base type to one of its subrange types (and vice versa) are allowed. @@ -1237,7 +1231,7 @@ inferred from the type of the first element. All other elements need to be implicitly convertable to this type. Sequences are similar to arrays but of dynamic length which may change -during execution (like strings). Sequences are implemented as growable arrays, +during runtime (like strings). Sequences are implemented as growable arrays, allocating pieces of memory as items are added. A sequence ``S`` is always indexed by integers from 0 to ``len(S)-1`` and its bounds are checked. Sequences can be constructed by the array constructor ``[]`` in conjunction @@ -1271,7 +1265,7 @@ operator, and remove (and get) the last element of a sequence with the The notation ``x[i]`` can be used to access the i-th element of ``x``. -Arrays are always bounds checked (statically or during execution). These +Arrays are always bounds checked (statically or at runtime). These checks can be disabled via pragmas or invoking the compiler with the ``--boundChecks:off`` command line switch. @@ -1378,7 +1372,7 @@ is currently not checked. **Future directions**: GC'ed memory should be allowed in unchecked arrays and there should be an explicit annotation of how the GC is to determine the -execution-time size of the array. +runtime size of the array. @@ -1435,7 +1429,7 @@ can also be defined with indentation instead of ``[]``: age: natural # and an age Objects provide many features that tuples do not. Object provide inheritance and -information hiding. Objects have access to their type during execution, so that +information hiding. Objects have access to their type during at runtime, so that the ``of`` operator can be used to determine the object's type. The ``of`` operator is similar to the ``instanceof`` operator in Java. @@ -1547,7 +1541,7 @@ contexts (``var/ref/ptr IncompleteObject``) in general since the compiler does not yet know the size of the object. To complete an incomplete object the ``package`` pragma has to be used. ``package`` implies ``byref``. -As long as a type ``T`` is incomplete, neither ``sizeof(T)`` nor execution-time +As long as a type ``T`` is incomplete, neither ``sizeof(T)`` nor runtime type information for ``T`` is available. @@ -2909,17 +2903,18 @@ When nimvm statement -------------------- ``nimvm`` is a special symbol, that may be used as expression of ``when nimvm`` -statement to differentiate execution path between runtime and compile time. +statement to differentiate execution path between compile time and the +executable. Example: .. code-block:: nim proc someProcThatMayRunInCompileTime(): bool = when nimvm: - # This code executes at compile time + # This branch is taken at compile time. result = true else: - # This code executes at runtime + # This branch is taken in the executable. result = false const ctValue = someProcThatMayRunInCompileTime() let rtValue = someProcThatMayRunInCompileTime() @@ -4215,7 +4210,7 @@ The exception tree is defined in the `system `_ module. Every exception inherits from ``system.Exception``. Exceptions that indicate programming bugs inherit from ``system.Defect`` (which is a subtype of ``Exception``) and are stricly speaking not catchable as they can also be mapped to an operation -that terminates the whole process. Exceptions that indicate any other execution +that terminates the whole process. Exceptions that indicate any other runtime error that can be caught inherit from ``system.CatchableError`` (which is a subtype of ``Exception``). @@ -5454,19 +5449,19 @@ Macros A macro is a special function that is executed at compile time. Normally the input for a macro is an abstract syntax tree (AST) of the code that is passed to it. The macro can then do -transformations on it and return the transformed AST. The -transformed AST is then passed to the compiler as if the macro -invocation would have been replaced by its result in the source -code. This can be used to implement `domain specific -languages`:idx:. +transformations on it and return the transformed AST. This can be used to +add custom language features and implement `domain specific languages`:idx:. Macro invocation is a case where semantic analyis does **not** entirely proceed -top to bottom and left to right. The compiler must +top to bottom and left to right. Instead, semantic analysis happens at least +twice: -* perform semantic analysis through the end of the macro invocation, -* execute the macro body, -* replace the AST of the macro invocation with the AST returned by the macro, -* and finally repeat semantic analysis of that region of the code. +* Semantic analysis recognizes and resolves the macro invocation. +* The compiler executes the macro body (which may invoke other procs). +* It replaces the AST of the macro invocation with the AST returned by the macro. +* It repeats semantic analysis of that region of the code. +* If the AST returned by the macro contains other macro invocations, + this process iterates. While macros enable advanced compile-time code transformations, they cannot change Nim's syntax. However, this is no real restriction because @@ -6968,7 +6963,7 @@ structure: pure pragma ----------- An object type can be marked with the ``pure`` pragma so that its type field -which is used for execution-time type identification is omitted. This used to be +which is used for runtime type identification is omitted. This used to be necessary for binary compatibility with other compiled languages. An enum type can be marked as ``pure``. Then access of its fields always @@ -7149,7 +7144,7 @@ others may be added later). =============== =============== ============================================ pragma allowed values description =============== =============== ============================================ -checks on|off Turns the code generation for all execution +checks on|off Turns the code generation for all runtime checks on or off. boundChecks on|off Turns the code generation for array bound checks on or off. @@ -7176,7 +7171,7 @@ Example: .. code-block:: nim {.checks: off, optimization: speed.} - # compile without execution-time checks and optimize for speed + # compile without runtime checks and optimize for speed push and pop pragmas @@ -7186,7 +7181,7 @@ but are used to override the settings temporarily. Example: .. code-block:: nim {.push checks: off.} - # compile this section without execution-time checks as it is + # compile this section without runtime checks as it is # speed critical # ... some code ... {.pop.} # restore old settings @@ -8408,9 +8403,9 @@ Top level accesses to ``gdata`` are always allowed so that it can be initialized conveniently. It is *assumed* (but not enforced) that every top level statement is executed before any concurrent action happens. -The ``locks`` section deliberately looks ugly because it has no execution-time +The ``locks`` section deliberately looks ugly because it has no runtime semantics and should not be used directly! It should only be used in templates -that also implement some form of locking during execution: +that also implement some form of locking at runtime: .. code-block:: nim template lock(a: TLock; body: untyped) = @@ -8529,7 +8524,7 @@ single ``locks`` section: Here is how a typical multilock statement can be implemented in Nim. Note how -the execution check is required to ensure a global ordering for two locks ``a`` +the runtime check is required to ensure a global ordering for two locks ``a`` and ``b`` of the same lock level: .. code-block:: nim From abcd9fd2e8120caf6e4588dabe0f22ae0cce07d3 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Wed, 30 Jan 2019 22:19:16 -0800 Subject: [PATCH 022/202] fixup #10466 to use runnableExamples (#10510) --- lib/pure/json.nim | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/lib/pure/json.nim b/lib/pure/json.nim index 4a3d5b43230e..176da1d9dca6 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -710,23 +710,20 @@ proc pretty*(node: JsonNode, indent = 2): string = ## on multiple lines. ## ## Similar to prettyprint in Python. - ## - ## **Examples:** - ## - ## .. code-block:: Nim - ## let j = %* {"name": "Isaac", "books": ["Robot Dreams"], - ## "details": {"age":35, "pi":3.1415}} - ## echo pretty(j) - ## # { - ## # "name": "Isaac", - ## # "books": [ - ## # "Robot Dreams" - ## # ], - ## # "details": { - ## # "age": 35, - ## # "pi": 3.1415 - ## # } - ## # } + runnableExamples: + let j = %* {"name": "Isaac", "books": ["Robot Dreams"], + "details": {"age":35, "pi":3.1415}} + doAssert pretty(j) == """ +{ + "name": "Isaac", + "books": [ + "Robot Dreams" + ], + "details": { + "age": 35, + "pi": 3.1415 + } +}""" result = "" toPretty(result, node, indent) From 81e5a35d56de9fa7b3c19b6bf9c3139e6481a0ed Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Thu, 31 Jan 2019 07:34:26 +0100 Subject: [PATCH 023/202] Show correct address for ipv6 sockets, fixes #7634 (#10505) --- lib/pure/net.nim | 2 +- lib/windows/winlean.nim | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pure/net.nim b/lib/pure/net.nim index a5bdf3ce6a9f..43284f8724f4 100644 --- a/lib/pure/net.nim +++ b/lib/pure/net.nim @@ -1295,7 +1295,7 @@ proc recvFrom*(socket: Socket, data: var string, length: int, if result != -1: data.setLen(result) - address = $inet_ntoa(sockAddress.sin_addr) + address = getAddrString(cast[ptr SockAddr](addr(sockAddress))) port = ntohs(sockAddress.sin_port).Port else: raiseOSError(osLastError()) diff --git a/lib/windows/winlean.nim b/lib/windows/winlean.nim index 6c480d03a006..d1bfbd447743 100644 --- a/lib/windows/winlean.nim +++ b/lib/windows/winlean.nim @@ -896,7 +896,7 @@ proc getProcessTimes*(hProcess: Handle; lpCreationTime, lpExitTime, dynlib: "kernel32", importc: "GetProcessTimes".} type inet_ntop_proc = proc(family: cint, paddr: pointer, pStringBuffer: cstring, - stringBufSize: int32): cstring {.gcsafe, stdcall.} + stringBufSize: int32): cstring {.gcsafe, stdcall, tags: [].} var inet_ntop_real: inet_ntop_proc = nil From fa058773db018405ff218bc8ff4682a192e9131f Mon Sep 17 00:00:00 2001 From: Miran Date: Thu, 31 Jan 2019 08:20:00 +0100 Subject: [PATCH 024/202] fixes #10042 (allow spaces in import) (#10504) This allows spaces in imports, by using the following syntax: * `import "directory with spaces" / subdir / file`, or * `import "directory with spaces/subdir/file"` --- compiler/modulepaths.nim | 1 - tests/dir with space/more spaces/mspace.nim | 1 + tests/dir with space/tspace.nim | 4 ++++ 3 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 tests/dir with space/more spaces/mspace.nim diff --git a/compiler/modulepaths.nim b/compiler/modulepaths.nim index 9e27a2d7d53f..129f719e2528 100644 --- a/compiler/modulepaths.nim +++ b/compiler/modulepaths.nim @@ -114,7 +114,6 @@ proc getModuleName*(conf: ConfigRef; n: PNode): string = try: result = pathSubs(conf, n.strVal, toFullPath(conf, n.info).splitFile().dir) - .replace(" ") except ValueError: localError(conf, n.info, "invalid path: " & n.strVal) result = n.strVal diff --git a/tests/dir with space/more spaces/mspace.nim b/tests/dir with space/more spaces/mspace.nim new file mode 100644 index 000000000000..bc2c90f5eaa9 --- /dev/null +++ b/tests/dir with space/more spaces/mspace.nim @@ -0,0 +1 @@ +proc tenTimes*(x: int): int = 10*x diff --git a/tests/dir with space/tspace.nim b/tests/dir with space/tspace.nim index 59237c9a167a..87a52c271374 100644 --- a/tests/dir with space/tspace.nim +++ b/tests/dir with space/tspace.nim @@ -2,5 +2,9 @@ discard """ output: "Successful" """ # Test for the compiler to be able to compile a Nim file with spaces in the directory name. +# Also test if import of a directory with a space works. +import "more spaces" / mspace + +assert tenTimes(5) == 50 echo("Successful") From ec6e5681daddde1ff81f9235a6f5348d46ab9eff Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Thu, 31 Jan 2019 02:44:11 -0800 Subject: [PATCH 025/202] fix #8063 by adding a testcase for: Deprecation warnings for enum values print twice (#10508) --- tests/deprecated/tdeprecated.nim | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/tests/deprecated/tdeprecated.nim b/tests/deprecated/tdeprecated.nim index 920f350cc6a8..ba8d579adfa0 100644 --- a/tests/deprecated/tdeprecated.nim +++ b/tests/deprecated/tdeprecated.nim @@ -1,8 +1,21 @@ discard """ - nimout: '''tdeprecated.nim(10, 3) Warning: a is deprecated [Deprecated] -tdeprecated.nim(17, 11) Warning: asdf; enum 'Foo' which contains field 'a' is deprecated [Deprecated] + nimout: ''' +tdeprecated.nim(23, 3) Warning: a is deprecated [Deprecated] +tdeprecated.nim(30, 11) Warning: asdf; enum 'Foo' which contains field 'a' is deprecated [Deprecated] +tdeprecated.nim(40, 16) Warning: use fooX instead; fooA is deprecated [Deprecated] +end ''' """ + + + + + + +## line 15 + + + block: var a {.deprecated.}: array[0..11, int] @@ -17,3 +30,13 @@ block t10111: var _ = a +block: # issue #8063 + type + Foo = enum + fooX + + {.deprecated: [fooA: fooX].} + let + foo: Foo = fooA + echo foo + static: echo "end" From ec26bc472d3f9f6c3fc299da00c9b13733b114f8 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Thu, 31 Jan 2019 13:47:51 +0100 Subject: [PATCH 026/202] Add `encodeQuery` and `?` to Uri module --- lib/pure/uri.nim | 56 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/lib/pure/uri.nim b/lib/pure/uri.nim index e49bfb3c6b02..f322718d1a28 100644 --- a/lib/pure/uri.nim +++ b/lib/pure/uri.nim @@ -119,6 +119,33 @@ proc decodeUrl*(s: string, decodePlus=true): string = inc(j) setLen(result, j) +proc encodeQuery*(query: openArray[(string, string)], usePlus=true, omitEq=true): string = + ## Encodes a set of (key, value) parameters into a URL query string. + ## + ## Every (key, value) pair is URL-encoded and written as ``key=value``. If the + ## value is an empty string then the ``=`` is omitted, unless ``omitEq`` is + ## false. + ## The pairs are joined together by a ``&`` character. + ## + ## The ``usePlus`` parameter is passed down to the `encodeUrl` function that + ## is used for the URL encoding of the string values. + ## + ## **See also:** + ## * `encodeUrl proc<#encodeUrl,string>`_ + runnableExamples: + assert encodeQuery({:}) == "" + assert encodeQuery({"a": "1", "b": "2"}) == "a=1&b=2" + assert encodeQuery({"a": "1", "b": ""}) == "a=1&b" + for elem in query: + # Encode the `key = value` pairs and separate them with a '&' + if result.len > 0: result.add('&') + let (key, val) = elem + result.add(encodeUrl(key, usePlus)) + # Omit the '=' if the value string is empty + if not omitEq or val.len > 0: + result.add('=') + result.add(encodeUrl(val, usePlus)) + proc parseAuthority(authority: string, result: var Uri) = var i = 0 var inPort = false @@ -392,6 +419,14 @@ proc `/`*(x: Uri, path: string): Uri = result.path.add '/' result.path.add(path) +proc `?`*(u: Uri, query: openArray[(string, string)]): Uri = + ## Concatenates the query parameters to the specified URI object. + runnableExamples: + let foo = parseUri("https://example.com") / "foo" ? {"bar": "qux"} + assert $foo == "https://example.com/foo?bar=qux" + result = u + result.query = encodeQuery(query) + proc `$`*(u: Uri): string = ## Returns the string representation of the specified URI object. runnableExamples: @@ -676,4 +711,25 @@ when isMainModule: doAssert "https://example.com/about/staff.html?".parseUri().isAbsolute == true doAssert "https://example.com/about/staff.html?parameters".parseUri().isAbsolute == true + # encodeQuery tests + block: + doAssert encodeQuery({:}) == "" + doAssert encodeQuery({"foo": "bar"}) == "foo=bar" + doAssert encodeQuery({"foo": "bar & baz"}) == "foo=bar+%26+baz" + doAssert encodeQuery({"foo": "bar & baz"}, usePlus=false) == "foo=bar%20%26%20baz" + doAssert encodeQuery({"foo": ""}) == "foo" + doAssert encodeQuery({"foo": ""}, omitEq=false) == "foo=" + doAssert encodeQuery({"a": "1", "b": "", "c": "3"}) == "a=1&b&c=3" + doAssert encodeQuery({"a": "1", "b": "", "c": "3"}, omitEq=false) == "a=1&b=&c=3" + + block: + var foo = parseUri("http://example.com") / "foo" ? {"bar": "1", "baz": "qux"} + var foo1 = parseUri("http://example.com/foo?bar=1&baz=qux") + doAssert foo == foo1 + + block: + var foo = parseUri("http://example.com") / "foo" ? {"do": "do", "bar": ""} + var foo1 = parseUri("http://example.com/foo?do=do&bar") + doAssert foo == foo1 + echo("All good!") From 0ecec8362a8a110603fc0a7afd4b3f74287074ef Mon Sep 17 00:00:00 2001 From: Araq Date: Thu, 31 Jan 2019 15:12:36 +0100 Subject: [PATCH 027/202] distros.nim: brew usually requires 'sudo' --- lib/pure/distros.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/distros.nim b/lib/pure/distros.nim index 0f1ffb1abc97..4c531a779e11 100644 --- a/lib/pure/distros.nim +++ b/lib/pure/distros.nim @@ -232,7 +232,7 @@ proc foreignDepInstallCmd*(foreignPackageName: string): (string, bool) = elif defined(haiku): result = ("pkgman install " & p, true) else: - result = ("brew install " & p, false) + result = ("brew install " & p, true) proc foreignDep*(foreignPackageName: string) = ## Registers 'foreignPackageName' to the internal list of foreign deps. From 1d5437e9d2c5800e4b90e87e32acc600c52cf739 Mon Sep 17 00:00:00 2001 From: cooldome Date: Thu, 31 Jan 2019 18:48:39 +0000 Subject: [PATCH 028/202] vm fix for bitwise signed ints (#10507) * fixes #10482 * add missing file * bug fix --- compiler/vmgen.nim | 8 ++++---- tests/vm/tbitops.nim | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 tests/vm/tbitops.nim diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index f87821da4fde..4b1551884fc6 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -987,10 +987,10 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = c.freeTemp(tmp2) of mShlI: genBinaryABCnarrowU(c, n, dest, opcShlInt) - of mAshrI: genBinaryABCnarrow(c, n, dest, opcAshrInt) - of mBitandI: genBinaryABCnarrowU(c, n, dest, opcBitandInt) - of mBitorI: genBinaryABCnarrowU(c, n, dest, opcBitorInt) - of mBitxorI: genBinaryABCnarrowU(c, n, dest, opcBitxorInt) + of mAshrI: genBinaryABC(c, n, dest, opcAshrInt) + of mBitandI: genBinaryABC(c, n, dest, opcBitandInt) + of mBitorI: genBinaryABC(c, n, dest, opcBitorInt) + of mBitxorI: genBinaryABC(c, n, dest, opcBitxorInt) of mAddU: genBinaryABCnarrowU(c, n, dest, opcAddu) of mSubU: genBinaryABCnarrowU(c, n, dest, opcSubu) of mMulU: genBinaryABCnarrowU(c, n, dest, opcMulu) diff --git a/tests/vm/tbitops.nim b/tests/vm/tbitops.nim new file mode 100644 index 000000000000..3d1a8aa0cdc0 --- /dev/null +++ b/tests/vm/tbitops.nim @@ -0,0 +1,39 @@ +discard """ +output: "" +""" + +import strutils + +const x = [1'i32, -1, -10, 10, -10, 10, -20, 30, -40, 50, 7 shl 28, -(7 shl 28), 7 shl 28, -(7 shl 28)] +const y = [-1'i32, 1, -10, -10, 10, 10, -20, -30, 40, 50, 1 shl 30, 1 shl 30, -(1 shl 30), -(1 shl 30)] + + +const res_xor = block: + var tmp: seq[int64] + for i in 0.. Date: Fri, 1 Feb 2019 12:12:10 +0100 Subject: [PATCH 029/202] Fix vm signed xor (#10519) * fix #10482 * undo changes * fix for bitwise not * remove dead opcode --- compiler/vmgen.nim | 7 +++++-- tests/vm/tbitops.nim | 16 +++++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index 4b1551884fc6..7f3ca84ae064 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -1009,7 +1009,7 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = of mLtPtr, mLtU, mLtU64: genBinaryABC(c, n, dest, opcLtu) of mEqProc, mEqRef, mEqUntracedRef: genBinaryABC(c, n, dest, opcEqRef) - of mXor: genBinaryABCnarrowU(c, n, dest, opcXor) + of mXor: genBinaryABC(c, n, dest, opcXor) of mNot: genUnaryABC(c, n, dest, opcNot) of mUnaryMinusI, mUnaryMinusI64: genUnaryABC(c, n, dest, opcUnaryMinusInt) @@ -1018,7 +1018,10 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = of mUnaryPlusI, mUnaryPlusF64: gen(c, n.sons[1], dest) of mBitnotI: genUnaryABC(c, n, dest, opcBitnotInt) - genNarrowU(c, n, dest) + #genNarrowU modified, do not narrow signed types + let t = skipTypes(n.typ, abstractVar-{tyTypeDesc}) + if t.kind in {tyUInt8..tyUInt32} or (t.kind == tyUInt and t.size < 8): + c.gABC(n, opcNarrowU, dest, TRegister(t.size*8)) of mToFloat, mToBiggestFloat, mToInt, mToBiggestInt, mCharToStr, mBoolToStr, mIntToStr, mInt64ToStr, mFloatToStr, mCStrToStr, mStrToStr, mEnumToStr: diff --git a/tests/vm/tbitops.nim b/tests/vm/tbitops.nim index 3d1a8aa0cdc0..90d463ec96ab 100644 --- a/tests/vm/tbitops.nim +++ b/tests/vm/tbitops.nim @@ -7,25 +7,29 @@ import strutils const x = [1'i32, -1, -10, 10, -10, 10, -20, 30, -40, 50, 7 shl 28, -(7 shl 28), 7 shl 28, -(7 shl 28)] const y = [-1'i32, 1, -10, -10, 10, 10, -20, -30, 40, 50, 1 shl 30, 1 shl 30, -(1 shl 30), -(1 shl 30)] - const res_xor = block: var tmp: seq[int64] - for i in 0.. Date: Fri, 1 Feb 2019 19:37:24 +0100 Subject: [PATCH 030/202] quick-fix for os.nim documentation --- lib/pure/os.nim | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 53bf880b648c..878444e7424e 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -425,8 +425,9 @@ proc `/../`*(head, tail: string): string {.noSideEffect.} = ## * `/ proc <#/,string,string>`_ ## * `parentDir proc <#parentDir,string>`_ runnableExamples: - assert "a/b/c" /../ "d/e" == "a/b/d/e" - assert "a" /../ "d/e" == "a/d/e" + when defined(posix): + assert "a/b/c" /../ "d/e" == "a/b/d/e" + assert "a" /../ "d/e" == "a/d/e" let sepPos = parentDirPos(head) if sepPos >= 0: From 237a36075a0af173fc4680e3e65eda246e097244 Mon Sep 17 00:00:00 2001 From: narimiran Date: Fri, 1 Feb 2019 19:44:19 +0100 Subject: [PATCH 031/202] another quickfix for os.nim documentation --- lib/pure/os.nim | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 878444e7424e..e88c3c6e8c25 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -1252,9 +1252,10 @@ proc normalizePath*(path: var string) {.rtl, extern: "nos$1", tags: [], noNimScr ## * `normalizedPath proc <#normalizedPath,string>`_ for a version which returns ## a new string runnableExamples: - var a = "a///b//..//c///d" - a.normalizePath() - assert a == "a/c/d" + when defined(posix): + var a = "a///b//..//c///d" + a.normalizePath() + assert a == "a/c/d" path = pathnorm.normalizePath(path) when false: @@ -1291,7 +1292,8 @@ proc normalizedPath*(path: string): string {.rtl, extern: "nos$1", tags: [], noN ## * `absolutePath proc <#absolutePath,string>`_ ## * `normalizePath proc <#normalizePath,string>`_ for the in-place version runnableExamples: - assert normalizedPath("a///b//..//c///d") == "a/c/d" + when defined(posix): + assert normalizedPath("a///b//..//c///d") == "a/c/d" result = pathnorm.normalizePath(path) when defined(Windows) and not weirdTarget: From 1ac28f6f5e82d8ca1c62ca8d27c60470e8249835 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Nihlg=C3=A5rd?= Date: Fri, 1 Feb 2019 20:05:05 +0100 Subject: [PATCH 032/202] Deprecate the times.countX procs (#10522) --- lib/pure/times.nim | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/pure/times.nim b/lib/pure/times.nim index e2d0b873983d..3fb9ccdb7016 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -2360,26 +2360,38 @@ proc `$`*(time: Time): string {.tags: [], raises: [], benign.} = doAssert $tm == "1970-01-01T00:00:00" & format(dt, "zzz") $time.local -proc countLeapYears*(yearSpan: int): int = +proc countLeapYears*(yearSpan: int): int + {.deprecated.} = ## Returns the number of leap years spanned by a given number of years. ## ## **Note:** For leap years, start date is assumed to be 1 AD. ## counts the number of leap years up to January 1st of a given year. ## Keep in mind that if specified year is a leap year, the leap day ## has not happened before January 1st of that year. + ## + ## **Deprecated since v0.20.0**. (yearSpan - 1) div 4 - (yearSpan - 1) div 100 + (yearSpan - 1) div 400 -proc countDays*(yearSpan: int): int = +proc countDays*(yearSpan: int): int + {.deprecated.} = ## Returns the number of days spanned by a given number of years. + ## + ## **Deprecated since v0.20.0**. (yearSpan - 1) * 365 + countLeapYears(yearSpan) -proc countYears*(daySpan: int): int = +proc countYears*(daySpan: int): int + {.deprecated.} = ## Returns the number of years spanned by a given number of days. + ## + ## **Deprecated since v0.20.0**. ((daySpan - countLeapYears(daySpan div 365)) div 365) -proc countYearsAndDays*(daySpan: int): tuple[years: int, days: int] = +proc countYearsAndDays*(daySpan: int): tuple[years: int, days: int] + {.deprecated.} = ## Returns the number of years spanned by a given number of days and the ## remainder as days. + ## + ## **Deprecated since v0.20.0**. let days = daySpan - countLeapYears(daySpan div 365) result.years = days div 365 result.days = days mod 365 From 2b35a516514872409b38729f13b340ede0f1beaf Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Fri, 1 Feb 2019 14:56:31 -0800 Subject: [PATCH 033/202] brew almost never requires sudo (#10525) --- lib/pure/distros.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/distros.nim b/lib/pure/distros.nim index 4c531a779e11..0f1ffb1abc97 100644 --- a/lib/pure/distros.nim +++ b/lib/pure/distros.nim @@ -232,7 +232,7 @@ proc foreignDepInstallCmd*(foreignPackageName: string): (string, bool) = elif defined(haiku): result = ("pkgman install " & p, true) else: - result = ("brew install " & p, true) + result = ("brew install " & p, false) proc foreignDep*(foreignPackageName: string) = ## Registers 'foreignPackageName' to the internal list of foreign deps. From f4c76bc8e574d998d4ed2052e9349c76015058b7 Mon Sep 17 00:00:00 2001 From: Araq Date: Sat, 2 Feb 2019 00:18:52 +0100 Subject: [PATCH 034/202] testament: revive ability to test specific important Nimble packages --- testament/categories.nim | 71 ++++++++++++++++++-------------- testament/important_packages.nim | 29 +++++++++++++ 2 files changed, 68 insertions(+), 32 deletions(-) create mode 100644 testament/important_packages.nim diff --git a/testament/categories.nim b/testament/categories.nim index e2b5f0a78967..1365690147ea 100644 --- a/testament/categories.nim +++ b/testament/categories.nim @@ -10,6 +10,8 @@ ## Include for the tester that contains test suites that test special features ## of the compiler. +import important_packages + const specialCategories = [ "assert", @@ -448,7 +450,7 @@ if nimbleDir.len == 0: nimbleDir = getHomeDir() / ".nimble" let nimbleExe = findExe("nimble") #packageDir = nimbleDir / "pkgs" # not used - packageIndex = nimbleDir / "packages.json" + packageIndex = nimbleDir / "packages_official.json" proc waitForExitEx(p: Process): int = var outp = outputStream(p) @@ -463,7 +465,7 @@ proc waitForExitEx(p: Process): int = proc getPackageDir(package: string): string = ## TODO - Replace this with dom's version comparison magic. - var commandOutput = execCmdEx("nimble path $#" % package) + let commandOutput = execCmdEx("nimble path $#" % package) if commandOutput.exitCode != QuitSuccess: return "" else: @@ -471,20 +473,26 @@ proc getPackageDir(package: string): string = iterator listPackages(filter: PackageFilter): tuple[name, url: string] = let packageList = parseFile(packageIndex) - for package in packageList.items(): - let - name = package["name"].str - url = package["url"].str - isCorePackage = "nim-lang" in normalize(url) - case filter: - of pfCoreOnly: - if isCorePackage: - yield (name, url) - of pfExtraOnly: - if not isCorePackage: - yield (name, url) - of pfAll: - yield (name, url) + for package in packageList.items: + if package.hasKey("url"): + let name = package["name"].str + if name notin ["nimble", "compiler"]: + let url = package["url"].str + case filter + of pfCoreOnly: + if "nim-lang" in normalize(url): + yield (name, url) + of pfExtraOnly: + if name in important_packages.packages: + yield (name, url) + of pfAll: + yield (name, url) + +proc makeSupTest(test, options: string, cat: Category): TTest = + result.cat = cat + result.name = test + result.options = options + result.startTime = epochTime() proc testNimblePackages(r: var TResults, cat: Category, filter: PackageFilter) = if nimbleExe == "": @@ -495,31 +503,30 @@ proc testNimblePackages(r: var TResults, cat: Category, filter: PackageFilter) = echo("[Warning] - Cannot run nimble tests: Nimble update failed.") return - let packageFileTest = makeTest("PackageFileParsed", "", cat) + let packageFileTest = makeSupTest("PackageFileParsed", "", cat) try: for name, url in listPackages(filter): - var test = makeTest(name, "", cat) - echo(url) - let - installProcess = startProcess(nimbleExe, "", ["install", "-y", name]) - installStatus = waitForExitEx(installProcess) + var test = makeSupTest(url, "", cat) + let buildPath = "pkgstemp" / name + let installProcess = startProcess("git", "", ["clone", url, buildPath]) + let installStatus = waitForExitEx(installProcess) installProcess.close if installStatus != QuitSuccess: r.addResult(test, targetC, "", "", reInstallFailed) - continue - - let - buildPath = getPackageDir(name).strip - buildProcess = startProcess(nimbleExe, buildPath, ["build"]) - buildStatus = waitForExitEx(buildProcess) - buildProcess.close - if buildStatus != QuitSuccess: - r.addResult(test, targetC, "", "", reBuildFailed) - r.addResult(test, targetC, "", "", reSuccess) + else: + let buildProcess = startProcess(nimbleExe, buildPath, ["test"]) + let buildStatus = waitForExitEx(buildProcess) + buildProcess.close + if buildStatus != QuitSuccess: + r.addResult(test, targetC, "", "", reBuildFailed) + else: + r.addResult(test, targetC, "", "", reSuccess) r.addResult(packageFileTest, targetC, "", "", reSuccess) except JsonParsingError: echo("[Warning] - Cannot run nimble tests: Invalid package file.") r.addResult(packageFileTest, targetC, "", "", reBuildFailed) + finally: + removeDir("pkgstemp") # ---------------------------------------------------------------------------- diff --git a/testament/important_packages.nim b/testament/important_packages.nim new file mode 100644 index 000000000000..1ded00b00703 --- /dev/null +++ b/testament/important_packages.nim @@ -0,0 +1,29 @@ +import strutils + +const + packages* = """ + karax + glob + """.splitWhitespace() + +#[ + jester + cligen + libffi + nimongo + nimx + karax + freeimage + regex + nimpy + zero_functional + arraymancer + inim + c2nim + sdl1 + iterutils + gnuplot + nimpb + lazy + choosenim +]# \ No newline at end of file From 17d0ce9c1c32827e5fc40cb486d4e8fb16f377fc Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Sat, 2 Feb 2019 00:21:13 +0100 Subject: [PATCH 035/202] DOM API: make compatible with Karax's requirements (#10517) * DOM API: make compatible with Karax's requirements * make tools\dochack.nim compile again --- lib/js/dom.nim | 889 ++++++++++++++++++++++++++++++++------ tools/dochack/dochack.nim | 45 +- tools/dochack/karax.nim | 344 --------------- 3 files changed, 797 insertions(+), 481 deletions(-) delete mode 100644 tools/dochack/karax.nim diff --git a/lib/js/dom.nim b/lib/js/dom.nim index 668dee822e98..945d6cda1a83 100644 --- a/lib/js/dom.nim +++ b/lib/js/dom.nim @@ -13,6 +13,9 @@ when not defined(js) and not defined(Nimdoc): {.error: "This module only works on the JavaScript platform".} +const + DomApiVersion* = 3 ## the version of DOM API we try to follow. No guarantees though. + type EventTarget* = ref EventTargetObj EventTargetObj {.importc.} = object of RootObj @@ -37,6 +40,38 @@ type onsubmit*: proc (event: Event) {.nimcall.} onunload*: proc (event: Event) {.nimcall.} + # https://developer.mozilla.org/en-US/docs/Web/Events + DomEvent* {.pure.} = enum + Abort = "abort", + BeforeInput = "beforeinput", + Blur = "blur", + Click = "click", + CompositionEnd = "compositionend", + CompositionStart = "compositionstart", + CompositionUpdate = "compositionupdate", + DblClick = "dblclick", + Error = "error", + Focus = "focus", + FocusIn = "focusin", + FocusOut = "focusout", + Input = "input", + KeyDown = "keydown", + KeyPress = "keypress", + KeyUp = "keyup", + Load = "load", + MouseDown = "mousedown", + MouseEnter = "mouseenter", + MouseLeave = "mouseleave", + MouseMove = "mousemove", + MouseOut = "mouseout", + MouseOver = "mouseover", + MouseUp = "mouseup", + Resize = "resize", + Scroll = "scroll", + Select = "select", + Unload = "unload", + Wheel = "wheel" + Window* = ref WindowObj WindowObj {.importc.} = object of EventTargetObj document*: Document @@ -45,24 +80,19 @@ type location*: Location closed*: bool defaultStatus*: cstring - devicePixelRatio*: float innerHeight*, innerWidth*: int - locationbar*: ref TLocationBar - menubar*: ref TMenuBar + locationbar*: ref LocationBar + menubar*: ref MenuBar name*: cstring outerHeight*, outerWidth*: int pageXOffset*, pageYOffset*: int - personalbar*: ref TPersonalBar - scrollbars*: ref TScrollBars - scrollX*: float - scrollY*: float - statusbar*: ref TStatusBar + personalbar*: ref PersonalBar + scrollbars*: ref ScrollBars + statusbar*: ref StatusBar status*: cstring - toolbar*: ref TToolBar - frames*: seq[TFrame] + toolbar*: ref ToolBar + frames*: seq[Frame] screen*: Screen - performance*: Performance - onpopstate*: proc (event: Event) Frame* = ref FrameObj FrameObj {.importc.} = object of WindowObj @@ -135,10 +165,8 @@ type name*: cstring readOnly*: bool options*: seq[OptionElement] + selectedOptions*: seq[OptionElement] clientWidth*, clientHeight*: int - - # https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement - HtmlElement* = ref object of Element contentEditable*: cstring isContentEditable*: bool dir*: cstring @@ -147,6 +175,87 @@ type offsetLeft*: int offsetTop*: int + # https://developer.mozilla.org/en-US/docs/Web/API/ValidityState + ValidityState* = ref ValidityStateObj + ValidityStateObj {.importc.} = object + badInput*: bool + customError*: bool + patternMismatch*: bool + rangeOverflow*: bool + rangeUnderflow*: bool + stepMismatch*: bool + tooLong*: bool + tooShort*: bool + typeMismatch*: bool + valid*: bool + valueMissing*: bool + + # https://developer.mozilla.org/en-US/docs/Web/API/Blob + Blob* = ref BlobObj + BlobObj {.importc.} = object of RootObj + size*: int + `type`*: cstring + + # https://developer.mozilla.org/en-US/docs/Web/API/File + File* = ref FileObj + FileObj {.importc.} = object of Blob + lastModified*: int + name*: cstring + + # https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement + InputElement* = ref InputElementObj + InputElementObj {.importc.} = object of Element + # Properties related to the parent form + formAction*: cstring + formEncType*: cstring + formMethod*: cstring + formNoValidate*: bool + formTarget*: cstring + + # Properties that apply to any type of input element that is not hidden + `type`*: cstring + autofocus*: bool + required*: bool + value*: cstring + validity*: ValidityState + validationMessage*: cstring + willValidate*: bool + + # Properties that apply only to elements of type "checkbox" or "radio" + indeterminate*: bool + + # Properties that apply only to elements of type "image" + alt*: cstring + height*: cstring + src*: cstring + width*: cstring + + # Properties that apply only to elements of type "file" + accept*: cstring + files*: seq[Blob] + + # Properties that apply only to text/number-containing or elements + autocomplete*: cstring + maxLength*: int + size*: int + pattern*: cstring + placeholder*: cstring + min*: cstring + max*: cstring + selectionStart*: int + selectionEnd*: int + selectionDirection*: cstring + + # Properties not yet categorized + dirName*: cstring + accessKey*: cstring + list*: Element + multiple*: bool + labels*: seq[Element] + step*: cstring + valueAsDate*: cstring + valueAsNumber*: float + LinkElement* = ref LinkObj LinkObj {.importc.} = object of ElementObj target*: cstring @@ -176,19 +285,19 @@ type text*: cstring value*: cstring - TextAreaElement* = ref object of ElementObj - value*: cstring - selectionStart*, selectionEnd*: int - selectionDirection*: cstring - rows*, cols*: int - + # https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement FormElement* = ref FormObj FormObj {.importc.} = object of ElementObj + acceptCharset*: cstring action*: cstring + autocomplete*: cstring + elements*: seq[Element] encoding*: cstring + enctype*: cstring + length*: int `method`*: cstring + noValidate*: bool target*: cstring - elements*: seq[Element] ImageElement* = ref ImageObj ImageObj {.importc.} = object of ElementObj @@ -264,8 +373,6 @@ type minHeight*: cstring minWidth*: cstring overflow*: cstring - overflowX*: cstring - overflowY*: cstring padding*: cstring paddingBottom*: cstring paddingLeft*: cstring @@ -273,6 +380,7 @@ type paddingTop*: cstring pageBreakAfter*: cstring pageBreakBefore*: cstring + pointerEvents*: cstring position*: cstring right*: cstring scrollbar3dLightColor*: cstring @@ -288,6 +396,7 @@ type textDecoration*: cstring textIndent*: cstring textTransform*: cstring + transform*: cstring top*: cstring verticalAlign*: cstring visibility*: cstring @@ -295,55 +404,467 @@ type wordSpacing*: cstring zIndex*: int - # TODO: A lot of the fields in Event belong to a more specific type of event. - # TODO: Should we clean this up? + EventPhase* = enum + None = 0, + CapturingPhase, + AtTarget, + BubblingPhase + + # https://developer.mozilla.org/en-US/docs/Web/API/Event Event* = ref EventObj EventObj {.importc.} = object of RootObj + bubbles*: bool + cancelBubble*: bool + cancelable*: bool + composed*: bool + currentTarget*: Node + defaultPrevented*: bool + eventPhase*: int target*: Node - altKey*, ctrlKey*, shiftKey*: bool + `type`*: cstring + isTrusted*: bool + + # https://developer.mozilla.org/en-US/docs/Web/API/UIEvent + UIEvent* = ref UIEventObj + UIEventObj {.importc.} = object of Event + detail*: int64 + view*: Window + + # https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent + KeyboardEvent* = ref KeyboardEventObj + KeyboardEventObj {.importc.} = object of UIEvent + altKey*, ctrlKey*, metaKey*, shiftKey*: bool + code*: cstring + isComposing*: bool + key*: cstring + keyCode*: int + location*: int + + # https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values + KeyboardEventKey* {.pure.} = enum + # Modifier keys + Alt, + AltGraph, + CapsLock, + Control, + Fn, + FnLock, + Hyper, + Meta, + NumLock, + ScrollLock, + Shift, + Super, + Symbol, + SymbolLock, + + # Whitespace keys + ArrowDown, + ArrowLeft, + ArrowRight, + ArrowUp, + End, + Home, + PageDown, + PageUp, + + # Editing keys + Backspace, + Clear, + Copy, + CrSel, + Cut, + Delete, + EraseEof, + ExSel, + Insert, + Paste, + Redo, + Undo, + + # UI keys + Accept, + Again, + Attn, + Cancel, + ContextMenu, + Escape, + Execute, + Find, + Finish, + Help, + Pause, + Play, + Props, + Select, + ZoomIn, + ZoomOut, + + # Device keys + BrigtnessDown, + BrigtnessUp, + Eject, + LogOff, + Power, + PowerOff, + PrintScreen, + Hibernate, + Standby, + WakeUp, + + # Common IME keys + AllCandidates, + Alphanumeric, + CodeInput, + Compose, + Convert, + Dead, + FinalMode, + GroupFirst, + GroupLast, + GroupNext, + GroupPrevious, + ModeChange, + NextCandidate, + NonConvert, + PreviousCandidate, + Process, + SingleCandidate, + + # Korean keyboards only + HangulMode, + HanjaMode, + JunjaMode, + + # Japanese keyboards only + Eisu, + Hankaku, + Hiragana, + HiraganaKatakana, + KanaMode, + KanjiMode, + Katakana, + Romaji, + Zenkaku, + ZenkakuHanaku, + + # Function keys + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + F13, + F14, + F15, + F16, + F17, + F18, + F19, + F20, + Soft1, + Soft2, + Soft3, + Soft4, + + # Phone keys + AppSwitch, + Call, + Camera, + CameraFocus, + EndCall, + GoBack, + GoHome, + HeadsetHook, + LastNumberRedial, + Notification, + MannerMode, + VoiceDial, + + # Multimedia keys + ChannelDown, + ChannelUp, + MediaFastForward, + MediaPause, + MediaPlay, + MediaPlayPause, + MediaRecord, + MediaRewind, + MediaStop, + MediaTrackNext, + MediaTrackPrevious, + + # Audio control keys + AudioBalanceLeft, + AudioBalanceRight, + AudioBassDown, + AudioBassBoostDown, + AudioBassBoostToggle, + AudioBassBoostUp, + AudioBassUp, + AudioFaderFront, + AudioFaderRear, + AudioSurroundModeNext, + AudioTrebleDown, + AudioTrebleUp, + AudioVolumeDown, + AUdioVolumeMute, + AudioVolumeUp, + MicrophoneToggle, + MicrophoneVolumeDown, + MicrophoneVolumeMute, + MicrophoneVolumeUp, + + # TV control keys + TV, + TV3DMode, + TVAntennaCable, + TVAudioDescription, + TVAudioDescriptionMixDown, + TVAudioDescriptionMixUp, + TVContentsMenu, + TVDataService, + TVInput, + TVInputComponent1, + TVInputComponent2, + TVInputComposite1, + TVInputComposite2, + TVInputHDMI1, + TVInputHDMI2, + TVInputHDMI3, + TVInputHDMI4, + TVInputVGA1, + TVMediaContext, + TVNetwork, + TVNumberEntry, + TVPower, + TVRadioService, + TVSatellite, + TVSatelliteBS, + TVSatelliteCS, + TVSatelliteToggle, + TVTerrestrialAnalog, + TVTerrestrialDigital, + TVTimer, + + # Media controller keys + AVRInput, + AVRPower, + ColorF0Red, + ColorF1Green, + ColorF2Yellow, + ColorF3Blue, + ColorF4Grey, + ColorF5Brown, + ClosedCaptionToggle, + Dimmer, + DisplaySwap, + DVR, + Exit, + FavoriteClear0, + FavoriteClear1, + FavoriteClear2, + FavoriteClear3, + FavoriteRecall0, + FavoriteRecall1, + FavoriteRecall2, + FavoriteRecall3, + FavoriteStore0, + FavoriteStore1, + FavoriteStore2, + FavoriteStore3, + Guide, + GuideNextDay, + GuidePreviousDay, + Info, + InstantReplay, + Link, + ListProgram, + LiveContent, + Lock, + MediaApps, + MediaAudioTrack, + MediaLast, + MediaSkipBackward, + MediaSkipForward, + MediaStepBackward, + MediaStepForward, + MediaTopMenu, + NavigateIn, + NavigateNext, + NavigateOut, + NavigatePrevious, + NextFavoriteChannel, + NextUserProfile, + OnDemand, + Pairing, + PinPDown, + PinPMove, + PinPUp, + PlaySpeedDown, + PlaySpeedReset, + PlaySpeedUp, + RandomToggle, + RcLowBattery, + RecordSpeedNext, + RfBypass, + ScanChannelsToggle, + ScreenModeNext, + Settings, + SplitScreenToggle, + STBInput, + STBPower, + Subtitle, + Teletext, + VideoModeNext, + Wink, + ZoomToggle, + + # Speech recognition keys + SpeechCorrectionList, + SpeechInputToggle, + + # Document keys + Close, + New, + Open, + Print, + Save, + SpellCheck, + MailForward, + MailReply, + MailSend, + + # Application selector keys + LaunchCalculator, + LaunchCalendar, + LaunchContacts, + LaunchMail, + LaunchMediaPlayer, + LaunchMusicPlayer, + LaunchMyComputer, + LaunchPhone, + LaunchScreenSaver, + LaunchSpreadsheet, + LaunchWebBrowser, + LaunchWebCam, + LaunchWordProcessor, + LaunchApplication1, + LaunchApplication2, + LaunchApplication3, + LaunchApplication4, + LaunchApplication5, + LaunchApplication6, + LaunchApplication7, + LaunchApplication8, + LaunchApplication9, + LaunchApplication10, + LaunchApplication11, + LaunchApplication12, + LaunchApplication13, + LaunchApplication14, + LaunchApplication15, + LaunchApplication16, + + # Browser control keys + BrowserBack, + BrowserFavorites, + BrowserForward, + BrowserHome, + BrowserRefresh, + BrowserSearch, + BrowserStop, + + # Numeric keypad keys + Key11, + Key12, + Separator + + MouseButtons* = enum + NoButton = 0, + PrimaryButton = 1, + SecondaryButton = 2, + AuxilaryButton = 4, + FourthButton = 8, + FifthButton = 16 + + # https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + MouseEvent* = ref MouseEventObj + MouseEventObj {.importc.} = object of UIEvent + altKey*, ctrlKey*, metaKey*, shiftKey*: bool button*: int + buttons*: int clientX*, clientY*: int - keyCode*: int - layerX*, layerY*: int - modifiers*: int - ALT_MASK*, CONTROL_MASK*, SHIFT_MASK*, META_MASK*: int + movementX*, movementY*: int offsetX*, offsetY*: int pageX*, pageY*: int + relatedTarget*: EventTarget + #region*: cstring screenX*, screenY*: int - which*: int - `type`*: cstring x*, y*: int - ABORT*: int - BLUR*: int - CHANGE*: int - CLICK*: int - DBLCLICK*: int - DRAGDROP*: int - ERROR*: int - FOCUS*: int - KEYDOWN*: int - KEYPRESS*: int - KEYUP*: int - LOAD*: int - MOUSEDOWN*: int - MOUSEMOVE*: int - MOUSEOUT*: int - MOUSEOVER*: int - MOUSEUP*: int - MOVE*: int - RESET*: int - RESIZE*: int - SELECT*: int - SUBMIT*: int - UNLOAD*: int + + DataTransferItemKind* {.pure.} = enum + File = "file", + String = "string" + + # https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem + DataTransferItem* = ref DataTransferItemObj + DataTransferItemObj {.importc.} = object of RootObj + kind*: cstring + `type`*: cstring + + # https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer + DataTransfer* = ref DataTransferObj + DataTransferObj {.importc.} = object of RootObj + dropEffect*: cstring + effectAllowed*: cstring + files*: seq[Element] + items*: seq[DataTransferItem] + types*: seq[cstring] + + DataTransferDropEffect* {.pure.} = enum + None = "none", + Copy = "copy", + Link = "link", + Move = "move" + + DataTransferEffectAllowed* {.pure.} = enum + None = "none", + Copy = "copy", + CopyLink = "copyLink", + CopyMove = "copyMove", + Link = "link", + LinkMove = "linkMove", + Move = "move", + All = "all", + Uninitialized = "uninitialized" + + DragEventTypes* = enum + Drag = "drag", + DragEnd = "dragend", + DragEnter = "dragenter", + DragExit = "dragexit", + DragLeave = "dragleave", + DragOver = "dragover", + DragStart = "dragstart", + Drop = "drop" + + # https://developer.mozilla.org/en-US/docs/Web/API/DragEvent + DragEvent* {.importc.} = object of MouseEvent + dataTransfer*: DataTransfer TouchList* {.importc.} = ref object of RootObj length*: int - TouchEvent* {.importc.} = ref object of Event - changedTouches*, targetTouches*, touches*: TouchList - - Touch* {.importc.} = ref object of RootObj + Touch* = ref TouchObj + TouchObj {.importc.} = object of RootObj identifier*: int screenX*, screenY*, clientX*, clientY*, pageX*, pageY*: int target*: Element @@ -351,6 +872,10 @@ type rotationAngle*: int force*: float + TouchEvent* = ref TouchEventObj + TouchEventObj {.importc.} = object of UIEvent + changedTouches*, targetTouches*, touches*: seq[Touch] + Location* = ref LocationObj LocationObj {.importc.} = object of RootObj hash*: cstring @@ -375,26 +900,26 @@ type language*: cstring platform*: cstring userAgent*: cstring - mimeTypes*: seq[ref TMimeType] + mimeTypes*: seq[ref MimeType] - TPlugin* {.importc.} = object of RootObj + Plugin* {.importc.} = object of RootObj description*: cstring filename*: cstring name*: cstring - TMimeType* {.importc.} = object of RootObj + MimeType* {.importc.} = object of RootObj description*: cstring - enabledPlugin*: ref TPlugin + enabledPlugin*: ref Plugin suffixes*: seq[cstring] `type`*: cstring - TLocationBar* {.importc.} = object of RootObj + LocationBar* {.importc.} = object of RootObj visible*: bool - TMenuBar* = TLocationBar - TPersonalBar* = TLocationBar - TScrollBars* = TLocationBar - TToolBar* = TLocationBar - TStatusBar* = TLocationBar + MenuBar* = LocationBar + PersonalBar* = LocationBar + ScrollBars* = LocationBar + ToolBar* = LocationBar + StatusBar* = LocationBar Screen = ref ScreenObj ScreenObj {.importc.} = object of RootObj @@ -405,62 +930,120 @@ type pixelDepth*: int width*: int - TTimeOut* {.importc.} = object of RootObj - TInterval* {.importc.} = object of RootObj + TimeOut* {.importc.} = ref object of RootObj + Interval* {.importc.} = object of RootObj AddEventListenerOptions* = object capture*: bool once*: bool passive*: bool - BoundingRect* {.importc.} = ref object - top*, bottom*, left*, right*, x*, y*, width*, height*: float +proc id*(n: Node): cstring {.importcpp: "#.id", nodecl.} +proc `id=`*(n: Node; x: cstring) {.importcpp: "#.id = #", nodecl.} +proc class*(n: Node): cstring {.importcpp: "#.className", nodecl.} +proc `class=`*(n: Node; v: cstring) {.importcpp: "#.className = #", nodecl.} + +proc value*(n: Node): cstring {.importcpp: "#.value", nodecl.} +proc `value=`*(n: Node; v: cstring) {.importcpp: "#.value = #", nodecl.} + +proc `disabled=`*(n: Node; v: bool) {.importcpp: "#.disabled = #", nodecl.} + +when defined(nodejs): + # we provide a dummy DOM for nodejs for testing purposes + proc len*(x: Node): int = x.childNodes.len + proc `[]`*(x: Node; idx: int): Element = + assert idx >= 0 and idx < x.childNodes.len + result = cast[Element](x.childNodes[idx]) + + var document* = Document(nodeType: DocumentNode) - PerformanceMemory* {.importc.} = ref object - jsHeapSizeLimit*: float - totalJSHeapSize*: float - usedJSHeapSize*: float - - PerformanceTiming* {.importc.} = ref object - connectStart*: float - domComplete*: float - domContentLoadedEventEnd*: float - domContentLoadedEventStart*: float - domInteractive*: float - domLoading*: float - domainLookupEnd*: float - domainLookupStart*: float - fetchStart*: float - loadEventEnd*: float - loadEventStart*: float - navigationStart*: float - redirectEnd*: float - redirectStart*: float - requestStart*: float - responseEnd*: float - responseStart*: float - secureConnectionStart*: float - unloadEventEnd*: float - unloadEventStart*: float - - Performance* {.importc.} = ref object - memory*: PerformanceMemory - timing*: PerformanceTiming + proc getElem(x: Element; id: cstring): Element = + if x.id == id: return x + for i in 0..= 0 and rect.left >= 0 and + rect.bottom <= clientHeight().float and + rect.right <= clientWidth().float + +proc scrollTop*(e: Node): int {.importcpp: "#.scrollTop", nodecl.} +proc offsetHeight*(e: Node): int {.importcpp: "#.offsetHeight", nodecl.} +proc offsetTop*(e: Node): int {.importcpp: "#.offsetTop", nodecl.} +proc offsetLeft*(e: Node): int {.importcpp: "#.offsetLeft", nodecl.} diff --git a/tools/dochack/dochack.nim b/tools/dochack/dochack.nim index 61c61225d95a..2f8465a6361c 100644 --- a/tools/dochack/dochack.nim +++ b/tools/dochack/dochack.nim @@ -1,6 +1,38 @@ -import karax +import dom import fuzzysearch +proc textContent(e: Element): cstring {. + importcpp: "#.textContent", nodecl.} + +proc textContent(e: Node): cstring {. + importcpp: "#.textContent", nodecl.} + +proc tree(tag: string; kids: varargs[Element]): Element = + result = document.createElement tag + for k in kids: + result.appendChild k + +proc add(parent, kid: Element) = + if parent.nodeName == cstring"TR" and ( + kid.nodeName == cstring"TD" or kid.nodeName == cstring"TH"): + let k = document.createElement("TD") + appendChild(k, kid) + appendChild(parent, k) + else: + appendChild(parent, kid) + +proc setClass(e: Element; value: string) = + e.setAttribute("class", value) +proc text(s: string): Element = cast[Element](document.createTextNode(s)) +proc text(s: cstring): Element = cast[Element](document.createTextNode(s)) + +proc getElementById(id: cstring): Element {.importc: "document.getElementById", nodecl.} + +proc replaceById(id: cstring; newTree: Node) = + let x = getElementById(id) + x.parentNode.replaceChild(newTree, x) + newTree.id = id + proc findNodeWith(x: Element; tag, content: cstring): Element = if x.nodeName == tag and x.textContent == content: return x @@ -182,7 +214,7 @@ proc buildToc(orig: TocEntry; types, procs: seq[Element]): TocEntry = t.markElement() for p in procs: if not isMarked(p): - let xx = karax.getElementsByClass(p.parent, cstring"attachedType") + let xx = getElementsByClass(p.parent, cstring"attachedType") if xx.len == 1 and xx[0].textContent == t.textContent: #kout(cstring"found ", p.nodeName) let q = tree("A", text(p.title)) @@ -230,7 +262,7 @@ proc groupBy*(value: cstring) {.exportc.} = togglevis(getElementById"toc-list") var - db: seq[Element] + db: seq[Node] contents: seq[cstring] template normalize(x: cstring): cstring = x.toLower.replace("_", "") @@ -258,7 +290,7 @@ proc dosearch(value: cstring): Element = let ul = tree("UL") result = tree("DIV") result.setClass"search_results" - var matches: seq[(Element, int)] = @[] + var matches: seq[(Node, int)] = @[] for i in 0.. int: - b[1] - a[1] + matches.sort(proc(a, b: auto): int = b[1] - a[1]) for i in 0 ..< min(matches.len, 19): matches[i][0].innerHTML = matches[i][0].getAttribute("data-doc-search-tag") - ul.add(tree("LI", matches[i][0])) + ul.add(tree("LI", cast[Element](matches[i][0]))) if ul.len == 0: result.add tree("B", text"no search results") else: diff --git a/tools/dochack/karax.nim b/tools/dochack/karax.nim deleted file mode 100644 index 020fd37d11b6..000000000000 --- a/tools/dochack/karax.nim +++ /dev/null @@ -1,344 +0,0 @@ -# Simple lib to write JS UIs - -import dom - -export dom.Element, dom.Event, dom.cloneNode, dom - -proc kout*[T](x: T) {.importc: "console.log", varargs.} - ## the preferred way of debugging karax applications. - -proc id*(e: Node): cstring {.importcpp: "#.id", nodecl.} -proc `id=`*(e: Node; x: cstring) {.importcpp: "#.id = #", nodecl.} -proc className*(e: Node): cstring {.importcpp: "#.className", nodecl.} -proc `className=`*(e: Node; v: cstring) {.importcpp: "#.className = #", nodecl.} - -proc value*(e: Element): cstring {.importcpp: "#.value", nodecl.} -proc `value=`*(e: Element; v: cstring) {.importcpp: "#.value = #", nodecl.} - -proc getElementsByClass*(e: Element; name: cstring): seq[Element] {.importcpp: "#.getElementsByClassName(#)", nodecl.} - -proc toLower*(x: cstring): cstring {. - importcpp: "#.toLowerCase()", nodecl.} -proc replace*(x: cstring; search, by: cstring): cstring {. - importcpp: "#.replace(#, #)", nodecl.} - -type - EventHandler* = proc(ev: Event) - EventHandlerId* = proc(ev: Event; id: int) - - Timeout* = ref object - -var document* {.importc.}: Document - -var - dorender: proc (): Element {.closure.} - drawTimeout: Timeout - currentTree: Element - -proc setRenderer*(renderer: proc (): Element) = - dorender = renderer - -proc setTimeout*(action: proc(); ms: int): Timeout {.importc, nodecl.} -proc clearTimeout*(t: Timeout) {.importc, nodecl.} -proc targetElem*(e: Event): Element = cast[Element](e.target) - -proc getElementById*(id: cstring): Element {.importc: "document.getElementById", nodecl.} - -proc getElementsByClassName*(cls: cstring): seq[Element] {.importc: - "document.getElementsByClassName", nodecl.} - -proc textContent*(e: Element): cstring {. - importcpp: "#.textContent", nodecl.} - -proc replaceById*(id: cstring; newTree: Node) = - let x = getElementById(id) - x.parentNode.replaceChild(newTree, x) - newTree.id = id - -proc equals(a, b: Node): bool = - if a.nodeType != b.nodeType: return false - if a.id != b.id: return false - if a.nodeName != b.nodeName: return false - if a.nodeType == TextNode: - if a.data != b.data: return false - elif a.childNodes.len != b.childNodes.len: - return false - if a.className != b.className: - # style differences are updated in place and we pretend - # it's still the same node - a.className = b.className - #return false - return true - -proc diffTree(parent, a, b: Node) = - if equals(a, b): - if a.nodeType != TextNode: - # we need to do this correctly in the presence of asyncronous - # DOM updates: - var i = 0 - while i < a.childNodes.len and a.childNodes.len == b.childNodes.len: - diffTree(a, a.childNodes[i], b.childNodes[i]) - inc i - elif parent == nil: - replaceById("ROOT", b) - else: - parent.replaceChild(b, a) - -proc dodraw() = - let newtree = dorender() - newtree.id = "ROOT" - if currentTree == nil: - currentTree = newtree - replaceById("ROOT", currentTree) - else: - diffTree(nil, currentTree, newtree) - -proc redraw*() = - # we buffer redraw requests: - if drawTimeout != nil: - clearTimeout(drawTimeout) - drawTimeout = setTimeout(dodraw, 30) - -proc tree*(tag: string; kids: varargs[Element]): Element = - result = document.createElement tag - for k in kids: - result.appendChild k - -proc tree*(tag: string; attrs: openarray[(string, string)]; - kids: varargs[Element]): Element = - result = tree(tag, kids) - for a in attrs: result.setAttribute(a[0], a[1]) - -proc text*(s: string): Element = cast[Element](document.createTextNode(s)) -proc text*(s: cstring): Element = cast[Element](document.createTextNode(s)) -proc add*(parent, kid: Element) = - if parent.nodeName == cstring"TR" and ( - kid.nodeName == cstring"TD" or kid.nodeName == cstring"TH"): - let k = document.createElement("TD") - appendChild(k, kid) - appendChild(parent, k) - else: - appendChild(parent, kid) - -proc len*(x: Element): int {.importcpp: "#.childNodes.length".} -proc `[]`*(x: Element; idx: int): Element {.importcpp: "#.childNodes[#]".} - -proc isInt*(s: cstring): bool {.asmNoStackFrame.} = - asm """ - return s.match(/^[0-9]+$/); - """ - -var - linkCounter: int - -proc link*(id: int): Element = - result = document.createElement("a") - result.setAttribute("href", "#") - inc linkCounter - result.setAttribute("id", $linkCounter & ":" & $id) - -proc link*(action: EventHandler): Element = - result = document.createElement("a") - result.setAttribute("href", "#") - addEventListener(result, "click", action) - -proc parseInt*(s: cstring): int {.importc, nodecl.} -proc parseFloat*(s: cstring): float {.importc, nodecl.} -proc split*(s, sep: cstring): seq[cstring] {.importcpp, nodecl.} - -proc startsWith*(a, b: cstring): bool {.importcpp: "startsWith", nodecl.} -proc contains*(a, b: cstring): bool {.importcpp: "(#.indexOf(#)>=0)", nodecl.} -proc substr*(s: cstring; start: int): cstring {.importcpp: "substr", nodecl.} -proc substr*(s: cstring; start, length: int): cstring {.importcpp: "substr", nodecl.} - -#proc len*(s: cstring): int {.importcpp: "#.length", nodecl.} -proc `&`*(a, b: cstring): cstring {.importcpp: "(# + #)", nodecl.} -proc toCstr*(s: int): cstring {.importcpp: "((#)+'')", nodecl.} - -proc suffix*(s, prefix: cstring): cstring = - if s.startsWith(prefix): - result = s.substr(prefix.len) - else: - kout(cstring"bug! " & s & cstring" does not start with " & prefix) - -proc valueAsInt*(e: Element): int = parseInt(e.value) -proc suffixAsInt*(s, prefix: cstring): int = parseInt(suffix(s, prefix)) - -proc scrollTop*(e: Element): int {.importcpp: "#.scrollTop", nodecl.} -proc offsetHeight*(e: Element): int {.importcpp: "#.offsetHeight", nodecl.} -proc offsetTop*(e: Element): int {.importcpp: "#.offsetTop", nodecl.} - -template onImpl(s) {.dirty} = - proc wrapper(ev: Event) = - action(ev) - redraw() - addEventListener(e, s, wrapper) - -proc setOnclick*(e: Element; action: proc(ev: Event)) = - onImpl "click" - -proc setOnclick*(e: Element; action: proc(ev: Event; id: int)) = - proc wrapper(ev: Event) = - let id = ev.target.id - let a = id.split(":") - if a.len == 2: - action(ev, parseInt(a[1])) - redraw() - else: - kout(cstring("cannot deal with id "), id) - addEventListener(e, "click", wrapper) - -proc setOnfocuslost*(e: Element; action: EventHandler) = - onImpl "blur" - -proc setOnchanged*(e: Element; action: EventHandler) = - onImpl "change" - -proc setOnscroll*(e: Element; action: EventHandler) = - onImpl "scroll" - -proc select*(choices: openarray[string]): Element = - result = document.createElement("select") - var i = 0 - for c in choices: - result.add tree("option", [("value", $i)], text(c)) - inc i - -proc select*(choices: openarray[(int, string)]): Element = - result = document.createElement("select") - for c in choices: - result.add tree("option", [("value", $c[0])], text(c[1])) - -var radioCounter: int - -proc radio*(choices: openarray[(int, string)]): Element = - result = document.createElement("fieldset") - var i = 0 - inc radioCounter - for c in choices: - let id = "radio_" & c[1] & $i - var kid = tree("input", [("type", "radio"), - ("id", id), ("name", "radio" & $radioCounter), - ("value", $c[0])]) - if i == 0: - kid.setAttribute("checked", "checked") - var lab = tree("label", [("for", id)], text(c[1])) - kid.add lab - result.add kid - inc i - -proc tag*(name: string; id="", class=""): Element = - result = document.createElement(name) - if id.len > 0: - result.setAttribute("id", id) - if class.len > 0: - result.setAttribute("class", class) - -proc tdiv*(id="", class=""): Element = tag("div", id, class) -proc span*(id="", class=""): Element = tag("span", id, class) - -proc th*(s: string): Element = - result = tag("th") - result.add text(s) - -proc td*(s: string): Element = - result = tag("td") - result.add text(s) - -proc td*(s: Element): Element = - result = tag("td") - result.add s - -proc td*(class: string; s: Element): Element = - result = tag("td") - result.add s - result.setAttribute("class", class) - -proc table*(class="", kids: varargs[Element]): Element = - result = tag("table", "", class) - for k in kids: result.add k - -proc tr*(kids: varargs[Element]): Element = - result = tag("tr") - for k in kids: - if k.nodeName == cstring"TD" or k.nodeName == cstring"TH": - result.add k - else: - result.add td(k) - -proc setClass*(e: Element; value: string) = - e.setAttribute("class", value) - -proc setAttr*(e: Element; key, value: cstring) = - e.setAttribute(key, value) - -proc getAttr*(e: Element; key: cstring): cstring {. - importcpp: "#.getAttribute(#)", nodecl.} - -proc realtimeInput*(id, val: string; changed: proc(value: cstring)): Element = - let oldElem = getElementById(id) - #if oldElem != nil: return oldElem - let newVal = if oldElem.isNil: val else: $oldElem.value - var timer: Timeout - proc wrapper() = - changed(getElementById(id).value) - redraw() - proc onkeyup(ev: Event) = - if timer != nil: clearTimeout(timer) - timer = setTimeout(wrapper, 400) - result = tree("input", [("type", "text"), - ("value", newVal), - ("id", id)]) - result.addEventListener("keyup", onkeyup) - -proc ajax(meth, url: cstring; headers: openarray[(string, string)]; - data: cstring; - cont: proc (httpStatus: int; response: cstring)) = - proc setRequestHeader(a, b: cstring) {.importc: "ajax.setRequestHeader".} - {.emit: """ - var ajax = new XMLHttpRequest(); - ajax.open(`meth`,`url`,true);""".} - for a, b in items(headers): - setRequestHeader(a, b) - {.emit: """ - ajax.onreadystatechange = function(){ - if(this.readyState == 4){ - if(this.status == 200){ - `cont`(this.status, this.responseText); - } else { - `cont`(this.status, this.statusText); - } - } - } - ajax.send(`data`); - """.} - -proc ajaxPut*(url: string; headers: openarray[(string, string)]; - data: cstring; - cont: proc (httpStatus: int, response: cstring)) = - ajax("PUT", url, headers, data, cont) - -proc ajaxGet*(url: string; headers: openarray[(string, string)]; - cont: proc (httpStatus: int, response: cstring)) = - ajax("GET", url, headers, nil, cont) - -{.push stackTrace:off.} - -proc setupErrorHandler*(useAlert=false) = - ## Installs an error handler that transforms native JS unhandled - ## exceptions into Nim based stack traces. If `useAlert` is false, - ## the error message it put into the console, otherwise `alert` - ## is called. - proc stackTraceAsCstring(): cstring = cstring(getStackTrace()) - {.emit: """ - window.onerror = function(msg, url, line, col, error) { - var x = "Error: " + msg + "\n" + `stackTraceAsCstring`() - if (`useAlert`) - alert(x); - else - console.log(x); - var suppressErrorAlert = true; - return suppressErrorAlert; - };""".} - -{.pop.} From ad4e3fd28c03a8429a3b4bc0068af77018e81724 Mon Sep 17 00:00:00 2001 From: Miran Date: Sat, 2 Feb 2019 13:23:10 +0100 Subject: [PATCH 036/202] devel docs should point to devel src (#10529) Currently, devel docs (https://nim-lang.github.io/Nim/lib.html) source links point to master branch, leading to outdated source code and showing the wrong line. --- compiler/docgen.nim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/docgen.nim b/compiler/docgen.nim index b70561a1d0b5..5af4c464e16e 100644 --- a/compiler/docgen.nim +++ b/compiler/docgen.nim @@ -656,7 +656,8 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) = path = path[cwd.len+1 .. ^1].replace('\\', '/') let gitUrl = getConfigVar(d.conf, "git.url") if gitUrl.len > 0: - let commit = getConfigVar(d.conf, "git.commit", "master") + let defaultBranch = if NimPatch mod 2 == 1: "devel" else: "master" + let commit = getConfigVar(d.conf, "git.commit", defaultBranch) let develBranch = getConfigVar(d.conf, "git.devel", "devel") dispA(d.conf, seeSrcRope, "$1", "", [ropeFormatNamedVars(d.conf, docItemSeeSrc, ["path", "line", "url", "commit", "devel"], [rope path.string, From 07553034de5bd9ffa89df303bf789664f9cb8ed8 Mon Sep 17 00:00:00 2001 From: genotrance Date: Sat, 2 Feb 2019 06:26:32 -0600 Subject: [PATCH 037/202] Enable Travis folding in winrelease (#10528) --- koch.nim | 16 ++++++++++------ tools/kochdocs.nim | 15 ++++++++++----- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/koch.nim b/koch.nim index 89ee28d5a7c1..6b799449e2e9 100644 --- a/koch.nim +++ b/koch.nim @@ -373,9 +373,10 @@ proc winReleaseArch(arch: string) = withMingw r"..\mingw" & arch & r"\bin": # Rebuilding koch is necessary because it uses its pointer size to # determine which mingw link to put in the NSIS installer. - nimexec "c --cpu:$# koch" % cpu - kochExec "boot -d:release --cpu:$#" % cpu - kochExec "--latest zip -d:release" + inFold "winrelease koch": + nimexec "c --cpu:$# koch" % cpu + kochExecFold("winrelease boot", "boot -d:release --cpu:$#" % cpu) + kochExecFold("winrelease zip", "--latest zip -d:release") overwriteFile r"build\nim-$#.zip" % VersionAsString, r"web\upload\download\nim-$#_x$#.zip" % [VersionAsString, arch] @@ -384,13 +385,16 @@ proc winRelease*() = # anymore! # Build -docs file: when true: - buildDocs(gaCode) + inFold "winrelease buildDocs": + buildDocs(gaCode) withDir "web/upload/" & VersionAsString: - exec "7z a -tzip docs-$#.zip *.html" % VersionAsString + inFold "winrelease zipdocs": + exec "7z a -tzip docs-$#.zip *.html" % VersionAsString overwriteFile "web/upload/$1/docs-$1.zip" % VersionAsString, "web/upload/download/docs-$1.zip" % VersionAsString when true: - csource("-d:release") + inFold "winrelease csource": + csource("-d:release") when sizeof(pointer) == 4: winReleaseArch "32" when sizeof(pointer) == 8: diff --git a/tools/kochdocs.nim b/tools/kochdocs.nim index c0e7ce66b7fc..e13ad3501d08 100644 --- a/tools/kochdocs.nim +++ b/tools/kochdocs.nim @@ -25,7 +25,6 @@ proc findNim*(): string = # assume there is a symlink to the exe or something: return nim - proc exec*(cmd: string, errorcode: int = QuitFailure, additionalPath = "") = let prevPath = getEnv("PATH") if additionalPath.len > 0: @@ -38,15 +37,21 @@ proc exec*(cmd: string, errorcode: int = QuitFailure, additionalPath = "") = if execShellCmd(cmd) != 0: quit("FAILURE", errorcode) putEnv("PATH", prevPath) -proc execFold*(desc, cmd: string, errorcode: int = QuitFailure, additionalPath = "") = - ## Execute shell command. Add log folding on Travis CI. - # https://github.com/travis-ci/travis-ci/issues/2285#issuecomment-42724719 +template inFold*(desc, body) = if existsEnv("TRAVIS"): echo "travis_fold:start:" & desc.replace(" ", "_") - exec(cmd, errorcode, additionalPath) + + body + if existsEnv("TRAVIS"): echo "travis_fold:end:" & desc.replace(" ", "_") +proc execFold*(desc, cmd: string, errorcode: int = QuitFailure, additionalPath = "") = + ## Execute shell command. Add log folding on Travis CI. + # https://github.com/travis-ci/travis-ci/issues/2285#issuecomment-42724719 + inFold(desc): + exec(cmd, errorcode, additionalPath) + proc execCleanPath*(cmd: string, additionalPath = ""; errorcode: int = QuitFailure) = # simulate a poor man's virtual environment From 0091f2ad3bfa57aa1d5bdd1a1062975135a2a6d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Nihlg=C3=A5rd?= Date: Sun, 3 Feb 2019 09:06:00 +0100 Subject: [PATCH 038/202] Implement {.booldefine.} (#10533) --- compiler/ast.nim | 2 +- compiler/condsyms.nim | 11 +++-------- compiler/options.nim | 2 +- compiler/pragmas.nim | 4 +++- compiler/semfold.nim | 12 +++++++++++- compiler/wordrecg.nim | 5 +++-- doc/manual.rst | 6 ++++-- lib/pure/strtabs.nim | 28 +++++++++++++++++++++++++++- tests/misc/tdefine.nim | 18 ++++++++++++++++++ 9 files changed, 71 insertions(+), 17 deletions(-) create mode 100644 tests/misc/tdefine.nim diff --git a/compiler/ast.nim b/compiler/ast.nim index fc470b7a8954..3146722cb960 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -655,7 +655,7 @@ type mEqIdent, mEqNimrodNode, mSameNodeType, mGetImpl, mNGenSym, mNHint, mNWarning, mNError, mInstantiationInfo, mGetTypeInfo, - mNimvm, mIntDefine, mStrDefine, mRunnableExamples, + mNimvm, mIntDefine, mStrDefine, mBoolDefine, mRunnableExamples, mException, mBuiltinType, mSymOwner, mUncheckedArray, mGetImplTransf, mSymIsInstantiationOf diff --git a/compiler/condsyms.nim b/compiler/condsyms.nim index 9a4c1701c2d5..5e7ce3a08184 100644 --- a/compiler/condsyms.nim +++ b/compiler/condsyms.nim @@ -15,26 +15,21 @@ import from options import Feature from lineinfos import HintsToStr, WarningsToStr -const - catNone = "false" - proc defineSymbol*(symbols: StringTableRef; symbol: string, value: string = "true") = symbols[symbol] = value proc undefSymbol*(symbols: StringTableRef; symbol: string) = - symbols[symbol] = catNone + symbols.del(symbol) #proc lookupSymbol*(symbols: StringTableRef; symbol: string): string = # result = if isDefined(symbol): gSymbols[symbol] else: nil iterator definedSymbolNames*(symbols: StringTableRef): string = for key, val in pairs(symbols): - if val != catNone: yield key + yield key proc countDefinedSymbols*(symbols: StringTableRef): int = - result = 0 - for key, val in pairs(symbols): - if val != catNone: inc(result) + symbols.len proc initDefines*(symbols: StringTableRef) = # for bootstrapping purposes and old code: diff --git a/compiler/options.nim b/compiler/options.nim index 54276f99d938..0a25b1b96975 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -360,7 +360,7 @@ proc cppDefine*(c: ConfigRef; define: string) = proc isDefined*(conf: ConfigRef; symbol: string): bool = if conf.symbols.hasKey(symbol): - result = conf.symbols[symbol] != "false" + result = true elif cmpIgnoreStyle(symbol, CPU[conf.target.targetCPU].name) == 0: result = true elif cmpIgnoreStyle(symbol, platform.OS[conf.target.targetOS].name) == 0: diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index 3967fa22dcbd..03c9127af0d6 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -67,7 +67,7 @@ const wGensym, wInject, wCodegenDecl, wGuard, wGoto, wExportNims, wUsed} constPragmas* = {wImportc, wExportc, wHeader, wDeprecated, wMagic, wNodecl, wExtern, wImportCpp, wImportObjC, wError, wGensym, wInject, wExportNims, - wIntDefine, wStrDefine, wUsed, wCompilerProc, wCore} + wIntDefine, wStrDefine, wBoolDefine, wUsed, wCompilerProc, wCore} letPragmas* = varPragmas procTypePragmas* = {FirstCallConv..LastCallConv, wVarargs, wNosideeffect, wThread, wRaises, wLocks, wTags, wGcSafe} @@ -1106,6 +1106,8 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, sym.magic = mIntDefine of wStrDefine: sym.magic = mStrDefine + of wBoolDefine: + sym.magic = mBoolDefine of wUsed: noVal(c, it) if sym == nil: invalidPragma(c, it) diff --git a/compiler/semfold.nim b/compiler/semfold.nim index 0bdd0b64c451..2a2942191620 100644 --- a/compiler/semfold.nim +++ b/compiler/semfold.nim @@ -569,10 +569,20 @@ proc getConstExpr(m: PSym, n: PNode; g: ModuleGraph): PNode = try: result = newIntNodeT(g.config.symbols[s.name.s].parseInt, n, g) except ValueError: - localError(g.config, n.info, "expression is not an integer literal") + localError(g.config, s.info, + "{.intdefine.} const was set to an invalid integer: '" & + g.config.symbols[s.name.s] & "'") of mStrDefine: if isDefined(g.config, s.name.s): result = newStrNodeT(g.config.symbols[s.name.s], n, g) + of mBoolDefine: + if isDefined(g.config, s.name.s): + try: + result = newIntNodeT(g.config.symbols[s.name.s].parseBool.int, n, g) + except ValueError: + localError(g.config, s.info, + "{.booldefine.} const was set to an invalid bool: '" & + g.config.symbols[s.name.s] & "'") else: result = copyTree(s.ast) of skProc, skFunc, skMethod: diff --git a/compiler/wordrecg.nim b/compiler/wordrecg.nim index 41bdc9fcbc2a..6f78c9d6fbf7 100644 --- a/compiler/wordrecg.nim +++ b/compiler/wordrecg.nim @@ -35,7 +35,7 @@ type wColon, wColonColon, wEquals, wDot, wDotDot, wStar, wMinus, wMagic, wThread, wFinal, wProfiler, wMemTracker, wObjChecks, - wIntDefine, wStrDefine, + wIntDefine, wStrDefine, wBoolDefine wDestroy, @@ -122,7 +122,8 @@ const ":", "::", "=", ".", "..", "*", "-", - "magic", "thread", "final", "profiler", "memtracker", "objchecks", "intdefine", "strdefine", + "magic", "thread", "final", "profiler", "memtracker", "objchecks", + "intdefine", "strdefine", "booldefine", "destroy", diff --git a/doc/manual.rst b/doc/manual.rst index 170f0d550554..bcb1581dd06e 100644 --- a/doc/manual.rst +++ b/doc/manual.rst @@ -7851,6 +7851,7 @@ pragma description ================= ============================================ `intdefine`:idx: Reads in a build-time define as an integer `strdefine`:idx: Reads in a build-time define as a string +`booldefine`:idx: Reads in a build-time define as a bool ================= ============================================ .. code-block:: nim @@ -7858,13 +7859,14 @@ pragma description echo FooBar :: - nim c -d:FooBar=42 foobar.c + nim c -d:FooBar=42 foobar.nim In the above example, providing the -d flag causes the symbol ``FooBar`` to be overwritten at compile time, printing out 42. If the ``-d:FooBar=42`` were to be omitted, the default value of 5 would be -used. +used. To see if a value was provided, `defined(FooBar)` can be used. +The syntax `-d:flag` is actually just a shortcut for `-d:flag=true`. Custom annotations ------------------ diff --git a/lib/pure/strtabs.nim b/lib/pure/strtabs.nim index 7bafe167560c..cff5293c9b6e 100644 --- a/lib/pure/strtabs.nim +++ b/lib/pure/strtabs.nim @@ -28,7 +28,7 @@ runnableExamples: ## When using the style insensitive mode ``modeStyleInsensitive``, -## all letters are compared case insensitively within the ASCII range +## all letters are compared case insensitively within the ASCII range ## and underscores are ignored. runnableExamples: @@ -272,6 +272,32 @@ proc `%`*(f: string, t: StringTableRef, flags: set[FormatFlag] = {}): string {. add(result, f[i]) inc(i) +proc del*(t: StringTableRef, key: string) = + ## Removes `key` from `t`. + # Impl adapted from `tableimpl.delImplIdx` + var i = rawGet(t, key) + let msk = high(t.data) + if i >= 0: + dec(t.counter) + block outer: + while true: # KnuthV3 Algo6.4R adapted for i=i+1 instead of i=i-1 + var j = i # The correctness of this depends on (h+1) in nextTry, + var r = j # though may be adaptable to other simple sequences. + t.data[i].hasValue = false # mark current EMPTY + t.data[i].key = "" + t.data[i].val = "" + while true: + i = (i + 1) and msk # increment mod table size + if not t.data[i].hasValue: # end of collision cluster; So all done + break outer + r = t.myhash(t.data[i].key) and msk # "home" location of key@i + if not ((i >= r and r > j) or (r > j and j > i) or (j > i and i >= r)): + break + when defined(js): + t.data[j] = t.data[i] + else: + shallowCopy(t.data[j], t.data[i]) # data[j] will be marked EMPTY next loop + proc `$`*(t: StringTableRef): string {.rtlFunc, extern: "nstDollar".} = ## The `$` operator for string tables. if t.len == 0: diff --git a/tests/misc/tdefine.nim b/tests/misc/tdefine.nim new file mode 100644 index 000000000000..1378b890147a --- /dev/null +++ b/tests/misc/tdefine.nim @@ -0,0 +1,18 @@ +discard """ +joinable: false +cmd: "nim c -d:booldef -d:booldef2=false -d:intdef=2 -d:strdef=foobar -r $file" +""" + +const booldef {.booldefine.} = false +const booldef2 {.booldefine.} = true +const intdef {.intdefine.} = 0 +const strdef {.strdefine.} = "" + +doAssert defined(booldef) +doAssert defined(booldef2) +doAssert defined(intdef) +doAssert defined(strdef) +doAssert booldef +doAssert not booldef2 +doAssert intdef == 2 +doAssert strdef == "foobar" From 315abd7163586778a03626c371d724cd138e7cba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Nihlg=C3=A5rd?= Date: Sun, 3 Feb 2019 09:37:11 +0100 Subject: [PATCH 039/202] Change strtabs.nextTry to match the assumption in strtabs.del --- lib/pure/strtabs.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/strtabs.nim b/lib/pure/strtabs.nim index cff5293c9b6e..377178f92b57 100644 --- a/lib/pure/strtabs.nim +++ b/lib/pure/strtabs.nim @@ -117,7 +117,7 @@ proc mustRehash(length, counter: int): bool = result = (length * 2 < counter * 3) or (length - counter < 4) proc nextTry(h, maxHash: Hash): Hash {.inline.} = - result = ((5 * h) + 1) and maxHash + result = (h + 1) and maxHash proc rawGet(t: StringTableRef, key: string): int = var h: Hash = myhash(t, key) and high(t.data) # start with real hash value From 8cb80238c4eedc3dbabdadd6a996bb905de8309d Mon Sep 17 00:00:00 2001 From: Miran Date: Mon, 4 Feb 2019 12:27:02 +0100 Subject: [PATCH 040/202] update documentation CSS (#10543) * update documentation CSS * update the expected htmls --- config/nimdoc.cfg | 909 +++++------------- .../expected/subdir/subdir_b/utils.html | 907 +++++------------ nimdoc/testproject/expected/testproject.html | 907 +++++------------ nimdoc/testproject/expected/theindex.html | 907 +++++------------ 4 files changed, 925 insertions(+), 2705 deletions(-) diff --git a/config/nimdoc.cfg b/config/nimdoc.cfg index f534264a0a6a..842354576230 100644 --- a/config/nimdoc.cfg +++ b/config/nimdoc.cfg @@ -100,7 +100,7 @@ doc.body_toc_group = """