From dec97924a4bb6a719380475de46485ce71eeb56c Mon Sep 17 00:00:00 2001 From: cooldome Date: Mon, 9 Jul 2018 12:11:03 +0200 Subject: [PATCH 01/43] Custom pragmas in proc types (#8205) --- compiler/ast.nim | 3 ++- compiler/sempass2.nim | 24 ++++++++++++++---------- compiler/vmdeps.nim | 3 ++- lib/core/macros.nim | 4 +++- tests/pragmas/tcustom_pragma.nim | 10 +++++++++- 5 files changed, 30 insertions(+), 14 deletions(-) diff --git a/compiler/ast.nim b/compiler/ast.nim index 6302c21b98..dbf1151a90 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -322,7 +322,8 @@ const usesEffects* = 1 # read effects at position 1 writeEffects* = 2 # write effects at position 2 tagEffects* = 3 # user defined tag ('gc', 'time' etc.) - effectListLen* = 4 # list of effects list + pragmasEffects* = 4 # not an effect, but a slot for pragmas in proc type + effectListLen* = 5 # list of effects list type TTypeKind* = enum # order is important! diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index 4d3ee0408a..e7a76ec224 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -552,6 +552,10 @@ proc isOwnedProcVar(n: PNode; owner: PSym): bool = # XXX prove the soundness of this effect system rule result = n.kind == nkSym and n.sym.kind == skParam and owner == n.sym.owner +proc isNoEffectList(n: PNode): bool {.inline.} = + assert n.kind == nkEffectList + n.len == 0 or (n[tagEffects] == nil and n[exceptionEffects] == nil) + proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) = let a = skipConvAndClosure(n) let op = a.typ @@ -561,7 +565,7 @@ proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) = let s = n.skipConv if s.kind == nkSym and s.sym.kind in routineKinds: propagateEffects(tracked, n, s.sym) - elif effectList.len == 0: + elif isNoEffectList(effectList): if isForwardedProc(n): # we have no explicit effects but it's a forward declaration and so it's # stated there are no additional effects, so simply propagate them: @@ -723,7 +727,7 @@ proc track(tracked: PEffects, n: PNode) = var effectList = op.n.sons[0] if a.kind == nkSym and a.sym.kind == skMethod: propagateEffects(tracked, n, a.sym) - elif effectList.len == 0: + elif isNoEffectList(effectList): if isForwardedProc(a): propagateEffects(tracked, n, a.sym) elif isIndirectCall(a, tracked.owner): @@ -897,19 +901,18 @@ proc checkMethodEffects*(g: ModuleGraph; disp, branch: PSym) = [$branch.typ.lockLevel, $disp.typ.lockLevel]) proc setEffectsForProcType*(g: ModuleGraph; t: PType, n: PNode) = - var effects = t.n.sons[0] + var effects = t.n[0] if t.kind != tyProc or effects.kind != nkEffectList: return - - let - raisesSpec = effectSpec(n, wRaises) - tagsSpec = effectSpec(n, wTags) - if not isNil(raisesSpec) or not isNil(tagsSpec): + if n.kind != nkEmpty: internalAssert g.config, effects.len == 0 newSeq(effects.sons, effectListLen) + let raisesSpec = effectSpec(n, wRaises) if not isNil(raisesSpec): - effects.sons[exceptionEffects] = raisesSpec + effects[exceptionEffects] = raisesSpec + let tagsSpec = effectSpec(n, wTags) if not isNil(tagsSpec): - effects.sons[tagEffects] = tagsSpec + effects[tagEffects] = tagsSpec + effects[pragmasEffects] = n proc initEffects(g: ModuleGraph; effects: PNode; s: PSym; t: var TEffects) = newSeq(effects.sons, effectListLen) @@ -917,6 +920,7 @@ proc initEffects(g: ModuleGraph; effects: PNode; s: PSym; t: var TEffects) = effects.sons[tagEffects] = newNodeI(nkArgList, s.info) effects.sons[usesEffects] = g.emptyNode effects.sons[writeEffects] = g.emptyNode + effects.sons[pragmasEffects] = g.emptyNode t.exc = effects.sons[exceptionEffects] t.tags = effects.sons[tagEffects] diff --git a/compiler/vmdeps.nim b/compiler/vmdeps.nim index 865ecd36ed..bf2418eaf0 100644 --- a/compiler/vmdeps.nim +++ b/compiler/vmdeps.nim @@ -229,7 +229,8 @@ proc mapTypeToAstX(cache: IdentCache; t: PType; info: TLineInfo; for i in 1.. 0: t.n[0][pragmasEffects].copyTree + else: newNodeI(nkEmpty, info) else: result = mapTypeToBracket("proc", mNone, t, info) of tyOpenArray: result = mapTypeToBracket("openArray", mOpenArray, t, info) diff --git a/lib/core/macros.nim b/lib/core/macros.nim index 1f251b73ee..8a1be3720b 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -1284,7 +1284,9 @@ proc customPragmaNode(n: NimNode): NimNode = let typ = n.getTypeInst() - if typ.typeKind == ntyTypeDesc: + if typ.kind == nnkBracketExpr and typ.len > 1 and typ[1].kind == nnkProcTy: + return typ[1][1] + elif typ.typeKind == ntyTypeDesc: let impl = typ[1].getImpl() if impl[0].kind == nnkPragmaExpr: return impl[0][1] diff --git a/tests/pragmas/tcustom_pragma.nim b/tests/pragmas/tcustom_pragma.nim index cd1edccf67..a5f86b54d3 100644 --- a/tests/pragmas/tcustom_pragma.nim +++ b/tests/pragmas/tcustom_pragma.nim @@ -145,4 +145,12 @@ block: type Annotated {.simpleAttr.} = object proc generic_proc[T]() = - assert Annotated.hasCustomPragma(simpleAttr) \ No newline at end of file + assert Annotated.hasCustomPragma(simpleAttr) + + +#-------------------------------------------------------------------------- +# Pragma on proc type + +let a: proc(x: int) {.defaultValue(5).} = nil +static: + doAssert hasCustomPragma(a.type, defaultValue) From c6671776a16127be30a627d1672fee9897a2320f Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Mon, 9 Jul 2018 15:02:48 +0200 Subject: [PATCH 02/43] Reset typedescMatched before paramTypesMatch (#8250) The flag should not be carried out across different parameters. Fixes #7794 --- compiler/sigmatch.nim | 3 +++ tests/generics/t7794.nim | 15 +++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 tests/generics/t7794.nim diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index f9d1edc894..09f7f23b50 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -2231,6 +2231,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode, m.state = csNoMatch return m.baseTypeMatch = false + m.typedescMatched = false n.sons[a].sons[1] = prepareOperand(c, formal.typ, n.sons[a].sons[1]) n.sons[a].typ = n.sons[a].sons[1].typ var arg = paramTypesMatch(m, formal.typ, n.sons[a].typ, @@ -2266,6 +2267,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode, # beware of the side-effects in 'prepareOperand'! So only do it for # varargs matching. See tests/metatype/tstatic_overloading. m.baseTypeMatch = false + m.typedescMatched = false incl(marker, formal.position) n.sons[a] = prepareOperand(c, formal.typ, n.sons[a]) var arg = paramTypesMatch(m, formal.typ, n.sons[a].typ, @@ -2300,6 +2302,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode, addSon(container, n.sons[a]) else: m.baseTypeMatch = false + m.typedescMatched = false n.sons[a] = prepareOperand(c, formal.typ, n.sons[a]) var arg = paramTypesMatch(m, formal.typ, n.sons[a].typ, n.sons[a], nOrig.sons[a]) diff --git a/tests/generics/t7794.nim b/tests/generics/t7794.nim new file mode 100644 index 0000000000..b295da8659 --- /dev/null +++ b/tests/generics/t7794.nim @@ -0,0 +1,15 @@ +discard """ +output: ''' +10 +2.0 +''' +""" + +type + Data*[T:SomeNumber, U:SomeReal] = ref object + x*: T + value*: U + +var d = Data[int, float64](x:10.int, value:2'f64) +echo d.x +echo d.value From 3b310e91cd592b76cd34678ae394c1af2c3808f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Nihlg=C3=A5rd?= Date: Mon, 9 Jul 2018 20:04:25 +0200 Subject: [PATCH 03/43] New implementations of times.parse & times.format (#8094) --- lib/pure/strformat.nim | 4 +- lib/pure/times.nim | 1374 +++++++++++++++++++++++---------------- tests/js/ttimes.nim | 6 +- tests/stdlib/ttimes.nim | 210 ++++-- 4 files changed, 975 insertions(+), 619 deletions(-) diff --git a/lib/pure/strformat.nim b/lib/pure/strformat.nim index 247b9ec5cb..f13eb5e8ea 100644 --- a/lib/pure/strformat.nim +++ b/lib/pure/strformat.nim @@ -683,8 +683,8 @@ when isMainModule: # works: import times - var nullTime: DateTime - check &"{nullTime:yyyy-mm-dd}", "0000-00-00" + var dt = initDateTime(01, mJan, 2000, 00, 00, 00) + check &"{dt:yyyy-MM-dd}", "2000-01-01" var tm = fromUnix(0) discard &"{tm}" diff --git a/lib/pure/times.nim b/lib/pure/times.nim index a134faef2d..05398cfad3 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -7,35 +7,127 @@ # distribution, for details about the copyright. # +##[ + This module contains routines and types for dealing with time using a proleptic Gregorian calendar. + It's also available for the `JavaScript target `_. + + Although the types use nanosecond time resolution, the underlying resolution used by ``getTime()`` + depends on the platform and backend (JS is limited to millisecond precision). + + Examples: + + .. code-block:: nim + + import times, os + let time = cpuTime() + + sleep(100) # replace this with something to be timed + echo "Time taken: ",cpuTime() - time + + echo "My formatted time: ", format(now(), "d MMMM yyyy HH:mm") + echo "Using predefined formats: ", getClockStr(), " ", getDateStr() + + echo "cpuTime() float value: ", cpuTime() + echo "An hour from now : ", now() + 1.hours + echo "An hour from (UTC) now: ", getTime().utc + initDuration(hours = 1) + + Parsing and Formatting Dates + ---------------------------- + + The ``DateTime`` type can be parsed and formatted using the different + ``parse`` and ``format`` procedures. + + .. code-block:: nim + + let dt = parse("2000-01-01", "yyyy-MM-dd") + echo dt.format("yyyy-MM-dd") + + The different format patterns that are supported are documented below. + + ============= ================================================================================= ================================================ + Pattern Description Example + ============= ================================================================================= ================================================ + ``d`` Numeric value representing the day of the month, | ``1/04/2012 -> 1`` + it will be either one or two digits long. | ``21/04/2012 -> 21`` + ``dd`` Same as above, but is always two digits. | ``1/04/2012 -> 01`` + | ``21/04/2012 -> 21`` + ``ddd`` Three letter string which indicates the day of the week. | ``Saturday -> Sat`` + | ``Monday -> Mon`` + ``dddd`` Full string for the day of the week. | ``Saturday -> Saturday`` + | ``Monday -> Monday`` + ``h`` The hours in one digit if possible. Ranging from 1-12. | ``5pm -> 5`` + | ``2am -> 2`` + ``hh`` The hours in two digits always. If the hour is one digit 0 is prepended. | ``5pm -> 05`` + | ``11am -> 11`` + ``H`` The hours in one digit if possible, ranging from 0-23. | ``5pm -> 17`` + | ``2am -> 2`` + ``HH`` The hours in two digits always. 0 is prepended if the hour is one digit. | ``5pm -> 17`` + | ``2am -> 02`` + ``m`` The minutes in 1 digit if possible. | ``5:30 -> 30`` + | ``2:01 -> 1`` + ``mm`` Same as above but always 2 digits, 0 is prepended if the minute is one digit. | ``5:30 -> 30`` + | ``2:01 -> 01`` + ``M`` The month in one digit if possible. | ``September -> 9`` + | ``December -> 12`` + ``MM`` The month in two digits always. 0 is prepended. | ``September -> 09`` + | ``December -> 12`` + ``MMM`` Abbreviated three-letter form of the month. | ``September -> Sep`` + | ``December -> Dec`` + ``MMMM`` Full month string, properly capitalized. | ``September -> September`` + ``s`` Seconds as one digit if possible. | ``00:00:06 -> 6`` + ``ss`` Same as above but always two digits. 0 is prepended. | ``00:00:06 -> 06`` + ``t`` ``A`` when time is in the AM. ``P`` when time is in the PM. | ``5pm -> P`` + | ``2am -> A`` + ``tt`` Same as above, but ``AM`` and ``PM`` instead of ``A`` and ``P`` respectively. | ``5pm -> PM`` + | ``2am -> AM`` + ``yy`` The last two digits of the year. When parsing, the current century is assumed. | ``2012 AD -> 12`` + ``yyyy`` The year, padded to atleast four digits. | ``2012 AD -> 2012`` + Is always positive, even when the year is BC. | ``24 AD -> 0024`` + When the year is more than four digits, '+' is prepended. | ``24 BC -> 00024`` + | ``12345 AD -> +12345`` + ``YYYY`` The year without any padding. | ``2012 AD -> 2012`` + Is always positive, even when the year is BC. | ``24 AD -> 24`` + | ``24 BC -> 24`` + | ``12345 AD -> 12345`` + ``uuuu`` The year, padded to atleast four digits. Will be negative when the year is BC. | ``2012 AD -> 2012`` + When the year is more than four digits, '+' is prepended unless the year is BC. | ``24 AD -> 0024`` + | ``24 BC -> -0023`` + | ``12345 AD -> +12345`` + ``UUUU`` The year without any padding. Will be negative when the year is BC. | ``2012 AD -> 2012`` + | ``24 AD -> 24`` + | ``24 BC -> -23`` + | ``12345 AD -> 12345`` + ``z`` Displays the timezone offset from UTC. | ``GMT+7 -> +7`` + | ``GMT-5 -> -5`` + ``zz`` Same as above but with leading 0. | ``GMT+7 -> +07`` + | ``GMT-5 -> -05`` + ``zzz`` Same as above but with ``:mm`` where *mm* represents minutes. | ``GMT+7 -> +07:00`` + | ``GMT-5 -> -05:00`` + ``zzzz`` Same as above but with ``:ss`` where *ss* represents seconds. | ``GMT+7 -> +07:00:00`` + | ``GMT-5 -> -05:00:00`` + ``g`` Era: AD or BC | ``300 AD -> AD`` + | ``300 BC -> BC`` + ``fff`` Milliseconds display | ``1000000 nanoseconds -> 1`` + ``ffffff`` Microseconds display | ``1000000 nanoseconds -> 1000`` + ``fffffffff`` Nanoseconds display | ``1000000 nanoseconds -> 1000000`` + ============= ================================================================================= ================================================ + + Other strings can be inserted by putting them in ``''``. For example + ``hh'->'mm`` will give ``01->56``. The following characters can be + inserted without quoting them: ``:`` ``-`` ``(`` ``)`` ``/`` ``[`` ``]`` + ``,``. A literal ``'`` can be specified with ``''``. + + However you don't need to necessarily separate format patterns, a + unambiguous format string like ``yyyyMMddhhmmss`` is valid too (although + only for years in the range 1..9999). +]## -## This module contains routines and types for dealing with time using a proleptic Gregorian calendar. -## It's is available for the `JavaScript target `_. -## -## The types uses nanosecond time resolution, but the underlying resolution used by ``getTime()`` -## depends on the platform and backend (JS is limited to millisecond precision). -## -## Examples: -## -## .. code-block:: nim -## -## import times, os -## let time = cpuTime() -## -## sleep(100) # replace this with something to be timed -## echo "Time taken: ",cpuTime() - time -## -## echo "My formatted time: ", format(now(), "d MMMM yyyy HH:mm") -## echo "Using predefined formats: ", getClockStr(), " ", getDateStr() -## -## echo "cpuTime() float value: ", cpuTime() -## echo "An hour from now : ", now() + 1.hours -## echo "An hour from (UTC) now: ", getTime().utc + initDuration(hours = 1) {.push debugger:off.} # the user does not want to trace a part # of the standard library! import - strutils, parseutils, algorithm, math + strutils, parseutils, algorithm, math, options, strformat include "system/inclrtl" @@ -306,7 +398,7 @@ proc fractional*(dur: Duration): Duration {.inline.} = proc fromUnix*(unix: int64): Time {.benign, tags: [], raises: [], noSideEffect.} = ## Convert a unix timestamp (seconds since ``1970-01-01T00:00:00Z``) to a ``Time``. runnableExamples: - doAssert $fromUnix(0).utc == "1970-01-01T00:00:00+00:00" + doAssert $fromUnix(0).utc == "1970-01-01T00:00:00Z" initTime(unix, 0) proc toUnix*(t: Time): int64 {.benign, tags: [], raises: [], noSideEffect.} = @@ -915,7 +1007,7 @@ proc initTimeInterval*(nanoseconds, microseconds, milliseconds, runnableExamples: let day = initTimeInterval(hours=24) let dt = initDateTime(01, mJan, 2000, 12, 00, 00, utc()) - doAssert $(dt + day) == "2000-01-02T12:00:00+00:00" + doAssert $(dt + day) == "2000-01-02T12:00:00Z" result.nanoseconds = nanoseconds result.microseconds = microseconds result.milliseconds = milliseconds @@ -1126,7 +1218,7 @@ proc initDateTime*(monthday: MonthdayRange, month: Month, year: int, ## Create a new ``DateTime`` in the specified timezone. runnableExamples: let dt1 = initDateTime(30, mMar, 2017, 00, 00, 00, 00, utc()) - doAssert $dt1 == "2017-03-30T00:00:00+00:00" + doAssert $dt1 == "2017-03-30T00:00:00Z" assertValidDate monthday, month, year let dt = DateTime( @@ -1146,7 +1238,7 @@ proc initDateTime*(monthday: MonthdayRange, month: Month, year: int, ## Create a new ``DateTime`` in the specified timezone. runnableExamples: let dt1 = initDateTime(30, mMar, 2017, 00, 00, 00, utc()) - doAssert $dt1 == "2017-03-30T00:00:00+00:00" + doAssert $dt1 == "2017-03-30T00:00:00Z" initDateTime(monthday, month, year, hour, minute, second, 0, zone) @@ -1162,9 +1254,9 @@ proc `+`*(dt: DateTime, interval: TimeInterval): DateTime = ## runnableExamples: let dt = initDateTime(30, mMar, 2017, 00, 00, 00, utc()) - doAssert $(dt + 1.months) == "2017-04-30T00:00:00+00:00" + doAssert $(dt + 1.months) == "2017-04-30T00:00:00Z" # This is correct and happens due to monthday overflow. - doAssert $(dt - 1.months) == "2017-03-02T00:00:00+00:00" + doAssert $(dt - 1.months) == "2017-03-02T00:00:00Z" let (adjDur, absDur) = evaluateInterval(dt, interval) if adjDur != DurationZero: @@ -1185,7 +1277,7 @@ proc `-`*(dt: DateTime, interval: TimeInterval): DateTime = ## component and so on. The returned ``DateTime`` will have the same timezone as the input. runnableExamples: let dt = initDateTime(30, mMar, 2017, 00, 00, 00, utc()) - doAssert $(dt - 5.days) == "2017-03-25T00:00:00+00:00" + doAssert $(dt - 5.days) == "2017-03-25T00:00:00Z" dt + (-interval) @@ -1193,7 +1285,7 @@ proc `+`*(dt: DateTime, dur: Duration): DateTime = runnableExamples: let dt = initDateTime(30, mMar, 2017, 00, 00, 00, utc()) let dur = initDuration(hours = 5) - doAssert $(dt + dur) == "2017-03-30T05:00:00+00:00" + doAssert $(dt + dur) == "2017-03-30T05:00:00Z" (dt.toTime + dur).inZone(dt.timezone) @@ -1201,7 +1293,7 @@ proc `-`*(dt: DateTime, dur: Duration): DateTime = runnableExamples: let dt = initDateTime(30, mMar, 2017, 00, 00, 00, utc()) let dur = initDuration(days = 5) - doAssert $(dt - dur) == "2017-03-25T00:00:00+00:00" + doAssert $(dt - dur) == "2017-03-25T00:00:00Z" (dt.toTime - dur).inZone(dt.timezone) @@ -1394,228 +1486,726 @@ proc `*=`*[T: TimesMutableTypes, U](a: var T, b: U) = a = a * b -proc formatToken(dt: DateTime, token: string, buf: var string) = - ## Helper of the format proc to parse individual tokens. - ## - ## Pass the found token in the user input string, and the buffer where the - ## final string is being built. This has to be a var value because certain - ## formatting tokens require modifying the previous characters. - case token - of "d": - buf.add($dt.monthday) - of "dd": - if dt.monthday < 10: - buf.add("0") - buf.add($dt.monthday) - of "ddd": - buf.add(($dt.weekday)[0 .. 2]) - of "dddd": - buf.add($dt.weekday) - of "h": - if dt.hour == 0: buf.add("12") - else: buf.add($(if dt.hour > 12: dt.hour - 12 else: dt.hour)) - of "hh": - if dt.hour == 0: - buf.add("12") - else: - let amerHour = if dt.hour > 12: dt.hour - 12 else: dt.hour - if amerHour < 10: - buf.add('0') - buf.add($amerHour) - of "H": - buf.add($dt.hour) - of "HH": - if dt.hour < 10: - buf.add('0') - buf.add($dt.hour) - of "m": - buf.add($dt.minute) - of "mm": - if dt.minute < 10: - buf.add('0') - buf.add($dt.minute) - of "M": - buf.add($ord(dt.month)) - of "MM": - if dt.month < mOct: - buf.add('0') - buf.add($ord(dt.month)) - of "MMM": - buf.add(($dt.month)[0..2]) - of "MMMM": - buf.add($dt.month) - of "s": - buf.add($dt.second) - of "ss": - if dt.second < 10: - buf.add('0') - buf.add($dt.second) - of "t": - if dt.hour >= 12: - buf.add('P') - else: buf.add('A') - of "tt": - if dt.hour >= 12: - buf.add("PM") - else: buf.add("AM") - of "y": - var fr = ($dt.year).len()-1 - if fr < 0: fr = 0 - buf.add(($dt.year)[fr .. ($dt.year).len()-1]) - of "yy": - var fr = ($dt.year).len()-2 - if fr < 0: fr = 0 - var fyear = ($dt.year)[fr .. ($dt.year).len()-1] - if fyear.len != 2: fyear = repeat('0', 2-fyear.len()) & fyear - buf.add(fyear) - of "yyy": - var fr = ($dt.year).len()-3 - if fr < 0: fr = 0 - var fyear = ($dt.year)[fr .. ($dt.year).len()-1] - if fyear.len != 3: fyear = repeat('0', 3-fyear.len()) & fyear - buf.add(fyear) - of "yyyy": - var fr = ($dt.year).len()-4 - if fr < 0: fr = 0 - var fyear = ($dt.year)[fr .. ($dt.year).len()-1] - if fyear.len != 4: fyear = repeat('0', 4-fyear.len()) & fyear - buf.add(fyear) - of "yyyyy": - var fr = ($dt.year).len()-5 - if fr < 0: fr = 0 - var fyear = ($dt.year)[fr .. ($dt.year).len()-1] - if fyear.len != 5: fyear = repeat('0', 5-fyear.len()) & fyear - buf.add(fyear) - of "z": - let - nonDstTz = dt.utcOffset - hours = abs(nonDstTz) div secondsInHour - if nonDstTz <= 0: buf.add('+') - else: buf.add('-') - buf.add($hours) - of "zz": - let - nonDstTz = dt.utcOffset - hours = abs(nonDstTz) div secondsInHour - if nonDstTz <= 0: buf.add('+') - else: buf.add('-') - if hours < 10: buf.add('0') - buf.add($hours) - of "zzz": - let - nonDstTz = dt.utcOffset - hours = abs(nonDstTz) div secondsInHour - minutes = (abs(nonDstTz) div secondsInMin) mod minutesInHour - if nonDstTz <= 0: buf.add('+') - else: buf.add('-') - if hours < 10: buf.add('0') - buf.add($hours) - buf.add(':') - if minutes < 10: buf.add('0') - buf.add($minutes) - of "fff": - buf.add(intToStr(convert(Nanoseconds, Milliseconds, dt.nanosecond), 3)) - of "ffffff": - buf.add(intToStr(convert(Nanoseconds, Microseconds, dt.nanosecond), 6)) - of "fffffffff": - buf.add(intToStr(dt.nanosecond, 9)) - of "": - discard - else: - raise newException(ValueError, "Invalid format string: " & token) +# +# Parse & format implementation +# -proc format*(dt: DateTime, f: string): string {.tags: [].}= - ## This procedure formats `dt` as specified by `f`. The following format - ## specifiers are available: - ## - ## ============ ================================================================================= ================================================ - ## Specifier Description Example - ## ============ ================================================================================= ================================================ - ## d Numeric value of the day of the month, it will be one or two digits long. ``1/04/2012 -> 1``, ``21/04/2012 -> 21`` - ## dd Same as above, but always two digits. ``1/04/2012 -> 01``, ``21/04/2012 -> 21`` - ## ddd Three letter string which indicates the day of the week. ``Saturday -> Sat``, ``Monday -> Mon`` - ## dddd Full string for the day of the week. ``Saturday -> Saturday``, ``Monday -> Monday`` - ## h The hours in one digit if possible. Ranging from 0-12. ``5pm -> 5``, ``2am -> 2`` - ## hh The hours in two digits always. If the hour is one digit 0 is prepended. ``5pm -> 05``, ``11am -> 11`` - ## H The hours in one digit if possible, randing from 0-24. ``5pm -> 17``, ``2am -> 2`` - ## HH The hours in two digits always. 0 is prepended if the hour is one digit. ``5pm -> 17``, ``2am -> 02`` - ## m The minutes in 1 digit if possible. ``5:30 -> 30``, ``2:01 -> 1`` - ## mm Same as above but always 2 digits, 0 is prepended if the minute is one digit. ``5:30 -> 30``, ``2:01 -> 01`` - ## M The month in one digit if possible. ``September -> 9``, ``December -> 12`` - ## MM The month in two digits always. 0 is prepended. ``September -> 09``, ``December -> 12`` - ## MMM Abbreviated three-letter form of the month. ``September -> Sep``, ``December -> Dec`` - ## MMMM Full month string, properly capitalized. ``September -> September`` - ## s Seconds as one digit if possible. ``00:00:06 -> 6`` - ## ss Same as above but always two digits. 0 is prepended. ``00:00:06 -> 06`` - ## t ``A`` when time is in the AM. ``P`` when time is in the PM. - ## tt Same as above, but ``AM`` and ``PM`` instead of ``A`` and ``P`` respectively. - ## y(yyyy) This displays the year to different digits. You most likely only want 2 or 4 'y's - ## yy Displays the year to two digits. ``2012 -> 12`` - ## yyyy Displays the year to four digits. ``2012 -> 2012`` - ## z Displays the timezone offset from UTC. ``GMT+7 -> +7``, ``GMT-5 -> -5`` - ## zz Same as above but with leading 0. ``GMT+7 -> +07``, ``GMT-5 -> -05`` - ## zzz Same as above but with ``:mm`` where *mm* represents minutes. ``GMT+7 -> +07:00``, ``GMT-5 -> -05:00`` - ## fff Milliseconds display ``1000000 nanoseconds -> 1`` - ## ffffff Microseconds display ``1000000 nanoseconds -> 1000`` - ## fffffffff Nanoseconds display ``1000000 nanoseconds -> 1000000`` - ## ============ ================================================================================= ================================================ - ## - ## Other strings can be inserted by putting them in ``''``. For example - ## ``hh'->'mm`` will give ``01->56``. The following characters can be - ## inserted without quoting them: ``:`` ``-`` ``(`` ``)`` ``/`` ``[`` ``]`` - ## ``,``. However you don't need to necessarily separate format specifiers, a - ## unambiguous format string like ``yyyyMMddhhmmss`` is valid too. +type + AmPm = enum + apUnknown, apAm, apPm + + Era = enum + eraUnknown, eraAd, eraBc + + ParsedTime = object + amPm: AmPm + era: Era + year: Option[int] + month: Option[int] + monthday: Option[int] + utcOffset: Option[int] + + # '0' as default for these work fine + # so no need for `Option`. + hour: int + minute: int + second: int + nanosecond: int + + FormatTokenKind = enum + tkPattern, tkLiteral + + FormatPattern {.pure.} = enum + d, dd, ddd, dddd + h, hh, H, HH + m, mm, M, MM, MMM, MMMM + s, ss + fff, ffffff, fffffffff + t, tt + y, yy, yyy, yyyy, yyyyy + YYYY + uuuu + UUUU + z, zz, zzz, zzzz + g + + # This is a special value used to mark literal format values. + # See the doc comment for ``TimeFormat.patterns``. + Lit + + TimeFormat* = object ## Represents a format for parsing and printing + ## time types. + patterns: seq[byte] ## \ + ## Contains the patterns encoded as bytes. + ## Literal values are encoded in a special way. + ## They start with ``Lit.byte``, then the length of the literal, then the + ## raw char values of the literal. For example, the literal `foo` would + ## be encoded as ``@[Lit.byte, 3.byte, 'f'.byte, 'o'.byte, 'o'.byte]``. + formatStr: string + +const FormatLiterals = { ' ', '-', '/', ':', '(', ')', '[', ']', ',' } + +proc `$`*(f: TimeFormat): string = + ## Returns the format string that was used to construct ``f``. runnableExamples: - let dt = initDateTime(01, mJan, 2000, 12, 00, 00, 01, utc()) - doAssert format(dt, "yyyy-MM-dd'T'HH:mm:ss'.'fffffffffzzz") == "2000-01-01T12:00:00.000000001+00:00" + let f = initTimeFormat("yyyy-MM-dd") + doAssert $f == "yyyy-MM-dd" + f.formatStr - result = "" +proc raiseParseException(f: TimeFormat, input: string, msg: string) = + raise newException(ValueError, + &"Failed to parse '{input}' with format '{f}'. {msg}") + +iterator tokens(f: string): tuple[kind: FormatTokenKind, token: string] = var i = 0 - var currentF = "" + var currToken = "" + + template yieldCurrToken() = + if currToken.len != 0: + yield (tkPattern, currToken) + currToken = "" + while i < f.len: case f[i] - of ' ', '-', '/', ':', '\'', '(', ')', '[', ']', ',': - formatToken(dt, currentF, result) - - currentF = "" - - if f[i] == '\'': + of '\'': + yieldCurrToken() + if i.succ < f.len and f[i.succ] == '\'': + yield (tkLiteral, "'") + i.inc 2 + else: + var token = "" inc(i) # Skip ' - while i < f.len-1 and f[i] != '\'': - result.add(f[i]) - inc(i) - else: result.add(f[i]) + while i < f.len and f[i] != '\'': + token.add f[i] + i.inc + if i > f.high: + raise newException(ValueError, + &"Unclosed ' in time format string. " & + "For a literal ', use ''.") + i.inc + yield (tkLiteral, token) + of FormatLiterals: + yieldCurrToken() + yield (tkLiteral, $f[i]) + i.inc else: # Check if the letter being added matches previous accumulated buffer. - if currentF.len == 0 or currentF[high(currentF)] == f[i]: - currentF.add(f[i]) + if currToken.len == 0 or currToken[0] == f[i]: + currToken.add(f[i]) + i.inc else: - formatToken(dt, currentF, result) - dec(i) # Move position back to re-process the character separately. - currentF = "" + yield (tkPattern, currToken) + currToken = $f[i] + i.inc - inc(i) - formatToken(dt, currentF, result) + yieldCurrToken() -proc format*(time: Time, f: string, zone: Timezone = local()): string {.tags: [].} = - ## Converts a `Time` value to a string representation. It will use format from - ## ``format(dt: DateTime, f: string)``. +proc stringToPattern(str: string): FormatPattern = + case str + of "d": result = d + of "dd": result = dd + of "ddd": result = ddd + of "dddd": result = dddd + of "h": result = h + of "hh": result = hh + of "H": result = H + of "HH": result = HH + of "m": result = m + of "mm": result = mm + of "M": result = M + of "MM": result = MM + of "MMM": result = MMM + of "MMMM": result = MMMM + of "s": result = s + of "ss": result = ss + of "fff": result = fff + of "ffffff": result = ffffff + of "fffffffff": result = fffffffff + of "t": result = t + of "tt": result = tt + of "y": result = y + of "yy": result = yy + of "yyy": result = yyy + of "yyyy": result = yyyy + of "yyyyy": result = yyyyy + of "YYYY": result = YYYY + of "uuuu": result = uuuu + of "UUUU": result = UUUU + of "z": result = z + of "zz": result = zz + of "zzz": result = zzz + of "zzzz": result = zzzz + of "g": result = g + else: raise newException(ValueError, &"'{str}' is not a valid pattern") + +proc initTimeFormat*(format: string): TimeFormat = + ## Construct a new time format for parsing & formatting time types. + ## + ## See `Parsing and formatting dates`_ for documentation of the + ## ``format`` argument. + runnableExamples: + let f = initTimeFormat("yyyy-MM-dd") + doAssert "2000-01-01" == f.format(f.parse("2000-01-01")) + result.formatStr = format + result.patterns = @[] + for kind, token in format.tokens: + case kind + of tkLiteral: + case token + else: + result.patterns.add(FormatPattern.Lit.byte) + if token.len > 255: + raise newException(ValueError, + "Format literal is to long:" & token) + result.patterns.add(token.len.byte) + for c in token: + result.patterns.add(c.byte) + of tkPattern: + result.patterns.add(stringToPattern(token).byte) + +proc formatPattern(dt: DateTime, pattern: FormatPattern, result: var string) = + template yearOfEra(dt: DateTime): int = + if dt.year <= 0: abs(dt.year) + 1 else: dt.year + + case pattern + of d: + result.add $dt.monthday + of dd: + result.add dt.monthday.intToStr(2) + of ddd: + result.add ($dt.weekday)[0..2] + of dddd: + result.add $dt.weekday + of h: + result.add( + if dt.hour == 0: "12" + elif dt.hour > 12: $(dt.hour - 12) + else: $dt.hour + ) + of hh: + result.add( + if dt.hour == 0: "12" + elif dt.hour > 12: (dt.hour - 12).intToStr(2) + else: dt.hour.intToStr(2) + ) + of H: + result.add $dt.hour + of HH: + result.add dt.hour.intToStr(2) + of m: + result.add $dt.minute + of mm: + result.add dt.minute.intToStr(2) + of M: + result.add $ord(dt.month) + of MM: + result.add ord(dt.month).intToStr(2) + of MMM: + result.add ($dt.month)[0..2] + of MMMM: + result.add $dt.month + of s: + result.add $dt.second + of ss: + result.add dt.second.intToStr(2) + of fff: + result.add(intToStr(convert(Nanoseconds, Milliseconds, dt.nanosecond), 3)) + of ffffff: + result.add(intToStr(convert(Nanoseconds, Microseconds, dt.nanosecond), 6)) + of fffffffff: + result.add(intToStr(dt.nanosecond, 9)) + of t: + result.add if dt.hour >= 12: "P" else: "A" + of tt: + result.add if dt.hour >= 12: "PM" else: "AM" + of y: # Deprecated + result.add $(dt.yearOfEra mod 10) + of yy: + result.add (dt.yearOfEra mod 100).intToStr(2) + of yyy: # Deprecated + result.add (dt.yearOfEra mod 1000).intToStr(3) + of yyyy: + let year = dt.yearOfEra + if year < 10000: + result.add year.intToStr(4) + else: + result.add '+' & $year + of yyyyy: # Deprecated + result.add (dt.yearOfEra mod 100_000).intToStr(5) + of YYYY: + if dt.year < 1: + result.add $(abs(dt.year) + 1) + else: + result.add $dt.year + of uuuu: + let year = dt.year + if year < 10000 or year < 0: + result.add year.intToStr(4) + else: + result.add '+' & $year + of UUUU: + result.add $dt.year + of z, zz, zzz, zzzz: + if dt.timezone.name == "Etc/UTC": + result.add 'Z' + else: + result.add if -dt.utcOffset >= 0: '+' else: '-' + let absOffset = abs(dt.utcOffset) + case pattern: + of z: + result.add $(absOffset div 3600) + of zz: + result.add (absOffset div 3600).intToStr(2) + of zzz: + let h = (absOffset div 3600).intToStr(2) + let m = ((absOffset div 60) mod 60).intToStr(2) + result.add h & ":" & m + of zzzz: + let absOffset = abs(dt.utcOffset) + let h = (absOffset div 3600).intToStr(2) + let m = ((absOffset div 60) mod 60).intToStr(2) + let s = (absOffset mod 60).intToStr(2) + result.add h & ":" & m & ":" & s + else: assert false + of g: + result.add if dt.year < 1: "BC" else: "AD" + of Lit: assert false # Can't happen + +proc parsePattern(input: string, pattern: FormatPattern, i: var int, + parsed: var ParsedTime): bool = + template takeInt(allowedWidth: Slice[int]): int = + var sv: int + let max = i + allowedWidth.b - 1 + var pd = + if max > input.high: + parseInt(input, sv, i) + else: + parseInt(input[i..max], sv) + if pd notin allowedWidth: + return false + i.inc pd + sv + + template contains[T](t: typedesc[T], i: int): bool = + i in low(t)..high(t) + + result = true + + case pattern + of d: + parsed.monthday = some(takeInt(1..2)) + result = parsed.monthday.get() in MonthdayRange + of dd: + parsed.monthday = some(takeInt(2..2)) + result = parsed.monthday.get() in MonthdayRange + of ddd: + result = input.substr(i, i+2).toLowerAscii() in [ + "sun", "mon", "tue", "wed", "thu", "fri", "sat"] + if result: + i.inc 3 + of dddd: + if input.substr(i, i+5).cmpIgnoreCase("sunday") == 0: + i.inc 6 + elif input.substr(i, i+5).cmpIgnoreCase("monday") == 0: + i.inc 6 + elif input.substr(i, i+6).cmpIgnoreCase("tuesday") == 0: + i.inc 7 + elif input.substr(i, i+8).cmpIgnoreCase("wednesday") == 0: + i.inc 9 + elif input.substr(i, i+7).cmpIgnoreCase("thursday") == 0: + i.inc 8 + elif input.substr(i, i+5).cmpIgnoreCase("friday") == 0: + i.inc 6 + elif input.substr(i, i+7).cmpIgnoreCase("saturday") == 0: + i.inc 8 + else: + result = false + of h, H: + parsed.hour = takeInt(1..2) + result = parsed.hour in HourRange + of hh, HH: + parsed.hour = takeInt(2..2) + result = parsed.hour in HourRange + of m: + parsed.minute = takeInt(1..2) + result = parsed.hour in MinuteRange + of mm: + parsed.minute = takeInt(2..2) + result = parsed.hour in MinuteRange + of M: + let month = takeInt(1..2) + result = month in 1..12 + parsed.month = some(month) + of MM: + let month = takeInt(2..2) + result = month in 1..12 + parsed.month = some(month) + of MMM: + case input.substr(i, i+2).toLowerAscii() + of "jan": parsed.month = some(1) + of "feb": parsed.month = some(2) + of "mar": parsed.month = some(3) + of "apr": parsed.month = some(4) + of "may": parsed.month = some(5) + of "jun": parsed.month = some(6) + of "jul": parsed.month = some(7) + of "aug": parsed.month = some(8) + of "sep": parsed.month = some(9) + of "oct": parsed.month = some(10) + of "nov": parsed.month = some(11) + of "dec": parsed.month = some(12) + else: + result = false + if result: + i.inc 3 + of MMMM: + if input.substr(i, i+6).cmpIgnoreCase("january") == 0: + parsed.month = some(1) + i.inc 7 + elif input.substr(i, i+7).cmpIgnoreCase("february") == 0: + parsed.month = some(2) + i.inc 8 + elif input.substr(i, i+4).cmpIgnoreCase("march") == 0: + parsed.month = some(3) + i.inc 5 + elif input.substr(i, i+4).cmpIgnoreCase("april") == 0: + parsed.month = some(4) + i.inc 5 + elif input.substr(i, i+2).cmpIgnoreCase("may") == 0: + parsed.month = some(5) + i.inc 3 + elif input.substr(i, i+3).cmpIgnoreCase("june") == 0: + parsed.month = some(6) + i.inc 4 + elif input.substr(i, i+3).cmpIgnoreCase("july") == 0: + parsed.month = some(7) + i.inc 4 + elif input.substr(i, i+5).cmpIgnoreCase("august") == 0: + parsed.month = some(8) + i.inc 6 + elif input.substr(i, i+8).cmpIgnoreCase("september") == 0: + parsed.month = some(9) + i.inc 9 + elif input.substr(i, i+6).cmpIgnoreCase("october") == 0: + parsed.month = some(10) + i.inc 7 + elif input.substr(i, i+7).cmpIgnoreCase("november") == 0: + parsed.month = some(11) + i.inc 8 + elif input.substr(i, i+7).cmpIgnoreCase("december") == 0: + parsed.month = some(12) + i.inc 8 + else: + result = false + of s: + parsed.second = takeInt(1..2) + of ss: + parsed.second = takeInt(2..2) + of fff, ffffff, fffffffff: + let len = ($pattern).len + let v = takeInt(len..len) + parsed.nanosecond = v * 10^(9 - len) + result = parsed.nanosecond in NanosecondRange + of t: + case input[i]: + of 'P': + parsed.amPm = apPm + of 'A': + parsed.amPm = apAm + else: + result = false + i.inc 1 + of tt: + if input.substr(i, i+1).cmpIgnoreCase("AM") == 0: + parsed.amPm = apAM + i.inc 2 + elif input.substr(i, i+1).cmpIgnoreCase("PM") == 0: + parsed.amPm = apPm + i.inc 2 + else: + result = false + of yy: + # Assumes current century + var year = takeInt(2..2) + var thisCen = now().year div 100 + parsed.year = some(thisCen*100 + year) + result = year > 0 + of yyyy: + let year = + if input[i] in { '+', '-' }: + takeInt(4..high(int)) + else: + takeInt(4..4) + result = year > 0 + parsed.year = some(year) + of YYYY: + let year = takeInt(1..high(int)) + parsed.year = some(year) + result = year > 0 + of uuuu: + let year = + if input[i] in { '+', '-' }: + takeInt(4..high(int)) + else: + takeInt(4..4) + parsed.year = some(year) + of UUUU: + parsed.year = some(takeInt(1..high(int))) + of z, zz, zzz, zzzz: + case input[i] + of '+', '-': + let sign = if input[i] == '-': 1 else: -1 + i.inc + var offset = 0 + case pattern + of z: + offset = takeInt(1..2) * -3600 + of zz: + offset = takeInt(2..2) * -3600 + of zzz: + offset.inc takeInt(2..2) * 3600 + if input[i] != ':': + return false + i.inc + offset.inc takeInt(2..2) * 60 + of zzzz: + offset.inc takeInt(2..2) * 3600 + if input[i] != ':': + return false + i.inc + offset.inc takeInt(2..2) * 60 + if input[i] != ':': + return false + i.inc + offset.inc takeInt(2..2) + else: assert false + parsed.utcOffset = some(offset * sign) + of 'Z': + parsed.utcOffset = some(0) + i.inc + else: + result = false + of g: + if input.substr(i, i+1).cmpIgnoreCase("BC") == 0: + parsed.era = eraBc + i.inc 2 + elif input.substr(i, i+1).cmpIgnoreCase("AD") == 0: + parsed.era = eraAd + i.inc 2 + else: + result = false + of y, yyy, yyyyy: + raise newException(ValueError, + &"The pattern '{pattern}' is only valid for formatting") + of Lit: assert false # Can't happen + +proc toDateTime(p: ParsedTime, zone: Timezone, f: TimeFormat, + input: string): DateTime = + var month = mJan + var year: int + var monthday: int + # `now()` is an expensive call, so we avoid it when possible + (year, month, monthday) = + if p.year.isNone or p.month.isNone or p.monthday.isNone: + let n = now() + (p.year.get(n.year), + p.month.get(n.month.int).Month, + p.monthday.get(n.monthday)) + else: + (p.year.get(), p.month.get().Month, p.monthday.get()) + + year = + case p.era + of eraUnknown: + year + of eraBc: + if year < 1: + raiseParseException(f, input, + "Expected year to be positive " & + "(use 'UUUU' or 'uuuu' for negative years).") + -year + 1 + of eraAd: + if year < 1: + raiseParseException(f, input, + "Expected year to be positive " & + "(use 'UUUU' or 'uuuu' for negative years).") + year + + let hour = + case p.amPm + of apUnknown: + p.hour + of apAm: + if p.hour notin 1..12: + raiseParseException(f, input, + "AM/PM time must be in the interval 1..12") + if p.hour == 12: 0 else: p.hour + of apPm: + if p.hour notin 1..12: + raiseParseException(f, input, + "AM/PM time must be in the interval 1..12") + if p.hour == 12: p.hour else: p.hour + 12 + let minute = p.minute + let second = p.second + let nanosecond = p.nanosecond + + if monthday > getDaysInMonth(month, year): + raiseParseException(f, input, + $year & "-" & ord(month).intToStr(2) & + "-" & $monthday & " is not a valid date") + + result = DateTime( + year: year, month: month, monthday: monthday, + hour: hour, minute: minute, second: second, nanosecond: nanosecond + ) + + if p.utcOffset.isNone: + # No timezone parsed - assume timezone is `zone` + result = initDateTime(zone.zoneInfoFromTz(result.toAdjTime), zone) + else: + # Otherwise convert to `zone` + result.utcOffset = p.utcOffset.get() + result = result.toTime.inZone(zone) + +proc format*(f: TimeFormat, dt: DateTime): string {.raises: [].} = + ## Format ``dt`` using the format specified by ``f``. + runnableExamples: + let f = initTimeFormat("yyyy-MM-dd") + let dt = initDateTime(01, mJan, 2000, 00, 00, 00, utc()) + doAssert "2000-01-01" == f.format(dt) + var idx = 0 + while idx <= f.patterns.high: + case f.patterns[idx].FormatPattern + of Lit: + idx.inc + let len = f.patterns[idx] + for i in 1'u8..len: + idx.inc + result.add f.patterns[idx].char + idx.inc + else: + formatPattern(dt, f.patterns[idx].FormatPattern, result = result) + idx.inc + +proc format*(dt: DateTime, f: string): string = + ## Shorthand for constructing a ``TimeFormat`` and using it to format ``dt``. + ## + ## See `Parsing and formatting dates`_ for documentation of the + ## ``format`` argument. + runnableExamples: + let dt = initDateTime(01, mJan, 2000, 00, 00, 00, utc()) + doAssert "2000-01-01" == format(dt, "yyyy-MM-dd") + let dtFormat = initTimeFormat(f) + result = dtFormat.format(dt) + +proc format*(dt: DateTime, format: static[string]): string {.raises: [].} = + ## Overload that validates ``format`` at compile time. + const f = initTimeFormat(format) + result = f.format(dt) + +proc format*(time: Time, format: string, zone: Timezone = local()): string {.tags: [].} = + ## Shorthand for constructing a ``TimeFormat`` and using it to format + ## ``time``. Will use the timezone specified by ``zone``. + ## + ## See `Parsing and formatting dates`_ for documentation of the + ## ``format`` argument. runnableExamples: var dt = initDateTime(01, mJan, 1970, 00, 00, 00, utc()) var tm = dt.toTime() doAssert format(tm, "yyyy-MM-dd'T'HH:mm:ss", utc()) == "1970-01-01T00:00:00" - time.inZone(zone).format(f) + time.inZone(zone).format(format) + +proc format*(time: Time, format: static[string], + zone: Timezone = local()): string {.tags: [].} = + ## Overload that validates ``format`` at compile time. + const f = initTimeFormat(format) + result = f.format(time.inZone(zone)) + +proc parse*(f: TimeFormat, input: string, zone: Timezone = local()): DateTime = + ## Parses ``input`` as a ``DateTime`` using the format specified by ``f``. + ## If no UTC offset was parsed, then ``input`` is assumed to be specified in + ## the ``zone`` timezone. If a UTC offset was parsed, the result will be + ## converted to the ``zone`` timezone. + runnableExamples: + let f = initTimeFormat("yyyy-MM-dd") + let dt = initDateTime(01, mJan, 2000, 00, 00, 00, utc()) + doAssert dt == f.parse("2000-01-01", utc()) + var inpIdx = 0 # Input index + var patIdx = 0 # Pattern index + var parsed: ParsedTime + while inpIdx <= input.high and patIdx <= f.patterns.high: + let pattern = f.patterns[patIdx].FormatPattern + case pattern + of Lit: + patIdx.inc + let len = f.patterns[patIdx] + patIdx.inc + for _ in 1'u8..len: + if input[inpIdx] != f.patterns[patIdx].char: + raiseParseException(f, input, + "Unexpected character: " & input[inpIdx]) + inpIdx.inc + patIdx.inc + else: + if not parsePattern(input, pattern, inpIdx, parsed): + raiseParseException(f, input, &"Failed on pattern '{pattern}'") + patIdx.inc + + if inpIdx <= input.high: + raiseParseException(f, input, + "Parsing ended but there was still input remaining") + + if patIdx <= f.patterns.high: + raiseParseException(f, input, + "Parsing ended but there was still patterns remaining") + + result = toDateTime(parsed, zone, f, input) + +proc parse*(input, format: string, tz: Timezone = local()): DateTime = + ## Shorthand for constructing a ``TimeFormat`` and using it to parse + ## ``input`` as a ``DateTime``. + ## + ## See `Parsing and formatting dates`_ for documentation of the + ## ``format`` argument. + runnableExamples: + let dt = initDateTime(01, mJan, 2000, 00, 00, 00, utc()) + doAssert dt == parse("2000-01-01", "yyyy-MM-dd", utc()) + let dtFormat = initTimeFormat(format) + result = dtFormat.parse(input, tz) + +proc parse*(input: string, format: static[string], zone: Timezone = local()): DateTime = + ## Overload that validates ``format`` at compile time. + const f = initTimeFormat(format) + result = f.parse(input, zone) + +proc parseTime*(input, format: string, zone: Timezone): Time = + ## Shorthand for constructing a ``TimeFormat`` and using it to parse + ## ``input`` as a ``DateTime``, then converting it a ``Time``. + ## + ## See `Parsing and formatting dates`_ for documentation of the + ## ``format`` argument. + runnableExamples: + let tStr = "1970-01-01T00:00:00+00:00" + doAssert parseTime(tStr, "yyyy-MM-dd'T'HH:mm:sszzz", utc()) == fromUnix(0) + parse(input, format, zone).toTime() + +proc parseTime*(input: string, format: static[string], zone: Timezone): Time = + ## Overload that validates ``format`` at compile time. + const f = initTimeFormat(format) + result = f.parse(input, zone).toTime() + +# +# End of parse & format implementation +# proc `$`*(dt: DateTime): string {.tags: [], raises: [], benign.} = ## Converts a `DateTime` object to a string representation. ## It uses the format ``yyyy-MM-dd'T'HH-mm-sszzz``. runnableExamples: let dt = initDateTime(01, mJan, 2000, 12, 00, 00, utc()) - doAssert $dt == "2000-01-01T12:00:00+00:00" - try: - result = format(dt, "yyyy-MM-dd'T'HH:mm:sszzz") # todo: optimize this - except ValueError: assert false # cannot happen because format string is valid + doAssert $dt == "2000-01-01T12:00:00Z" + result = format(dt, "yyyy-MM-dd'T'HH:mm:sszzz") proc `$`*(time: Time): string {.tags: [], raises: [], benign.} = ## converts a `Time` value to a string representation. It will use the local @@ -1628,328 +2218,6 @@ proc `$`*(time: Time): string {.tags: [], raises: [], benign.} = {.pop.} -proc parseToken(dt: var DateTime; token, value: string; j: var int) = - ## Helper of the parse proc to parse individual tokens. - - # Overwrite system.`[]` to raise a ValueError on index out of bounds. - proc `[]`[T, U](s: string, x: HSlice[T, U]): string = - if x.a >= s.len or x.b >= s.len: - raise newException(ValueError, "Value is missing required tokens, got: " & - s) - return system.`[]`(s, x) - - var sv: int - case token - of "d": - var pd = parseInt(value[j..j+1], sv) - dt.monthday = sv - j += pd - of "dd": - dt.monthday = value[j..j+1].parseInt() - j += 2 - of "ddd": - case value[j..j+2].toLowerAscii() - of "sun": dt.weekday = dSun - of "mon": dt.weekday = dMon - of "tue": dt.weekday = dTue - of "wed": dt.weekday = dWed - of "thu": dt.weekday = dThu - of "fri": dt.weekday = dFri - of "sat": dt.weekday = dSat - else: - raise newException(ValueError, - "Couldn't parse day of week (ddd), got: " & value[j..j+2]) - j += 3 - of "dddd": - if value.len >= j+6 and value[j..j+5].cmpIgnoreCase("sunday") == 0: - dt.weekday = dSun - j += 6 - elif value.len >= j+6 and value[j..j+5].cmpIgnoreCase("monday") == 0: - dt.weekday = dMon - j += 6 - elif value.len >= j+7 and value[j..j+6].cmpIgnoreCase("tuesday") == 0: - dt.weekday = dTue - j += 7 - elif value.len >= j+9 and value[j..j+8].cmpIgnoreCase("wednesday") == 0: - dt.weekday = dWed - j += 9 - elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("thursday") == 0: - dt.weekday = dThu - j += 8 - elif value.len >= j+6 and value[j..j+5].cmpIgnoreCase("friday") == 0: - dt.weekday = dFri - j += 6 - elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("saturday") == 0: - dt.weekday = dSat - j += 8 - else: - raise newException(ValueError, - "Couldn't parse day of week (dddd), got: " & value) - of "h", "H": - var pd = parseInt(value[j..j+1], sv) - dt.hour = sv - j += pd - of "hh", "HH": - dt.hour = value[j..j+1].parseInt() - j += 2 - of "m": - var pd = parseInt(value[j..j+1], sv) - dt.minute = sv - j += pd - of "mm": - dt.minute = value[j..j+1].parseInt() - j += 2 - of "M": - var pd = parseInt(value[j..j+1], sv) - dt.month = sv.Month - j += pd - of "MM": - var month = value[j..j+1].parseInt() - j += 2 - dt.month = month.Month - of "MMM": - case value[j..j+2].toLowerAscii(): - of "jan": dt.month = mJan - of "feb": dt.month = mFeb - of "mar": dt.month = mMar - of "apr": dt.month = mApr - of "may": dt.month = mMay - of "jun": dt.month = mJun - of "jul": dt.month = mJul - of "aug": dt.month = mAug - of "sep": dt.month = mSep - of "oct": dt.month = mOct - of "nov": dt.month = mNov - of "dec": dt.month = mDec - else: - raise newException(ValueError, - "Couldn't parse month (MMM), got: " & value) - j += 3 - of "MMMM": - if value.len >= j+7 and value[j..j+6].cmpIgnoreCase("january") == 0: - dt.month = mJan - j += 7 - elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("february") == 0: - dt.month = mFeb - j += 8 - elif value.len >= j+5 and value[j..j+4].cmpIgnoreCase("march") == 0: - dt.month = mMar - j += 5 - elif value.len >= j+5 and value[j..j+4].cmpIgnoreCase("april") == 0: - dt.month = mApr - j += 5 - elif value.len >= j+3 and value[j..j+2].cmpIgnoreCase("may") == 0: - dt.month = mMay - j += 3 - elif value.len >= j+4 and value[j..j+3].cmpIgnoreCase("june") == 0: - dt.month = mJun - j += 4 - elif value.len >= j+4 and value[j..j+3].cmpIgnoreCase("july") == 0: - dt.month = mJul - j += 4 - elif value.len >= j+6 and value[j..j+5].cmpIgnoreCase("august") == 0: - dt.month = mAug - j += 6 - elif value.len >= j+9 and value[j..j+8].cmpIgnoreCase("september") == 0: - dt.month = mSep - j += 9 - elif value.len >= j+7 and value[j..j+6].cmpIgnoreCase("october") == 0: - dt.month = mOct - j += 7 - elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("november") == 0: - dt.month = mNov - j += 8 - elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("december") == 0: - dt.month = mDec - j += 8 - else: - raise newException(ValueError, - "Couldn't parse month (MMMM), got: " & value) - of "s": - var pd = parseInt(value[j..j+1], sv) - dt.second = sv - j += pd - of "ss": - dt.second = value[j..j+1].parseInt() - j += 2 - of "t": - if value[j] == 'A' and dt.hour == 12: - dt.hour = 0 - elif value[j] == 'P' and dt.hour > 0 and dt.hour < 12: - dt.hour += 12 - j += 1 - of "tt": - if value[j..j+1] == "AM" and dt.hour == 12: - dt.hour = 0 - elif value[j..j+1] == "PM" and dt.hour > 0 and dt.hour < 12: - dt.hour += 12 - j += 2 - of "yy": - # Assumes current century - var year = value[j..j+1].parseInt() - var thisCen = now().year div 100 - dt.year = thisCen*100 + year - j += 2 - of "yyyy": - dt.year = value[j..j+3].parseInt() - j += 4 - of "z": - dt.isDst = false - let ch = if j < value.len: value[j] else: '\0' - if ch == '+': - dt.utcOffset = 0 - parseInt($value[j+1]) * secondsInHour - elif ch == '-': - dt.utcOffset = parseInt($value[j+1]) * secondsInHour - elif ch == 'Z': - dt.utcOffset = 0 - j += 1 - return - else: - raise newException(ValueError, - "Couldn't parse timezone offset (z), got: " & ch) - j += 2 - of "zz": - dt.isDst = false - let ch = if j < value.len: value[j] else: '\0' - if ch == '+': - dt.utcOffset = 0 - value[j+1..j+2].parseInt() * secondsInHour - elif ch == '-': - dt.utcOffset = value[j+1..j+2].parseInt() * secondsInHour - elif ch == 'Z': - dt.utcOffset = 0 - j += 1 - return - else: - raise newException(ValueError, - "Couldn't parse timezone offset (zz), got: " & ch) - j += 3 - of "zzz": - dt.isDst = false - var factor = 0 - let ch = if j < value.len: value[j] else: '\0' - if ch == '+': factor = -1 - elif ch == '-': factor = 1 - elif ch == 'Z': - dt.utcOffset = 0 - j += 1 - return - else: - raise newException(ValueError, - "Couldn't parse timezone offset (zzz), got: " & ch) - dt.utcOffset = factor * value[j+1..j+2].parseInt() * secondsInHour - j += 4 - dt.utcOffset += factor * value[j..j+1].parseInt() * 60 - j += 2 - of "fff", "ffffff", "fffffffff": - var numStr = "" - let n = parseWhile(value[j..len(value) - 1], numStr, {'0'..'9'}) - dt.nanosecond = parseInt(numStr) * (10 ^ (9 - n)) - j += n - else: - # Ignore the token and move forward in the value string by the same length - j += token.len - -proc parse*(value, layout: string, zone: Timezone = local()): DateTime = - ## This procedure parses a date/time string using the standard format - ## identifiers as listed below. The procedure defaults information not provided - ## in the format string from the running program (month, year, etc). - ## - ## The return value will always be in the `zone` timezone. If no UTC offset was - ## parsed, then the input will be assumed to be specified in the `zone` timezone - ## already, so no timezone conversion will be done in that case. - ## - ## ======================= ================================================================================= ================================================ - ## Specifier Description Example - ## ======================= ================================================================================= ================================================ - ## d Numeric value of the day of the month, it will be one or two digits long. ``1/04/2012 -> 1``, ``21/04/2012 -> 21`` - ## dd Same as above, but always two digits. ``1/04/2012 -> 01``, ``21/04/2012 -> 21`` - ## ddd Three letter string which indicates the day of the week. ``Saturday -> Sat``, ``Monday -> Mon`` - ## dddd Full string for the day of the week. ``Saturday -> Saturday``, ``Monday -> Monday`` - ## h The hours in one digit if possible. Ranging from 0-12. ``5pm -> 5``, ``2am -> 2`` - ## hh The hours in two digits always. If the hour is one digit 0 is prepended. ``5pm -> 05``, ``11am -> 11`` - ## H The hours in one digit if possible, randing from 0-24. ``5pm -> 17``, ``2am -> 2`` - ## HH The hours in two digits always. 0 is prepended if the hour is one digit. ``5pm -> 17``, ``2am -> 02`` - ## m The minutes in 1 digit if possible. ``5:30 -> 30``, ``2:01 -> 1`` - ## mm Same as above but always 2 digits, 0 is prepended if the minute is one digit. ``5:30 -> 30``, ``2:01 -> 01`` - ## M The month in one digit if possible. ``September -> 9``, ``December -> 12`` - ## MM The month in two digits always. 0 is prepended. ``September -> 09``, ``December -> 12`` - ## MMM Abbreviated three-letter form of the month. ``September -> Sep``, ``December -> Dec`` - ## MMMM Full month string, properly capitalized. ``September -> September`` - ## s Seconds as one digit if possible. ``00:00:06 -> 6`` - ## ss Same as above but always two digits. 0 is prepended. ``00:00:06 -> 06`` - ## t ``A`` when time is in the AM. ``P`` when time is in the PM. - ## tt Same as above, but ``AM`` and ``PM`` instead of ``A`` and ``P`` respectively. - ## yy Displays the year to two digits. ``2012 -> 12`` - ## yyyy Displays the year to four digits. ``2012 -> 2012`` - ## z Displays the timezone offset from UTC. ``Z`` is parsed as ``+0`` ``GMT+7 -> +7``, ``GMT-5 -> -5`` - ## zz Same as above but with leading 0. ``GMT+7 -> +07``, ``GMT-5 -> -05`` - ## zzz Same as above but with ``:mm`` where *mm* represents minutes. ``GMT+7 -> +07:00``, ``GMT-5 -> -05:00`` - ## fff/ffffff/fffffffff for consistency with format - nanoseconds ``1 -> 1 nanosecond`` - ## ======================= ================================================================================= ================================================ - ## - ## Other strings can be inserted by putting them in ``''``. For example - ## ``hh'->'mm`` will give ``01->56``. The following characters can be - ## inserted without quoting them: ``:`` ``-`` ``(`` ``)`` ``/`` ``[`` ``]`` - ## ``,``. However you don't need to necessarily separate format specifiers, a - ## unambiguous format string like ``yyyyMMddhhmmss`` is valid too. - runnableExamples: - let tStr = "1970-01-01T00:00:00.0+00:00" - doAssert parse(tStr, "yyyy-MM-dd'T'HH:mm:ss.fffzzz") == fromUnix(0).utc - - var i = 0 # pointer for format string - var j = 0 # pointer for value string - var token = "" - # Assumes current day of month, month and year, but time is reset to 00:00:00. Weekday will be reset after parsing. - var dt = now() - dt.hour = 0 - dt.minute = 0 - dt.second = 0 - dt.nanosecond = 0 - dt.isDst = true # using this is flag for checking whether a timezone has \ - # been read (because DST is always false when a tz is parsed) - while i < layout.len: - case layout[i] - of ' ', '-', '/', ':', '\'', '(', ')', '[', ']', ',': - if token.len > 0: - parseToken(dt, token, value, j) - # Reset token - token = "" - # Skip separator and everything between single quotes - # These are literals in both the layout and the value string - if layout[i] == '\'': - inc(i) - while i < layout.len-1 and layout[i] != '\'': - inc(i) - inc(j) - inc(i) - else: - inc(i) - inc(j) - else: - # Check if the letter being added matches previous accumulated buffer. - if token.len == 0 or token[high(token)] == layout[i]: - token.add(layout[i]) - inc(i) - else: - parseToken(dt, token, value, j) - token = "" - - if i >= layout.len and token.len > 0: - parseToken(dt, token, value, j) - if dt.isDst: - # No timezone parsed - assume timezone is `zone` - result = initDateTime(zone.zoneInfoFromTz(dt.toAdjTime), zone) - else: - # Otherwise convert to `zone` - result = dt.toTime.inZone(zone) - -proc parseTime*(value, layout: string, zone: Timezone): Time = - ## Simple wrapper for parsing string to time - runnableExamples: - let tStr = "1970-01-01T00:00:00+00:00" - doAssert parseTime(tStr, "yyyy-MM-dd'T'HH:mm:sszzz", local()) == fromUnix(0) - parse(value, layout, zone).toTime() - proc countLeapYears*(yearSpan: int): int = ## Returns the number of leap years spanned by a given number of years. ## diff --git a/tests/js/ttimes.nim b/tests/js/ttimes.nim index 63972dd76c..bd599a7aec 100644 --- a/tests/js/ttimes.nim +++ b/tests/js/ttimes.nim @@ -36,8 +36,8 @@ let utcPlus2 = Timezone(zoneInfoFromUtc: staticZoneInfoFromUtc, zoneInfoFromTz: block timezoneTests: let dt = initDateTime(01, mJan, 2017, 12, 00, 00, utcPlus2) doAssert $dt == "2017-01-01T12:00:00+02:00" - doAssert $dt.utc == "2017-01-01T10:00:00+00:00" + doAssert $dt.utc == "2017-01-01T10:00:00Z" doAssert $dt.utc.inZone(utcPlus2) == $dt -doAssert $initDateTime(01, mJan, 1911, 12, 00, 00, utc()) == "1911-01-01T12:00:00+00:00" -doAssert $initDateTime(01, mJan, 0023, 12, 00, 00, utc()) == "0023-01-01T12:00:00+00:00" \ No newline at end of file +doAssert $initDateTime(01, mJan, 1911, 12, 00, 00, utc()) == "1911-01-01T12:00:00Z" +doAssert $initDateTime(01, mJan, 0023, 12, 00, 00, utc()) == "0023-01-01T12:00:00Z" \ No newline at end of file diff --git a/tests/stdlib/ttimes.nim b/tests/stdlib/ttimes.nim index 4ab3ba5812..e3f61ff77d 100644 --- a/tests/stdlib/ttimes.nim +++ b/tests/stdlib/ttimes.nim @@ -8,6 +8,24 @@ discard """ import times, os, strutils, unittest +proc staticTz(hours, minutes, seconds: int = 0): Timezone {.noSideEffect.} = + let offset = hours * 3600 + minutes * 60 + seconds + + proc zoneInfoFromTz(adjTime: Time): ZonedTime {.locks: 0.} = + result.isDst = false + result.utcOffset = offset + result.adjTime = adjTime + + proc zoneInfoFromUtc(time: Time): ZonedTime {.locks: 0.}= + result.isDst = false + result.utcOffset = offset + result.adjTime = fromUnix(time.toUnix - offset) + + result.name = "" + result.zoneInfoFromTz = zoneInfoFromTz + result.zoneInfoFromUtc = zoneInfoFromUtc + + # $ date --date='@2147483647' # Tue 19 Jan 03:14:07 GMT 2038 @@ -19,25 +37,10 @@ proc checkFormat(t: DateTime, format, expected: string) = echo "actual : ", actual doAssert false -let t = fromUnix(2147483647).utc -t.checkFormat("ddd dd MMM hh:mm:ss yyyy", "Tue 19 Jan 03:14:07 2038") -t.checkFormat("ddd ddMMMhh:mm:ssyyyy", "Tue 19Jan03:14:072038") -t.checkFormat("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" & - " ss t tt y yy yyy yyyy yyyyy z zz zzz", - "19 19 Tue Tuesday 3 03 3 03 14 14 1 01 Jan January 7 07 A AM 8 38 038 2038 02038 +0 +00 +00:00") - -t.checkFormat("yyyyMMddhhmmss", "20380119031407") - -# issue 7620 -let t7620_am = parse("4/15/2017 12:01:02 AM +0", "M/d/yyyy' 'h:mm:ss' 'tt' 'z", utc()) -t7620_am.checkFormat("M/d/yyyy' 'h:mm:ss' 'tt' 'z", "4/15/2017 12:01:02 AM +0") -let t7620_pm = parse("4/15/2017 12:01:02 PM +0", "M/d/yyyy' 'h:mm:ss' 'tt' 'z", utc()) -t7620_pm.checkFormat("M/d/yyyy' 'h:mm:ss' 'tt' 'z", "4/15/2017 12:01:02 PM +0") - -let t2 = fromUnix(160070789).utc # Mon 27 Jan 16:06:29 GMT 1975 +let t2 = fromUnix(160070789).utc() # Mon 27 Jan 16:06:29 GMT 1975 t2.checkFormat("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" & " ss t tt y yy yyy yyyy yyyyy z zz zzz", - "27 27 Mon Monday 4 04 16 16 6 06 1 01 Jan January 29 29 P PM 5 75 975 1975 01975 +0 +00 +00:00") + "27 27 Mon Monday 4 04 16 16 6 06 1 01 Jan January 29 29 P PM 5 75 975 1975 01975 Z Z Z") var t4 = fromUnix(876124714).utc # Mon 6 Oct 08:58:34 BST 1997 t4.checkFormat("M MM MMM MMMM", "10 10 Oct October") @@ -83,16 +86,16 @@ let seqB: seq[Time] = @[] doAssert seqA == seqB for tz in [ - (0, "+0", "+00", "+00:00"), # UTC - (-3600, "+1", "+01", "+01:00"), # CET - (-39600, "+11", "+11", "+11:00"), # two digits - (-1800, "+0", "+00", "+00:30"), # half an hour - (7200, "-2", "-02", "-02:00"), # positive - (38700, "-10", "-10", "-10:45")]: # positive with three quaters hour - let ti = DateTime(month: mJan, monthday: 1, utcOffset: tz[0]) - doAssert ti.format("z") == tz[1] - doAssert ti.format("zz") == tz[2] - doAssert ti.format("zzz") == tz[3] + (staticTz(seconds = 0), "+0", "+00", "+00:00"), # UTC + (staticTz(seconds = -3600), "+1", "+01", "+01:00"), # CET + (staticTz(seconds = -39600), "+11", "+11", "+11:00"), # two digits + (staticTz(seconds = -1800), "+0", "+00", "+00:30"), # half an hour + (staticTz(seconds = 7200), "-2", "-02", "-02:00"), # positive + (staticTz(seconds = 38700), "-10", "-10", "-10:45")]: # positive with three quaters hour + let dt = initDateTime(1, mJan, 2000, 00, 00, 00, tz[0]) + doAssert dt.format("z") == tz[1] + doAssert dt.format("zz") == tz[2] + doAssert dt.format("zzz") == tz[3] block countLeapYears: # 1920, 2004 and 2020 are leap years, and should be counted starting at the following year @@ -112,11 +115,9 @@ template parseTest(s, f, sExpected: string, ydExpected: int) = let parsed = s.parse(f, utc()) parsedStr = $parsed + if parsedStr != sExpected: + echo "GOT ", parsedStr, " EXPECTED ", sExpected, " FORMAT ", f check parsedStr == sExpected - if parsed.yearday != ydExpected: - echo s - echo parsed.repr - echo parsed.yearday, " exp: ", ydExpected check(parsed.yearday == ydExpected) template parseTestExcp(s, f: string) = @@ -130,51 +131,43 @@ template parseTestTimeOnly(s, f, sExpected: string) = # explicit timezone offsets in all tests. template runTimezoneTests() = parseTest("Tuesday at 09:04am on Dec 15, 2015 +0", - "dddd at hh:mmtt on MMM d, yyyy z", "2015-12-15T09:04:00+00:00", 348) + "dddd 'at' hh:mmtt 'on' MMM d, yyyy z", "2015-12-15T09:04:00Z", 348) # ANSIC = "Mon Jan _2 15:04:05 2006" parseTest("Thu Jan 12 15:04:05 2006 +0", "ddd MMM dd HH:mm:ss yyyy z", - "2006-01-12T15:04:05+00:00", 11) + "2006-01-12T15:04:05Z", 11) # UnixDate = "Mon Jan _2 15:04:05 MST 2006" parseTest("Thu Jan 12 15:04:05 2006 +0", "ddd MMM dd HH:mm:ss yyyy z", - "2006-01-12T15:04:05+00:00", 11) + "2006-01-12T15:04:05Z", 11) # RubyDate = "Mon Jan 02 15:04:05 -0700 2006" parseTest("Mon Feb 29 15:04:05 -07:00 2016 +0", "ddd MMM dd HH:mm:ss zzz yyyy z", - "2016-02-29T15:04:05+00:00", 59) # leap day + "2016-02-29T15:04:05Z", 59) # leap day # RFC822 = "02 Jan 06 15:04 MST" parseTest("12 Jan 16 15:04 +0", "dd MMM yy HH:mm z", - "2016-01-12T15:04:00+00:00", 11) + "2016-01-12T15:04:00Z", 11) # RFC822Z = "02 Jan 06 15:04 -0700" # RFC822 with numeric zone parseTest("01 Mar 16 15:04 -07:00", "dd MMM yy HH:mm zzz", - "2016-03-01T22:04:00+00:00", 60) # day after february in leap year + "2016-03-01T22:04:00Z", 60) # day after february in leap year # RFC850 = "Monday, 02-Jan-06 15:04:05 MST" parseTest("Monday, 12-Jan-06 15:04:05 +0", "dddd, dd-MMM-yy HH:mm:ss z", - "2006-01-12T15:04:05+00:00", 11) + "2006-01-12T15:04:05Z", 11) # RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST" parseTest("Sun, 01 Mar 2015 15:04:05 +0", "ddd, dd MMM yyyy HH:mm:ss z", - "2015-03-01T15:04:05+00:00", 59) # day after february in non-leap year + "2015-03-01T15:04:05Z", 59) # day after february in non-leap year # RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" # RFC1123 with numeric zone parseTest("Thu, 12 Jan 2006 15:04:05 -07:00", "ddd, dd MMM yyyy HH:mm:ss zzz", - "2006-01-12T22:04:05+00:00", 11) + "2006-01-12T22:04:05Z", 11) # RFC3339 = "2006-01-02T15:04:05Z07:00" - parseTest("2006-01-12T15:04:05Z-07:00", "yyyy-MM-ddTHH:mm:ssZzzz", - "2006-01-12T22:04:05+00:00", 11) parseTest("2006-01-12T15:04:05Z-07:00", "yyyy-MM-dd'T'HH:mm:ss'Z'zzz", - "2006-01-12T22:04:05+00:00", 11) + "2006-01-12T22:04:05Z", 11) # RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00" parseTest("2006-01-12T15:04:05.999999999Z-07:00", - "yyyy-MM-ddTHH:mm:ss.999999999Zzzz", "2006-01-12T22:04:05+00:00", 11) + "yyyy-MM-dd'T'HH:mm:ss'.999999999Z'zzz", "2006-01-12T22:04:05Z", 11) for tzFormat in ["z", "zz", "zzz"]: # formatting timezone as 'Z' for UTC parseTest("2001-01-12T22:04:05Z", "yyyy-MM-dd'T'HH:mm:ss" & tzFormat, - "2001-01-12T22:04:05+00:00", 11) + "2001-01-12T22:04:05Z", 11) # Kitchen = "3:04PM" parseTestTimeOnly("3:04PM", "h:mmtt", "15:04:00") - #when not defined(testing): - # echo "Kitchen: " & $s.parse(f) - # var ti = timeToTimeInfo(getTime()) - # echo "Todays date after decoding: ", ti - # var tint = timeToTimeInterval(getTime()) - # echo "Todays date after decoding to interval: ", tint # Bug with parse not setting DST properly if the current local DST differs from # the date being parsed. Need to test parse dates both in and out of DST. We @@ -195,8 +188,8 @@ template runTimezoneTests() = let parsedJan = parse("2016-01-05 04:00:00+01:00", "yyyy-MM-dd HH:mm:sszzz") parsedJul = parse("2016-07-01 04:00:00+01:00", "yyyy-MM-dd HH:mm:sszzz") - doAssert toTime(parsedJan).toUnix == 1451962800 - doAssert toTime(parsedJul).toUnix == 1467342000 + check toTime(parsedJan).toUnix == 1451962800 + check toTime(parsedJul).toUnix == 1467342000 suite "ttimes": @@ -256,7 +249,7 @@ suite "ttimes": check $(dt + initDuration(days = 1)) == "2017-03-26T13:00:00+02:00" test "datetime before epoch": - check $fromUnix(-2147483648).utc == "1901-12-13T20:45:52+00:00" + check $fromUnix(-2147483648).utc == "1901-12-13T20:45:52Z" test "adding/subtracting time across dst": putenv("TZ", "Europe/Stockholm") @@ -319,6 +312,15 @@ suite "ttimes": test "incorrect inputs: timezone (zzz) 3": parseTestExcp("2018-02-19 16:30:00 +01:0", "yyyy-MM-dd hh:mm:ss zzz") + test "incorrect inputs: year (yyyy/uuuu)": + parseTestExcp("-0001", "yyyy") + parseTestExcp("-0001", "YYYY") + parseTestExcp("1", "yyyy") + parseTestExcp("12345", "yyyy") + parseTestExcp("1", "uuuu") + parseTestExcp("12345", "uuuu") + parseTestExcp("-1 BC", "UUUU g") + test "dynamic timezone": proc staticOffset(offset: int): Timezone = proc zoneInfoFromTz(adjTime: Time): ZonedTime = @@ -340,7 +342,7 @@ suite "ttimes": check dt.utcOffset == -9000 check dt.isDst == false check $dt == "2000-01-01T12:00:00+02:30" - check $dt.utc == "2000-01-01T09:30:00+00:00" + check $dt.utc == "2000-01-01T09:30:00Z" check $dt.utc.inZone(tz) == $dt test "isLeapYear": @@ -351,12 +353,12 @@ suite "ttimes": test "subtract months": var dt = initDateTime(1, mFeb, 2017, 00, 00, 00, utc()) - check $(dt - initTimeInterval(months = 1)) == "2017-01-01T00:00:00+00:00" + check $(dt - initTimeInterval(months = 1)) == "2017-01-01T00:00:00Z" dt = initDateTime(15, mMar, 2017, 00, 00, 00, utc()) - check $(dt - initTimeInterval(months = 1)) == "2017-02-15T00:00:00+00:00" + check $(dt - initTimeInterval(months = 1)) == "2017-02-15T00:00:00Z" dt = initDateTime(31, mMar, 2017, 00, 00, 00, utc()) # This happens due to monthday overflow. It's consistent with Phobos. - check $(dt - initTimeInterval(months = 1)) == "2017-03-03T00:00:00+00:00" + check $(dt - initTimeInterval(months = 1)) == "2017-03-03T00:00:00Z" test "duration": let d = initDuration @@ -384,11 +386,11 @@ suite "ttimes": discard initDateTime(1, mJan, -35_000, 12, 00, 00) discard initDateTime(1, mJan, 35_000, 12, 00, 00) # with duration/timeinterval - let dt = initDateTime(1, mJan, 35_000, 12, 00, 00, utc()) + + let dt = initDateTime(1, mJan, -35_000, 12, 00, 00, utc()) + initDuration(seconds = 1) check dt.second == 1 let dt2 = dt + 35_001.years - check $dt2 == "0001-01-01T12:00:01+00:00" + check $dt2 == "0001-01-01T12:00:01Z" test "compare datetimes": var dt1 = now() @@ -426,4 +428,90 @@ suite "ttimes": check (-1).fromWinTime.nanosecond == convert(Seconds, Nanoseconds, 1) - 100 check -1.fromWinTime.toWinTime == -1 # One nanosecond is discarded due to differences in time resolution - check initTime(0, 101).toWinTime.fromWinTime.nanosecond == 100 \ No newline at end of file + check initTime(0, 101).toWinTime.fromWinTime.nanosecond == 100 + check initTime(0, 101).toWinTime.fromWinTime.nanosecond == 100 + + test "issue 7620": + let layout = "M/d/yyyy' 'h:mm:ss' 'tt' 'z" + let t7620_am = parse("4/15/2017 12:01:02 AM +0", layout, utc()) + check t7620_am.format(layout) == "4/15/2017 12:01:02 AM Z" + let t7620_pm = parse("4/15/2017 12:01:02 PM +0", layout, utc()) + check t7620_pm.format(layout) == "4/15/2017 12:01:02 PM Z" + + test "format": + var dt = initDateTime(1, mJan, -0001, + 17, 01, 02, 123_456_789, + staticTz(hours = 1, minutes = 2, seconds = 3)) + check dt.format("d") == "1" + check dt.format("dd") == "01" + check dt.format("ddd") == "Fri" + check dt.format("dddd") == "Friday" + check dt.format("h") == "5" + check dt.format("hh") == "05" + check dt.format("H") == "17" + check dt.format("HH") == "17" + check dt.format("m") == "1" + check dt.format("mm") == "01" + check dt.format("M") == "1" + check dt.format("MM") == "01" + check dt.format("MMM") == "Jan" + check dt.format("MMMM") == "January" + check dt.format("s") == "2" + check dt.format("ss") == "02" + check dt.format("t") == "P" + check dt.format("tt") == "PM" + check dt.format("yy") == "02" + check dt.format("yyyy") == "0002" + check dt.format("YYYY") == "2" + check dt.format("uuuu") == "-0001" + check dt.format("UUUU") == "-1" + check dt.format("z") == "-1" + check dt.format("zz") == "-01" + check dt.format("zzz") == "-01:02" + check dt.format("zzzz") == "-01:02:03" + check dt.format("g") == "BC" + + check dt.format("fff") == "123" + check dt.format("ffffff") == "123456" + check dt.format("fffffffff") == "123456789" + dt.nanosecond = 1 + check dt.format("fff") == "000" + check dt.format("ffffff") == "000000" + check dt.format("fffffffff") == "000000001" + + dt.year = 12345 + check dt.format("yyyy") == "+12345" + check dt.format("uuuu") == "+12345" + dt.year = -12345 + check dt.format("yyyy") == "+12346" + check dt.format("uuuu") == "-12345" + + expect ValueError: + discard initTimeFormat("'") + + expect ValueError: + discard initTimeFormat("'foo") + + expect ValueError: + discard initTimeFormat("foo'") + + test "parse": + check $parse("20180101", "yyyyMMdd", utc()) == "2018-01-01T00:00:00Z" + parseTestExcp("+120180101", "yyyyMMdd") + + check parse("1", "YYYY", utc()).year == 1 + check parse("1 BC", "YYYY g", utc()).year == 0 + check parse("0001 BC", "yyyy g", utc()).year == 0 + check parse("+12345 BC", "yyyy g", utc()).year == -12344 + check parse("1 AD", "YYYY g", utc()).year == 1 + check parse("0001 AD", "yyyy g", utc()).year == 1 + check parse("+12345 AD", "yyyy g", utc()).year == 12345 + + check parse("-1", "UUUU", utc()).year == -1 + check parse("-0001", "uuuu", utc()).year == -1 + + discard parse("foobar", "'foobar'") + discard parse("foo'bar", "'foo''''bar'") + discard parse("'", "''") + + parseTestExcp("2000 A", "yyyy g") From 854aa3958faa3603ab4ebfc72ea4c275e025a70c Mon Sep 17 00:00:00 2001 From: Dmitry Atamanov Date: Mon, 9 Jul 2018 21:04:57 +0300 Subject: [PATCH 04/43] Fixes maxLineLength's bug in the renderer (#8240) --- compiler/renderer.nim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/renderer.nim b/compiler/renderer.nim index 3ce2e157de..60ff5ec180 100644 --- a/compiler/renderer.nim +++ b/compiler/renderer.nim @@ -189,7 +189,7 @@ proc putComment(g: var TSrcGen, s: string) = put(g, tkComment, com) com = "## " inc(i) - if i < s.len and s[i] == '\x0A': inc(i) + if i <= hi and s[i] == '\x0A': inc(i) optNL(g, ind) of '\x0A': put(g, tkComment, com) @@ -226,7 +226,7 @@ proc maxLineLength(s: string): int = break of '\x0D': inc(i) - if s[i] == '\x0A': inc(i) + if i <= hi and s[i] == '\x0A': inc(i) result = max(result, lineLen) lineLen = 0 of '\x0A': @@ -247,7 +247,7 @@ proc putRawStr(g: var TSrcGen, kind: TTokType, s: string) = put(g, kind, str) str = "" inc(i) - if (i <= hi) and (s[i] == '\x0A'): inc(i) + if i <= hi and s[i] == '\x0A': inc(i) optNL(g, 0) of '\x0A': put(g, kind, str) From 5c5388c0a66c35319e2f360744ab0b9183ca5160 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Mon, 9 Jul 2018 20:05:53 +0200 Subject: [PATCH 05/43] Handle subtype relations for converter parameters (#8248) Fixes #7098 --- compiler/sigmatch.nim | 9 +++++++-- tests/converter/t7098.nim | 31 +++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 tests/converter/t7098.nim diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 09f7f23b50..4bac3d3ea2 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -1801,7 +1801,7 @@ proc userConvMatch(c: PContext, m: var TCandidate, f, a: PType, # 'f <- dest' in order to not break the unification: # see tests/tgenericconverter: let srca = typeRel(m, src, a) - if srca notin {isEqual, isGeneric}: continue + if srca notin {isEqual, isGeneric, isSubtype}: continue let destIsGeneric = containsGenericType(dest) if destIsGeneric: @@ -1814,7 +1814,12 @@ proc userConvMatch(c: PContext, m: var TCandidate, f, a: PType, s.info = arg.info result = newNodeIT(nkHiddenCallConv, arg.info, dest) addSon(result, s) - addSon(result, copyTree(arg)) + var param: PNode = nil + if srca == isSubtype: + param = implicitConv(nkHiddenSubConv, src, copyTree(arg), m, c) + else: + param = copyTree(arg) + addSon(result, param) inc(m.convMatches) m.genericConverter = srca == isGeneric or destIsGeneric return result diff --git a/tests/converter/t7098.nim b/tests/converter/t7098.nim new file mode 100644 index 0000000000..66e629fa8c --- /dev/null +++ b/tests/converter/t7098.nim @@ -0,0 +1,31 @@ +type + Byte* = uint8 + Bytes* = seq[Byte] + + BytesRange* = object + bytes: Bytes + ibegin, iend: int + +proc initBytesRange*(s: var Bytes, ibegin = 0, iend = -1): BytesRange = + let e = if iend < 0: s.len + iend + 1 + else: iend + assert ibegin >= 0 and e <= s.len + + shallow(s) + result.bytes = s + result.ibegin = ibegin + result.iend = e + +converter fromSeq*(s: Bytes): BytesRange = + var seqCopy = s + return initBytesRange(seqCopy) + +type + Reader* = object + data: BytesRange + position: int + +proc readerFromBytes*(input: BytesRange): Reader = + discard + +let r = readerFromBytes(@[]) From f80501846137cd276536b1e489f4caa8acdcbd5d Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Mon, 9 Jul 2018 23:33:31 +0100 Subject: [PATCH 06/43] Fixes #5880. (#7229) --- changelog.md | 1 + lib/pure/includes/oserr.nim | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 0943d78364..681b4b0a44 100644 --- a/changelog.md +++ b/changelog.md @@ -50,6 +50,7 @@ - For string inputs, ``strutils.isUpperAscii`` and ``strutils.isLowerAscii`` now require a second mandatory parameter ``skipNonAlpha``. +- ``osLastError`` is now marked with ``sideEffect`` - The procs ``parseHexInt`` and ``parseOctInt`` now fail on empty strings and strings containing only valid prefixes, e.g. "0x" for hex integers. diff --git a/lib/pure/includes/oserr.nim b/lib/pure/includes/oserr.nim index bfb6118f24..eb350cbd44 100644 --- a/lib/pure/includes/oserr.nim +++ b/lib/pure/includes/oserr.nim @@ -66,7 +66,7 @@ proc raiseOSError*(errorCode: OSErrorCode; additionalInfo = "") {.noinline.} = raise e {.push stackTrace:off.} -proc osLastError*(): OSErrorCode = +proc osLastError*(): OSErrorCode {.sideEffect.} = ## Retrieves the last operating system error code. ## ## This procedure is useful in the event when an OS call fails. In that case From 25bf0d1683ce7203c004a5b14d92dcbe6ff40331 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Tue, 10 Jul 2018 12:31:13 -0700 Subject: [PATCH 07/43] add os.absolutePath; fixes #8174 (#8213) * add os.absolutePath * fixup * fixup * Fixes absolutePath error message. --- lib/pure/os.nim | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 6cf5e1fb7e..84f492c9df 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -296,6 +296,26 @@ proc setCurrentDir*(newDir: string) {.inline, tags: [].} = else: if chdir(newDir) != 0'i32: raiseOSError(osLastError()) +proc absolutePath*(path: string, root = getCurrentDir()): string = + ## Returns the absolute path of `path`, rooted at `root` (which must be absolute) + ## if `path` is absolute, return it, ignoring `root` + runnableExamples: + doAssert absolutePath("a") == getCurrentDir() / "a" + if isAbsolute(path): path + else: + if not root.isAbsolute: + raise newException(ValueError, "The specified root is not absolute: " & root) + joinPath(root, path) + +when isMainModule: + doAssertRaises(ValueError): discard absolutePath("a", "b") + doAssert absolutePath("a") == getCurrentDir() / "a" + doAssert absolutePath("a", "/b") == "/b" / "a" + when defined(Posix): + doAssert absolutePath("a", "/b/") == "/b" / "a" + doAssert absolutePath("a", "/b/c") == "/b/c" / "a" + doAssert absolutePath("/a", "b/") == "/a" + proc expandFilename*(filename: string): string {.rtl, extern: "nos$1", tags: [ReadDirEffect].} = ## Returns the full (`absolute`:idx:) path of an existing file `filename`, From 6fe79fd15818bee4662500b8febb8239d2381ce8 Mon Sep 17 00:00:00 2001 From: Quelklef Date: Wed, 11 Jul 2018 02:52:09 -0400 Subject: [PATCH 08/43] Fixed a wrong AST example (#8269) --- doc/astspec.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/astspec.txt b/doc/astspec.txt index 73058cd933..830dc7da9c 100644 --- a/doc/astspec.txt +++ b/doc/astspec.txt @@ -1270,10 +1270,10 @@ AST: nnkIdent("float32"), nnkEmpty() ) - nnkPragma(nnkIdent("inline")), - nnkEmpty(), # reserved slot for future use - nnkStmtList(nnkDiscardStmt(nnkEmpty())) # the meat of the proc - ) + ), + nnkPragma(nnkIdent("inline")), + nnkEmpty(), # reserved slot for future use + nnkStmtList(nnkDiscardStmt(nnkEmpty())) # the meat of the proc ) There is another consideration. Nim has flexible type identification for From 32441d01e597ff0c94ca037d10a0d2674d4012b3 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Wed, 11 Jul 2018 01:33:33 -0700 Subject: [PATCH 09/43] better doc for hard to find --define:SYMBOL:VAL (#8257) --- doc/basicopt.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/basicopt.txt b/doc/basicopt.txt index 90c7ba09c8..a9166d36cb 100644 --- a/doc/basicopt.txt +++ b/doc/basicopt.txt @@ -12,7 +12,8 @@ Options: -p, --path:PATH add path to search paths -d, --define:SYMBOL(:VAL) define a conditional symbol - (Optionally: Define the value for that symbol) + (Optionally: Define the value for that symbol, + see: "compile time define pragmas") -u, --undef:SYMBOL undefine a conditional symbol -f, --forceBuild force rebuilding of all modules --stackTrace:on|off turn stack tracing on|off From ac3c4a94add9e21e9f0731f39be88cdba2bec010 Mon Sep 17 00:00:00 2001 From: Quelklef Date: Thu, 12 Jul 2018 05:01:48 -0400 Subject: [PATCH 10/43] Fixed $ on None[T] for T with .name (#8293) --- lib/pure/options.nim | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/pure/options.nim b/lib/pure/options.nim index bd01b208ae..ce58943f93 100644 --- a/lib/pure/options.nim +++ b/lib/pure/options.nim @@ -189,7 +189,7 @@ proc `$`*[T](self: Option[T]): string = if self.isSome: "Some(" & $self.val & ")" else: - "None[" & T.name & "]" + "None[" & name(T) & "]" when isMainModule: import unittest, sequtils @@ -298,3 +298,17 @@ when isMainModule: test "none[T]": check(none[int]().isNone) check(none(int) == none[int]()) + + test "$ on typed with .name": + type Named = object + name: string + + let nobody = none(Named) + check($nobody == "None[Named]") + + test "$ on type with name()": + type Person = object + myname: string + + let noperson = none(Person) + check($noperson == "None[Person]") From 231a83a6b108dc676b53ba5f906d51ff14aa5b51 Mon Sep 17 00:00:00 2001 From: cooldome Date: Thu, 12 Jul 2018 11:03:08 +0200 Subject: [PATCH 11/43] Fixes #8287 (#8288) --- compiler/renderer.nim | 16 ++++++++++------ tests/macros/tmacrostmt.nim | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/compiler/renderer.nim b/compiler/renderer.nim index 60ff5ec180..83cf288ffe 100644 --- a/compiler/renderer.nim +++ b/compiler/renderer.nim @@ -416,7 +416,7 @@ proc lsub(g: TSrcGen; n: PNode): int = of nkCast: result = lsub(g, n.sons[0]) + lsub(g, n.sons[1]) + len("cast[]()") of nkAddr: result = (if n.len>0: lsub(g, n.sons[0]) + len("addr()") else: 4) of nkStaticExpr: result = lsub(g, n.sons[0]) + len("static_") - of nkHiddenAddr, nkHiddenDeref: result = lsub(g, n.sons[0]) + of nkHiddenAddr, nkHiddenDeref, nkStringToCString, nkCStringToString: result = lsub(g, n.sons[0]) of nkCommand: result = lsub(g, n.sons[0]) + lcomma(g, n, 1) + 1 of nkExprEqExpr, nkAsgn, nkFastAsgn: result = lsons(g, n) + 3 of nkPar, nkCurly, nkBracket, nkClosure: result = lcomma(g, n) + 2 @@ -446,7 +446,7 @@ proc lsub(g: TSrcGen; n: PNode): int = of nkChckRangeF: result = len("chckRangeF") + 2 + lcomma(g, n) of nkChckRange64: result = len("chckRange64") + 2 + lcomma(g, n) of nkChckRange: result = len("chckRange") + 2 + lcomma(g, n) - of nkObjDownConv, nkObjUpConv, nkStringToCString, nkCStringToString: + of nkObjDownConv, nkObjUpConv: result = 2 if sonsLen(n) >= 1: result = result + lsub(g, n.sons[0]) result = result + lcomma(g, n, 1) @@ -968,7 +968,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = put(g, tkParLe, "(") gcomma(g, n) put(g, tkParRi, ")") - of nkObjDownConv, nkObjUpConv, nkStringToCString, nkCStringToString: + of nkObjDownConv, nkObjUpConv: if sonsLen(n) >= 1: gsub(g, n.sons[0]) put(g, tkParLe, "(") gcomma(g, n, 1) @@ -1020,7 +1020,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = of nkBind: putWithSpace(g, tkBind, "bind") gsub(g, n, 0) - of nkCheckedFieldExpr, nkHiddenAddr, nkHiddenDeref: + of nkCheckedFieldExpr, nkHiddenAddr, nkHiddenDeref, nkStringToCString, nkCStringToString: gsub(g, n, 0) of nkLambda: putWithSpace(g, tkProc, "proc") @@ -1073,9 +1073,13 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = elif n[0].kind == nkSym: n[0].sym.name elif n[0].kind in {nkOpenSymChoice, nkClosedSymChoice}: n[0][0].sym.name else: nil - if n[1].kind == nkPrefix or (opr != nil and renderer.isKeyword(opr)): + var n_next = n[1] + while n_next.kind in {nkCheckedFieldExpr, nkHiddenAddr, nkHiddenDeref, + nkStringToCString, nkCStringToString} and n_next.len > 0: + n_next = n_next[0] + if n_next.kind == nkPrefix or (opr != nil and renderer.isKeyword(opr)): put(g, tkSpaces, Space) - if n.sons[1].kind == nkInfix: + if n_next.kind == nkInfix: put(g, tkParLe, "(") gsub(g, n.sons[1]) put(g, tkParRi, ")") diff --git a/tests/macros/tmacrostmt.nim b/tests/macros/tmacrostmt.nim index 849a32ea3c..a6e1e66dd9 100644 --- a/tests/macros/tmacrostmt.nim +++ b/tests/macros/tmacrostmt.nim @@ -24,3 +24,23 @@ macro foo: typed = else: echo "Does not compute! (test OK)" foo() + +#------------------------------------ +# bug #8287 +type MyString = distinct string + +proc `$` (c: MyString): string {.borrow.} + +proc `!!` (c: cstring): int = + c.len + +proc f(name: MyString): int = + !! $ name + +macro repr_and_parse(fn: typed): typed = + let fn_impl = fn.getImpl + fn_impl.name = genSym(nskProc, $fn_impl.name) + echo fn_impl.repr + result = parseStmt(fn_impl.repr) + +repr_and_parse(f) \ No newline at end of file From 6de52d2b747074e993707adca2f74143134ce127 Mon Sep 17 00:00:00 2001 From: skilchen Date: Thu, 12 Jul 2018 11:06:20 +0200 Subject: [PATCH 12/43] make runnableExamples use a private nimcache so that they can be tested in parallel (#8281) * make runnableExamples use a private nimcache so that they can be tested in parallel --- compiler/sem.nim | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/compiler/sem.nim b/compiler/sem.nim index b242e4db62..0d484d2767 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -610,14 +610,21 @@ proc myProcess(context: PPassContext, n: PNode): PNode = proc testExamples(c: PContext) = let inp = toFullPath(c.config, c.module.info) let outp = inp.changeFileExt"" & "_examples.nim" + let nimcache = outp.changeFileExt"" & "_nimcache" renderModule(c.runnableExamples, inp, outp) let backend = if isDefined(c.config, "js"): "js" elif isDefined(c.config, "cpp"): "cpp" elif isDefined(c.config, "objc"): "objc" else: "c" - if os.execShellCmd(os.getAppFilename() & " " & backend & " -r " & outp) != 0: + if os.execShellCmd(os.getAppFilename() & " " & backend & " --nimcache:" & nimcache & " -r " & outp) != 0: quit "[Examples] failed" - removeFile(outp) + else: + removeFile(outp) + removeFile(outp.changeFileExt(ExeExt)) + try: + removeDir(nimcache) + except OSError: + discard proc myClose(graph: ModuleGraph; context: PPassContext, n: PNode): PNode = var c = PContext(context) From 1102f9aaf1c3d09640f0bb1cbd5e543871fcdc7c Mon Sep 17 00:00:00 2001 From: Kaushal Modi Date: Thu, 12 Jul 2018 05:30:51 -0400 Subject: [PATCH 13/43] Make the Style enum a proper Ordinal (no holes) (#8282) The Style enum needs to be an Ordinal so that it can be used in sets. --- lib/pure/terminal.nim | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/pure/terminal.nim b/lib/pure/terminal.nim index ac41a0aad3..f2be5cf187 100644 --- a/lib/pure/terminal.nim +++ b/lib/pure/terminal.nim @@ -471,11 +471,12 @@ type styleBright = 1, ## bright text styleDim, ## dim text styleItalic, ## italic (or reverse on terminals not supporting) - styleUnderscore = 4, ## underscored text + styleUnderscore, ## underscored text styleBlink, ## blinking/bold text - styleReverse = 7, ## reverse - styleHidden ## hidden text - styleStrikethrough, ## strikethrough + styleBlinkRapid, ## rapid blinking/bold text (not widely supported) + styleReverse, ## reverse + styleHidden, ## hidden text + styleStrikethrough ## strikethrough {.deprecated: [TStyle: Style].} {.deprecated: [styleUnknown: styleItalic].} @@ -921,6 +922,8 @@ proc disableTrueColors*() = trueColorIsEnabled = false when not defined(testing) and isMainModule: + assert ansiStyleCode(styleBright) == "\e[1m" + assert ansiStyleCode(styleStrikethrough) == "\e[9m" #system.addQuitProc(resetAttributes) write(stdout, "never mind") stdout.eraseLine() From 9aad193d2753ffb6ec619d45c8f29cb9db864b35 Mon Sep 17 00:00:00 2001 From: Joey Yakimowich-Payne Date: Thu, 12 Jul 2018 19:48:43 +0900 Subject: [PATCH 14/43] Fix undefined PObject reference --- lib/deprecated/pure/asyncio.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/deprecated/pure/asyncio.nim b/lib/deprecated/pure/asyncio.nim index 34cabefb06..161941e53e 100644 --- a/lib/deprecated/pure/asyncio.nim +++ b/lib/deprecated/pure/asyncio.nim @@ -272,7 +272,7 @@ proc asyncSockHandleWrite(h: RootRef) = AsyncSocket(h).deleg.mode = fmRead when defined(ssl): - proc asyncSockDoHandshake(h: PObject) {.gcsafe.} = + proc asyncSockDoHandshake(h: RootRef) {.gcsafe.} = if AsyncSocket(h).socket.isSSL and not AsyncSocket(h).socket.gotHandshake: if AsyncSocket(h).sslNeedAccept: From ba1f036a56362885d543f470642230de40df217d Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Thu, 12 Jul 2018 13:00:11 +0200 Subject: [PATCH 15/43] Correctly hash inferred types (#8286) We don't really want to hash the tyInferred container since that'd make the "real" type and its inferred counterpart produce different hashes and types for the very same type. Fixes #8280 --- compiler/sighashes.nim | 2 +- tests/concepts/t8280.nim | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 tests/concepts/t8280.nim diff --git a/compiler/sighashes.nim b/compiler/sighashes.nim index 0bf2b84598..8f95175e5e 100644 --- a/compiler/sighashes.nim +++ b/compiler/sighashes.nim @@ -163,7 +163,7 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]) = c.hashType t.sons[i], flags else: c.hashType t.lastSon, flags - of tyAlias, tySink, tyUserTypeClasses: + of tyAlias, tySink, tyUserTypeClasses, tyInferred: c.hashType t.lastSon, flags of tyBool, tyChar, tyInt..tyUInt64: # no canonicalization for integral types, so that e.g. ``pid_t`` is diff --git a/tests/concepts/t8280.nim b/tests/concepts/t8280.nim new file mode 100644 index 0000000000..ba33af34e0 --- /dev/null +++ b/tests/concepts/t8280.nim @@ -0,0 +1,16 @@ +discard """ + output: "()" +""" + +type + Iterable[T] = concept x + for elem in x: + elem is T + +proc max[A](iter: Iterable[A]): A = + discard + +type + MyType = object + +echo max(@[MyType()]) From 3fec2ba5e5a29402aeb1bbb0d160deb85a93ffd1 Mon Sep 17 00:00:00 2001 From: Eugene Kabanov Date: Thu, 12 Jul 2018 19:06:15 +0300 Subject: [PATCH 16/43] Fix terminal.nim to be GCSAFE. (#8296) * Also fix deprecated callsite(). --- lib/pure/terminal.nim | 158 +++++++++++++++++++----------------------- 1 file changed, 73 insertions(+), 85 deletions(-) diff --git a/lib/pure/terminal.nim b/lib/pure/terminal.nim index f2be5cf187..904274ade2 100644 --- a/lib/pure/terminal.nim +++ b/lib/pure/terminal.nim @@ -19,22 +19,30 @@ import macros import strformat from strutils import toLowerAscii -import colors +import colors, tables -const - hasThreadSupport = compileOption("threads") +when defined(windows): + import winlean -when not hasThreadSupport: - import tables - var - colorsFGCache = initTable[Color, string]() - colorsBGCache = initTable[Color, string]() - styleCache = initTable[int, string]() +type + PTerminal = ref object + trueColorIsSupported: bool + trueColorIsEnabled: bool + fgSetColor: bool + when defined(windows): + hStdout: Handle + hStderr: Handle + oldStdoutAttr: int16 + oldStderrAttr: int16 -var - trueColorIsSupported: bool - trueColorIsEnabled: bool - fgSetColor: bool +var gTerm {.threadvar.}: PTerminal + +proc newTerminal(): PTerminal + +proc getTerminal(): PTerminal {.inline.} = + if isNil(gTerm): + gTerm = newTerminal() + result = gTerm const fgPrefix = "\x1b[38;2;" @@ -156,23 +164,6 @@ when defined(windows): proc setConsoleMode(hConsoleHandle: Handle, dwMode: DWORD): WINBOOL{. stdcall, dynlib: "kernel32", importc: "SetConsoleMode".} - var - hStdout: Handle # = createFile("CONOUT$", GENERIC_WRITE, 0, nil, - # OPEN_ALWAYS, 0, 0) - hStderr: Handle - - block: - var hStdoutTemp = getStdHandle(STD_OUTPUT_HANDLE) - if duplicateHandle(getCurrentProcess(), hStdoutTemp, getCurrentProcess(), - addr(hStdout), 0, 1, DUPLICATE_SAME_ACCESS) == 0: - when defined(consoleapp): - raiseOSError(osLastError()) - var hStderrTemp = getStdHandle(STD_ERROR_HANDLE) - if duplicateHandle(getCurrentProcess(), hStderrTemp, getCurrentProcess(), - addr(hStderr), 0, 1, DUPLICATE_SAME_ACCESS) == 0: - when defined(consoleapp): - raiseOSError(osLastError()) - proc getCursorPos(h: Handle): tuple [x,y: int] = var c: CONSOLESCREENBUFFERINFO if getConsoleScreenBufferInfo(h, addr(c)) == 0: @@ -193,12 +184,23 @@ when defined(windows): return c.wAttributes return 0x70'i16 # ERROR: return white background, black text - var - oldStdoutAttr = getAttributes(hStdout) - oldStderrAttr = getAttributes(hStderr) + proc initTerminal(term: PTerminal) = + var hStdoutTemp = getStdHandle(STD_OUTPUT_HANDLE) + if duplicateHandle(getCurrentProcess(), hStdoutTemp, getCurrentProcess(), + addr(term.hStdout), 0, 1, DUPLICATE_SAME_ACCESS) == 0: + when defined(consoleapp): + raiseOSError(osLastError()) + var hStderrTemp = getStdHandle(STD_ERROR_HANDLE) + if duplicateHandle(getCurrentProcess(), hStderrTemp, getCurrentProcess(), + addr(term.hStderr), 0, 1, DUPLICATE_SAME_ACCESS) == 0: + when defined(consoleapp): + raiseOSError(osLastError()) + term.oldStdoutAttr = getAttributes(term.hStdout) + term.oldStderrAttr = getAttributes(term.hStderr) template conHandle(f: File): Handle = - if f == stderr: hStderr else: hStdout + let term = getTerminal() + if f == stderr: term.hStderr else: term.hStdout else: import termios, posix, os, parseutils @@ -459,10 +461,11 @@ proc eraseScreen*(f: File) = proc resetAttributes*(f: File) = ## Resets all attributes. when defined(windows): + let term = getTerminal() if f == stderr: - discard setConsoleTextAttribute(hStderr, oldStderrAttr) + discard setConsoleTextAttribute(term.hStderr, term.oldStderrAttr) else: - discard setConsoleTextAttribute(hStdout, oldStdoutAttr) + discard setConsoleTextAttribute(term.hStdout, term.oldStdoutAttr) else: f.write(ansiResetCode) @@ -487,14 +490,7 @@ when not defined(windows): gBG {.threadvar.}: int proc ansiStyleCode*(style: int): string = - when hasThreadSupport: - result = fmt"{stylePrefix}{style}m" - else: - if styleCache.hasKey(style): - result = styleCache[style] - else: - result = fmt"{stylePrefix}{style}m" - styleCache[style] = result + result = fmt"{stylePrefix}{style}m" template ansiStyleCode*(style: Style): string = ansiStyleCode(style.int) @@ -521,10 +517,11 @@ proc setStyle*(f: File, style: set[Style]) = proc writeStyled*(txt: string, style: set[Style] = {styleBright}) = ## Writes the text `txt` in a given `style` to stdout. when defined(windows): - var old = getAttributes(hStdout) + let term = getTerminal() + var old = getAttributes(term.hStdout) stdout.setStyle(style) stdout.write(txt) - discard setConsoleTextAttribute(hStdout, old) + discard setConsoleTextAttribute(term.hStdout, old) else: stdout.setStyle(style) stdout.write(txt) @@ -633,32 +630,16 @@ template ansiForegroundColorCode*(fg: static[ForegroundColor], ansiStyleCode(fg.int + bright.int * 60) proc ansiForegroundColorCode*(color: Color): string = - when hasThreadSupport: - let rgb = extractRGB(color) - result = fmt"{fgPrefix}{rgb.r};{rgb.g};{rgb.b}m" - else: - if colorsFGCache.hasKey(color): - result = colorsFGCache[color] - else: - let rgb = extractRGB(color) - result = fmt"{fgPrefix}{rgb.r};{rgb.g};{rgb.b}m" - colorsFGCache[color] = result + let rgb = extractRGB(color) + result = fmt"{fgPrefix}{rgb.r};{rgb.g};{rgb.b}m" template ansiForegroundColorCode*(color: static[Color]): string = const rgb = extractRGB(color) (static(fmt"{fgPrefix}{rgb.r};{rgb.g};{rgb.b}m")) proc ansiBackgroundColorCode*(color: Color): string = - when hasThreadSupport: - let rgb = extractRGB(color) - result = fmt"{bgPrefix}{rgb.r};{rgb.g};{rgb.b}m" - else: - if colorsBGCache.hasKey(color): - result = colorsBGCache[color] - else: - let rgb = extractRGB(color) - result = fmt"{bgPrefix}{rgb.r};{rgb.g};{rgb.b}m" - colorsFGCache[color] = result + let rgb = extractRGB(color) + result = fmt"{bgPrefix}{rgb.r};{rgb.g};{rgb.b}m" template ansiBackgroundColorCode*(color: static[Color]): string = const rgb = extractRGB(color) @@ -666,16 +647,17 @@ template ansiBackgroundColorCode*(color: static[Color]): string = proc setForegroundColor*(f: File, color: Color) = ## Sets the terminal's foreground true color. - if trueColorIsEnabled: + if getTerminal().trueColorIsEnabled: f.write(ansiForegroundColorCode(color)) proc setBackgroundColor*(f: File, color: Color) = ## Sets the terminal's background true color. - if trueColorIsEnabled: + if getTerminal().trueColorIsEnabled: f.write(ansiBackgroundColorCode(color)) proc setTrueColor(f: File, color: Color) = - if fgSetColor: + let term = getTerminal() + if term.fgSetColor: setForegroundColor(f, color) else: setBackgroundColor(f, color) @@ -727,11 +709,10 @@ macro styledWrite*(f: File, m: varargs[typed]): untyped = ## stdout.styledWrite(fgRed, "red text ") ## stdout.styledWrite(fgGreen, "green text") ## - let m = callsite() var reset = false result = newNimNode(nnkStmtList) - for i in countup(2, m.len - 1): + for i in countup(0, m.len - 1): let item = m[i] case item.kind of nnkStrLit..nnkTripleStrLit: @@ -872,54 +853,61 @@ proc resetAttributes*() {.noconv.} = proc isTrueColorSupported*(): bool = ## Returns true if a terminal supports true color. - return trueColorIsSupported + return getTerminal().trueColorIsSupported when defined(windows): import os proc enableTrueColors*() = ## Enable true color. + var term = getTerminal() when defined(windows): var ver: OSVERSIONINFO ver.dwOSVersionInfoSize = sizeof(ver).DWORD let res = getVersionExW(addr ver) if res == 0: - trueColorIsSupported = false + term.trueColorIsSupported = false else: - trueColorIsSupported = ver.dwMajorVersion > 10 or + term.trueColorIsSupported = ver.dwMajorVersion > 10 or (ver.dwMajorVersion == 10 and (ver.dwMinorVersion > 0 or (ver.dwMinorVersion == 0 and ver.dwBuildNumber >= 10586))) - if not trueColorIsSupported: - trueColorIsSupported = getEnv("ANSICON_DEF").len > 0 + if not term.trueColorIsSupported: + term.trueColorIsSupported = getEnv("ANSICON_DEF").len > 0 - if trueColorIsSupported: + if term.trueColorIsSupported: if getEnv("ANSICON_DEF").len == 0: var mode: DWORD = 0 if getConsoleMode(getStdHandle(STD_OUTPUT_HANDLE), addr(mode)) != 0: mode = mode or ENABLE_VIRTUAL_TERMINAL_PROCESSING if setConsoleMode(getStdHandle(STD_OUTPUT_HANDLE), mode) != 0: - trueColorIsEnabled = true + term.trueColorIsEnabled = true else: - trueColorIsEnabled = false + term.trueColorIsEnabled = false else: - trueColorIsEnabled = true + term.trueColorIsEnabled = true else: - trueColorIsSupported = string(getEnv("COLORTERM")).toLowerAscii() in ["truecolor", "24bit"] - trueColorIsEnabled = trueColorIsSupported + term.trueColorIsSupported = string(getEnv("COLORTERM")).toLowerAscii() in ["truecolor", "24bit"] + term.trueColorIsEnabled = term.trueColorIsSupported proc disableTrueColors*() = ## Disable true color. + var term = getTerminal() when defined(windows): - if trueColorIsSupported: + if term.trueColorIsSupported: if getEnv("ANSICON_DEF").len == 0: var mode: DWORD = 0 if getConsoleMode(getStdHandle(STD_OUTPUT_HANDLE), addr(mode)) != 0: mode = mode and not ENABLE_VIRTUAL_TERMINAL_PROCESSING discard setConsoleMode(getStdHandle(STD_OUTPUT_HANDLE), mode) - trueColorIsEnabled = false + term.trueColorIsEnabled = false else: - trueColorIsEnabled = false + term.trueColorIsEnabled = false + +proc newTerminal(): PTerminal = + new result + when defined(windows): + initTerminal(result) when not defined(testing) and isMainModule: assert ansiStyleCode(styleBright) == "\e[1m" From 3163a0f46649bcb54e181b055bb32cc7bfec98df Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Thu, 12 Jul 2018 18:08:45 +0200 Subject: [PATCH 17/43] Do not consider enums with holes as ordinals (#8264) Make the compiler behave consistently with respect to what's written in the manual. Fixes #1239 --- compiler/semmagic.nim | 2 +- compiler/types.nim | 14 +++++++------- lib/system.nim | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim index 1975fb77b8..b5875c67a9 100644 --- a/compiler/semmagic.nim +++ b/compiler/semmagic.nim @@ -171,7 +171,7 @@ proc semTypeTraits(c: PContext, n: PNode): PNode = proc semOrd(c: PContext, n: PNode): PNode = result = n let parType = n.sons[1].typ - if isOrdinalType(parType): + if isOrdinalType(parType, allowEnumWithHoles=true): discard elif parType.kind == tySet: result.typ = makeRangeType(c, firstOrd(c.config, parType), lastOrd(c.config, parType), n.info) diff --git a/compiler/types.nim b/compiler/types.nim index 4d3f99c310..4180d34a75 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -133,18 +133,18 @@ proc elemType*(t: PType): PType = else: result = t.lastSon assert(result != nil) -proc isOrdinalType*(t: PType): bool = +proc enumHasHoles*(t: PType): bool = + var b = t.skipTypes({tyRange, tyGenericInst, tyAlias, tySink}) + result = b.kind == tyEnum and tfEnumHasHoles in b.flags + +proc isOrdinalType*(t: PType, allowEnumWithHoles = false): bool = assert(t != nil) const # caution: uint, uint64 are no ordinal types! baseKinds = {tyChar,tyInt..tyInt64,tyUInt8..tyUInt32,tyBool,tyEnum} parentKinds = {tyRange, tyOrdinal, tyGenericInst, tyAlias, tySink, tyDistinct} - t.kind in baseKinds or (t.kind in parentKinds and isOrdinalType(t.sons[0])) - -proc enumHasHoles*(t: PType): bool = - var b = t - while b.kind in {tyRange, tyGenericInst, tyAlias, tySink}: b = b.sons[0] - result = b.kind == tyEnum and tfEnumHasHoles in b.flags + (t.kind in baseKinds and not (t.enumHasHoles and not allowEnumWithHoles)) or + (t.kind in parentKinds and isOrdinalType(t.lastSon)) proc iterOverTypeAux(marker: var IntSet, t: PType, iter: TTypeIter, closure: RootRef): bool diff --git a/lib/system.nim b/lib/system.nim index 9e91bc7db8..10f1df1508 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -263,14 +263,14 @@ proc high*[T: Ordinal](x: T): T {.magic: "High", noSideEffect.} ## high(2) #=> 9223372036854775807 ## high(int) #=> 9223372036854775807 -proc high*[T: Ordinal](x: typeDesc[T]): T {.magic: "High", noSideEffect.} +proc high*[T: Ordinal|enum](x: typeDesc[T]): T {.magic: "High", noSideEffect.} proc high*[T](x: openArray[T]): int {.magic: "High", noSideEffect.} proc high*[I, T](x: array[I, T]): I {.magic: "High", noSideEffect.} proc high*[I, T](x: typeDesc[array[I, T]]): I {.magic: "High", noSideEffect.} proc high*(x: cstring): int {.magic: "High", noSideEffect.} proc high*(x: string): int {.magic: "High", noSideEffect.} -proc low*[T: Ordinal](x: typeDesc[T]): T {.magic: "Low", noSideEffect.} +proc low*[T: Ordinal|enum](x: typeDesc[T]): T {.magic: "Low", noSideEffect.} proc low*[T](x: openArray[T]): int {.magic: "Low", noSideEffect.} proc low*[I, T](x: array[I, T]): I {.magic: "Low", noSideEffect.} proc low*[T](x: T): T {.magic: "Low", noSideEffect.} @@ -792,7 +792,7 @@ proc card*[T](x: set[T]): int {.magic: "Card", noSideEffect.} ## var i = {1,2,3,4} ## card(i) #=> 4 -proc ord*[T](x: T): int {.magic: "Ord", noSideEffect.} +proc ord*[T: Ordinal|enum](x: T): int {.magic: "Ord", noSideEffect.} ## returns the internal int value of an ordinal value ``x``. ## ## .. code-block:: nim From 40f44a0c16a8aed8f97416f672d73f8bda6344ab Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Fri, 13 Jul 2018 00:54:48 -0700 Subject: [PATCH 18/43] fix issue #8251 ospaths.isAbsolute: out of bound errors (#8291) * fix issue #8251 ospaths.isAbsolute: out of bound errors * address comments * add reference to a spec for quirky macos paths --- lib/pure/ospaths.nim | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/pure/ospaths.nim b/lib/pure/ospaths.nim index 305052e419..4ae5afd6cd 100644 --- a/lib/pure/ospaths.nim +++ b/lib/pure/ospaths.nim @@ -423,12 +423,22 @@ proc isAbsolute*(path: string): bool {.rtl, noSideEffect, extern: "nos$1".} = ## Checks whether a given `path` is absolute. ## ## On Windows, network paths are considered absolute too. + runnableExamples: + doAssert(not "".isAbsolute) + doAssert(not ".".isAbsolute) + when defined(posix): + doAssert "/".isAbsolute + doAssert(not "a/".isAbsolute) + + if len(path) == 0: return false + when doslikeFileSystem: var len = len(path) - result = (len > 0 and path[0] in {'/', '\\'}) or + result = (path[0] in {'/', '\\'}) or (len > 1 and path[0] in {'a'..'z', 'A'..'Z'} and path[1] == ':') elif defined(macos): - result = path.len > 0 and path[0] != ':' + # according to https://perldoc.perl.org/File/Spec/Mac.html `:a` is a relative path + result = path[0] != ':' elif defined(RISCOS): result = path[0] == '$' elif defined(posix): From 54a85b4ff56393e7279f244fe8557ebb36f864ee Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Fri, 13 Jul 2018 15:23:41 +0200 Subject: [PATCH 19/43] Give different names to objects coming from cpp files (#8278) Prevent some nasty linker errors if the user switches between c and cpp backends. --- compiler/extccomp.nim | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/compiler/extccomp.nim b/compiler/extccomp.nim index a48067abb7..575e30a794 100644 --- a/compiler/extccomp.nim +++ b/compiler/extccomp.nim @@ -434,10 +434,7 @@ proc completeCFilePath*(conf: ConfigRef; cfile: string, createSubDir: bool = tru proc toObjFile*(conf: ConfigRef; filename: string): string = # Object file for compilation - #if filename.endsWith(".cpp"): - # result = changeFileExt(filename, "cpp." & CC[cCompiler].objExt) - #else: - result = changeFileExt(filename, CC[conf.cCompiler].objExt) + result = filename & "." & CC[conf.cCompiler].objExt proc addFileToCompile*(conf: ConfigRef; cf: Cfile) = conf.toCompile.add(cf) From dfe3f160227dadd5d93bd6c697106e71899eccce Mon Sep 17 00:00:00 2001 From: Yuriy Glukhov Date: Fri, 13 Jul 2018 18:41:59 +0300 Subject: [PATCH 20/43] Don't depend on string.h in codegen (#8299) --- compiler/ccgexprs.nim | 34 ++++++++++++------------------- compiler/cgen.nim | 13 +++--------- lib/pure/memfiles.nim | 3 +-- lib/system.nim | 34 +++++++++++++++---------------- lib/system/alloc.nim | 4 ++-- lib/system/ansi_c.nim | 2 ++ lib/system/memory.nim | 47 +++++++++++++++++++++++++++++++++++++++++++ lib/system/sysio.nim | 2 +- lib/system/sysstr.nim | 2 +- 9 files changed, 87 insertions(+), 54 deletions(-) create mode 100644 lib/system/memory.nim diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index 9b31167e3e..2e9fb2cc2a 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -257,9 +257,8 @@ proc genGenericAsgn(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = if needToCopy notin flags or tfShallow in skipTypes(dest.t, abstractVarRange).flags: if dest.storage == OnStack or not usesNativeGC(p.config): - useStringh(p.module) linefmt(p, cpsStmts, - "memcpy((void*)$1, (NIM_CONST void*)$2, sizeof($3));$n", + "#nimCopyMem((void*)$1, (NIM_CONST void*)$2, sizeof($3));$n", addrLoc(p.config, dest), addrLoc(p.config, src), rdLoc(dest)) else: linefmt(p, cpsStmts, "#genericShallowAssign((void*)$1, (void*)$2, $3);$n", @@ -336,9 +335,8 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = if needsComplexAssignment(dest.t): genGenericAsgn(p, dest, src, flags) else: - useStringh(p.module) linefmt(p, cpsStmts, - "memcpy((void*)$1, (NIM_CONST void*)$2, sizeof($3));$n", + "#nimCopyMem((void*)$1, (NIM_CONST void*)$2, sizeof($3));$n", rdLoc(dest), rdLoc(src), getTypeDesc(p.module, dest.t)) of tyOpenArray, tyVarargs: # open arrays are always on the stack - really? What if a sequence is @@ -349,16 +347,14 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = addrLoc(p.config, dest), addrLoc(p.config, src), genTypeInfo(p.module, dest.t, dest.lode.info)) else: - useStringh(p.module) linefmt(p, cpsStmts, - # bug #4799, keep the memcpy for a while - #"memcpy((void*)$1, (NIM_CONST void*)$2, sizeof($1[0])*$1Len_0);$n", + # bug #4799, keep the nimCopyMem for a while + #"#nimCopyMem((void*)$1, (NIM_CONST void*)$2, sizeof($1[0])*$1Len_0);$n", "$1 = $2;$n", rdLoc(dest), rdLoc(src)) of tySet: if mapType(p.config, ty) == ctArray: - useStringh(p.module) - linefmt(p, cpsStmts, "memcpy((void*)$1, (NIM_CONST void*)$2, $3);$n", + linefmt(p, cpsStmts, "#nimCopyMem((void*)$1, (NIM_CONST void*)$2, $3);$n", rdLoc(dest), rdLoc(src), rope(getSize(p.config, dest.t))) else: linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src)) @@ -403,8 +399,7 @@ proc genDeepCopy(p: BProc; dest, src: TLoc) = genTypeInfo(p.module, dest.t, dest.lode.info)) of tySet: if mapType(p.config, ty) == ctArray: - useStringh(p.module) - linefmt(p, cpsStmts, "memcpy((void*)$1, (NIM_CONST void*)$2, $3);$n", + linefmt(p, cpsStmts, "#nimCopyMem((void*)$1, (NIM_CONST void*)$2, $3);$n", rdLoc(dest), rdLoc(src), rope(getSize(p.config, dest.t))) else: linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src)) @@ -1481,9 +1476,8 @@ proc genArrayLen(p: BProc, e: PNode, d: var TLoc, op: TMagic) = if op == mHigh: unaryExpr(p, e, d, "($1Len_0-1)") else: unaryExpr(p, e, d, "$1Len_0") of tyCString: - useStringh(p.module) - if op == mHigh: unaryExpr(p, e, d, "($1 ? (strlen($1)-1) : -1)") - else: unaryExpr(p, e, d, "($1 ? strlen($1) : 0)") + if op == mHigh: unaryExpr(p, e, d, "($1 ? (#nimCStrLen($1)-1) : -1)") + else: unaryExpr(p, e, d, "($1 ? #nimCStrLen($1) : 0)") of tyString: if not p.module.compileToCpp: if op == mHigh: unaryExpr(p, e, d, "($1 ? ($1->Sup.len-1) : -1)") @@ -1632,7 +1626,7 @@ proc genSetOp(p: BProc, e: PNode, d: var TLoc, op: TMagic) = " $3 = (($4[$1] & ~ $5[$1]) == 0);$n" & " if (!$3) break;}$n", "for ($1 = 0; $1 < $2; $1++) { $n" & " $3 = (($4[$1] & ~ $5[$1]) == 0);$n" & " if (!$3) break;}$n" & - "if ($3) $3 = (memcmp($4, $5, $2) != 0);$n", + "if ($3) $3 = (#nimCmpMem($4, $5, $2) != 0);$n", "&", "|", "& ~", "^"] var a, b, i: TLoc var setType = skipTypes(e.sons[1].typ, abstractVar) @@ -1671,11 +1665,10 @@ proc genSetOp(p: BProc, e: PNode, d: var TLoc, op: TMagic) = initLocExpr(p, e.sons[1], a) initLocExpr(p, e.sons[2], b) if d.k == locNone: getTemp(p, getSysType(p.module.g.graph, unknownLineInfo(), tyBool), d) - lineF(p, cpsStmts, lookupOpr[op], + linefmt(p, cpsStmts, lookupOpr[op], [rdLoc(i), rope(size), rdLoc(d), rdLoc(a), rdLoc(b)]) of mEqSet: - useStringh(p.module) - binaryExprChar(p, e, d, "(memcmp($1, $2, " & $(size) & ")==0)") + binaryExprChar(p, e, d, "(#nimCmpMem($1, $2, " & $(size) & ")==0)") of mMulSet, mPlusSet, mMinusSet, mSymDiffSet: # we inline the simple for loop for better code generation: getTemp(p, getSysType(p.module.g.graph, unknownLineInfo(), tyInt), i) # our counter @@ -1934,7 +1927,7 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = proc genSetConstr(p: BProc, e: PNode, d: var TLoc) = # example: { a..b, c, d, e, f..g } # we have to emit an expression of the form: - # memset(tmp, 0, sizeof(tmp)); inclRange(tmp, a, b); incl(tmp, c); + # nimZeroMem(tmp, sizeof(tmp)); inclRange(tmp, a, b); incl(tmp, c); # incl(tmp, d); incl(tmp, e); inclRange(tmp, f, g); var a, b, idx: TLoc @@ -1944,8 +1937,7 @@ proc genSetConstr(p: BProc, e: PNode, d: var TLoc) = if d.k == locNone: getTemp(p, e.typ, d) if getSize(p.config, e.typ) > 8: # big set: - useStringh(p.module) - lineF(p, cpsStmts, "memset($1, 0, sizeof($2));$n", + linefmt(p, cpsStmts, "#nimZeroMem($1, sizeof($2));$n", [rdLoc(d), getTypeDesc(p.module, e.typ)]) for it in e.sons: if it.kind == nkRange: diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 32891c62ca..ea82c496f5 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -80,11 +80,6 @@ proc isSimpleConst(typ: PType): bool = {tyTuple, tyObject, tyArray, tySet, tySequence} and not (t.kind == tyProc and t.callConv == ccClosure) -proc useStringh(m: BModule) = - if includesStringh notin m.flags: - incl m.flags, includesStringh - m.includeHeader("") - proc useHeader(m: BModule, sym: PSym) = if lfHeader in sym.loc.flags: assert(sym.annex != nil) @@ -320,10 +315,9 @@ proc resetLoc(p: BProc, loc: var TLoc) = # field, so disabling this should be safe: genObjectInit(p, cpsStmts, loc.t, loc, true) else: - useStringh(p.module) # array passed as argument decayed into pointer, bug #7332 # so we use getTypeDesc here rather than rdLoc(loc) - linefmt(p, cpsStmts, "memset((void*)$1, 0, sizeof($2));$n", + linefmt(p, cpsStmts, "#nimZeroMem((void*)$1, sizeof($2));$n", addrLoc(p.config, loc), getTypeDesc(p.module, loc.t)) # XXX: We can be extra clever here and call memset only # on the bytes following the m_type field? @@ -336,11 +330,10 @@ proc constructLoc(p: BProc, loc: TLoc, isTemp = false) = getTypeDesc(p.module, typ)) else: if not isTemp or containsGarbageCollectedRef(loc.t): - # don't use memset for temporary values for performance if we can + # don't use nimZeroMem for temporary values for performance if we can # avoid it: if not isImportedCppType(typ): - useStringh(p.module) - linefmt(p, cpsStmts, "memset((void*)$1, 0, sizeof($2));$n", + linefmt(p, cpsStmts, "#nimZeroMem((void*)$1, sizeof($2));$n", addrLoc(p.config, loc), getTypeDesc(p.module, typ)) genObjectInit(p, cpsStmts, loc.t, loc, true) diff --git a/lib/pure/memfiles.nim b/lib/pure/memfiles.nim index bda0ecb771..0249b74135 100644 --- a/lib/pure/memfiles.nim +++ b/lib/pure/memfiles.nim @@ -322,8 +322,7 @@ type MemSlice* = object ## represent slice of a MemFile for iteration over deli proc `==`*(x, y: MemSlice): bool = ## Compare a pair of MemSlice for strict equality. - proc memcmp(a, b: pointer, n:int):int {.importc: "memcmp",header: "string.h".} - result = (x.size == y.size and memcmp(x.data, y.data, x.size) == 0) + result = (x.size == y.size and equalMem(x.data, y.data, x.size)) proc `$`*(ms: MemSlice): string {.inline.} = ## Return a Nim string built from a MemSlice. diff --git a/lib/system.nim b/lib/system.nim index 10f1df1508..fed02f6d0f 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -2903,6 +2903,22 @@ when not defined(JS): #and not defined(nimscript): ## useful for low-level file access include "system/ansi_c" + include "system/memory" + + proc zeroMem(p: pointer, size: Natural) = + nimZeroMem(p, size) + when declared(memTrackerOp): + memTrackerOp("zeroMem", p, size) + proc copyMem(dest, source: pointer, size: Natural) = + nimCopyMem(dest, source, size) + when declared(memTrackerOp): + memTrackerOp("copyMem", dest, size) + proc moveMem(dest, source: pointer, size: Natural) = + c_memmove(dest, source, size) + when declared(memTrackerOp): + memTrackerOp("moveMem", dest, size) + proc equalMem(a, b: pointer, size: Natural): bool = + nimCmpMem(a, b, size) == 0 proc cmp(x, y: string): int = when nimvm: @@ -2911,7 +2927,7 @@ when not defined(JS): #and not defined(nimscript): else: result = 0 else: let minlen = min(x.len, y.len) - result = int(c_memcmp(x.cstring, y.cstring, minlen.csize)) + result = int(nimCmpMem(x.cstring, y.cstring, minlen.csize)) if result == 0: result = x.len - y.len @@ -3200,22 +3216,6 @@ when not defined(JS): #and not defined(nimscript): when defined(memtracker): include "system/memtracker" - when not defined(nimscript): - proc zeroMem(p: pointer, size: Natural) = - c_memset(p, 0, size) - when declared(memTrackerOp): - memTrackerOp("zeroMem", p, size) - proc copyMem(dest, source: pointer, size: Natural) = - c_memcpy(dest, source, size) - when declared(memTrackerOp): - memTrackerOp("copyMem", dest, size) - proc moveMem(dest, source: pointer, size: Natural) = - c_memmove(dest, source, size) - when declared(memTrackerOp): - memTrackerOp("moveMem", dest, size) - proc equalMem(a, b: pointer, size: Natural): bool = - c_memcmp(a, b, size) == 0 - when hostOS == "standalone": include "system/embedded" else: diff --git a/lib/system/alloc.nim b/lib/system/alloc.nim index 6aef4f4117..95becde229 100644 --- a/lib/system/alloc.nim +++ b/lib/system/alloc.nim @@ -830,7 +830,7 @@ proc rawDealloc(a: var MemRegion, p: pointer) = c.freeList = f when overwriteFree: # set to 0xff to check for usage after free bugs: - c_memset(cast[pointer](cast[int](p) +% sizeof(FreeCell)), -1'i32, + nimSetMem(cast[pointer](cast[int](p) +% sizeof(FreeCell)), -1'i32, s -% sizeof(FreeCell)) # check if it is not in the freeSmallChunks[s] list: if c.free < s: @@ -847,7 +847,7 @@ proc rawDealloc(a: var MemRegion, p: pointer) = s == 0, "rawDealloc 2") else: # set to 0xff to check for usage after free bugs: - when overwriteFree: c_memset(p, -1'i32, c.size -% bigChunkOverhead()) + when overwriteFree: nimSetMem(p, -1'i32, c.size -% bigChunkOverhead()) # free big chunk var c = cast[PBigChunk](c) dec a.occ, c.size diff --git a/lib/system/ansi_c.nim b/lib/system/ansi_c.nim index 52cb15e39b..4d21f87479 100644 --- a/lib/system/ansi_c.nim +++ b/lib/system/ansi_c.nim @@ -25,6 +25,8 @@ proc c_memset(p: pointer, value: cint, size: csize): pointer {. importc: "memset", header: "", discardable.} proc c_strcmp(a, b: cstring): cint {. importc: "strcmp", header: "", noSideEffect.} +proc c_strlen(a: cstring): csize {. + importc: "strlen", header: "", noSideEffect.} when defined(linux) and defined(amd64): type diff --git a/lib/system/memory.nim b/lib/system/memory.nim new file mode 100644 index 0000000000..f86fd4696c --- /dev/null +++ b/lib/system/memory.nim @@ -0,0 +1,47 @@ +const useLibC = not defined(nimNoLibc) + +proc nimCopyMem(dest, source: pointer, size: Natural) {.compilerproc, inline.} = + when useLibC: + c_memcpy(dest, source, size) + else: + let d = cast[ptr UncheckedArray[byte]](dest) + let s = cast[ptr UncheckedArray[byte]](source) + var i = 0 + while i < size: + d[i] = s[i] + inc i + +proc nimSetMem(a: pointer, v: cint, size: Natural) {.inline.} = + when useLibC: + c_memset(a, v, size) + else: + let a = cast[ptr UncheckedArray[byte]](a) + var i = 0 + let v = cast[byte](v) + while i < size: + a[i] = v + inc i + +proc nimZeroMem(p: pointer, size: Natural) {.compilerproc, inline.} = + nimSetMem(p, 0, size) + +proc nimCmpMem(a, b: pointer, size: Natural): cint {.compilerproc, inline.} = + when useLibC: + c_memcmp(a, b, size) + else: + let a = cast[ptr UncheckedArray[byte]](a) + let b = cast[ptr UncheckedArray[byte]](b) + var i = 0 + while i < size: + let d = a[i].cint - b[i].cint + if d != 0: return d + inc i + +proc nimCStrLen(a: cstring): csize {.compilerproc, inline.} = + when useLibC: + c_strlen(a) + else: + var a = cast[ptr byte](a) + while a[] != 0: + a = cast[ptr byte](cast[uint](a) + 1) + inc result diff --git a/lib/system/sysio.nim b/lib/system/sysio.nim index f3a576be09..2ece56916f 100644 --- a/lib/system/sysio.nim +++ b/lib/system/sysio.nim @@ -154,7 +154,7 @@ proc readLine(f: File, line: var TaintedString): bool = while true: # memset to \L so that we can tell how far fgets wrote, even on EOF, where # fgets doesn't append an \L - c_memset(addr line.string[pos], '\L'.ord, sp) + nimSetMem(addr line.string[pos], '\L'.ord, sp) var fgetsSuccess = c_fgets(addr line.string[pos], sp, f) != nil if not fgetsSuccess: checkErr(f) let m = c_memchr(addr line.string[pos], '\L'.ord, sp) diff --git a/lib/system/sysstr.nim b/lib/system/sysstr.nim index 90ae91cf53..8e9e18b053 100644 --- a/lib/system/sysstr.nim +++ b/lib/system/sysstr.nim @@ -32,7 +32,7 @@ proc cmpStrings(a, b: NimString): int {.inline, compilerProc.} = let blen = b.len let minlen = min(alen, blen) if minlen > 0: - result = c_memcmp(addr a.data, addr b.data, minlen.csize) + result = nimCmpMem(addr a.data, addr b.data, minlen.csize) if result == 0: result = alen - blen else: From dd47013017d75a2b1a8c80e12f273e18e86f7d72 Mon Sep 17 00:00:00 2001 From: momf Date: Sat, 14 Jul 2018 01:09:22 +0900 Subject: [PATCH 21/43] add SSL_set_SSL_CTX for SNI(Server Name Indication) (#8308) * add SSL_set_SSL_CTX for SNI * fix SSL_set_SSL_CTX --- lib/wrappers/openssl.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/wrappers/openssl.nim b/lib/wrappers/openssl.nim index de3bfa616a..47fff83976 100644 --- a/lib/wrappers/openssl.nim +++ b/lib/wrappers/openssl.nim @@ -327,6 +327,7 @@ proc ERR_load_BIO_strings*(){.cdecl, dynlib: DLLUtilName, importc.} proc SSL_new*(context: SslCtx): SslPtr{.cdecl, dynlib: DLLSSLName, importc.} proc SSL_free*(ssl: SslPtr){.cdecl, dynlib: DLLSSLName, importc.} proc SSL_get_SSL_CTX*(ssl: SslPtr): SslCtx {.cdecl, dynlib: DLLSSLName, importc.} +proc SSL_set_SSL_CTX*(ssl: SslPtr, ctx: SslCtx): SslCtx {.cdecl, dynlib: DLLSSLName, importc.} proc SSL_CTX_new*(meth: PSSL_METHOD): SslCtx{.cdecl, dynlib: DLLSSLName, importc.} proc SSL_CTX_load_verify_locations*(ctx: SslCtx, CAfile: cstring, From 9b98add6c714474e261df1a0a17819282dfd1975 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Sat, 14 Jul 2018 01:44:40 +0200 Subject: [PATCH 22/43] Do not crash while instantiating a generic outside a call (#8279) Fixes #8270 --- compiler/sigmatch.nim | 5 ++++- tests/generics/t8270.nim | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 tests/generics/t8270.nim diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 4bac3d3ea2..7e9143d94e 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -569,7 +569,10 @@ proc procParamTypeRel(c: var TCandidate, f, a: PType): TTypeRelation = # signature. There is a change that the target # type is already fully-determined, so we are # going to try resolve it - f = generateTypeInstance(c.c, c.bindings, c.call.info, f) + if c.call != nil: + f = generateTypeInstance(c.c, c.bindings, c.call.info, f) + else: + f = nil if f == nil or f.isMetaType: # no luck resolving the type, so the inference fails return isBothMetaConvertible diff --git a/tests/generics/t8270.nim b/tests/generics/t8270.nim new file mode 100644 index 0000000000..707e981fab --- /dev/null +++ b/tests/generics/t8270.nim @@ -0,0 +1,7 @@ +discard """ + line: 6 + errormsg: "cannot instantiate: \'T\'" +""" + +proc m[T](x: T): int = discard +echo [m] From 332469692d5053c6c935163e196b117b6979c8e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Nihlg=C3=A5rd?= Date: Sat, 14 Jul 2018 16:00:52 +0200 Subject: [PATCH 23/43] Bugfix for modules with multi byte characters in the name (#8319) --- compiler/ccgtypes.nim | 2 +- tests/misc/åäö.nim | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index 66ad34c324..a16255f6e1 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -48,7 +48,7 @@ proc mangleName(m: BModule; s: PSym): Rope = result = s.loc.r if result == nil: result = s.name.s.mangle.rope - add(result, idOrSig(s, m.module.name.s, m.sigConflicts)) + add(result, idOrSig(s, m.module.name.s.mangle, m.sigConflicts)) s.loc.r = result writeMangledName(m.ndi, s, m.config) diff --git a/tests/misc/åäö.nim b/tests/misc/åäö.nim index 69bb3e22cd..b3caa9861e 100644 --- a/tests/misc/åäö.nim +++ b/tests/misc/åäö.nim @@ -5,4 +5,7 @@ discard """ # Tests that module names can contain multi byte characters let a = 1 -doAssert åäö.a == 1 \ No newline at end of file +doAssert åäö.a == 1 + +proc inlined() {.inline.} = discard +inlined() \ No newline at end of file From ab840f445e01fc7e9e01f4b29d390f1227513e22 Mon Sep 17 00:00:00 2001 From: Joey Date: Sun, 15 Jul 2018 10:54:50 +0900 Subject: [PATCH 24/43] Remove cross-platform breaking env var in Nintendo Switch config This was bad to put in here, so my bad. It doesn't work on anything except platforms with shell support. Users can use `--passC` and `--passL` options for customization. --- config/nim.cfg | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/config/nim.cfg b/config/nim.cfg index 626f4494a1..c30190a181 100644 --- a/config/nim.cfg +++ b/config/nim.cfg @@ -111,10 +111,10 @@ path="$lib/pure" @if nintendoswitch: cc = "switch_gcc" - switch_gcc.options.linker = "-g -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE $SWITCH_LIBS" - switch_gcc.cpp.options.linker = "-g -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE $SWITCH_LIBS" - switch_gcc.options.always = "-g -Wall -O2 -ffunction-sections -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE $SWITCH_INCLUDES -D__SWITCH__" - switch_gcc.cpp.options.always = "-g -Wall -O2 -ffunction-sections -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE $SWITCH_INCLUDES -D__SWITCH__ -fno-rtti -fno-exceptions -std=gnu++11" + switch_gcc.options.linker = "-g -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE" + switch_gcc.cpp.options.linker = "-g -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE" + switch_gcc.options.always = "-g -Wall -O2 -ffunction-sections -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE -D__SWITCH__" + switch_gcc.cpp.options.always = "-g -Wall -O2 -ffunction-sections -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE -D__SWITCH__ -fno-rtti -fno-exceptions -std=gnu++11" @end # Configuration for the Intel C/C++ compiler: From d3c2fb22dbe2831d2b85aa92fd5c86f0eb961c54 Mon Sep 17 00:00:00 2001 From: Joey Yakimowich-Payne Date: Sun, 15 Jul 2018 11:23:22 +0900 Subject: [PATCH 25/43] Remove env vars from docs --- doc/nimc.rst | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/doc/nimc.rst b/doc/nimc.rst index a3adcc143d..0939f67e86 100644 --- a/doc/nimc.rst +++ b/doc/nimc.rst @@ -232,12 +232,12 @@ Cross compilation for Nintendo Switch ===================================== Simply add --os:nintendoswitch -to your usual ``nim c`` or ``nim cpp`` command and set the ``SWITCH_LIBS`` -and ``SWITCH_INCLUDES`` environment variables to something like: +to your usual ``nim c`` or ``nim cpp`` command and set the ``passC`` +and ``passL`` command line switches to something like: .. code-block:: console - export SWITCH_INCLUDES="-I$DEVKITPRO/libnx/include" - export SWITCH_LIBS="-specs=$DEVKITPRO/libnx/switch.specs -L$DEVKITPRO/libnx/lib -lnx" + nim c ... --passC="-I$DEVKITPRO/libnx/include" ... + --passL="-specs=$DEVKITPRO/libnx/switch.specs -L$DEVKITPRO/libnx/lib -lnx" or setup a nim.cfg file like so: @@ -259,10 +259,6 @@ an nro file with the ``elf2nro`` tool in the DevkitPro release. Examples can be `the nim-libnx github repo `_ or you can use `the switch builder tool `_. -Environment variables are: - - ``SWITCH_LIBS`` for any extra libraries required by your application (``-lLIBNAME`` or ``-LLIBPATH``) - - ``SWITCH_INCLUDES`` for any extra include files (``-IINCLUDE_PATH``) - There are a few things that don't work because the DevkitPro libraries don't support them. They are: From d07489abf4a04273adfb0cf36860c64d1831221a Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Mon, 16 Jul 2018 04:34:44 -0700 Subject: [PATCH 26/43] fix #7405 and #8195 (#8198) * fix #7405 and #8195 * control pushInfoContext in semExprNoType,genStmts via a new hintExtendedContext; make NotesVerbosity computation more DRY * addressed comments --- compiler/ccgstmts.nim | 4 ++++ compiler/lineinfos.nim | 44 +++++++++++++++++------------------------- compiler/semexprs.nim | 3 +++ 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index 7bb929d2b5..54759edb33 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -1146,5 +1146,9 @@ proc genAsgn(p: BProc, e: PNode, fastAsgn: bool) = proc genStmts(p: BProc, t: PNode) = var a: TLoc + + let isPush = hintExtendedContext in p.config.notes + if isPush: pushInfoContext(p.config, t.info) expr(p, t, a) + if isPush: popInfoContext(p.config) internalAssert p.config, a.k in {locNone, locTemp, locLocalVar} diff --git a/compiler/lineinfos.nim b/compiler/lineinfos.nim index cad1fe6aa6..b8678e6ba7 100644 --- a/compiler/lineinfos.nim +++ b/compiler/lineinfos.nim @@ -45,7 +45,8 @@ type hintExecuting, hintLinking, hintDependency, hintSource, hintPerformance, hintStackTrace, hintGCStats, hintGlobalVar, - hintUser, hintUserRaw + hintUser, hintUserRaw, + hintExtendedContext const MsgKindToStr*: array[TMsgKind, string] = [ @@ -116,7 +117,9 @@ const hintGCStats: "$1", hintGlobalVar: "global variable declared here", hintUser: "$1", - hintUserRaw: "$1"] + hintUserRaw: "$1", + hintExtendedContext: "$1", + ] const WarningsToStr* = ["CannotOpenFile", "OctalEscape", @@ -132,12 +135,14 @@ const "GcMem", "Destructor", "LockLevel", "ResultShadowed", "Spacing", "User"] - HintsToStr* = ["Success", "SuccessX", "LineTooLong", + HintsToStr* = [ + "Success", "SuccessX", "LineTooLong", "XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded", "ExprAlwaysX", "QuitCalled", "Processing", "CodeBegin", "CodeEnd", "Conf", "Path", "CondTrue", "Name", "Pattern", "Exec", "Link", "Dependency", "Source", "Performance", "StackTrace", "GCStats", "GlobalVar", - "User", "UserRaw"] + "User", "UserRaw", "ExtendedContext", + ] const fatalMin* = errUnknown @@ -157,30 +162,17 @@ type TNoteKind* = range[warnMin..hintMax] # "notes" are warnings or hints TNoteKinds* = set[TNoteKind] -const - NotesVerbosity*: array[0..3, TNoteKinds] = [ - {low(TNoteKind)..high(TNoteKind)} - {warnShadowIdent, warnUninit, - warnProveField, warnProveIndex, - warnGcUnsafe, - hintSuccessX, hintPath, hintConf, - hintProcessing, hintPattern, - hintDependency, - hintExecuting, hintLinking, - hintCodeBegin, hintCodeEnd, - hintSource, hintStackTrace, - hintGlobalVar, hintGCStats}, - {low(TNoteKind)..high(TNoteKind)} - {warnShadowIdent, warnUninit, - warnProveField, warnProveIndex, - warnGcUnsafe, - hintPath, - hintDependency, - hintCodeBegin, hintCodeEnd, - hintSource, hintStackTrace, - hintGlobalVar, hintGCStats}, - {low(TNoteKind)..high(TNoteKind)} - {hintStackTrace, warnUninit}, - {low(TNoteKind)..high(TNoteKind)}] +proc computeNotesVerbosity(): array[0..3, TNoteKinds] = + result[3] = {low(TNoteKind)..high(TNoteKind)} - {} + result[2] = result[3] - {hintStackTrace, warnUninit, hintExtendedContext} + result[1] = result[2] - {warnShadowIdent, warnProveField, warnProveIndex, + warnGcUnsafe, hintPath, hintDependency, hintCodeBegin, hintCodeEnd, + hintSource, hintGlobalVar, hintGCStats} + result[0] = result[1] - {hintSuccessX, hintConf, hintProcessing, + hintPattern, hintExecuting, hintLinking} const + NotesVerbosity* = computeNotesVerbosity() errXMustBeCompileTime* = "'$1' can only be used in compile-time context" errArgsNeedRunOption* = "arguments can only be given if the '--run' option is selected" diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 55cf05094c..3a72d1f5ab 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -907,8 +907,11 @@ proc buildEchoStmt(c: PContext, n: PNode): PNode = result = semExpr(c, result) proc semExprNoType(c: PContext, n: PNode): PNode = + let isPush = hintExtendedContext in c.config.notes + if isPush: pushInfoContext(c.config, n.info) result = semExpr(c, n, {efWantStmt}) discardCheck(c, result) + if isPush: popInfoContext(c.config) proc isTypeExpr(n: PNode): bool = case n.kind From 217a2cf0982302b9d89d2c06fc96eaf72f0518fe Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Mon, 16 Jul 2018 16:19:31 +0200 Subject: [PATCH 27/43] Resolve converter call to constants in case arms (#8336) Fixes #8333 --- compiler/semtypes.nim | 5 ++++- tests/casestmt/t8333.nim | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 tests/casestmt/t8333.nim diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 17420111f1..25d8674b86 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -557,7 +557,10 @@ proc semCaseBranch(c: PContext, t, branch: PNode, branchIndex: int, return elif r.kind notin {nkCurly, nkBracket} or len(r) == 0: checkMinSonsLen(t, 1, c.config) - branch.sons[i] = skipConv(fitNode(c, t.sons[0].typ, r, r.info)) + var tmp = fitNode(c, t.sons[0].typ, r, r.info) + # the call to fitNode may introduce a call to a converter + if tmp.kind in {nkHiddenCallConv}: tmp = semConstExpr(c, tmp) + branch.sons[i] = skipConv(tmp) inc(covered) else: if r.kind == nkCurly: diff --git a/tests/casestmt/t8333.nim b/tests/casestmt/t8333.nim new file mode 100644 index 0000000000..ca35233581 --- /dev/null +++ b/tests/casestmt/t8333.nim @@ -0,0 +1,10 @@ +discard """ + output: "1" +""" + +converter toInt*(x: char): int = + x.int + +case 0 +of 'a': echo 0 +else: echo 1 From 97d37aeb0bb1ed2997eddb3b7f8ef53cd04f10ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20D=C3=B6ring?= Date: Mon, 16 Jul 2018 19:30:05 +0200 Subject: [PATCH 28/43] Gdb pretty printers (#8263) --- bin/nim-gdb | 18 + compiler/astalgo.nim | 6 +- .../untestable/gdb/gdb_pretty_printer_test.py | 33 ++ .../gdb/gdb_pretty_printer_test_output.txt | 3 + .../gdb/gdb_pretty_printer_test_program.nim | 53 ++ .../gdb/gdb_pretty_printer_test_run.sh | 15 + tools/nim-gdb.py | 513 ++++++++++++++++++ 7 files changed, 638 insertions(+), 3 deletions(-) create mode 100755 bin/nim-gdb create mode 100644 tests/untestable/gdb/gdb_pretty_printer_test.py create mode 100644 tests/untestable/gdb/gdb_pretty_printer_test_output.txt create mode 100644 tests/untestable/gdb/gdb_pretty_printer_test_program.nim create mode 100755 tests/untestable/gdb/gdb_pretty_printer_test_run.sh create mode 100644 tools/nim-gdb.py diff --git a/bin/nim-gdb b/bin/nim-gdb new file mode 100755 index 0000000000..e7b41094df --- /dev/null +++ b/bin/nim-gdb @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +# Exit if anything fails +set -e + +# Find out where the pretty printer Python module is +NIM_SYSROOT=$(dirname $(dirname $(readlink -e $(which nim)))) +GDB_PYTHON_MODULE_PATH="$NIM_SYSROOT/tools/nim-gdb.py" + +# Run GDB with the additional arguments that load the pretty printers +# Set the environment variable `NIM_GDB` to overwrite the call to a +# different/specific command (defaults to `gdb`). +NIM_GDB="${NIM_GDB:-gdb}" +# exec replaces the new process of bash with gdb. It is always good to +# have fewer processes. +exec ${NIM_GDB} \ + -eval-command "source $GDB_PYTHON_MODULE_PATH" \ + "$@" diff --git a/compiler/astalgo.nim b/compiler/astalgo.nim index f474ca65e1..333376f6a0 100644 --- a/compiler/astalgo.nim +++ b/compiler/astalgo.nim @@ -27,9 +27,9 @@ proc lineInfoToStr*(conf: ConfigRef; info: TLineInfo): Rope when declared(echo): # these are for debugging only: They are not really deprecated, but I want # the warning so that release versions do not contain debugging statements: - proc debug*(n: PSym; conf: ConfigRef = nil) {.deprecated.} - proc debug*(n: PType; conf: ConfigRef = nil) {.deprecated.} - proc debug*(n: PNode; conf: ConfigRef = nil) {.deprecated.} + proc debug*(n: PSym; conf: ConfigRef = nil) {.exportc: "debugSym", deprecated.} + proc debug*(n: PType; conf: ConfigRef = nil) {.exportc: "debugType", deprecated.} + proc debug*(n: PNode; conf: ConfigRef = nil) {.exportc: "debugNode", deprecated.} template debug*(x: PSym|PType|PNode) {.deprecated.} = when compiles(c.config): diff --git a/tests/untestable/gdb/gdb_pretty_printer_test.py b/tests/untestable/gdb/gdb_pretty_printer_test.py new file mode 100644 index 0000000000..54af65d9a4 --- /dev/null +++ b/tests/untestable/gdb/gdb_pretty_printer_test.py @@ -0,0 +1,33 @@ +import gdb +# this test should test the gdb pretty printers of the nim +# library. But be aware this test is not complete. It only tests the +# command line version of gdb. It does not test anything for the +# machine interface of gdb. This means if if this test passes gdb +# frontends might still be broken. + +gdb.execute("source ../../../tools/nim-gdb.py") +# debug all instances of the generic function `myDebug`, should be 8 +gdb.execute("rbreak myDebug") +gdb.execute("run") + +outputs = [ + 'meTwo', + '"meTwo"', + '{meOne, meThree}', + 'MyOtherEnum(1)', + '5', + 'array = {1, 2, 3, 4, 5}', + 'seq(3, 3) = {"one", "two", "three"}', + 'Table(3, 64) = {["two"] = 2, ["three"] = 3, ["one"] = 1}', +] + +for i, expected in enumerate(outputs): + if i == 5: + # myArray is passed as pointer to int to myDebug. I look up myArray up in the stack + gdb.execute("up") + output = str(gdb.parse_and_eval("myArray")) + else: + output = str(gdb.parse_and_eval("arg")) + + assert output == expected, output + " != " + expected + gdb.execute("continue") diff --git a/tests/untestable/gdb/gdb_pretty_printer_test_output.txt b/tests/untestable/gdb/gdb_pretty_printer_test_output.txt new file mode 100644 index 0000000000..cbc9bde8d3 --- /dev/null +++ b/tests/untestable/gdb/gdb_pretty_printer_test_output.txt @@ -0,0 +1,3 @@ +Loading Nim Runtime support. +NimEnumPrinter: lookup global symbol 'NTI_z9cu80OJCfNgw9bUdzn5ZEzw_ failed for tyEnum_MyOtherEnum_z9cu80OJCfNgw9bUdzn5ZEzw. +8 diff --git a/tests/untestable/gdb/gdb_pretty_printer_test_program.nim b/tests/untestable/gdb/gdb_pretty_printer_test_program.nim new file mode 100644 index 0000000000..458435c1ac --- /dev/null +++ b/tests/untestable/gdb/gdb_pretty_printer_test_program.nim @@ -0,0 +1,53 @@ + + +import tables + +type + MyEnum = enum + meOne, + meTwo, + meThree, + meFour, + + MyOtherEnum = enum + moOne, + moTwo, + moThree, + moFoure, + + +var counter = 0 + +proc myDebug[T](arg: T): void = + counter += 1 + +proc testProc(): void = + var myEnum = meTwo + myDebug(myEnum) + # create a string object but also make the NTI for MyEnum is generated + var myString = $myEnum + myDebug(myString) + var mySet = {meOne,meThree} + myDebug(mySet) + + # for MyOtherEnum there is no NTI. This tests the fallback for the pretty printer. + var moEnum = moTwo + myDebug(moEnum) + var moSet = {moOne,moThree} + myDebug(moSet) + + let myArray = [1,2,3,4,5] + myDebug(myArray) + let mySeq = @["one","two","three"] + myDebug(mySeq) + + var myTable = initTable[string, int]() + myTable["one"] = 1 + myTable["two"] = 2 + myTable["three"] = 3 + myDebug(myTable) + + echo(counter) + + +testProc() diff --git a/tests/untestable/gdb/gdb_pretty_printer_test_run.sh b/tests/untestable/gdb/gdb_pretty_printer_test_run.sh new file mode 100755 index 0000000000..525f547059 --- /dev/null +++ b/tests/untestable/gdb/gdb_pretty_printer_test_run.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# Exit if anything fails +set -e +#!/usr/bin/env bash +# Compile the test project with fresh debug information. +nim c --debugger:native gdb_pretty_printer_test_program.nim &> /dev/null +# 2>&1 redirects stderr to stdout (all output in stdout) +# <(...) is a bash feature that makes the output of a command into a +# file handle. +# diff compares the two files, the expected output, and the file +# handle that is created by the execution of gdb. +diff ./gdb_pretty_printer_test_output.txt <(gdb -x gdb_pretty_printer_test.py --batch-silent --args gdb_pretty_printer_test_program 2>&1) +# The exit code of diff is forwarded as the exit code of this +# script. So when the comparison fails, the exit code of this script +# won't be 0. So this script should be embeddable in a test suite. diff --git a/tools/nim-gdb.py b/tools/nim-gdb.py new file mode 100644 index 0000000000..b98dc96fea --- /dev/null +++ b/tools/nim-gdb.py @@ -0,0 +1,513 @@ + +import gdb +import re +import sys + +# some feedback that the nim runtime support is loading, isn't a bad +# thing at all. +gdb.write("Loading Nim Runtime support.\n", gdb.STDERR) + +# When error occure they occur regularly. This 'caches' known errors +# and prevents them from being reprinted over and over again. +errorSet = set() +def printErrorOnce(id, message): + global errorSet + if id not in errorSet: + errorSet.add(id) + gdb.write(message, gdb.STDERR) + +nimobjfile = gdb.current_objfile() or gdb.objfiles()[0] +nimobjfile.type_printers = [] + +################################################################################ +##### Type pretty printers +################################################################################ + +type_hash_regex = re.compile("^\w*_([A-Za-z0-9]*)$") + +def getNimRti(type_name): + """ Return a ``gdb.Value`` object for the Nim Runtime Information of ``type_name``. """ + + # Get static const TNimType variable. This should be available for + # every non trivial Nim type. + m = type_hash_regex.match(type_name) + if m: + try: + return gdb.parse_and_eval("NTI_" + m.group(1) + "_") + except: + return None + +class NimTypeRecognizer: + # this type map maps from types that are generated in the C files to + # how they are called in nim. To not mix up the name ``int`` from + # system.nim with the name ``int`` that could still appear in + # generated code, ``NI`` is mapped to ``system.int`` and not just + # ``int``. + + type_map_static = { + 'NI': 'system.int', 'NI8': 'int8', 'NI16': 'int16', 'NI32': 'int32', 'NI64': 'int64', + 'NU': 'uint', 'NU8': 'uint8','NU16': 'uint16', 'NU32': 'uint32', 'NU64': 'uint64', + 'NF': 'float', 'NF32': 'float32', 'NF64': 'float64', + 'NIM_BOOL': 'bool', 'NIM_CHAR': 'char', 'NCSTRING': 'cstring', + 'NimStringDesc': 'string' + } + + # Normally gdb distinguishes between the command `ptype` and + # `whatis`. `ptype` prints a very detailed view of the type, and + # `whatis` a very brief representation of the type. I haven't + # figured out a way to know from the type printer that is + # implemented here how to know if a type printer should print the + # short representation or the long representation. As a hacky + # workaround I just say I am not resposible for printing pointer + # types (seq and string are exception as they are semantically + # values). this way the default type printer will handle pointer + # types and dive into the members of that type. So I can still + # control with `ptype myval` and `ptype *myval` if I want to have + # detail or not. I this this method stinks but I could not figure + # out a better solution. + + object_type_pattern = re.compile("^(\w*):ObjectType$") + + def recognize(self, type_obj): + + tname = None + if type_obj.tag is not None: + tname = type_obj.tag + elif type_obj.name is not None: + tname = type_obj.name + + # handle pointer types + if not tname: + if type_obj.code == gdb.TYPE_CODE_PTR: + target_type = type_obj.target() + target_type_name = target_type.name + if target_type_name: + # visualize 'string' as non pointer type (unpack pointer type). + if target_type_name == "NimStringDesc": + tname = target_type_name # could also just return 'string' + # visualize 'seq[T]' as non pointer type. + if target_type_name.find('tySequence_') == 0: + tname = target_type_name + + if not tname: + # We are not resposible for this type printing. + # Basically this means we don't print pointer types. + return None + + result = self.type_map_static.get(tname, None) + if result: + return result + + rti = getNimRti(tname) + if rti: + return rti['name'].string("utf-8", "ignore") + else: + return None + +class NimTypePrinter: + """Nim type printer. One printer for all Nim types.""" + + + # enabling and disabling of type printers can be done with the + # following gdb commands: + # + # enable type-printer NimTypePrinter + # disable type-printer NimTypePrinter + + name = "NimTypePrinter" + def __init__ (self): + self.enabled = True + + def instantiate(self): + return NimTypeRecognizer() + + +nimobjfile.type_printers = [NimTypePrinter()] + +################################################################################ +##### GDB Function, equivalent of Nim's $ operator +################################################################################ + +class DollarPrintFunction (gdb.Function): + "Nim's equivalent of $ operator as a gdb function, available in expressions `print $dollar(myvalue)" + + _gdb_dollar_functions = gdb.execute("info functions dollar__", True, True) + dollar_functions = re.findall('NimStringDesc \*(dollar__[A-z0-9_]+?)\(([^,)]*)\);', _gdb_dollar_functions) + + def __init__ (self): + super (DollarPrintFunction, self).__init__("dollar") + + @staticmethod + def invoke_static(arg): + + for func, arg_typ in DollarPrintFunction.dollar_functions: + + if arg.type.name == arg_typ: + func_value = gdb.lookup_global_symbol(func, gdb.SYMBOL_FUNCTIONS_DOMAIN).value() + return func_value(arg) + + if arg.type.name + " *" == arg_typ: + func_value = gdb.lookup_global_symbol(func, gdb.SYMBOL_FUNCTIONS_DOMAIN).value() + return func_value(arg.address) + + typeName = arg.type.name + printErrorOnce(typeName, "No suitable Nim $ operator found for type: " + typeName + ".\n") + + def invoke(self, arg): + return self.invoke_static(arg) + +DollarPrintFunction() + +################################################################################ +##### GDB Command, equivalent of Nim's $ operator +################################################################################ + +class DollarPrintCmd (gdb.Command): + """Dollar print command for Nim, `$ expr` will invoke Nim's $ operator""" + + def __init__ (self): + super (DollarPrintCmd, self).__init__ ("$", gdb.COMMAND_DATA, gdb.COMPLETE_EXPRESSION) + + def invoke (self, arg, from_tty): + param = gdb.parse_and_eval(arg) + gdb.write(str(DollarPrintFunction.invoke_static(param)) + "\n", gdb.STDOUT) + +DollarPrintCmd() + +################################################################################ +##### Value pretty printers +################################################################################ + +class NimBoolPrinter: + + pattern = re.compile(r'^NIM_BOOL$') + + def __init__(self, val): + self.val = val + + def to_string(self): + if self.val == 0: + return "false" + else: + return "true" + +################################################################################ + +class NimStringPrinter: + pattern = re.compile(r'^NimStringDesc \*$') + + def __init__(self, val): + self.val = val + + def display_hint(self): + return 'string' + + def to_string(self): + if self.val: + l = int(self.val['Sup']['len']) + return self.val['data'][0].address.string("utf-8", "ignore", l) + else: + return "" + +################################################################################ + +# proc reprEnum(e: int, typ: PNimType): string {.compilerRtl.} = +# ## Return string representation for enumeration values +# var n = typ.node +# if ntfEnumHole notin typ.flags: +# let o = e - n.sons[0].offset +# if o >= 0 and o <% typ.node.len: +# return $n.sons[o].name +# else: +# # ugh we need a slow linear search: +# var s = n.sons +# for i in 0 .. n.len-1: +# if s[i].offset == e: +# return $s[i].name +# result = $e & " (invalid data!)" + +def reprEnum(e, typ): + """ this is a port of the nim runtime function `reprEnum` to python """ + e = int(e) + n = typ["node"] + flags = int(typ["flags"]) + # 1 << 2 is {ntfEnumHole} + if ((1 << 2) & flags) == 0: + o = e - int(n["sons"][0]["offset"]) + if o >= 0 and 0 < int(n["len"]): + return n["sons"][o]["name"].string("utf-8", "ignore") + else: + # ugh we need a slow linear search: + s = n["sons"] + for i in range(0, int(n["len"])): + if int(s[i]["offset"]) == e: + return s[i]["name"].string("utf-8", "ignore") + + return str(e) + " (invalid data!)" + +class NimEnumPrinter: + pattern = re.compile(r'^tyEnum_(\w*)_([A-Za-z0-9]*)$') + + def __init__(self, val): + self.val = val + match = self.pattern.match(self.val.type.name) + self.typeNimName = match.group(1) + typeInfoName = "NTI_" + match.group(2) + "_" + self.nti = gdb.lookup_global_symbol(typeInfoName) + + if self.nti is None: + printErrorOnce(typeInfoName, "NimEnumPrinter: lookup global symbol '" + typeInfoName + " failed for " + self.val.type.name + ".\n") + + def to_string(self): + if self.nti: + arg0 = self.val + arg1 = self.nti.value(gdb.newest_frame()) + return reprEnum(arg0, arg1) + else: + return self.typeNimName + "(" + str(int(self.val)) + ")" + +################################################################################ + +class NimSetPrinter: + ## the set printer is limited to sets that fit in an integer. Other + ## sets are compiled to `NU8 *` (ptr uint8) and are invisible to + ## gdb (currently). + pattern = re.compile(r'^tySet_tyEnum_(\w*)_([A-Za-z0-9]*)$') + + def __init__(self, val): + self.val = val + match = self.pattern.match(self.val.type.name) + self.typeNimName = match.group(1) + + typeInfoName = "NTI_" + match.group(2) + "_" + self.nti = gdb.lookup_global_symbol(typeInfoName) + + if self.nti is None: + printErrorOnce(typeInfoName, "NimSetPrinter: lookup global symbol '"+ typeInfoName +" failed for " + self.val.type.name + ".\n") + + def to_string(self): + if self.nti: + nti = self.nti.value(gdb.newest_frame()) + enumStrings = [] + val = int(self.val) + i = 0 + while val > 0: + if (val & 1) == 1: + enumStrings.append(reprEnum(i, nti)) + val = val >> 1 + i += 1 + + return '{' + ', '.join(enumStrings) + '}' + else: + return str(int(self.val)) + +################################################################################ + +class NimHashSetPrinter: + pattern = re.compile(r'^tyObject_(HashSet)_([A-Za-z0-9]*)$') + + def __init__(self, val): + self.val = val + + def display_hint(self): + return 'array' + + def to_string(self): + counter = 0 + capacity = 0 + if self.val: + counter = int(self.val['counter']) + if self.val['data']: + capacity = int(self.val['data']['Sup']['len']) + + return 'HashSet({0}, {1})'.format(counter, capacity) + + def children(self): + if self.val: + data = NimSeqPrinter(self.val['data']) + for idxStr, entry in data.children(): + if int(entry['Field0']) > 0: + yield ("data." + idxStr + ".Field1", str(entry['Field1'])) + +################################################################################ + +class NimSeqPrinter: + # the pointer is explicity part of the type. So it is part of + # ``pattern``. + pattern = re.compile(r'^tySequence_\w* \*$') + + def __init__(self, val): + self.val = val + + def display_hint(self): + return 'array' + + def to_string(self): + len = 0 + cap = 0 + if self.val: + len = int(self.val['Sup']['len']) + cap = int(self.val['Sup']['reserved']) + + return 'seq({0}, {1})'.format(len, cap) + + def children(self): + if self.val: + length = int(self.val['Sup']['len']) + #align = len(str(length - 1)) + for i in range(length): + yield ("data[{0}]".format(i), self.val["data"][i]) + +################################################################################ + +class NimArrayPrinter: + pattern = re.compile(r'^tyArray_\w*$') + + def __init__(self, val): + self.val = val + + def display_hint(self): + return 'array' + + def to_string(self): + return 'array' + + def children(self): + length = self.val.type.sizeof // self.val[0].type.sizeof + align = len(str(length-1)) + for i in range(length): + yield ("[{0:>{1}}]".format(i, align), self.val[i]) + +################################################################################ + +class NimStringTablePrinter: + pattern = re.compile(r'^tyObject_(StringTableObj)_([A-Za-z0-9]*)(:? \*)?$') + + def __init__(self, val): + self.val = val + + def display_hint(self): + return 'map' + + def to_string(self): + counter = 0 + capacity = 0 + if self.val: + counter = int(self.val['counter']) + if self.val['data']: + capacity = int(self.val['data']['Sup']['len']) + + return 'StringTableObj({0}, {1})'.format(counter, capacity) + + def children(self): + if self.val: + data = NimSeqPrinter(self.val['data']) + for idxStr, entry in data.children(): + if int(entry['Field2']) > 0: + yield (idxStr + ".Field0", entry['Field0']) + yield (idxStr + ".Field1", entry['Field1']) + +################################################################ + +class NimTablePrinter: + pattern = re.compile(r'^tyObject_(Table)_([A-Za-z0-9]*)(:? \*)?$') + + def __init__(self, val): + self.val = val + # match = self.pattern.match(self.val.type.name) + + def display_hint(self): + return 'map' + + def to_string(self): + counter = 0 + capacity = 0 + if self.val: + counter = int(self.val['counter']) + if self.val['data']: + capacity = int(self.val['data']['Sup']['len']) + + return 'Table({0}, {1})'.format(counter, capacity) + + def children(self): + if self.val: + data = NimSeqPrinter(self.val['data']) + for idxStr, entry in data.children(): + if int(entry['Field0']) > 0: + yield (idxStr + '.Field1', entry['Field1']) + yield (idxStr + '.Field2', entry['Field2']) + + +################################################################ + +# this is untested, therefore disabled + +# class NimObjectPrinter: +# pattern = re.compile(r'^tyObject_.*$') + +# def __init__(self, val): +# self.val = val + +# def display_hint(self): +# return 'object' + +# def to_string(self): +# return str(self.val.type) + +# def children(self): +# if not self.val: +# yield "object", "" +# raise StopIteration + +# for (i, field) in enumerate(self.val.type.fields()): +# if field.type.code == gdb.TYPE_CODE_UNION: +# yield _union_field +# else: +# yield (field.name, self.val[field]) + +# def _union_field(self, i, field): +# rti = getNimRti(self.val.type.name) +# if rti is None: +# return (field.name, "UNION field can't be displayed without RTI") + +# node_sons = rti['node'].dereference()['sons'] +# prev_field = self.val.type.fields()[i - 1] + +# descriminant_node = None +# for i in range(int(node['len'])): +# son = node_sons[i].dereference() +# if son['name'].string("utf-8", "ignore") == str(prev_field.name): +# descriminant_node = son +# break +# if descriminant_node is None: +# raise ValueError("Can't find union descriminant field in object RTI") + +# if descriminant_node is None: raise ValueError("Can't find union field in object RTI") +# union_node = descriminant_node['sons'][int(self.val[prev_field])].dereference() +# union_val = self.val[field] + +# for f1 in union_val.type.fields(): +# for f2 in union_val[f1].type.fields(): +# if str(f2.name) == union_node['name'].string("utf-8", "ignore"): +# return (str(f2.name), union_val[f1][f2]) + +# raise ValueError("RTI is absent or incomplete, can't find union definition in RTI") + + +################################################################################ + +def makematcher(klass): + def matcher(val): + typeName = str(val.type) + try: + if hasattr(klass, 'pattern') and hasattr(klass, '__name__'): + # print(typeName + " <> " + klass.__name__) + if klass.pattern.match(typeName): + return klass(val) + except Exception as e: + print(klass) + printErrorOnce(typeName, "No matcher for type '" + typeName + "': " + str(e) + "\n") + return matcher + +nimobjfile.pretty_printers = [] +nimobjfile.pretty_printers.extend([makematcher(var) for var in list(vars().values()) if hasattr(var, 'pattern')]) From 2ac22b4cf07bc7dd3a4b3f79c35be70973c815e8 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Mon, 16 Jul 2018 12:15:17 -0700 Subject: [PATCH 29/43] fix #8273 times format regression, and fix inconsistent ordering in 1 format overload (#8290) * Fix issue #8273 [regression] [times.format] Error: attempting to call undeclared routine: 'format' * rename format to f for consistency with other overloads and avoid similar bugs as #8273 * breaking change since PR 8094: changed format*(f: TimeFormat, dt: DateTime) to format*(dt: DateTime, f: TimeFormat) for consistency w other overloads * use consistent ordering for times.parse procs --- lib/pure/times.nim | 58 +++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/lib/pure/times.nim b/lib/pure/times.nim index 05398cfad3..bc4de7ee43 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -1646,7 +1646,7 @@ proc initTimeFormat*(format: string): TimeFormat = ## ``format`` argument. runnableExamples: let f = initTimeFormat("yyyy-MM-dd") - doAssert "2000-01-01" == f.format(f.parse("2000-01-01")) + doAssert "2000-01-01" == "2000-01-01".parse(f).format(f) result.formatStr = format result.patterns = @[] for kind, token in format.tokens: @@ -2068,12 +2068,12 @@ proc toDateTime(p: ParsedTime, zone: Timezone, f: TimeFormat, result.utcOffset = p.utcOffset.get() result = result.toTime.inZone(zone) -proc format*(f: TimeFormat, dt: DateTime): string {.raises: [].} = +proc format*(dt: DateTime, f: TimeFormat): string {.raises: [].} = ## Format ``dt`` using the format specified by ``f``. runnableExamples: let f = initTimeFormat("yyyy-MM-dd") let dt = initDateTime(01, mJan, 2000, 00, 00, 00, utc()) - doAssert "2000-01-01" == f.format(dt) + doAssert "2000-01-01" == dt.format(f) var idx = 0 while idx <= f.patterns.high: case f.patterns[idx].FormatPattern @@ -2097,32 +2097,32 @@ proc format*(dt: DateTime, f: string): string = let dt = initDateTime(01, mJan, 2000, 00, 00, 00, utc()) doAssert "2000-01-01" == format(dt, "yyyy-MM-dd") let dtFormat = initTimeFormat(f) - result = dtFormat.format(dt) + result = dt.format(dtFormat) -proc format*(dt: DateTime, format: static[string]): string {.raises: [].} = +proc format*(dt: DateTime, f: static[string]): string {.raises: [].} = ## Overload that validates ``format`` at compile time. - const f = initTimeFormat(format) - result = f.format(dt) + const f2 = initTimeFormat(f) + result = dt.format(f2) -proc format*(time: Time, format: string, zone: Timezone = local()): string {.tags: [].} = +proc format*(time: Time, f: string, zone: Timezone = local()): string {.tags: [].} = ## Shorthand for constructing a ``TimeFormat`` and using it to format ## ``time``. Will use the timezone specified by ``zone``. ## ## See `Parsing and formatting dates`_ for documentation of the - ## ``format`` argument. + ## ``f`` argument. runnableExamples: var dt = initDateTime(01, mJan, 1970, 00, 00, 00, utc()) var tm = dt.toTime() doAssert format(tm, "yyyy-MM-dd'T'HH:mm:ss", utc()) == "1970-01-01T00:00:00" - time.inZone(zone).format(format) + time.inZone(zone).format(f) -proc format*(time: Time, format: static[string], +proc format*(time: Time, f: static[string], zone: Timezone = local()): string {.tags: [].} = - ## Overload that validates ``format`` at compile time. - const f = initTimeFormat(format) - result = f.format(time.inZone(zone)) + ## Overload that validates ``f`` at compile time. + const f2 = initTimeFormat(f) + result = time.inZone(zone).format(f2) -proc parse*(f: TimeFormat, input: string, zone: Timezone = local()): DateTime = +proc parse*(input: string, f: TimeFormat, zone: Timezone = local()): DateTime = ## Parses ``input`` as a ``DateTime`` using the format specified by ``f``. ## If no UTC offset was parsed, then ``input`` is assumed to be specified in ## the ``zone`` timezone. If a UTC offset was parsed, the result will be @@ -2130,7 +2130,7 @@ proc parse*(f: TimeFormat, input: string, zone: Timezone = local()): DateTime = runnableExamples: let f = initTimeFormat("yyyy-MM-dd") let dt = initDateTime(01, mJan, 2000, 00, 00, 00, utc()) - doAssert dt == f.parse("2000-01-01", utc()) + doAssert dt == "2000-01-01".parse(f, utc()) var inpIdx = 0 # Input index var patIdx = 0 # Pattern index var parsed: ParsedTime @@ -2162,24 +2162,24 @@ proc parse*(f: TimeFormat, input: string, zone: Timezone = local()): DateTime = result = toDateTime(parsed, zone, f, input) -proc parse*(input, format: string, tz: Timezone = local()): DateTime = +proc parse*(input, f: string, tz: Timezone = local()): DateTime = ## Shorthand for constructing a ``TimeFormat`` and using it to parse ## ``input`` as a ``DateTime``. ## ## See `Parsing and formatting dates`_ for documentation of the - ## ``format`` argument. + ## ``f`` argument. runnableExamples: let dt = initDateTime(01, mJan, 2000, 00, 00, 00, utc()) doAssert dt == parse("2000-01-01", "yyyy-MM-dd", utc()) - let dtFormat = initTimeFormat(format) - result = dtFormat.parse(input, tz) + let dtFormat = initTimeFormat(f) + result = input.parse(dtFormat, tz) -proc parse*(input: string, format: static[string], zone: Timezone = local()): DateTime = - ## Overload that validates ``format`` at compile time. - const f = initTimeFormat(format) - result = f.parse(input, zone) +proc parse*(input: string, f: static[string], zone: Timezone = local()): DateTime = + ## Overload that validates ``f`` at compile time. + const f2 = initTimeFormat(f) + result = input.parse(f2, zone) -proc parseTime*(input, format: string, zone: Timezone): Time = +proc parseTime*(input, f: string, zone: Timezone): Time = ## Shorthand for constructing a ``TimeFormat`` and using it to parse ## ``input`` as a ``DateTime``, then converting it a ``Time``. ## @@ -2188,12 +2188,12 @@ proc parseTime*(input, format: string, zone: Timezone): Time = runnableExamples: let tStr = "1970-01-01T00:00:00+00:00" doAssert parseTime(tStr, "yyyy-MM-dd'T'HH:mm:sszzz", utc()) == fromUnix(0) - parse(input, format, zone).toTime() + parse(input, f, zone).toTime() -proc parseTime*(input: string, format: static[string], zone: Timezone): Time = +proc parseTime*(input: string, f: static[string], zone: Timezone): Time = ## Overload that validates ``format`` at compile time. - const f = initTimeFormat(format) - result = f.parse(input, zone).toTime() + const f2 = initTimeFormat(f) + result = input.parse(f2, zone).toTime() # # End of parse & format implementation From f8723cd1e36ec939cc8e0e5bd4f181ff9ec5e99a Mon Sep 17 00:00:00 2001 From: Jeff Ciesielski Date: Mon, 16 Jul 2018 15:15:47 -0400 Subject: [PATCH 30/43] Add extended baud rates to posix/termios (#8322) 115200 is really common. I'd like to get support in nimserial, but I'm guessing it would help to have support in the stdlib first. --- lib/posix/termios.nim | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/posix/termios.nim b/lib/posix/termios.nim index 60d5401077..c08de73424 100644 --- a/lib/posix/termios.nim +++ b/lib/posix/termios.nim @@ -122,6 +122,21 @@ var B9600* {.importc, header: "".}: Speed B19200* {.importc, header: "".}: Speed B38400* {.importc, header: "".}: Speed + B57600* {.importc, header: "".}: Speed + B115200* {.importc, header: "".}: Speed + B230400* {.importc, header: "".}: Speed + B460800* {.importc, header: "".}: Speed + B500000* {.importc, header: "".}: Speed + B576000* {.importc, header: "".}: Speed + B921600* {.importc, header: "".}: Speed + B1000000* {.importc, header: "".}: Speed + B1152000* {.importc, header: "".}: Speed + B1500000* {.importc, header: "".}: Speed + B2000000* {.importc, header: "".}: Speed + B2500000* {.importc, header: "".}: Speed + B3000000* {.importc, header: "".}: Speed + B3500000* {.importc, header: "".}: Speed + B4000000* {.importc, header: "".}: Speed EXTA* {.importc, header: "".}: Speed EXTB* {.importc, header: "".}: Speed CSIZE* {.importc, header: "".}: Cflag From 692d1da95d82adc30e0ba650124330f1a098a697 Mon Sep 17 00:00:00 2001 From: John Novak Date: Tue, 17 Jul 2018 19:35:26 +1000 Subject: [PATCH 31/43] Fix setCursorPos and setCursorXPos on POSIX (#8310) --- changelog.md | 5 +++++ lib/pure/terminal.nim | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index 681b4b0a44..43b351ef11 100644 --- a/changelog.md +++ b/changelog.md @@ -54,6 +54,11 @@ - The procs ``parseHexInt`` and ``parseOctInt`` now fail on empty strings and strings containing only valid prefixes, e.g. "0x" for hex integers. +- ``terminal.setCursorPos`` and ``terminal.setCursorXPos`` now work correctly + with 0-based coordinates on POSIX (previously, you needed to use + 1-based coordinates on POSIX for correct behaviour; the Windows behaviour + was always correct). + #### Breaking changes in the compiler diff --git a/lib/pure/terminal.nim b/lib/pure/terminal.nim index 904274ade2..2e138b27ed 100644 --- a/lib/pure/terminal.nim +++ b/lib/pure/terminal.nim @@ -308,7 +308,7 @@ proc setCursorPos*(f: File, x, y: int) = let h = conHandle(f) setCursorPos(h, x, y) else: - f.write(fmt"{stylePrefix}{y};{x}f") + f.write(fmt"{stylePrefix}{y+1};{x+1}f") proc setCursorXPos*(f: File, x: int) = ## Sets the terminal's cursor to the x position. @@ -323,7 +323,7 @@ proc setCursorXPos*(f: File, x: int) = if setConsoleCursorPosition(h, origin) == 0: raiseOSError(osLastError()) else: - f.write(fmt"{stylePrefix}{x}G") + f.write(fmt"{stylePrefix}{x+1}G") when defined(windows): proc setCursorYPos*(f: File, y: int) = From f2b6efb7dcffd0a41543ee755329aa5748887b42 Mon Sep 17 00:00:00 2001 From: Quelklef Date: Tue, 17 Jul 2018 06:14:31 -0400 Subject: [PATCH 32/43] Make pragmas look clickable in docs (#8176) * Visual cues for hidden pragmas in docs. * Add hover cue --- compiler/docgen.nim | 14 +++++++--- config/nimdoc.cfg | 64 +++++++++++++++++++++++++-------------------- 2 files changed, 46 insertions(+), 32 deletions(-) diff --git a/compiler/docgen.nim b/compiler/docgen.nim index db4e301d4b..6e2111c754 100644 --- a/compiler/docgen.nim +++ b/compiler/docgen.nim @@ -16,7 +16,7 @@ import wordrecg, syntaxes, renderer, lexer, packages/docutils/rstast, packages/docutils/rst, packages/docutils/rstgen, times, packages/docutils/highlite, sempass2, json, xmltree, cgi, - typesrenderer, astalgo, modulepaths, lineinfos + typesrenderer, astalgo, modulepaths, lineinfos, sequtils type TSections = array[TSymKind, Rope] @@ -259,11 +259,19 @@ proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var Rope; renderFlags: TRe of tkSpaces, tkInvalid: add(result, literal) of tkCurlyDotLe: - dispA(d.conf, result, """$1
""", + dispA(d.conf, result, "" & # This span is required for the JS to work properly + """{...} + + +$1 +""".replace("\n", ""), # Must remove newlines because wrapped in a
                     "\\spanOther{$1}",
                   [rope(esc(d.target, literal))])
     of tkCurlyDotRi:
-      dispA(d.conf, result, "
$1", + dispA(d.conf, result, """ + +$1 +""".replace("\n", ""), "\\spanOther{$1}", [rope(esc(d.target, literal))]) of tkParLe, tkParRi, tkBracketLe, tkBracketRi, tkCurlyLe, tkCurlyRi, diff --git a/config/nimdoc.cfg b/config/nimdoc.cfg index 11d9987d8c..6b6ec2d833 100644 --- a/config/nimdoc.cfg +++ b/config/nimdoc.cfg @@ -1330,15 +1330,6 @@ dt pre > span.Operator ~ span.Identifier { background-repeat: no-repeat; background-image: url("data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AAAAAAUAAAAF////AP///wD///8A////AP///wD///8A////AP///wD///8A////AAAAAAIAAABbAAAAlQAAAKIAAACbAAAAmwAAAKIAAACVAAAAWwAAAAL///8A////AP///wD///8A////AAAAABQAAADAAAAAYwAAAA3///8A////AP///wD///8AAAAADQAAAGMAAADAAAAAFP///wD///8A////AP///wAAAACdAAAAOv///wD///8A////AP///wD///8A////AP///wD///8AAAAAOgAAAJ3///8A////AP///wAAAAAnAAAAcP///wAAAAAoAAAASv///wD///8A////AP///wAAAABKAAAAKP///wAAAABwAAAAJ////wD///8AAAAAgQAAABwAAACIAAAAkAAAAJMAAACtAAAAFQAAABUAAACtAAAAkwAAAJAAAACIAAAAHAAAAIH///8A////AAAAAKQAAACrAAAAaP///wD///8AAAAARQAAANIAAADSAAAARf///wD///8AAAAAaAAAAKsAAACk////AAAAADMAAACcAAAAnQAAABj///8A////AP///wAAAAAYAAAAGP///wD///8A////AAAAABgAAACdAAAAnAAAADMAAAB1AAAAwwAAAP8AAADpAAAAsQAAAE4AAAAb////AP///wAAAAAbAAAATgAAALEAAADpAAAA/wAAAMMAAAB1AAAAtwAAAOkAAAD/AAAA/wAAAP8AAADvAAAA3gAAAN4AAADeAAAA3gAAAO8AAAD/AAAA/wAAAP8AAADpAAAAtwAAAGUAAAA/AAAA3wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAADfAAAAPwAAAGX///8A////AAAAAEgAAADtAAAAvwAAAL0AAADGAAAA7wAAAO8AAADGAAAAvQAAAL8AAADtAAAASP///wD///8A////AP///wD///8AAAAAO////wD///8A////AAAAAIcAAACH////AP///wD///8AAAAAO////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A//8AAP//AAD4HwAA7/cAAN/7AAD//wAAoYUAAJ55AACf+QAAh+EAAAAAAADAAwAA4AcAAP5/AAD//wAA//8AAA=="); margin-bottom: -5px; } -div.pragma { - display: none; -} -span.pragmabegin { - cursor: pointer; -} -span.pragmaend { - cursor: pointer; -} div.search_results { background-color: antiquewhite; @@ -1351,32 +1342,47 @@ div#global-links ul { margin-left: 0; list-style-type: none; } + +span.pragmadots { + /* Position: relative frees us up to make the dots + look really nice without fucking up the layout and + causing bulging in the parent container */ + position: relative; + /* 1px down looks slightly nicer */ + top: 1px; + + padding: 2px; + background-color: #D3D3D3; + border-radius: 4px; + margin: 0 2px; + cursor: pointer; + + /* For some reason on Chrome, making the font size + smaller than 1em is causing the parent container to + bulge slightly. So, we're stuck with inheriting 1em, + which is sad, because 0.8em looks better... */ +} +span.pragmadots:hover { + background-color: #DBDBDB; +} +span.pragmawrap { + display: none; +} + From 1aa34353177aaa63ff1f89b66b4b1673e5ff94ab Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Tue, 17 Jul 2018 15:49:42 +0200 Subject: [PATCH 33/43] manual.rst: fixes a typo --- doc/manual.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual.rst b/doc/manual.rst index 4ab6803265..7298b02a30 100644 --- a/doc/manual.rst +++ b/doc/manual.rst @@ -253,7 +253,7 @@ 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. Historically, Nim was a fully `style-insensitive`:idx: language. This meant that -it was not case-sensitive and underscores were ignored and there was no even a +it was not case-sensitive and underscores were ignored and there was not even a distinction between ``foo`` and ``Foo``. From 59a5c0462ffbfaecc4ff6739e9ee424e3e67bdb1 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Tue, 17 Jul 2018 15:49:55 +0200 Subject: [PATCH 34/43] fixes #8338 --- lib/pure/htmlparser.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/htmlparser.nim b/lib/pure/htmlparser.nim index 9d4b57dc0b..c38c36874d 100644 --- a/lib/pure/htmlparser.nim +++ b/lib/pure/htmlparser.nim @@ -1890,7 +1890,7 @@ proc entityToUtf8*(entity: string): string = proc addNode(father, son: XmlNode) = if son != nil: add(father, son) -proc parse(x: var XmlParser, errors: var seq[string]): XmlNode +proc parse(x: var XmlParser, errors: var seq[string]): XmlNode {.gcsafe.} proc expected(x: var XmlParser, n: XmlNode): string = result = errorMsg(x, " expected") From f29225fc639b85523d137271005ae40b360d38c6 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Tue, 17 Jul 2018 16:14:12 +0200 Subject: [PATCH 35/43] sigmatch: no trailing whitespace --- compiler/sigmatch.nim | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 7e9143d94e..29f16b808f 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -640,7 +640,7 @@ proc procTypeRel(c: var TCandidate, f, a: PType): TTypeRelation = else: discard proc typeRangeRel(f, a: PType): TTypeRelation {.noinline.} = - template checkRange[T](a0, a1, f0, f1: T): TTypeRelation = + template checkRange[T](a0, a1, f0, f1: T): TTypeRelation = if a0 == f0 and a1 == f1: isEqual elif a0 >= f0 and a1 <= f1: @@ -650,12 +650,12 @@ proc typeRangeRel(f, a: PType): TTypeRelation {.noinline.} = isConvertible else: isNone - - if f.isOrdinalType: + + if f.isOrdinalType: checkRange(firstOrd(nil, a), lastOrd(nil, a), firstOrd(nil, f), lastOrd(nil, f)) - else: + else: checkRange(firstFloat(a), lastFloat(a), firstFloat(f), lastFloat(f)) - + proc matchUserTypeClass*(m: var TCandidate; ff, a: PType): PType = var From fc0bcccc15ec5cf3351cf65b22c7a038d82d4b35 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Tue, 17 Jul 2018 16:43:05 +0200 Subject: [PATCH 36/43] fixes #8230 --- compiler/semtypes.nim | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 25d8674b86..05a9d9882a 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -1344,6 +1344,10 @@ proc semTypeClass(c: PContext, n: PNode, prev: PType): PType = dummyName = param dummyType = candidateTypeSlot + # this can be true for 'nim check' on incomplete concepts, + # see bug #8230 + if dummyName.kind == nkEmpty: continue + internalAssert c.config, dummyName.kind == nkIdent var dummyParam = newSym(if modifier == tyTypeDesc: skType else: skVar, dummyName.ident, owner, param.info) From ebf4e9f7177da28fad4b81c74d4fc184581e9a70 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Tue, 17 Jul 2018 17:50:05 +0200 Subject: [PATCH 37/43] Extend init variable tracking to tuple assignments (#8321) Fixes #8314 --- compiler/sempass2.nim | 9 +++++++++ tests/init/t8314.nim | 21 +++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 tests/init/t8314.nim diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index e7a76ec224..bdea07ea8a 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -785,6 +785,15 @@ proc track(tracked: PEffects, n: PNode) = initVar(tracked, child.sons[i], volatileCheck=false) addAsgnFact(tracked.guards, child.sons[i], last) notNilCheck(tracked, last, child.sons[i].typ) + elif child.kind == nkVarTuple and last.kind != nkEmpty: + for i in 0 .. child.len-2: + if child[i].kind == nkEmpty or + child[i].kind == nkSym and child[i].sym.name.s == "_": + continue + initVar(tracked, child[i], volatileCheck=false) + if last.kind in {nkPar, nkTupleConstr}: + addAsgnFact(tracked.guards, child[i], last[i]) + notNilCheck(tracked, last[i], child[i].typ) # since 'var (a, b): T = ()' is not even allowed, there is always type # inference for (a, b) and thus no nil checking is necessary. of nkConstSection: diff --git a/tests/init/t8314.nim b/tests/init/t8314.nim new file mode 100644 index 0000000000..59d46eb335 --- /dev/null +++ b/tests/init/t8314.nim @@ -0,0 +1,21 @@ +discard """ + nimout: ''' +t8314.nim(8, 7) Hint: BEGIN [User] +t8314.nim(19, 7) Hint: END [User] + ''' +""" + +{.hint: "BEGIN".} +proc foo(x: range[1..10]) = + block: + var (y,) = (x,) + echo y + block: + var (_,y) = (1,x) + echo y + block: + var (y,_,) = (x,1,) + echo y +{.hint: "END".} + +foo(1) From ae09879b3a1a87b3949a9847ce517b2a507b7167 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Tue, 17 Jul 2018 18:21:09 +0200 Subject: [PATCH 38/43] fixes #8223 --- lib/system.nim | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/system.nim b/lib/system.nim index fed02f6d0f..f96f97dd66 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -4128,11 +4128,10 @@ template doAssertRaises*(exception, code: untyped): typed = ## .. code-block:: nim ## doAssertRaises(ValueError): ## raise newException(ValueError, "Hello World") - # TODO: investigate why runnableExamples here caused - # https://github.com/nim-lang/Nim/issues/8223 var wrong = false try: - code + if true: + code wrong = true except exception: discard From 2e3f477957614fbbd182495cedf718590284d98a Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Tue, 17 Jul 2018 18:22:20 +0200 Subject: [PATCH 39/43] VM: accessing the string terminator is not allowed anymore; cleanup tests/system/tostring.nim --- compiler/vm.nim | 4 +++- tests/system/{toString.nim => tostring.nim} | 17 ++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) rename tests/system/{toString.nim => tostring.nim} (87%) diff --git a/compiler/vm.nim b/compiler/vm.nim index 7f0dd80edc..373a64e39d 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -533,8 +533,10 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = let s = regs[rb].node.strVal if s.isNil: stackTrace(c, tos, pc, errNilAccess) - elif idx <=% s.len: + elif idx <% s.len: regs[ra].intVal = s[idx].ord + elif idx == s.len and optLaxStrings in c.config.options: + regs[ra].intVal = 0 else: stackTrace(c, tos, pc, errIndexOutOfBounds) of opcWrArr: diff --git a/tests/system/toString.nim b/tests/system/tostring.nim similarity index 87% rename from tests/system/toString.nim rename to tests/system/tostring.nim index ea10f998ca..9a6b83f95f 100644 --- a/tests/system/toString.nim +++ b/tests/system/tostring.nim @@ -1,12 +1,12 @@ discard """ - output:"" + output: "" """ doAssert "@[23, 45]" == $(@[23, 45]) doAssert "[32, 45]" == $([32, 45]) doAssert """@["", "foo", "bar"]""" == $(@["", "foo", "bar"]) -doAssert """["", "foo", "bar"]""" == $(["", "foo", "bar"]) -doAssert """["", "foo", "bar"]""" == $(@["", "foo", "bar"].toOpenArray(0, 2)) +doAssert """["", "foo", "bar"]""" == $(["", "foo", "bar"]) +doAssert """["", "foo", "bar"]""" == $(@["", "foo", "bar"].toOpenArray(0, 2)) # bug #2395 let alphaSet: set[char] = {'a'..'c'} @@ -69,13 +69,13 @@ var yy: string doAssert xx == @[] doAssert yy == "" -proc bar(arg: cstring): void = +proc bar(arg: cstring) = doAssert arg[0] == '\0' -proc baz(arg: openarray[char]): void = +proc baz(arg: openarray[char]) = doAssert arg.len == 0 -proc stringCompare(): void = +proc stringCompare() = var a,b,c,d,e,f,g: string a.add 'a' doAssert a == "a" @@ -102,9 +102,12 @@ proc stringCompare(): void = doAssert "" != "\0\0\0\0\0\0\0\0\0\0" var nilstring: string - bar(nilstring) + #bar(nilstring) baz(nilstring) stringCompare() +var nilstring: string +bar(nilstring) + static: stringCompare() \ No newline at end of file From 0cbfd67522f97a37d707fd4eec3ca59b0b5e0d74 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Tue, 17 Jul 2018 20:33:42 +0200 Subject: [PATCH 40/43] make typesafeprintf test green --- tests/macros/typesafeprintf.nim | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/macros/typesafeprintf.nim b/tests/macros/typesafeprintf.nim index 2f4622f3e1..daf213bd37 100644 --- a/tests/macros/typesafeprintf.nim +++ b/tests/macros/typesafeprintf.nim @@ -9,7 +9,7 @@ proc printfImpl(formatstr: cstring) {.importc: "printf", varargs.} iterator tokenize(format: string): char = var i = 0 - while true: + while i < format.len: case format[i] of '%': case format[i+1] @@ -42,7 +42,8 @@ macro printf(formatString: string{lit}, args: varargs[typed]): untyped = $expectedType & ", actual type: " & $actualType # keep the original callsite, but use cprintf instead - result = callsite() - result[0] = bindSym"printfImpl" + result = newCall(bindSym"printfImpl") + result.add formatString + for a in args: result.add a printf("test %d\n", 10) From 2262c255e23dd44aeee92505e67feb4739c4ec17 Mon Sep 17 00:00:00 2001 From: skilchen Date: Wed, 18 Jul 2018 07:51:39 +0200 Subject: [PATCH 41/43] let runnableExamples start in a new paragraph (#8355) fixes #8121, fixes #8346 --- compiler/docgen.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/docgen.nim b/compiler/docgen.nim index 6e2111c754..75b599ae91 100644 --- a/compiler/docgen.nim +++ b/compiler/docgen.nim @@ -288,7 +288,7 @@ proc getAllRunnableExamples(d: PDoc; n: PNode; dest: var Rope) = of nkCallKinds: if n[0].kind == nkSym and n[0].sym.magic == mRunnableExamples and n.len >= 2 and n.lastSon.kind == nkStmtList: - dispA(d.conf, dest, "\n$1\n", + dispA(d.conf, dest, "\n

$1

\n", "\n\\textbf{$1}\n", [rope"Examples:"]) inc d.listingCounter let id = $d.listingCounter From 6512f8688ddf991ec16a2da2ec797a3530e3c73f Mon Sep 17 00:00:00 2001 From: Aaron Levine Date: Wed, 18 Jul 2018 02:08:49 -0400 Subject: [PATCH 42/43] Fix #8345 (#8350) --- compiler/cgen.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/cgen.nim b/compiler/cgen.nim index ea82c496f5..0bf9239b34 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -664,6 +664,7 @@ proc generateHeaders(m: BModule) = add(m.s[cfsHeaders], "#undef linux\L") add(m.s[cfsHeaders], "#undef mips\L") add(m.s[cfsHeaders], "#undef near\L") + add(m.s[cfsHeaders], "#undef far\L") add(m.s[cfsHeaders], "#undef powerpc\L") add(m.s[cfsHeaders], "#undef unix\L") From 6eedac3207cad9f7b4bfe8d631a9373a91b85846 Mon Sep 17 00:00:00 2001 From: Chris de Graaf Date: Wed, 18 Jul 2018 02:17:34 -0500 Subject: [PATCH 43/43] Fix link to first class iterator docs (#8356) --- doc/tut1.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/tut1.rst b/doc/tut1.rst index aa6114cf78..67ef439480 100644 --- a/doc/tut1.rst +++ b/doc/tut1.rst @@ -891,7 +891,7 @@ important differences: future version of the compiler.) However, you can also use a ``closure`` iterator to get a different set of -restrictions. See `first class iterators `_ +restrictions. See `first class iterators `_ for details. Iterators can have the same name and parameters as a proc, since essentially they have their own namespaces. Therefore it is common practice to wrap iterators in procs of the same name which accumulate the result of the