diff options
Diffstat (limited to 'lib')
52 files changed, 1508 insertions, 950 deletions
diff --git a/lib/core/unsigned.nim b/lib/deprecated/core/unsigned.nim index 93a29e1c9..93a29e1c9 100644 --- a/lib/core/unsigned.nim +++ b/lib/deprecated/core/unsigned.nim diff --git a/lib/pure/actors.nim b/lib/deprecated/pure/actors.nim index f0791f954..f0791f954 100644 --- a/lib/pure/actors.nim +++ b/lib/deprecated/pure/actors.nim diff --git a/lib/pure/actors.nim.cfg b/lib/deprecated/pure/actors.nim.cfg index c6bb9c545..c6bb9c545 100644 --- a/lib/pure/actors.nim.cfg +++ b/lib/deprecated/pure/actors.nim.cfg diff --git a/lib/pure/asyncio.nim b/lib/deprecated/pure/asyncio.nim index 5fd45b215..5fd45b215 100644 --- a/lib/pure/asyncio.nim +++ b/lib/deprecated/pure/asyncio.nim diff --git a/lib/pure/ftpclient.nim b/lib/deprecated/pure/ftpclient.nim index 778ba6857..1188c0795 100644 --- a/lib/pure/ftpclient.nim +++ b/lib/deprecated/pure/ftpclient.nim @@ -11,9 +11,14 @@ include "system/inclrtl" import sockets, strutils, parseutils, times, os, asyncio from asyncnet import nil -from rawsockets import nil +from nativesockets import nil from asyncdispatch import PFuture - +## **Note**: This module is deprecated since version 0.11.3. +## You should use the async version of this module +## `asyncftpclient <asyncftpclient.html>`_. +## +## ---- +## ## This module **partially** implements an FTP client as specified ## by `RFC 959 <http://tools.ietf.org/html/rfc959>`_. ## @@ -36,6 +41,8 @@ from asyncdispatch import PFuture ## **Warning:** The API of this module is unstable, and therefore is subject ## to change. +{.deprecated.} + type FtpBase*[SockType] = ref FtpBaseObj[SockType] FtpBaseObj*[SockType] = object @@ -48,7 +55,7 @@ type user*, pass*: string address*: string when SockType is asyncnet.AsyncSocket: - port*: rawsockets.Port + port*: nativesockets.Port else: port*: Port diff --git a/lib/pure/parseurl.nim b/lib/deprecated/pure/parseurl.nim index 6d58e8a73..6d58e8a73 100644 --- a/lib/pure/parseurl.nim +++ b/lib/deprecated/pure/parseurl.nim diff --git a/lib/deprecated/pure/rawsockets.nim b/lib/deprecated/pure/rawsockets.nim new file mode 100644 index 000000000..ee77b232e --- /dev/null +++ b/lib/deprecated/pure/rawsockets.nim @@ -0,0 +1,14 @@ +import nativesockets +export nativesockets + +{.warning: "rawsockets module is deprecated, use nativesockets instead".} + +template newRawSocket*(domain, sockType, protocol: cint): expr = + {.warning: "newRawSocket is deprecated, use newNativeSocket instead".} + newNativeSocket(domain, sockType, protocol) + +template newRawSocket*(domain: Domain = AF_INET, + sockType: SockType = SOCK_STREAM, + protocol: Protocol = IPPROTO_TCP): expr = + {.warning: "newRawSocket is deprecated, use newNativeSocket instead".} + newNativeSocket(domain, sockType, protocol) diff --git a/lib/pure/sockets.nim b/lib/deprecated/pure/sockets.nim index 29f0bf87d..5d6fa0078 100644 --- a/lib/pure/sockets.nim +++ b/lib/deprecated/pure/sockets.nim @@ -9,7 +9,7 @@ ## **Warning:** Since version 0.10.2 this module is deprecated. ## Use the `net <net.html>`_ or the -## `rawsockets <rawsockets.html>`_ module instead. +## `nativesockets <nativesockets.html>`_ module instead. ## ## This module implements portable sockets, it supports a mix of different types ## of sockets. Sockets are buffered by default meaning that data will be @@ -314,10 +314,7 @@ when defined(ssl): of protSSLv23: newCTX = SSL_CTX_new(SSLv23_method()) # SSlv2,3 and TLS1 support. of protSSLv2: - when not defined(linux) and not defined(OpenBSD): - newCTX = SSL_CTX_new(SSLv2_method()) - else: - raiseSslError() + raiseSslError("SSLv2 is no longer secure and has been deprecated, use protSSLv3") of protSSLv3: newCTX = SSL_CTX_new(SSLv3_method()) of protTLSv1: diff --git a/lib/impure/graphics.nim b/lib/impure/graphics.nim deleted file mode 100644 index 8bd769fd8..000000000 --- a/lib/impure/graphics.nim +++ /dev/null @@ -1,577 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2012 Andreas Rumpf, Dominik Picheta -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## This module implements graphical output for Nim; the current -## implementation uses SDL but the interface is meant to support multiple -## backends some day. There is no need to init SDL as this module does that -## implicitly. - -import colors, math -from sdl import PSurface # Bug -from sdl_ttf import openFont, closeFont - -type - Rect* = tuple[x, y, width, height: int] - Point* = tuple[x, y: int] - - PSurface* = ref Surface ## a surface to draw onto - Surface* {.pure, final.} = object - w*, h*: Natural - s*: sdl.PSurface - - EGraphics* = object of IOError - - Font {.pure, final.} = object - f: sdl_ttf.PFont - color: sdl.Color - PFont* = ref Font ## represents a font -{.deprecated: [TSurface: Surface, TFont: Font, TRect: Rect, TPoint: Point].} - -proc toSdlColor*(c: Color): sdl.Color = - ## Convert colors.Color to sdl.Color - var x = c.extractRGB - result.r = x.r and 0xff - result.g = x.g and 0xff - result.b = x.b and 0xff - -proc createSdlColor*(sur: PSurface, c: Color, alpha: int = 0): int32 = - ## Creates a color using ``sdl.MapRGBA``. - var x = c.extractRGB - return sdl.mapRGBA(sur.s.format, x.r and 0xff, x.g and 0xff, - x.b and 0xff, alpha and 0xff) - -proc toSdlRect*(r: Rect): sdl.Rect = - ## Convert ``graphics.Rect`` to ``sdl.Rect``. - result.x = int16(r.x) - result.y = int16(r.y) - result.w = uint16(r.width) - result.h = uint16(r.height) - -proc raiseEGraphics = - raise newException(EGraphics, $sdl.getError()) - -proc surfaceFinalizer(s: PSurface) = sdl.freeSurface(s.s) - -proc newSurface*(width, height: int): PSurface = - ## creates a new surface. - new(result, surfaceFinalizer) - result.w = width - result.h = height - result.s = sdl.createRGBSurface(sdl.SWSURFACE, width, height, - 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0) - if result.s == nil: - raiseEGraphics() - - assert(not sdl.mustLock(result.s)) - -proc fontFinalizer(f: PFont) = closeFont(f.f) - -proc newFont*(name = "VeraMono.ttf", size = 9, color = colBlack): PFont = - ## Creates a new font object. Raises ``EIO`` if the font cannot be loaded. - new(result, fontFinalizer) - result.f = openFont(name, size.cint) - if result.f == nil: - raise newException(IOError, "Could not open font file: " & name) - result.color = toSdlColor(color) - -var - defaultFont*: PFont ## default font that is used; this needs to initialized - ## by the client! - -proc initDefaultFont*(name = "VeraMono.ttf", size = 9, color = colBlack) = - ## initializes the `defaultFont` var. - defaultFont = newFont(name, size, color) - -proc newScreenSurface*(width, height: int): PSurface = - ## Creates a new screen surface - new(result, surfaceFinalizer) - result.w = width - result.h = height - result.s = sdl.setVideoMode(width, height, 0, 0) - if result.s == nil: - raiseEGraphics() - -proc writeToBMP*(sur: PSurface, filename: string) = - ## Saves the contents of the surface `sur` to the file `filename` as a - ## BMP file. - if sdl.saveBMP(sur.s, filename) != 0: - raise newException(IOError, "cannot write: " & filename) - -type - Pixels = array[0..1000_000-1, int32] - PPixels = ptr Pixels -{.deprecated: [TPixels: Pixels].} - -template setPix(video, pitch, x, y, col: expr): stmt = - video[y * pitch + x] = int32(col) - -template getPix(video, pitch, x, y: expr): expr = - colors.Color(video[y * pitch + x]) - -const - ColSize = 4 - -proc getPixel(sur: PSurface, x, y: Natural): colors.Color {.inline.} = - assert x <% sur.w - assert y <% sur.h - result = getPix(cast[PPixels](sur.s.pixels), sur.s.pitch.int div ColSize, - x, y) - -proc setPixel(sur: PSurface, x, y: Natural, col: colors.Color) {.inline.} = - assert x <% sur.w - assert y <% sur.h - var pixs = cast[PPixels](sur.s.pixels) - #pixs[y * (sur.s.pitch div colSize) + x] = int(col) - setPix(pixs, sur.s.pitch.int div ColSize, x, y, col) - -proc `[]`*(sur: PSurface, p: Point): Color = - ## get pixel at position `p`. No range checking is done! - result = getPixel(sur, p.x, p.y) - -proc `[]`*(sur: PSurface, x, y: int): Color = - ## get pixel at position ``(x, y)``. No range checking is done! - result = getPixel(sur, x, y) - -proc `[]=`*(sur: PSurface, p: Point, col: Color) = - ## set the pixel at position `p`. No range checking is done! - setPixel(sur, p.x, p.y, col) - -proc `[]=`*(sur: PSurface, x, y: int, col: Color) = - ## set the pixel at position ``(x, y)``. No range checking is done! - setPixel(sur, x, y, col) - -proc blit*(destSurf: PSurface, destRect: Rect, srcSurf: PSurface, - srcRect: Rect) = - ## Copies ``srcSurf`` into ``destSurf`` - var destTRect, srcTRect: sdl.Rect - - destTRect.x = int16(destRect.x) - destTRect.y = int16(destRect.y) - destTRect.w = uint16(destRect.width) - destTRect.h = uint16(destRect.height) - - srcTRect.x = int16(srcRect.x) - srcTRect.y = int16(srcRect.y) - srcTRect.w = uint16(srcRect.width) - srcTRect.h = uint16(srcRect.height) - - if sdl.blitSurface(srcSurf.s, addr(srcTRect), destSurf.s, addr(destTRect)) != 0: - raiseEGraphics() - -proc textBounds*(text: string, font = defaultFont): tuple[width, height: int] = - var w, h: cint - if sdl_ttf.sizeUTF8(font.f, text, w, h) < 0: raiseEGraphics() - result.width = int(w) - result.height = int(h) - -proc drawText*(sur: PSurface, p: Point, text: string, font = defaultFont) = - ## Draws text with a transparent background, at location ``p`` with the given - ## font. - var textSur: PSurface # This surface will have the text drawn on it - new(textSur, surfaceFinalizer) - - # Render the text - textSur.s = sdl_ttf.renderTextBlended(font.f, text, font.color) - # Merge the text surface with sur - sur.blit((p.x, p.y, sur.w, sur.h), textSur, (0, 0, sur.w, sur.h)) - -proc drawText*(sur: PSurface, p: Point, text: string, - bg: Color, font = defaultFont) = - ## Draws text, at location ``p`` with font ``font``. ``bg`` - ## is the background color. - var textSur: PSurface # This surface will have the text drawn on it - new(textSur, surfaceFinalizer) - textSur.s = sdl_ttf.renderTextShaded(font.f, text, font.color, toSdlColor(bg)) - # Merge the text surface with sur - sur.blit((p.x, p.y, sur.w, sur.h), textSur, (0, 0, sur.w, sur.h)) - -proc drawCircle*(sur: PSurface, p: Point, r: Natural, color: Color) = - ## draws a circle with center `p` and radius `r` with the given color - ## onto the surface `sur`. - var video = cast[PPixels](sur.s.pixels) - var pitch = sur.s.pitch.int div ColSize - var a = 1 - r - var py = r - var px = 0 - var x = p.x - var y = p.y - while px <= py + 1: - if x+px <% sur.w: - if y+py <% sur.h: setPix(video, pitch, x+px, y+py, color) - if y-py <% sur.h: setPix(video, pitch, x+px, y-py, color) - - if x-px <% sur.w: - if y+py <% sur.h: setPix(video, pitch, x-px, y+py, color) - if y-py <% sur.h: setPix(video, pitch, x-px, y-py, color) - - if x+py <% sur.w: - if y+px <% sur.h: setPix(video, pitch, x+py, y+px, color) - if y-px <% sur.h: setPix(video, pitch, x+py, y-px, color) - - if x-py <% sur.w: - if y+px <% sur.h: setPix(video, pitch, x-py, y+px, color) - if y-px <% sur.h: setPix(video, pitch, x-py, y-px, color) - - if a < 0: - a = a + (2 * px + 3) - else: - a = a + (2 * (px - py) + 5) - py = py - 1 - px = px + 1 - -proc `>-<`(val: int, s: PSurface): int {.inline.} = - return if val < 0: 0 elif val >= s.w: s.w-1 else: val - -proc `>|<`(val: int, s: PSurface): int {.inline.} = - return if val < 0: 0 elif val >= s.h: s.h-1 else: val - -proc drawLine*(sur: PSurface, p1, p2: Point, color: Color) = - ## draws a line between the two points `p1` and `p2` with the given color - ## onto the surface `sur`. - var stepx, stepy: int = 0 - var x0 = p1.x >-< sur - var x1 = p2.x >-< sur - var y0 = p1.y >|< sur - var y1 = p2.y >|< sur - var dy = y1 - y0 - var dx = x1 - x0 - if dy < 0: - dy = -dy - stepy = -1 - else: - stepy = 1 - if dx < 0: - dx = -dx - stepx = -1 - else: - stepx = 1 - dy = dy * 2 - dx = dx * 2 - var video = cast[PPixels](sur.s.pixels) - var pitch = sur.s.pitch.int div ColSize - setPix(video, pitch, x0, y0, color) - if dx > dy: - var fraction = dy - (dx div 2) - while x0 != x1: - if fraction >= 0: - y0 = y0 + stepy - fraction = fraction - dx - x0 = x0 + stepx - fraction = fraction + dy - setPix(video, pitch, x0, y0, color) - else: - var fraction = dx - (dy div 2) - while y0 != y1: - if fraction >= 0: - x0 = x0 + stepx - fraction = fraction - dy - y0 = y0 + stepy - fraction = fraction + dx - setPix(video, pitch, x0, y0, color) - -proc drawHorLine*(sur: PSurface, x, y, w: Natural, color: Color) = - ## draws a horizontal line from (x,y) to (x+w-1, y). - var video = cast[PPixels](sur.s.pixels) - var pitch = sur.s.pitch.int div ColSize - - if y >= 0 and y <= sur.s.h: - for i in 0 .. min(sur.s.w-x, w)-1: - setPix(video, pitch, x + i, y, color) - -proc drawVerLine*(sur: PSurface, x, y, h: Natural, color: Color) = - ## draws a vertical line from (x,y) to (x, y+h-1). - var video = cast[PPixels](sur.s.pixels) - var pitch = sur.s.pitch.int div ColSize - - if x >= 0 and x <= sur.s.w: - for i in 0 .. min(sur.s.h-y, h)-1: - setPix(video, pitch, x, y + i, color) - -proc fillCircle*(s: PSurface, p: Point, r: Natural, color: Color) = - ## draws a circle with center `p` and radius `r` with the given color - ## onto the surface `sur` and fills it. - var a = 1 - r - var py: int = r - var px = 0 - var x = p.x - var y = p.y - while px <= py: - # Fill up the middle half of the circle - drawVerLine(s, x + px, y, py + 1, color) - drawVerLine(s, x + px, y - py, py, color) - if px != 0: - drawVerLine(s, x - px, y, py + 1, color) - drawVerLine(s, x - px, y - py, py, color) - if a < 0: - a = a + (2 * px + 3) - else: - a = a + (2 * (px - py) + 5) - py = py - 1 - # Fill up the left/right half of the circle - if py >= px: - drawVerLine(s, x + py + 1, y, px + 1, color) - drawVerLine(s, x + py + 1, y - px, px, color) - drawVerLine(s, x - py - 1, y, px + 1, color) - drawVerLine(s, x - py - 1, y - px, px, color) - px = px + 1 - -proc drawRect*(sur: PSurface, r: Rect, color: Color) = - ## draws a rectangle. - var video = cast[PPixels](sur.s.pixels) - var pitch = sur.s.pitch.int div ColSize - if (r.x >= 0 and r.x <= sur.s.w) and (r.y >= 0 and r.y <= sur.s.h): - var minW = min(sur.s.w - r.x, r.width) - var minH = min(sur.s.h - r.y, r.height) - - # Draw Top - for i in 0 .. minW - 1: - setPix(video, pitch, r.x + i, r.y, color) - setPix(video, pitch, r.x + i, r.y + minH - 1, color) # Draw bottom - - # Draw left side - for i in 0 .. minH - 1: - setPix(video, pitch, r.x, r.y + i, color) - setPix(video, pitch, r.x + minW - 1, r.y + i, color) # Draw right side - -proc fillRect*(sur: PSurface, r: Rect, col: Color) = - ## Fills a rectangle using sdl's ``FillRect`` function. - var rect = toSdlRect(r) - if sdl.fillRect(sur.s, addr(rect), sur.createSdlColor(col)) == -1: - raiseEGraphics() - -proc plot4EllipsePoints(sur: PSurface, cx, cy, x, y: Natural, col: Color) = - var video = cast[PPixels](sur.s.pixels) - var pitch = sur.s.pitch.int div ColSize - if cx+x <= sur.s.w-1: - if cy+y <= sur.s.h-1: setPix(video, pitch, cx+x, cy+y, col) - if cy-y <= sur.s.h-1: setPix(video, pitch, cx+x, cy-y, col) - if cx-x <= sur.s.w-1: - if cy+y <= sur.s.h-1: setPix(video, pitch, cx-x, cy+y, col) - if cy-y <= sur.s.h-1: setPix(video, pitch, cx-x, cy-y, col) - -proc drawEllipse*(sur: PSurface, cx, cy, xRadius, yRadius: Natural, - col: Color) = - ## Draws an ellipse, ``CX`` and ``CY`` specify the center X and Y of the - ## ellipse, ``XRadius`` and ``YRadius`` specify half the width and height - ## of the ellipse. - var - x, y: Natural - xChange, yChange: int - ellipseError: Natural - twoASquare, twoBSquare: Natural - stoppingX, stoppingY: Natural - - twoASquare = 2 * xRadius * xRadius - twoBSquare = 2 * yRadius * yRadius - x = xRadius - y = 0 - xChange = yRadius * yRadius * (1 - 2 * xRadius) - yChange = xRadius * xRadius - ellipseError = 0 - stoppingX = twoBSquare * xRadius - stoppingY = 0 - - while stoppingX >= stoppingY: # 1st set of points, y` > - 1 - sur.plot4EllipsePoints(cx, cy, x, y, col) - inc(y) - inc(stoppingY, twoASquare) - inc(ellipseError, yChange) - inc(yChange, twoASquare) - if (2 * ellipseError + xChange) > 0 : - dec(x) - dec(stoppingX, twoBSquare) - inc(ellipseError, xChange) - inc(xChange, twoBSquare) - - # 1st point set is done; start the 2nd set of points - x = 0 - y = yRadius - xChange = yRadius * yRadius - yChange = xRadius * xRadius * (1 - 2 * yRadius) - ellipseError = 0 - stoppingX = 0 - stoppingY = twoASquare * yRadius - while stoppingX <= stoppingY: - sur.plot4EllipsePoints(cx, cy, x, y, col) - inc(x) - inc(stoppingX, twoBSquare) - inc(ellipseError, xChange) - inc(xChange,twoBSquare) - if (2 * ellipseError + yChange) > 0: - dec(y) - dec(stoppingY, twoASquare) - inc(ellipseError, yChange) - inc(yChange,twoASquare) - - -proc plotAA(sur: PSurface, x, y: int, c: float, color: Color) = - if (x > 0 and x < sur.s.w) and (y > 0 and y < sur.s.h): - var video = cast[PPixels](sur.s.pixels) - var pitch = sur.s.pitch.int div ColSize - - var pixColor = getPix(video, pitch, x, y) - - setPix(video, pitch, x, y, - pixColor.intensity(1.0 - c) + color.intensity(c)) - - -template ipart(x: expr): expr = floor(x) -template cround(x: expr): expr = ipart(x + 0.5) -template fpart(x: expr): expr = x - ipart(x) -template rfpart(x: expr): expr = 1.0 - fpart(x) - -proc drawLineAA*(sur: PSurface, p1, p2: Point, color: Color) = - ## Draws a anti-aliased line from ``p1`` to ``p2``, using Xiaolin Wu's - ## line algorithm - var (x1, x2, y1, y2) = (p1.x.toFloat(), p2.x.toFloat(), - p1.y.toFloat(), p2.y.toFloat()) - var dx = x2 - x1 - var dy = y2 - y1 - - var ax = dx - if ax < 0'f64: - ax = 0'f64 - ax - var ay = dy - if ay < 0'f64: - ay = 0'f64 - ay - - if ax < ay: - swap(x1, y1) - swap(x2, y2) - swap(dx, dy) - - template doPlot(x, y: int, c: float, color: Color): stmt = - if ax < ay: - sur.plotAA(y, x, c, color) - else: - sur.plotAA(x, y, c, color) - - if x2 < x1: - swap(x1, x2) - swap(y1, y2) - - var gradient = dy / dx - # handle first endpoint - var xend = cround(x1) - var yend = y1 + gradient * (xend - x1) - var xgap = rfpart(x1 + 0.5) - var xpxl1 = int(xend) # this will be used in the main loop - var ypxl1 = int(ipart(yend)) - doPlot(xpxl1, ypxl1, rfpart(yend)*xgap, color) - doPlot(xpxl1, ypxl1 + 1, fpart(yend)*xgap, color) - var intery = yend + gradient # first y-intersection for the main loop - - # handle second endpoint - xend = cround(x2) - yend = y2 + gradient * (xend - x2) - xgap = fpart(x2 + 0.5) - var xpxl2 = int(xend) # this will be used in the main loop - var ypxl2 = int(ipart(yend)) - doPlot(xpxl2, ypxl2, rfpart(yend) * xgap, color) - doPlot(xpxl2, ypxl2 + 1, fpart(yend) * xgap, color) - - # main loop - var x = xpxl1 + 1 - while x <= xpxl2-1: - doPlot(x, int(ipart(intery)), rfpart(intery), color) - doPlot(x, int(ipart(intery)) + 1, fpart(intery), color) - intery = intery + gradient - inc(x) - -proc fillSurface*(sur: PSurface, color: Color) = - ## Fills the entire surface with ``color``. - if sdl.fillRect(sur.s, nil, sur.createSdlColor(color)) == -1: - raiseEGraphics() - -template withEvents*(surf: PSurface, event: expr, actions: stmt): stmt {. - immediate.} = - ## Simple template which creates an event loop. ``Event`` is the name of the - ## variable containing the Event object. - while true: - var event: sdl.Event - if sdl.waitEvent(addr(event)) == 1: - actions - -if sdl.init(sdl.INIT_VIDEO) < 0: raiseEGraphics() -if sdl_ttf.init() < 0: raiseEGraphics() - -when not defined(testing) and isMainModule: - var surf = newScreenSurface(800, 600) - - surf.fillSurface(colWhite) - - # Draw the shapes - surf.drawLineAA((150, 170), (400, 471), colTan) - surf.drawLine((100, 170), (400, 471), colRed) - - surf.drawEllipse(200, 300, 200, 30, colSeaGreen) - surf.drawHorLine(1, 300, 400, colViolet) - # Check if the ellipse is the size it's suppose to be. - surf.drawVerLine(200, 300 - 30 + 1, 60, colViolet) # ^^ | i suppose it is - - surf.drawEllipse(400, 300, 300, 300, colOrange) - surf.drawEllipse(5, 5, 5, 5, colGreen) - - surf.drawHorLine(5, 5, 900, colRed) - surf.drawVerLine(5, 60, 800, colRed) - surf.drawCircle((600, 500), 60, colRed) - - surf.fillRect((50, 50, 100, 100), colFuchsia) - surf.fillRect((150, 50, 100, 100), colGreen) - surf.drawRect((50, 150, 100, 100), colGreen) - surf.drawRect((150, 150, 100, 100), colAqua) - surf.drawRect((250, 150, 100, 100), colBlue) - surf.drawHorLine(250, 150, 100, colRed) - - surf.drawLineAA((592, 160), (592, 280), colPurple) - - #surf.drawText((300, 300), "TEST", colMidnightBlue) - #var textSize = textBounds("TEST") - #surf.drawText((300, 300 + textSize.height), $textSize.width & ", " & - # $textSize.height, colDarkGreen) - - var mouseStartX = -1 - var mouseStartY = -1 - withEvents(surf, event): - var eventp = addr(event) - case event.kind: - of sdl.QUITEV: - break - of sdl.KEYDOWN: - var evk = sdl.evKeyboard(eventp) - if evk.keysym.sym == sdl.K_LEFT: - surf.drawHorLine(395, 300, 50, colBlack) - echo("Drawing") - elif evk.keysym.sym == sdl.K_ESCAPE: - break - else: - echo(evk.keysym.sym) - of sdl.MOUSEBUTTONDOWN: - var mbd = sdl.evMouseButton(eventp) - if mouseStartX == -1 or mouseStartY == -1: - mouseStartX = int(mbd.x) - mouseStartY = int(mbd.y) - else: - surf.drawLineAA((mouseStartX, mouseStartY), (int(mbd.x), int(mbd.y)), colPurple) - mouseStartX = -1 - mouseStartY = -1 - - of sdl.MOUSEMOTION: - var mm = sdl.evMouseMotion(eventp) - if mouseStartX != -1 and mouseStartY != -1: - surf.drawLineAA((mouseStartX, mouseStartY), (int(mm.x), int(mm.y)), colPurple) - #echo(mm.x, " ", mm.y, " ", mm.yrel) - - else: - discard "echo(event.kind)" - - sdl.updateRect(surf.s, 0, 0, 800, 600) - - surf.writeToBMP("test.bmp") - sdl.quit() diff --git a/lib/posix/kqueue.nim b/lib/posix/kqueue.nim new file mode 100644 index 000000000..511ada9ac --- /dev/null +++ b/lib/posix/kqueue.nim @@ -0,0 +1,71 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2015 Adam Strzelecki +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +{.deadCodeElim:on.} + +from posix import Timespec + +# Filters: +const + EVFILT_READ* = -1 + EVFILT_WRITE* = -2 + EVFILT_AIO* = -3 + EVFILT_VNODE* = -4 + EVFILT_PROC* = -5 + EVFILT_SIGNAL* = -6 + EVFILT_TIMER* = -7 + EVFILT_MACHPORT* = -8 + EVFILT_FS* = -9 + EVFILT_USER* = -10 + # -11 is unused + EVFILT_VM* = -12 + +# Actions: +const + EV_ADD* = 0x0001 ## Add event to queue (implies enable). + ## Re-adding an existing element modifies it. + EV_DELETE* = 0x0002 ## Delete event from queue. + EV_ENABLE* = 0x0004 ## Enable event. + EV_DISABLE* = 0x0008 ## Disable event (not reported). + +# Flags: +const + EV_ONESHOT* = 0x0010 ## Only report one occurrence. + EV_CLEAR* = 0x0020 ## Clear event state after reporting. + EV_RECEIPT* = 0x0040 ## Force EV_ERROR on success, data == 0 + EV_DISPATCH* = 0x0080 ## Disable event after reporting. + +# Return values: +const + EV_EOF* = 0x8000 ## EOF detected + EV_ERROR* = 0x4000 ## Error, data contains errno + +type + KEvent* {.importc: "struct kevent", + header: "<sys/event.h>", pure, final.} = object + ident*: cuint ## identifier for this event (uintptr_t) + filter*: cshort ## filter for event + flags*: cushort ## general flags + fflags*: cuint ## filter-specific flags + data*: cuint ## filter-specific data (intptr_t) + #udata*: ptr void ## opaque user data identifier + +proc kqueue*(): cint {.importc: "kqueue", header: "<sys/event.h>".} + ## Creates new queue and returns its descriptor. + +proc kevent*(kqFD: cint, + changelist: ptr KEvent, nchanges: cint, + eventlist: ptr KEvent, nevents: cint, timeout: ptr Timespec): cint + {.importc: "kevent", header: "<sys/event.h>".} + ## Manipulates queue for given ``kqFD`` descriptor. + +proc EV_SET*(event: ptr KEvent, ident: cuint, filter: cshort, flags: cushort, + fflags: cuint, data: cuint, udata: ptr void) + {.importc: "EV_SET", header: "<sys/event.h>".} + ## Fills event with given data. diff --git a/lib/posix/posix.nim b/lib/posix/posix.nim index 8486fa04f..5f1dfcfcd 100644 --- a/lib/posix/posix.nim +++ b/lib/posix/posix.nim @@ -1810,7 +1810,7 @@ proc ntohs*(a1: int16): int16 {.importc, header: "<arpa/inet.h>".} proc inet_addr*(a1: cstring): InAddrT {.importc, header: "<arpa/inet.h>".} proc inet_ntoa*(a1: InAddr): cstring {.importc, header: "<arpa/inet.h>".} proc inet_ntop*(a1: cint, a2: pointer, a3: cstring, a4: int32): cstring {. - importc, header: "<arpa/inet.h>".} + importc:"(char *)$1", header: "<arpa/inet.h>".} proc inet_pton*(a1: cint, a2: cstring, a3: pointer): cint {. importc, header: "<arpa/inet.h>".} @@ -2381,7 +2381,7 @@ proc sched_setscheduler*(a1: Pid, a2: cint, a3: var Sched_param): cint {. proc sched_yield*(): cint {.importc, header: "<sched.h>".} proc strerror*(errnum: cint): cstring {.importc, header: "<string.h>".} -proc hstrerror*(herrnum: cint): cstring {.importc, header: "<netdb.h>".} +proc hstrerror*(herrnum: cint): cstring {.importc:"(char *)$1", header: "<netdb.h>".} proc FD_CLR*(a1: cint, a2: var TFdSet) {.importc, header: "<sys/select.h>".} proc FD_ISSET*(a1: cint | SocketHandle, a2: var TFdSet): cint {. @@ -2565,7 +2565,7 @@ proc endprotoent*() {.importc, header: "<netdb.h>".} proc endservent*() {.importc, header: "<netdb.h>".} proc freeaddrinfo*(a1: ptr AddrInfo) {.importc, header: "<netdb.h>".} -proc gai_strerror*(a1: cint): cstring {.importc, header: "<netdb.h>".} +proc gai_strerror*(a1: cint): cstring {.importc:"(char *)$1", header: "<netdb.h>".} proc getaddrinfo*(a1, a2: cstring, a3: ptr AddrInfo, a4: var ptr AddrInfo): cint {.importc, header: "<netdb.h>".} diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim index d91507a85..cc337452f 100644 --- a/lib/pure/asyncdispatch.nim +++ b/lib/pure/asyncdispatch.nim @@ -11,7 +11,7 @@ include "system/inclrtl" import os, oids, tables, strutils, macros, times -import rawsockets, net +import nativesockets, net export Port, SocketFlag @@ -475,7 +475,7 @@ when defined(windows) or defined(nimdoc): addr bytesRet, nil, nil) == 0 proc initAll() = - let dummySock = newRawSocket() + let dummySock = newNativeSocket() if not initPointer(dummySock, connectExPtr, WSAID_CONNECTEX): raiseOSError(osLastError()) if not initPointer(dummySock, acceptExPtr, WSAID_ACCEPTEX): @@ -528,7 +528,7 @@ when defined(windows) or defined(nimdoc): RemoteSockaddr, RemoteSockaddrLength) proc connect*(socket: AsyncFD, address: string, port: Port, - domain = rawsockets.AF_INET): Future[void] = + domain = nativesockets.AF_INET): Future[void] = ## Connects ``socket`` to server at ``address:port``. ## ## Returns a ``Future`` which will complete when the connection succeeds @@ -611,7 +611,7 @@ when defined(windows) or defined(nimdoc): var retFuture = newFuture[string]("recv") var dataBuf: TWSABuf dataBuf.buf = cast[cstring](alloc0(size)) - dataBuf.len = size + dataBuf.len = size.ULONG var bytesReceived: Dword var flagsio = flags.toOSFlags().Dword @@ -706,7 +706,7 @@ when defined(windows) or defined(nimdoc): #buf[] = '\0' var dataBuf: TWSABuf dataBuf.buf = buf - dataBuf.len = size + dataBuf.len = size.ULONG var bytesReceived: Dword var flagsio = flags.toOSFlags().Dword @@ -777,7 +777,7 @@ when defined(windows) or defined(nimdoc): var dataBuf: TWSABuf dataBuf.buf = data # since this is not used in a callback, this is fine - dataBuf.len = data.len + dataBuf.len = data.len.ULONG var bytesReceived, lowFlags: Dword var ol = PCustomOverlapped() @@ -827,7 +827,7 @@ when defined(windows) or defined(nimdoc): verifyPresence(socket) var retFuture = newFuture[tuple[address: string, client: AsyncFD]]("acceptAddr") - var clientSock = newRawSocket() + var clientSock = newNativeSocket() if clientSock == osInvalidSocket: raiseOSError(osLastError()) const lpOutputLen = 1024 @@ -900,17 +900,17 @@ when defined(windows) or defined(nimdoc): return retFuture - proc newAsyncRawSocket*(domain, sockType, protocol: cint): AsyncFD = + proc newAsyncNativeSocket*(domain, sockType, protocol: cint): AsyncFD = ## Creates a new socket and registers it with the dispatcher implicitly. - result = newRawSocket(domain, sockType, protocol).AsyncFD + result = newNativeSocket(domain, sockType, protocol).AsyncFD result.SocketHandle.setBlocking(false) register(result) - proc newAsyncRawSocket*(domain: Domain = rawsockets.AF_INET, - sockType: SockType = SOCK_STREAM, - protocol: Protocol = IPPROTO_TCP): AsyncFD = + proc newAsyncNativeSocket*(domain: Domain = nativesockets.AF_INET, + sockType: SockType = SOCK_STREAM, + protocol: Protocol = IPPROTO_TCP): AsyncFD = ## Creates a new socket and registers it with the dispatcher implicitly. - result = newRawSocket(domain, sockType, protocol).AsyncFD + result = newNativeSocket(domain, sockType, protocol).AsyncFD result.SocketHandle.setBlocking(false) register(result) @@ -973,18 +973,18 @@ else: var data = PData(fd: fd, readCBs: @[], writeCBs: @[]) p.selector.register(fd.SocketHandle, {}, data.RootRef) - proc newAsyncRawSocket*(domain: cint, sockType: cint, - protocol: cint): AsyncFD = - result = newRawSocket(domain, sockType, protocol).AsyncFD + proc newAsyncNativeSocket*(domain: cint, sockType: cint, + protocol: cint): AsyncFD = + result = newNativeSocket(domain, sockType, protocol).AsyncFD result.SocketHandle.setBlocking(false) when defined(macosx): result.SocketHandle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1) register(result) - proc newAsyncRawSocket*(domain: Domain = AF_INET, - sockType: SockType = SOCK_STREAM, - protocol: Protocol = IPPROTO_TCP): AsyncFD = - result = newRawSocket(domain, sockType, protocol).AsyncFD + proc newAsyncNativeSocket*(domain: Domain = AF_INET, + sockType: SockType = SOCK_STREAM, + protocol: Protocol = IPPROTO_TCP): AsyncFD = + result = newNativeSocket(domain, sockType, protocol).AsyncFD result.SocketHandle.setBlocking(false) when defined(macosx): result.SocketHandle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1) @@ -992,8 +992,8 @@ else: proc closeSocket*(sock: AsyncFD) = let disp = getGlobalDispatcher() - sock.SocketHandle.close() disp.selector.unregister(sock.SocketHandle) + sock.SocketHandle.close() proc unregister*(fd: AsyncFD) = getGlobalDispatcher().selector.unregister(fd.SocketHandle) @@ -1468,16 +1468,25 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} = hint("Processing " & prc[0].getName & " as an async proc.") let returnType = prc[3][0] + var baseType: NimNode # Verify that the return type is a Future[T] - if returnType.kind == nnkIdent: - error("Expected return type of 'Future' got '" & $returnType & "'") - elif returnType.kind == nnkBracketExpr: - if $returnType[0] != "Future": - error("Expected return type of 'Future' got '" & $returnType[0] & "'") + if returnType.kind == nnkBracketExpr: + let fut = repr(returnType[0]) + if fut != "Future": + error("Expected return type of 'Future' got '" & fut & "'") + baseType = returnType[1] + elif returnType.kind in nnkCallKinds and $returnType[0] == "[]": + let fut = repr(returnType[1]) + if fut != "Future": + error("Expected return type of 'Future' got '" & fut & "'") + baseType = returnType[2] + elif returnType.kind == nnkEmpty: + baseType = returnType + else: + error("Expected return type of 'Future' got '" & repr(returnType) & "'") let subtypeIsVoid = returnType.kind == nnkEmpty or - (returnType.kind == nnkBracketExpr and - returnType[1].kind == nnkIdent and returnType[1].ident == !"void") + (baseType.kind == nnkIdent and returnType[1].ident == !"void") var outerProcBody = newNimNode(nnkStmtList, prc[6]) @@ -1485,7 +1494,7 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} = var retFutureSym = genSym(nskVar, "retFuture") var subRetType = if returnType.kind == nnkEmpty: newIdentNode("void") - else: returnType[1] + else: baseType outerProcBody.add( newVarStmt(retFutureSym, newCall( @@ -1509,7 +1518,7 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} = newIdentNode("off")))) # -> {.push warning[resultshadowed]: off.} procBody.insert(1, newNimNode(nnkVarSection, prc[6]).add( - newIdentDefs(newIdentNode("result"), returnType[1]))) # -> var result: T + newIdentDefs(newIdentNode("result"), baseType))) # -> var result: T procBody.insert(2, newNimNode(nnkPragma).add( newIdentNode("pop"))) # -> {.pop.}) @@ -1550,8 +1559,8 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} = result[6] = outerProcBody #echo(treeRepr(result)) - if prc[0].getName == "hubConnectionLoop": - echo(toStrLit(result)) + #if prc[0].getName == "hubConnectionLoop": + # echo(toStrLit(result)) macro async*(prc: stmt): stmt {.immediate.} = ## Macro which processes async procedures into the appropriate diff --git a/lib/pure/asyncftpclient.nim b/lib/pure/asyncftpclient.nim index fe4577ed9..b806f4235 100644 --- a/lib/pure/asyncftpclient.nim +++ b/lib/pure/asyncftpclient.nim @@ -6,23 +6,74 @@ # distribution, for details about the copyright. # -## This module implement an asynchronous FTP client. +## This module implements an asynchronous FTP client. It allows you to connect +## to an FTP server and perform operations on it such as for example: ## -## Examples -## -------- +## * The upload of new files. +## * The removal of existing files. +## * Download of files. +## * Changing of files' permissions. +## * Navigation through the FTP server's directories. ## -## .. code-block::nim +## Connecting to an FTP server +## ------------------------ ## -## var ftp = newAsyncFtpClient("example.com", user = "test", pass = "test") -## proc main(ftp: AsyncFtpClient) {.async.} = +## In order to begin any sort of transfer of files you must first +## connect to an FTP server. You can do so with the ``connect`` procedure. +## +## .. code-block::nim +## import asyncdispatch, asyncftpclient +## proc main() {.async.} = +## var ftp = newAsyncFtpClient("example.com", user = "test", pass = "test") +## await ftp.connect() +## echo("Connected") +## waitFor(main()) +## +## A new ``main`` async procedure must be declared to allow the use of the +## ``await`` keyword. The connection will complete asynchronously and the +## client will be connected after the ``await ftp.connect()`` call. +## +## Uploading a new file +## -------------------- +## +## After a connection is made you can use the ``store`` procedure to upload +## a new file to the FTP server. Make sure to check you are in the correct +## working directory before you do so with the ``pwd`` procedure, you can also +## instead specify an absolute path. +## +## .. code-block::nim +## import asyncdispatch, asyncftpclient +## proc main() {.async.} = +## var ftp = newAsyncFtpClient("example.com", user = "test", pass = "test") ## await ftp.connect() -## echo await ftp.pwd() -## echo await ftp.listDirs() -## await ftp.store("payload.jpg", "payload.jpg") -## await ftp.retrFile("payload.jpg", "payload2.jpg") -## echo("Finished") +## let currentDir = await ftp.pwd() +## assert currentDir == "/home/user/" +## await ftp.store("file.txt", "file.txt") +## echo("File finished uploading") +## waitFor(main()) ## -## waitFor main(ftp) +## Checking the progress of a file transfer +## ---------------------------------------- +## +## The progress of either a file upload or a file download can be checked +## by specifying a ``onProgressChanged`` procedure to the ``store`` or +## ``retrFile`` procedures. +## +## .. code-block::nim +## import asyncdispatch, asyncftpclient +## +## proc onProgressChanged(total, progress: BiggestInt, +## speed: float): Future[void] = +## echo("Uploaded ", progress, " of ", total, " bytes") +## echo("Current speed: ", speed, " kb/s") +## +## proc main() {.async.} = +## var ftp = newAsyncFtpClient("example.com", user = "test", pass = "test") +## await ftp.connect() +## await ftp.store("file.txt", "/home/user/file.txt", onProgressChanged) +## echo("File finished uploading") +## waitFor(main()) + import asyncdispatch, asyncnet, strutils, parseutils, os, times diff --git a/lib/pure/asynchttpserver.nim b/lib/pure/asynchttpserver.nim index aa7d458b3..5d74896bf 100644 --- a/lib/pure/asynchttpserver.nim +++ b/lib/pure/asynchttpserver.nim @@ -67,6 +67,12 @@ type Http409 = "409 Conflict", Http410 = "410 Gone", Http411 = "411 Length Required", + Http412 = "412 Precondition Failed", + Http413 = "413 Request Entity Too Large", + Http414 = "414 Request-URI Too Long", + Http415 = "415 Unsupported Media Type", + Http416 = "416 Requested Range Not Satisfiable", + Http417 = "417 Expectation Failed", Http418 = "418 I'm a teapot", Http500 = "500 Internal Server Error", Http501 = "501 Not Implemented", diff --git a/lib/pure/asyncnet.nim b/lib/pure/asyncnet.nim index ba314af10..6b19a48be 100644 --- a/lib/pure/asyncnet.nim +++ b/lib/pure/asyncnet.nim @@ -56,7 +56,7 @@ ## import asyncdispatch -import rawsockets +import nativesockets import net import os @@ -112,8 +112,8 @@ proc newAsyncSocket*(domain: Domain = AF_INET, sockType: SockType = SOCK_STREAM, ## ## This procedure will also create a brand new file descriptor for ## this socket. - result = newAsyncSocket(newAsyncRawSocket(domain, sockType, protocol), domain, - sockType, protocol, buffered) + result = newAsyncSocket(newAsyncNativeSocket(domain, sockType, protocol), + domain, sockType, protocol, buffered) proc newAsyncSocket*(domain, sockType, protocol: cint, buffered = true): AsyncSocket = @@ -121,8 +121,9 @@ proc newAsyncSocket*(domain, sockType, protocol: cint, ## ## This procedure will also create a brand new file descriptor for ## this socket. - result = newAsyncSocket(newAsyncRawSocket(domain, sockType, protocol), - Domain(domain), SockType(sockType), Protocol(protocol), buffered) + result = newAsyncSocket(newAsyncNativeSocket(domain, sockType, protocol), + Domain(domain), SockType(sockType), + Protocol(protocol), buffered) when defined(ssl): proc getSslError(handle: SslPtr, err: cint): cint = @@ -432,6 +433,7 @@ proc recvLine*(socket: AsyncSocket, # TODO: Optimise this var resString = newFutureVar[string]("asyncnet.recvLine") + resString.mget() = "" await socket.recvLineInto(resString, flags) result = resString.mget() diff --git a/lib/pure/base64.nim b/lib/pure/base64.nim index deab39c7c..32d37ce02 100644 --- a/lib/pure/base64.nim +++ b/lib/pure/base64.nim @@ -8,6 +8,38 @@ # ## This module implements a base64 encoder and decoder. +## +## Encoding data +## ------------- +## +## In order to encode some text simply call the ``encode`` procedure: +## +## .. code-block::nim +## import base64 +## let encoded = encode("Hello World") +## echo(encoded) # SGVsbG8gV29ybGQ= +## +## Apart from strings you can also encode lists of integers or characters: +## +## .. code-block::nim +## import base64 +## let encodedInts = encode([1,2,3]) +## echo(encodedInts) # AQID +## let encodedChars = encode(['h','e','y']) +## echo(encodedChars) # aGV5 +## +## The ``encode`` procedure takes an ``openarray`` so both arrays and sequences +## can be passed as parameters. +## +## Decoding data +## ------------- +## +## To decode a base64 encoded data string simply call the ``decode`` +## procedure: +## +## .. code-block::nim +## import base64 +## echo(decode("SGVsbG8gV29ybGQ=")) # Hello World const cb64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" @@ -64,11 +96,16 @@ template encodeInternal(s: expr, lineLen: int, newLine: string): stmt {.immediat proc encode*[T:SomeInteger|char](s: openarray[T], lineLen = 75, newLine="\13\10"): string = ## encodes `s` into base64 representation. After `lineLen` characters, a ## `newline` is added. + ## + ## This procedure encodes an openarray (array or sequence) of either integers + ## or characters. encodeInternal(s, lineLen, newLine) proc encode*(s: string, lineLen = 75, newLine="\13\10"): string = ## encodes `s` into base64 representation. After `lineLen` characters, a ## `newline` is added. + ## + ## This procedure encodes a string. encodeInternal(s, lineLen, newLine) proc decodeByte(b: char): int {.inline.} = diff --git a/lib/pure/basic2d.nim b/lib/pure/basic2d.nim index ad8f8653d..7d74424fa 100644 --- a/lib/pure/basic2d.nim +++ b/lib/pure/basic2d.nim @@ -18,6 +18,8 @@ import strutils ## ## Quick start example: ## +## .. code-block:: nim +## ## # Create a matrix which first rotates, then scales and at last translates ## ## var m:Matrix2d=rotate(DEG90) & scale(2.0) & move(100.0,200.0) @@ -93,11 +95,11 @@ let IDMATRIX*:Matrix2d=matrix2d(1.0,0.0,0.0,1.0,0.0,0.0) ## Quick access to an identity matrix ORIGO*:Point2d=point2d(0.0,0.0) - ## Quick acces to point (0,0) + ## Quick access to point (0,0) XAXIS*:Vector2d=vector2d(1.0,0.0) - ## Quick acces to an 2d x-axis unit vector + ## Quick access to an 2d x-axis unit vector YAXIS*:Vector2d=vector2d(0.0,1.0) - ## Quick acces to an 2d y-axis unit vector + ## Quick access to an 2d y-axis unit vector # *************************************** diff --git a/lib/pure/basic3d.nim b/lib/pure/basic3d.nim index 7fea54d58..424c191f8 100644 --- a/lib/pure/basic3d.nim +++ b/lib/pure/basic3d.nim @@ -23,6 +23,8 @@ import times ## ## Quick start example: ## +## .. code-block:: nim +## ## # Create a matrix which first rotates, then scales and at last translates ## ## var m:Matrix3d=rotate(PI,vector3d(1,1,2.5)) & scale(2.0) & move(100.0,200.0,300.0) diff --git a/lib/pure/collections/critbits.nim b/lib/pure/collections/critbits.nim index 424bcdcca..09b20fd45 100644 --- a/lib/pure/collections/critbits.nim +++ b/lib/pure/collections/critbits.nim @@ -123,6 +123,14 @@ proc containsOrIncl*(c: var CritBitTree[void], key: string): bool = var n = rawInsert(c, key) result = c.count == oldCount +proc inc*(c: var CritBitTree[int]; key: string) = + ## counts the 'key'. + let oldCount = c.count + var n = rawInsert(c, key) + if c.count == oldCount: + # not a new key: + inc n.val + proc incl*(c: var CritBitTree[void], key: string) = ## includes `key` in `c`. discard rawInsert(c, key) diff --git a/lib/pure/collections/sequtils.nim b/lib/pure/collections/sequtils.nim index e6ea19a6b..fd012e811 100644 --- a/lib/pure/collections/sequtils.nim +++ b/lib/pure/collections/sequtils.nim @@ -47,7 +47,7 @@ proc concat*[T](seqs: varargs[seq[T]]): seq[T] = result[i] = itm inc(i) -proc repeat*[T](s: seq[T], n: Natural): seq[T] = +proc cycle*[T](s: seq[T], n: Natural): seq[T] = ## Returns a new sequence with the items of `s` repeated `n` times. ## ## Example: @@ -56,15 +56,29 @@ proc repeat*[T](s: seq[T], n: Natural): seq[T] = ## ## let ## s = @[1, 2, 3] - ## total = s.repeat(3) + ## total = s.cycle(3) ## assert total == @[1, 2, 3, 1, 2, 3, 1, 2, 3] result = newSeq[T](n * s.len) var o = 0 - for x in 1..n: + for x in 0..<n: for e in s: result[o] = e inc o +proc repeat*[T](x: T, n: Natural): seq[T] = + ## Returns a new sequence with the item `x` repeated `n` times. + ## + ## Example: + ## + ## .. code-block: + ## + ## let + ## total = repeat(5, 3) + ## assert total == @[5, 5, 5] + result = newSeq[T](n) + for i in 0..<n: + result[i] = x + proc deduplicate*[T](seq1: seq[T]): seq[T] = ## Returns a new sequence without duplicates. ## @@ -169,6 +183,77 @@ proc distribute*[T](s: seq[T], num: Positive, spread = true): seq[seq[T]] = first = last +proc map*[T, S](data: openArray[T], op: proc (x: T): S {.closure.}): + seq[S]{.inline.} = + ## Returns a new sequence with the results of `op` applied to every item in + ## `data`. + ## + ## Since the input is not modified you can use this version of ``map`` to + ## transform the type of the elements in the input sequence. Example: + ## + ## .. code-block:: nim + ## let + ## a = @[1, 2, 3, 4] + ## b = map(a, proc(x: int): string = $x) + ## assert b == @["1", "2", "3", "4"] + newSeq(result, data.len) + for i in 0..data.len-1: result[i] = op(data[i]) + +proc map*[T](data: var openArray[T], op: proc (x: var T) {.closure.}) + {.deprecated.} = + ## Applies `op` to every item in `data` modifying it directly. + ## + ## Note that this version of ``map`` requires your input and output types to + ## be the same, since they are modified in-place. Example: + ## + ## .. code-block:: nim + ## var a = @["1", "2", "3", "4"] + ## echo repr(a) + ## # --> ["1", "2", "3", "4"] + ## map(a, proc(x: var string) = x &= "42") + ## echo repr(a) + ## # --> ["142", "242", "342", "442"] + ## **Deprecated since version 0.12.0:** Use the ``apply`` proc instead. + for i in 0..data.len-1: op(data[i]) + +proc apply*[T](data: var seq[T], op: proc (x: var T) {.closure.}) + {.inline.} = + ## Applies `op` to every item in `data` modifying it directly. + ## + ## Note that this requires your input and output types to + ## be the same, since they are modified in-place. + ## The parameter function takes a ``var T`` type parameter. + ## Example: + ## + ## .. code-block:: nim + ## var a = @["1", "2", "3", "4"] + ## echo repr(a) + ## # --> ["1", "2", "3", "4"] + ## map(a, proc(x: var string) = x &= "42") + ## echo repr(a) + ## # --> ["142", "242", "342", "442"] + ## + for i in 0..data.len-1: op(data[i]) + +proc apply*[T](data: var seq[T], op: proc (x: T): T {.closure.}) + {.inline.} = + ## Applies `op` to every item in `data` modifying it directly. + ## + ## Note that this requires your input and output types to + ## be the same, since they are modified in-place. + ## The parameter function takes and returns a ``T`` type variable. + ## Example: + ## + ## .. code-block:: nim + ## var a = @["1", "2", "3", "4"] + ## echo repr(a) + ## # --> ["1", "2", "3", "4"] + ## map(a, proc(x: string): string = x & "42") + ## echo repr(a) + ## # --> ["142", "242", "342", "442"] + ## + for i in 0..data.len-1: data[i] = op(data[i]) + iterator filter*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): T = ## Iterates through a sequence and yields every item that fulfills the @@ -181,11 +266,12 @@ iterator filter*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): T = ## for n in filter(numbers, proc (x: int): bool = x mod 2 == 0): ## echo($n) ## # echoes 4, 8, 4 in separate lines - for i in countup(0, len(seq1)-1): - var item = seq1[i] - if pred(item): yield seq1[i] + for i in 0..<seq1.len: + if pred(seq1[i]): + yield seq1[i] -proc filter*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): seq[T] = +proc filter*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): seq[T] + {.inline.} = ## Returns a new sequence with all the items that fulfilled the predicate. ## ## Example: @@ -197,9 +283,13 @@ proc filter*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): seq[T] = ## f2 = filter(colors) do (x: string) -> bool : x.len > 5 ## assert f1 == @["red", "black"] ## assert f2 == @["yellow"] - accumulateResult(filter(seq1, pred)) + result = newSeq[T]() + for i in 0..<seq1.len: + if pred(seq1[i]): + result.add(seq1[i]) -proc keepIf*[T](seq1: var seq[T], pred: proc(item: T): bool {.closure.}) = +proc keepIf*[T](seq1: var seq[T], pred: proc(item: T): bool {.closure.}) + {.inline.} = ## Keeps the items in the passed sequence if they fulfilled the predicate. ## Same as the ``filter`` proc, but modifies the sequence directly. ## @@ -213,7 +303,7 @@ proc keepIf*[T](seq1: var seq[T], pred: proc(item: T): bool {.closure.}) = for i in 0 .. <len(seq1): if pred(seq1[i]): if pos != i: - seq1[pos] = seq1[i] + shallowCopy(seq1[pos], seq1[i]) inc(pos) setLen(seq1, pos) @@ -268,7 +358,7 @@ proc insert*[T](dest: var seq[T], src: openArray[T], pos=0) = inc(j) -template filterIt*(seq1, pred: expr): expr {.immediate.} = +template filterIt*(seq1, pred: expr): expr = ## Returns a new sequence with all the items that fulfilled the predicate. ## ## Unlike the `proc` version, the predicate needs to be an expression using @@ -282,12 +372,12 @@ template filterIt*(seq1, pred: expr): expr {.immediate.} = ## notAcceptable = filterIt(temperatures, it > 50 or it < -10) ## assert acceptable == @[-2.0, 24.5, 44.31] ## assert notAcceptable == @[-272.15, 99.9, -113.44] - var result {.gensym.}: type(seq1) = @[] + var result {.gensym.} = newSeq[type(seq1[0])]() for it {.inject.} in items(seq1): if pred: result.add(it) result -template keepItIf*(varSeq, pred: expr) = +template keepItIf*(varSeq: seq, pred: expr) = ## Convenience template around the ``keepIf`` proc to reduce typing. ## ## Unlike the `proc` version, the predicate needs to be an expression using @@ -303,7 +393,7 @@ template keepItIf*(varSeq, pred: expr) = let it {.inject.} = varSeq[i] if pred: if pos != i: - varSeq[pos] = varSeq[i] + shallowCopy(varSeq[pos], varSeq[i]) inc(pos) setLen(varSeq, pos) @@ -320,14 +410,19 @@ template toSeq*(iter: expr): expr {.immediate.} = ## if x mod 2 == 1: ## result = true) ## assert odd_numbers == @[1, 3, 5, 7, 9] - ## - ## **Note**: Since this is an immediate macro, you cannot always invoke this - ## as ``x.toSeq``, depending on the ``x``. - ## See `this <manual.html#limitations-of-the-method-call-syntax>`_ - ## for an explanation. - var result {.gensym.}: seq[type(iter)] = @[] - for x in iter: add(result, x) - result + + when compiles(iter.len): + var i = 0 + var result = newSeq[type(iter)](iter.len) + for x in iter: + result[i] = x + inc i + result + else: + var result: seq[type(iter)] = @[] + for x in iter: + result.add(x) + result template foldl*(sequence, operation: expr): expr = ## Template to fold a sequence from left to right, returning the accumulation. @@ -358,7 +453,7 @@ template foldl*(sequence, operation: expr): expr = assert sequence.len > 0, "Can't fold empty sequences" var result {.gensym.}: type(sequence[0]) result = sequence[0] - for i in countup(1, sequence.len - 1): + for i in 1..<sequence.len: let a {.inject.} = result b {.inject.} = sequence[i] @@ -401,7 +496,7 @@ template foldr*(sequence, operation: expr): expr = result = operation result -template mapIt*(seq1, typ, op: expr): expr = +template mapIt*(seq1, typ, op: expr): expr {.deprecated.}= ## Convenience template around the ``map`` proc to reduce typing. ## ## The template injects the ``it`` variable which you can use directly in an @@ -414,13 +509,45 @@ template mapIt*(seq1, typ, op: expr): expr = ## nums = @[1, 2, 3, 4] ## strings = nums.mapIt(string, $(4 * it)) ## assert strings == @["4", "8", "12", "16"] + ## **Deprecated since version 0.12.0:** Use the ``mapIt(seq1, op)`` + ## template instead. var result {.gensym.}: seq[typ] = @[] for it {.inject.} in items(seq1): result.add(op) result -template mapIt*(varSeq, op: expr) = - ## Convenience template around the mutable ``map`` proc to reduce typing. + +template mapIt*(seq1, op: expr): expr = + ## Convenience template around the ``map`` proc to reduce typing. + ## + ## The template injects the ``it`` variable which you can use directly in an + ## expression. Example: + ## + ## .. code-block:: + ## let + ## nums = @[1, 2, 3, 4] + ## strings = nums.mapIt($(4 * it)) + ## assert strings == @["4", "8", "12", "16"] + type outType = type(( + block: + var it{.inject.}: type(items(seq1)); + op)) + var result: seq[outType] + when compiles(seq1.len): + let s = seq1 + var i = 0 + result = newSeq[outType](s.len) + for it {.inject.} in s: + result[i] = op + i += 1 + else: + result = @[] + for it {.inject.} in seq1: + result.add(op) + result + +template applyIt*(varSeq, op: expr) = + ## Convenience template around the mutable ``apply`` proc to reduce typing. ## ## The template injects the ``it`` variable which you can use directly in an ## expression. The expression has to return the same type as the sequence you @@ -428,12 +555,14 @@ template mapIt*(varSeq, op: expr) = ## ## .. code-block:: ## var nums = @[1, 2, 3, 4] - ## nums.mapIt(it * 3) + ## nums.applyIt(it * 3) ## assert nums[0] + nums[3] == 15 - for i in 0 .. <len(varSeq): + for i in 0 .. <varSeq.len: let it {.inject.} = varSeq[i] varSeq[i] = op + + template newSeqWith*(len: int, init: expr): expr = ## creates a new sequence, calling `init` to initialize each value. Example: ## @@ -568,8 +697,8 @@ when isMainModule: block: # mapIt tests var nums = @[1, 2, 3, 4] - strings = nums.mapIt(string, $(4 * it)) - nums.mapIt(it * 3) + strings = nums.mapIt($(4 * it)) + nums.applyIt(it * 3) assert nums[0] + nums[3] == 15 block: # distribute tests @@ -605,15 +734,19 @@ when isMainModule: seq2D[0][1] = true doAssert seq2D == @[@[true, true], @[true, false], @[false, false], @[false, false]] - block: # repeat tests + block: # cycle tests let a = @[1, 2, 3] b: seq[int] = @[] - doAssert a.repeat(3) == @[1, 2, 3, 1, 2, 3, 1, 2, 3] - doAssert a.repeat(0) == @[] - #doAssert a.repeat(-1) == @[] # will not compile! - doAssert b.repeat(3) == @[] + doAssert a.cycle(3) == @[1, 2, 3, 1, 2, 3, 1, 2, 3] + doAssert a.cycle(0) == @[] + #doAssert a.cycle(-1) == @[] # will not compile! + doAssert b.cycle(3) == @[] + + block: # repeat tests + assert repeat(10, 5) == @[10, 10, 10, 10, 10] + assert repeat(@[1,2,3], 2) == @[@[1,2,3], @[1,2,3]] when not defined(testing): echo "Finished doc tests" diff --git a/lib/pure/coro.nim b/lib/pure/coro.nim index 8fa529474..c5724f26f 100644 --- a/lib/pure/coro.nim +++ b/lib/pure/coro.nim @@ -119,7 +119,7 @@ proc wait*(c: proc(), interval=0.01) = while alive(c): suspend interval -when isMainModule: +when defined(nimCoroutines) and isMainModule: var stackCheckValue = 1100220033 proc c2() diff --git a/lib/pure/coro.nimcfg b/lib/pure/coro.nimcfg new file mode 100644 index 000000000..b011bc585 --- /dev/null +++ b/lib/pure/coro.nimcfg @@ -0,0 +1 @@ +-d:nimCoroutines diff --git a/lib/pure/future.nim b/lib/pure/future.nim index 661afd7b3..4767266e5 100644 --- a/lib/pure/future.nim +++ b/lib/pure/future.nim @@ -75,7 +75,7 @@ macro `=>`*(p, b: expr): expr {.immediate.} = identDefs.add(newEmptyNode()) of nnkIdent: identDefs.add(c) - identDefs.add(newEmptyNode()) + identDefs.add(newIdentNode("auto")) identDefs.add(newEmptyNode()) of nnkInfix: if c[0].kind == nnkIdent and c[0].ident == !"->": @@ -93,7 +93,7 @@ macro `=>`*(p, b: expr): expr {.immediate.} = of nnkIdent: var identDefs = newNimNode(nnkIdentDefs) identDefs.add(p) - identDefs.add(newEmptyNode()) + identDefs.add(newIdentNode("auto")) identDefs.add(newEmptyNode()) params.add(identDefs) of nnkInfix: diff --git a/lib/pure/hashes.nim b/lib/pure/hashes.nim index 61c16129b..11af81149 100644 --- a/lib/pure/hashes.nim +++ b/lib/pure/hashes.nim @@ -8,9 +8,10 @@ # ## This module implements efficient computations of hash values for diverse -## Nim types. All the procs are based on these two building blocks: the `!& -## proc <#!&>`_ used to start or mix a hash value, and the `!$ proc <#!$>`_ -## used to *finish* the hash value. If you want to implement hash procs for +## Nim types. All the procs are based on these two building blocks: +## - `!& proc <#!&>`_ used to start or mix a hash value, and +## - `!$ proc <#!$>`_ used to *finish* the hash value. +## If you want to implement hash procs for ## your custom types you will end up writing the following kind of skeleton of ## code: ## @@ -108,7 +109,7 @@ proc hash*(x: int): Hash {.inline.} = result = x proc hash*(x: int64): Hash {.inline.} = - ## efficient hashing of integers + ## efficient hashing of int64 integers result = toU32(x) proc hash*(x: char): Hash {.inline.} = @@ -126,6 +127,16 @@ proc hash*(x: string): Hash = h = h !& ord(x[i]) result = !$h +proc hash*(sBuf: string, sPos, ePos: int): Hash = + ## efficient hashing of a string buffer, from starting + ## position `sPos` to ending position `ePos` + ## + ## ``hash(myStr, 0, myStr.high)`` is equivalent to ``hash(myStr)`` + var h: Hash = 0 + for i in sPos..ePos: + h = h !& ord(sBuf[i]) + result = !$h + proc hashIgnoreStyle*(x: string): Hash = ## efficient hashing of strings; style is ignored var h: Hash = 0 @@ -145,6 +156,27 @@ proc hashIgnoreStyle*(x: string): Hash = result = !$h +proc hashIgnoreStyle*(sBuf: string, sPos, ePos: int): Hash = + ## efficient hashing of a string buffer, from starting + ## position `sPos` to ending position `ePos`; style is ignored + ## + ## ``hashIgnoreStyle(myBuf, 0, myBuf.high)`` is equivalent + ## to ``hashIgnoreStyle(myBuf)`` + var h: Hash = 0 + var i = sPos + while i <= ePos: + var c = sBuf[i] + if c == '_': + inc(i) + elif isMagicIdentSeparatorRune(cstring(sBuf), i): + inc(i, magicIdentSeparatorRuneByteWidth) + else: + if c in {'A'..'Z'}: + c = chr(ord(c) + (ord('a') - ord('A'))) # toLower() + h = h !& ord(c) + inc(i) + result = !$h + proc hashIgnoreCase*(x: string): Hash = ## efficient hashing of strings; case is ignored var h: Hash = 0 @@ -155,7 +187,22 @@ proc hashIgnoreCase*(x: string): Hash = h = h !& ord(c) result = !$h +proc hashIgnoreCase*(sBuf: string, sPos, ePos: int): Hash = + ## efficient hashing of a string buffer, from starting + ## position `sPos` to ending position `ePos`; case is ignored + ## + ## ``hashIgnoreCase(myBuf, 0, myBuf.high)`` is equivalent + ## to ``hashIgnoreCase(myBuf)`` + var h: Hash = 0 + for i in sPos..ePos: + var c = sBuf[i] + if c in {'A'..'Z'}: + c = chr(ord(c) + (ord('a') - ord('A'))) # toLower() + h = h !& ord(c) + result = !$h + proc hash*(x: float): Hash {.inline.} = + ## efficient hashing of floats. var y = x + 1.0 result = cast[ptr Hash](addr(y))[] @@ -173,10 +220,29 @@ proc hash*[T: tuple](x: T): Hash = result = !$result proc hash*[A](x: openArray[A]): Hash = + ## efficient hashing of arrays and sequences. for it in items(x): result = result !& hash(it) result = !$result +proc hash*[A](aBuf: openArray[A], sPos, ePos: int): Hash = + ## efficient hashing of portions of arrays and sequences. + ## + ## ``hash(myBuf, 0, myBuf.high)`` is equivalent to ``hash(myBuf)`` + for i in sPos..ePos: + result = result !& hash(aBuf[i]) + result = !$result + proc hash*[A](x: set[A]): Hash = + ## efficient hashing of sets. for it in items(x): result = result !& hash(it) result = !$result +when isMainModule: + doAssert( hash("aa bb aaaa1234") == hash("aa bb aaaa1234", 0, 13) ) + doAssert( hashIgnoreCase("aa bb aaaa1234") == hash("aa bb aaaa1234") ) + doAssert( hashIgnoreStyle("aa bb aaaa1234") == hashIgnoreCase("aa bb aaaa1234") ) + let xx = @['H','e','l','l','o'] + let ss = "Hello" + doAssert( hash(xx) == hash(ss) ) + doAssert( hash(xx) == hash(xx, 0, xx.high) ) + doAssert( hash(ss) == hash(ss, 0, ss.high) ) diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim index 30b838b7e..a5d4ec1a1 100644 --- a/lib/pure/httpclient.nim +++ b/lib/pure/httpclient.nim @@ -81,7 +81,7 @@ import net, strutils, uri, parseutils, strtabs, base64, os, mimetypes, math import asyncnet, asyncdispatch -import rawsockets +import nativesockets type Response* = tuple[ @@ -764,10 +764,10 @@ proc newConnection(client: AsyncHttpClient, url: Uri) {.async.} = let port = if url.port == "": if url.scheme.toLower() == "https": - rawsockets.Port(443) + nativesockets.Port(443) else: - rawsockets.Port(80) - else: rawsockets.Port(url.port.parseInt) + nativesockets.Port(80) + else: nativesockets.Port(url.port.parseInt) if url.scheme.toLower() == "https": when defined(ssl): diff --git a/lib/pure/json.nim b/lib/pure/json.nim index abf6305f2..06d5a13e2 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -622,9 +622,12 @@ proc getNum*(n: JsonNode, default: BiggestInt = 0): BiggestInt = proc getFNum*(n: JsonNode, default: float = 0.0): float = ## Retrieves the float value of a `JFloat JsonNode`. ## - ## Returns ``default`` if ``n`` is not a ``JFloat``, or if ``n`` is nil. - if n.isNil or n.kind != JFloat: return default - else: return n.fnum + ## Returns ``default`` if ``n`` is not a ``JFloat`` or ``JInt``, or if ``n`` is nil. + if n.isNil: return default + case n.kind + of JFloat: return n.fnum + of JInt: return float(n.num) + else: return default proc getBVal*(n: JsonNode, default: bool = false): bool = ## Retrieves the bool value of a `JBool JsonNode`. diff --git a/lib/pure/logging.nim b/lib/pure/logging.nim index 7a900daae..aa55b5ade 100644 --- a/lib/pure/logging.nim +++ b/lib/pure/logging.nim @@ -77,7 +77,7 @@ type ## console FileLogger* = ref object of Logger ## logger that writes the messages to a file - f: File + file*: File ## the wrapped file. RollingFileLogger* = ref object of FileLogger ## logger that writes the ## messages to a file and @@ -92,7 +92,9 @@ type {.deprecated: [TLevel: Level, PLogger: Logger, PConsoleLogger: ConsoleLogger, PFileLogger: FileLogger, PRollingFileLogger: RollingFileLogger].} -proc substituteLog(frmt: string, level: Level, args: varargs[string, `$`]): string = +proc substituteLog*(frmt: string, level: Level, args: varargs[string, `$`]): string = + ## Format a log message using the ``frmt`` format string, ``level`` and varargs. + ## See the module documentation for the format string syntax. var msgLen = 0 for arg in args: msgLen += arg.len @@ -124,7 +126,7 @@ proc substituteLog(frmt: string, level: Level, args: varargs[string, `$`]): stri method log*(logger: Logger, level: Level, args: varargs[string, `$`]) {. raises: [Exception], - tags: [TimeEffect, WriteIOEffect, ReadIOEffect].} = + tags: [TimeEffect, WriteIOEffect, ReadIOEffect], base.} = ## Override this method in custom loggers. Default implementation does ## nothing. discard @@ -133,15 +135,17 @@ method log*(logger: ConsoleLogger, level: Level, args: varargs[string, `$`]) = ## Logs to the console using ``logger`` only. if level >= logger.levelThreshold: writeLine(stdout, substituteLog(logger.fmtStr, level, args)) + if level in {lvlError, lvlFatal}: flushFile(stdout) method log*(logger: FileLogger, level: Level, args: varargs[string, `$`]) = ## Logs to a file using ``logger`` only. if level >= logger.levelThreshold: - writeLine(logger.f, substituteLog(logger.fmtStr, level, args)) + writeLine(logger.file, substituteLog(logger.fmtStr, level, args)) + if level in {lvlError, lvlFatal}: flushFile(logger.file) proc defaultFilename*(): string = ## Returns the default filename for a logger. - var (path, name, ext) = splitFile(getAppFilename()) + var (path, name, _) = splitFile(getAppFilename()) result = changeFileExt(path / name, "log") proc newConsoleLogger*(levelThreshold = lvlAll, fmtStr = defaultFmtStr): ConsoleLogger = @@ -160,14 +164,14 @@ proc newFileLogger*(filename = defaultFilename(), ## (-1: use system defaults, 0: unbuffered, >0: fixed buffer size). new(result) result.levelThreshold = levelThreshold - result.f = open(filename, mode, bufSize = bufSize) + result.file = open(filename, mode, bufSize = bufSize) result.fmtStr = fmtStr # ------ proc countLogLines(logger: RollingFileLogger): int = result = 0 - for line in logger.f.lines(): + for line in logger.file.lines(): result.inc() proc countFiles(filename: string): int = @@ -200,7 +204,7 @@ proc newRollingFileLogger*(filename = defaultFilename(), result.fmtStr = fmtStr result.maxLines = maxLines result.bufSize = bufSize - result.f = open(filename, mode, bufSize=result.bufSize) + result.file = open(filename, mode, bufSize=result.bufSize) result.curLine = 0 result.baseName = filename result.baseMode = mode @@ -222,13 +226,14 @@ method log*(logger: RollingFileLogger, level: Level, args: varargs[string, `$`]) ## Logs to a file using rolling ``logger`` only. if level >= logger.levelThreshold: if logger.curLine >= logger.maxLines: - logger.f.close() + logger.file.close() rotate(logger) logger.logFiles.inc logger.curLine = 0 - logger.f = open(logger.baseName, logger.baseMode, bufSize = logger.bufSize) + logger.file = open(logger.baseName, logger.baseMode, bufSize = logger.bufSize) - writeLine(logger.f, substituteLog(logger.fmtStr, level, args)) + writeLine(logger.file, substituteLog(logger.fmtStr, level, args)) + if level in {lvlError, lvlFatal}: flushFile(logger.file) logger.curLine.inc # -------- diff --git a/lib/pure/math.nim b/lib/pure/math.nim index c1d5c9439..391a880ae 100644 --- a/lib/pure/math.nim +++ b/lib/pure/math.nim @@ -21,6 +21,20 @@ include "system/inclrtl" {.push debugger:off .} # the user does not want to trace a part # of the standard library! +proc binom*(n, k: int): int {.noSideEffect.} = + ## Computes the binomial coefficient + if k <= 0: return 1 + if 2*k > n: return binom(n, n-k) + result = n + for i in countup(2, k): + result = (result * (n + 1 - i)) div i + +proc fac*(n: int): int {.noSideEffect.} = + ## Computes the faculty/factorial function. + result = 1 + for i in countup(2, n): + result = result * i + {.push checks:off, line_dir:off, stack_trace:off.} when defined(Posix) and not defined(haiku): @@ -72,21 +86,6 @@ proc classify*(x: float): FloatClass = return fcNormal # XXX: fcSubnormal is not detected! - -proc binom*(n, k: int): int {.noSideEffect.} = - ## Computes the binomial coefficient - if k <= 0: return 1 - if 2*k > n: return binom(n, n-k) - result = n - for i in countup(2, k): - result = (result * (n + 1 - i)) div i - -proc fac*(n: int): int {.noSideEffect.} = - ## Computes the faculty/factorial function. - result = 1 - for i in countup(2, n): - result = result * i - proc isPowerOfTwo*(x: int): bool {.noSideEffect.} = ## Returns true, if `x` is a power of two, false otherwise. ## Zero and negative numbers are not a power of two. @@ -476,7 +475,7 @@ when isMainModule and not defined(JS): return sqrt(num) # check gamma function - assert(tgamma(5.0) == 24.0) # 4! + assert($tgamma(5.0) == $24.0) # 4! assert(lgamma(1.0) == 0.0) # ln(1.0) == 0.0 assert(erf(6.0) > erf(5.0)) assert(erfc(6.0) < erfc(5.0)) diff --git a/lib/pure/md5.nim b/lib/pure/md5.nim index 5ee301b15..44b9ed0d4 100644 --- a/lib/pure/md5.nim +++ b/lib/pure/md5.nim @@ -9,8 +9,6 @@ ## Module for computing MD5 checksums. -import unsigned - type MD5State = array[0..3, uint32] MD5Block = array[0..15, uint32] diff --git a/lib/pure/memfiles.nim b/lib/pure/memfiles.nim index 27b989597..b9c574944 100644 --- a/lib/pure/memfiles.nim +++ b/lib/pure/memfiles.nim @@ -32,8 +32,9 @@ type size*: int ## size of the memory mapped file when defined(windows): - fHandle: int - mapHandle: int + fHandle: Handle + mapHandle: Handle + wasOpened: bool ## only close if wasOpened else: handle: cint @@ -115,7 +116,8 @@ proc open*(filename: string, mode: FileMode = fmRead, template callCreateFile(winApiProc, filename: expr): expr = winApiProc( filename, - if readonly: GENERIC_READ else: GENERIC_ALL, + # GENERIC_ALL != (GENERIC_READ or GENERIC_WRITE) + if readonly: GENERIC_READ else: GENERIC_READ or GENERIC_WRITE, FILE_SHARE_READ, nil, if newFileSize != -1: CREATE_ALWAYS else: OPEN_EXISTING, @@ -172,6 +174,8 @@ proc open*(filename: string, mode: FileMode = fmRead, if mappedSize != -1: result.size = min(fileSize, mappedSize).int else: result.size = fileSize.int + result.wasOpened = true + else: template fail(errCode: OSErrorCode, msg: expr) = rollback() @@ -226,7 +230,7 @@ proc close*(f: var MemFile) = var lastErr: OSErrorCode when defined(windows): - if f.fHandle != INVALID_HANDLE_VALUE: + if f.fHandle != INVALID_HANDLE_VALUE and f.wasOpened: error = unmapViewOfFile(f.mem) == 0 lastErr = osLastError() error = (closeHandle(f.mapHandle) == 0) or error @@ -243,6 +247,7 @@ proc close*(f: var MemFile) = when defined(windows): f.fHandle = 0 f.mapHandle = 0 + f.wasOpened = false else: f.handle = 0 diff --git a/lib/pure/rawsockets.nim b/lib/pure/nativesockets.nim index f5860ef28..c9e067a3e 100644 --- a/lib/pure/rawsockets.nim +++ b/lib/pure/nativesockets.nim @@ -93,8 +93,8 @@ when useWinVersion: IOC_IN* = int(-2147483648) FIONBIO* = IOC_IN.int32 or ((sizeof(int32) and IOCPARM_MASK) shl 16) or (102 shl 8) or 126 - rawAfInet = winlean.AF_INET - rawAfInet6 = winlean.AF_INET6 + nativeAfInet = winlean.AF_INET + nativeAfInet6 = winlean.AF_INET6 proc ioctlsocket*(s: SocketHandle, cmd: clong, argptr: ptr clong): cint {. @@ -102,8 +102,8 @@ when useWinVersion: else: let osInvalidSocket* = posix.INVALID_SOCKET - rawAfInet = posix.AF_INET - rawAfInet6 = posix.AF_INET6 + nativeAfInet = posix.AF_INET + nativeAfInet6 = posix.AF_INET6 proc `==`*(a, b: Port): bool {.borrow.} ## ``==`` for ports. @@ -157,12 +157,14 @@ else: result = cint(ord(p)) -proc newRawSocket*(domain: Domain = AF_INET, sockType: SockType = SOCK_STREAM, - protocol: Protocol = IPPROTO_TCP): SocketHandle = +proc newNativeSocket*(domain: Domain = AF_INET, + sockType: SockType = SOCK_STREAM, + protocol: Protocol = IPPROTO_TCP): SocketHandle = ## Creates a new socket; returns `InvalidSocket` if an error occurs. socket(toInt(domain), toInt(sockType), toInt(protocol)) -proc newRawSocket*(domain: cint, sockType: cint, protocol: cint): SocketHandle = +proc newNativeSocket*(domain: cint, sockType: cint, + protocol: cint): SocketHandle = ## Creates a new socket; returns `InvalidSocket` if an error occurs. ## ## Use this overload if one of the enums specified above does @@ -201,7 +203,9 @@ proc getAddrInfo*(address: string, port: Port, domain: Domain = AF_INET, hints.ai_family = toInt(domain) hints.ai_socktype = toInt(sockType) hints.ai_protocol = toInt(protocol) - hints.ai_flags = AI_V4MAPPED + # https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=198092 + when not defined(freebsd): + hints.ai_flags = AI_V4MAPPED var gaiResult = getaddrinfo(address, $port, addr(hints), result) if gaiResult != 0'i32: when useWinVersion: @@ -229,17 +233,17 @@ proc ntohs*(x: int16): int16 = when cpuEndian == bigEndian: result = x else: result = (x shr 8'i16) or (x shl 8'i16) -proc htonl*(x: int32): int32 = +template htonl*(x: int32): expr = ## Converts 32-bit integers from host to network byte order. On machines ## where the host byte order is the same as network byte order, this is ## a no-op; otherwise, it performs a 4-byte swap operation. - result = rawsockets.ntohl(x) + nativesockets.ntohl(x) -proc htons*(x: int16): int16 = +template htons*(x: int16): expr = ## Converts 16-bit positive integers from host to network byte order. ## On machines where the host byte order is the same as network byte ## order, this is a no-op; otherwise, it performs a 2-byte swap operation. - result = rawsockets.ntohs(x) + nativesockets.ntohs(x) proc getServByName*(name, proto: string): Servent {.tags: [ReadIOEffect].} = ## Searches the database from the beginning and finds the first entry for @@ -280,7 +284,7 @@ proc getHostByAddr*(ip: string): Hostent {.tags: [ReadIOEffect].} = when useWinVersion: var s = winlean.gethostbyaddr(addr(myaddr), sizeof(myaddr).cuint, - cint(rawsockets.AF_INET)) + cint(AF_INET)) if s == nil: raiseOSError(osLastError()) else: var s = posix.gethostbyaddr(addr(myaddr), sizeof(myaddr).Socklen, @@ -330,9 +334,9 @@ proc getSockDomain*(socket: SocketHandle): Domain = if getsockname(socket, cast[ptr SockAddr](addr(name)), addr(namelen)) == -1'i32: raiseOSError(osLastError()) - if name.sa_family == rawAfInet: + if name.sa_family == nativeAfInet: result = AF_INET - elif name.sa_family == rawAfInet6: + elif name.sa_family == nativeAfInet6: result = AF_INET6 else: raiseOSError(osLastError(), "unknown socket family in getSockFamily") @@ -340,12 +344,11 @@ proc getSockDomain*(socket: SocketHandle): Domain = proc getAddrString*(sockAddr: ptr SockAddr): string = ## return the string representation of address within sockAddr - if sockAddr.sa_family == rawAfInet: + if sockAddr.sa_family == nativeAfInet: result = $inet_ntoa(cast[ptr Sockaddr_in](sockAddr).sin_addr) - elif sockAddr.sa_family == rawAfInet6: + elif sockAddr.sa_family == nativeAfInet6: when not useWinVersion: # TODO: Windows - var v6addr = cast[ptr Sockaddr_in6](sockAddr).sin6_addr result = newString(posix.INET6_ADDRSTRLEN) let addr6 = addr cast[ptr Sockaddr_in6](sockAddr).sin6_addr discard posix.inet_ntop(posix.AF_INET6, addr6, result.cstring, @@ -369,7 +372,7 @@ proc getSockName*(socket: SocketHandle): Port = if getsockname(socket, cast[ptr SockAddr](addr(name)), addr(namelen)) == -1'i32: raiseOSError(osLastError()) - result = Port(rawsockets.ntohs(name.sin_port)) + result = Port(nativesockets.ntohs(name.sin_port)) proc getLocalAddr*(socket: SocketHandle, domain: Domain): (string, Port) = ## returns the socket's local address and port number. @@ -386,7 +389,8 @@ proc getLocalAddr*(socket: SocketHandle, domain: Domain): (string, Port) = if getsockname(socket, cast[ptr SockAddr](addr(name)), addr(namelen)) == -1'i32: raiseOSError(osLastError()) - result = ($inet_ntoa(name.sin_addr), Port(rawsockets.ntohs(name.sin_port))) + result = ($inet_ntoa(name.sin_addr), + Port(nativesockets.ntohs(name.sin_port))) of AF_INET6: var name: Sockaddr_in6 when useWinVersion: @@ -402,7 +406,7 @@ proc getLocalAddr*(socket: SocketHandle, domain: Domain): (string, Port) = if inet_ntop(name.sin6_family.cint, addr name, buf.cstring, sizeof(buf).int32).isNil: raiseOSError(osLastError()) - result = ($buf, Port(rawsockets.ntohs(name.sin6_port))) + result = ($buf, Port(nativesockets.ntohs(name.sin6_port))) else: raiseOSError(OSErrorCode(-1), "invalid socket family in getLocalAddr") @@ -421,7 +425,8 @@ proc getPeerAddr*(socket: SocketHandle, domain: Domain): (string, Port) = if getpeername(socket, cast[ptr SockAddr](addr(name)), addr(namelen)) == -1'i32: raiseOSError(osLastError()) - result = ($inet_ntoa(name.sin_addr), Port(rawsockets.ntohs(name.sin_port))) + result = ($inet_ntoa(name.sin_addr), + Port(nativesockets.ntohs(name.sin_port))) of AF_INET6: var name: Sockaddr_in6 when useWinVersion: @@ -437,7 +442,7 @@ proc getPeerAddr*(socket: SocketHandle, domain: Domain): (string, Port) = if inet_ntop(name.sin6_family.cint, addr name, buf.cstring, sizeof(buf).int32).isNil: raiseOSError(osLastError()) - result = ($buf, Port(rawsockets.ntohs(name.sin6_port))) + result = ($buf, Port(nativesockets.ntohs(name.sin6_port))) else: raiseOSError(OSErrorCode(-1), "invalid socket family in getLocalAddr") diff --git a/lib/pure/net.nim b/lib/pure/net.nim index 0ce5b4d25..d1016011e 100644 --- a/lib/pure/net.nim +++ b/lib/pure/net.nim @@ -10,7 +10,7 @@ ## This module implements a high-level cross-platform sockets interface. {.deadCodeElim: on.} -import rawsockets, os, strutils, unsigned, parseutils, times +import nativesockets, os, strutils, unsigned, parseutils, times export Port, `$`, `==` const useWinVersion = defined(Windows) or defined(nimdoc) @@ -145,7 +145,7 @@ proc newSocket*(domain, sockType, protocol: cint, buffered = true): Socket = ## Creates a new socket. ## ## If an error occurs EOS will be raised. - let fd = newRawSocket(domain, sockType, protocol) + let fd = newNativeSocket(domain, sockType, protocol) if fd == osInvalidSocket: raiseOSError(osLastError()) result = newSocket(fd, domain.Domain, sockType.SockType, protocol.Protocol, @@ -156,7 +156,7 @@ proc newSocket*(domain: Domain = AF_INET, sockType: SockType = SOCK_STREAM, ## Creates a new socket. ## ## If an error occurs EOS will be raised. - let fd = newRawSocket(domain, sockType, protocol) + let fd = newNativeSocket(domain, sockType, protocol) if fd == osInvalidSocket: raiseOSError(osLastError()) result = newSocket(fd, domain, sockType, protocol, buffered) @@ -223,10 +223,7 @@ when defined(ssl): of protSSLv23: newCTX = SSL_CTX_new(SSLv23_method()) # SSlv2,3 and TLS1 support. of protSSLv2: - when not defined(linux): - newCTX = SSL_CTX_new(SSLv2_method()) - else: - raiseSslError() + raiseSslError("SSLv2 is no longer secure and has been deprecated, use protSSLv3") of protSSLv3: newCTX = SSL_CTX_new(SSLv3_method()) of protTLSv1: @@ -357,7 +354,7 @@ proc listen*(socket: Socket, backlog = SOMAXCONN) {.tags: [ReadIOEffect].} = ## queue of pending connections. ## ## Raises an EOS error upon failure. - if rawsockets.listen(socket.fd, backlog) < 0'i32: + if nativesockets.listen(socket.fd, backlog) < 0'i32: raiseOSError(osLastError()) proc bindAddr*(socket: Socket, port = Port(0), address = "") {. diff --git a/lib/pure/options.nim b/lib/pure/options.nim index ef01e1260..3122d58b1 100644 --- a/lib/pure/options.nim +++ b/lib/pure/options.nim @@ -108,6 +108,36 @@ proc get*[T](self: Option[T]): T = raise UnpackError(msg : "Can't obtain a value from a `none`") self.val +proc get*[T](self: Option[T], otherwise: T): T = + ## Returns the contents of this option or `otherwise` if the option is none. + if self.isSome: + self.val + else: + otherwise + + +proc map*[T](self: Option[T], callback: proc (input: T)) = + ## Applies a callback to the value in this Option + if self.has: + callback(self.val) + +proc map*[T, R](self: Option[T], callback: proc (input: T): R): Option[R] = + ## Applies a callback to the value in this Option and returns an option + ## containing the new value. If this option is None, None will be returned + if self.has: + some[R]( callback(self.val) ) + else: + none(R) + +proc filter*[T](self: Option[T], callback: proc (input: T): bool): Option[T] = + ## Applies a callback to the value in this Option. If the callback returns + ## `true`, the option is returned as a Some. If it returns false, it is + ## returned as a None. + if self.has and not callback(self.val): + none(T) + else: + self + proc `==`*(a, b: Option): bool = ## Returns ``true`` if both ``Option``s are ``none``, @@ -115,8 +145,16 @@ proc `==`*(a, b: Option): bool = (a.has and b.has and a.val == b.val) or (not a.has and not b.has) +proc `$`*[T]( self: Option[T] ): string = + ## Returns the contents of this option or `otherwise` if the option is none. + if self.has: + "Some(" & $self.val & ")" + else: + "None[" & T.name & "]" + + when isMainModule: - import unittest + import unittest, sequtils suite "optionals": # work around a bug in unittest @@ -158,3 +196,27 @@ when isMainModule: check false when compiles(none(string) == none(int)): check false + + test "get with a default value": + check( some("Correct").get("Wrong") == "Correct" ) + check( stringNone.get("Correct") == "Correct" ) + + test "$": + check( $(some("Correct")) == "Some(Correct)" ) + check( $(stringNone) == "None[string]" ) + + test "map with a void result": + var procRan = 0 + some(123).map(proc (v: int) = procRan = v) + check procRan == 123 + intNone.map(proc (v: int) = check false) + + test "map": + check( some(123).map(proc (v: int): int = v * 2) == some(246) ) + check( intNone.map(proc (v: int): int = v * 2).isNone ) + + test "filter": + check( some(123).filter(proc (v: int): bool = v == 123) == some(123) ) + check( some(456).filter(proc (v: int): bool = v == 123).isNone ) + check( intNone.filter(proc (v: int): bool = check false).isNone ) + diff --git a/lib/pure/os.nim b/lib/pure/os.nim index f413371cb..c01228563 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -810,11 +810,12 @@ type {.deprecated: [TPathComponent: PathComponent].} -iterator walkDir*(dir: string): tuple[kind: PathComponent, path: string] {. +iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path: string] {. tags: [ReadDirEffect].} = ## walks over the directory `dir` and yields for each directory or file in ## `dir`. The component type and full path for each item is returned. - ## Walking is not recursive. + ## Walking is not recursive. If ``relative`` is true the resulting path is + ## shortened to be relative to ``dir``. ## Example: This directory structure:: ## dirA / dirB / fileB1.txt ## / dirC @@ -843,7 +844,9 @@ iterator walkDir*(dir: string): tuple[kind: PathComponent, path: string] {. k = pcDir if (f.dwFileAttributes and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32: k = succ(k) - yield (k, dir / extractFilename(getFilename(f))) + let xx = if relative: extractFilename(getFilename(f)) + else: dir / extractFilename(getFilename(f)) + yield (k, xx) if findNextFile(h, f) == 0'i32: break findClose(h) else: @@ -855,7 +858,8 @@ iterator walkDir*(dir: string): tuple[kind: PathComponent, path: string] {. var y = $x.d_name if y != "." and y != "..": var s: Stat - y = dir / y + if not relative: + y = dir / y var k = pcFile when defined(linux) or defined(macosx) or defined(bsd): diff --git a/lib/pure/ospaths.nim b/lib/pure/ospaths.nim index 99f6bcd4d..e9f5bee0a 100644 --- a/lib/pure/ospaths.nim +++ b/lib/pure/ospaths.nim @@ -10,6 +10,10 @@ # Included by the ``os`` module but a module in its own right for NimScript # support. +when isMainModule: + {.pragma: rtl.} + import strutils + when defined(nimscript) or (defined(nimdoc) and not declared(os)): {.pragma: rtl.} {.push hint[ConvFromXtoItselfNotNeeded]:off.} @@ -22,8 +26,10 @@ when not declared(getEnv) or defined(nimscript): ## to an environment variable ReadDirEffect* = object of ReadIOEffect ## effect that denotes a write - ## operation to the directory structure - WriteDirEffect* = object of WriteIOEffect ## effect that denotes a write operation to + ## operation to the directory + ## structure + WriteDirEffect* = object of WriteIOEffect ## effect that denotes a write + ## operation to ## the directory structure OSErrorCode* = distinct int32 ## Specifies an OS Error Code. @@ -59,13 +65,13 @@ when not declared(getEnv) or defined(nimscript): AltSep* = '/' ## An alternative character used by the operating system to separate ## pathname components, or the same as `DirSep` if only one separator - ## character exists. This is set to '/' on Windows systems where `DirSep` - ## is a backslash. + ## character exists. This is set to '/' on Windows systems + ## where `DirSep` is a backslash. PathSep* = ':' ## The character conventionally used by the operating system to separate - ## search patch components (as in PATH), such as ':' for POSIX or ';' for - ## Windows. + ## search patch components (as in PATH), such as ':' for POSIX + ## or ';' for Windows. FileSystemCaseSensitive* = true ## true if the file system is case sensitive, false otherwise. Used by @@ -100,7 +106,8 @@ when not declared(getEnv) or defined(nimscript): # MacOS directory separator is a colon ":" which is the only character not # allowed in filenames. # - # A path containing no colon or which begins with a colon is a partial path. + # A path containing no colon or which begins with a colon is a partial + # path. # E.g. ":kalle:petter" ":kalle" "kalle" # # All other paths are full (absolute) paths. E.g. "HD:kalle:" "HD:" @@ -202,9 +209,9 @@ when not declared(getEnv) or defined(nimscript): proc joinPath*(parts: varargs[string]): string {.noSideEffect, rtl, extern: "nos$1OpenArray".} = - ## The same as `joinPath(head, tail)`, but works with any number of directory - ## parts. You need to pass at least one element or the proc will assert in - ## debug builds and crash on release builds. + ## The same as `joinPath(head, tail)`, but works with any number of + ## directory parts. You need to pass at least one element or the proc + ## will assert in debug builds and crash on release builds. result = parts[0] for i in 1..high(parts): result = joinPath(result, parts[i]) @@ -312,8 +319,8 @@ when not declared(getEnv) or defined(nimscript): if inclusive: yield path proc `/../` * (head, tail: string): string {.noSideEffect.} = - ## The same as ``parentDir(head) / tail`` unless there is no parent directory. - ## Then ``head / tail`` is performed instead. + ## The same as ``parentDir(head) / tail`` unless there is no parent + ## directory. Then ``head / tail`` is performed instead. let sepPos = parentDirPos(head) if sepPos >= 0: result = substr(head, 0, sepPos-1) / tail @@ -496,7 +503,8 @@ when defined(nimdoc) and not declared(os): proc existsFile(x: string): bool = discard when declared(getEnv) or defined(nimscript): - proc getHomeDir*(): string {.rtl, extern: "nos$1", tags: [ReadEnvEffect].} = + proc getHomeDir*(): string {.rtl, extern: "nos$1", + tags: [ReadEnvEffect, ReadIOEffect].} = ## Returns the home directory of the current user. ## ## This proc is wrapped by the expandTilde proc for the convenience of @@ -504,18 +512,21 @@ when declared(getEnv) or defined(nimscript): when defined(windows): return string(getEnv("USERPROFILE")) & "\\" else: return string(getEnv("HOME")) & "/" - proc getConfigDir*(): string {.rtl, extern: "nos$1", tags: [ReadEnvEffect].} = + proc getConfigDir*(): string {.rtl, extern: "nos$1", + tags: [ReadEnvEffect, ReadIOEffect].} = ## Returns the config directory of the current user for applications. when defined(windows): return string(getEnv("APPDATA")) & "\\" else: return string(getEnv("HOME")) & "/.config/" - proc getTempDir*(): string {.rtl, extern: "nos$1", tags: [ReadEnvEffect].} = + proc getTempDir*(): string {.rtl, extern: "nos$1", + tags: [ReadEnvEffect, ReadIOEffect].} = ## Returns the temporary directory of the current user for applications to ## save temporary files in. when defined(windows): return string(getEnv("TEMP")) & "\\" else: return "/tmp/" - proc expandTilde*(path: string): string {.tags: [ReadEnvEffect].} = + proc expandTilde*(path: string): string {. + tags: [ReadEnvEffect, ReadIOEffect].} = ## Expands a path starting with ``~/`` to a full path. ## ## If `path` starts with the tilde character and is followed by `/` or `\\` @@ -523,8 +534,8 @@ when declared(getEnv) or defined(nimscript): ## the getHomeDir() proc, otherwise the input path will be returned without ## modification. ## - ## The behaviour of this proc is the same on the Windows platform despite not - ## having this convention. Example: + ## The behaviour of this proc is the same on the Windows platform despite + ## not having this convention. Example: ## ## .. code-block:: nim ## let configFile = expandTilde("~" / "appname.cfg") @@ -545,7 +556,8 @@ when declared(getEnv) or defined(nimscript): yield substr(s, first, last-1) inc(last) - proc findExe*(exe: string): string {.tags: [ReadDirEffect, ReadEnvEffect].} = + proc findExe*(exe: string): string {. + tags: [ReadDirEffect, ReadEnvEffect, ReadIOEffect].} = ## Searches for `exe` in the current working directory and then ## in directories listed in the ``PATH`` environment variable. ## Returns "" if the `exe` cannot be found. On DOS-like platforms, `exe` diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index 7431be702..fa20afff0 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -24,6 +24,20 @@ when defined(linux): import linux type + ProcessOption* = enum ## options that can be passed `startProcess` + poEchoCmd, ## echo the command before execution + poUsePath, ## Asks system to search for executable using PATH environment + ## variable. + ## On Windows, this is the default. + poEvalCommand, ## Pass `command` directly to the shell, without quoting. + ## Use it only if `command` comes from trused source. + poStdErrToStdOut, ## merge stdout and stderr to the stdout stream + poParentStreams, ## use the parent's streams + poInteractive ## optimize the buffer handling for responsiveness for + ## UI applications. Currently this only affects + ## Windows: Named pipes are used so that you can peek + ## at the process' output streams. + ProcessObj = object of RootObj when defined(windows): fProcessHandle: Handle @@ -34,18 +48,10 @@ type inStream, outStream, errStream: Stream id: Pid exitCode: cint + options: set[ProcessOption] Process* = ref ProcessObj ## represents an operating system process - ProcessOption* = enum ## options that can be passed `startProcess` - poEchoCmd, ## echo the command before execution - poUsePath, ## Asks system to search for executable using PATH environment - ## variable. - ## On Windows, this is the default. - poEvalCommand, ## Pass `command` directly to the shell, without quoting. - ## Use it only if `command` comes from trused source. - poStdErrToStdOut, ## merge stdout and stderr to the stdout stream - poParentStreams ## use the parent's streams {.deprecated: [TProcess: ProcessObj, PProcess: Process, TProcessOption: ProcessOption].} @@ -242,7 +248,8 @@ proc countProcessors*(): int {.rtl, extern: "nosp$1".} = proc execProcesses*(cmds: openArray[string], options = {poStdErrToStdOut, poParentStreams}, n = countProcessors(), - beforeRunEvent: proc(idx: int) = nil): int + beforeRunEvent: proc(idx: int) = nil, + afterRunEvent: proc(idx: int, p: Process) = nil): int {.rtl, extern: "nosp$1", tags: [ExecIOEffect, TimeEffect, ReadEnvEffect, RootEffect]} = ## executes the commands `cmds` in parallel. Creates `n` processes @@ -272,6 +279,7 @@ proc execProcesses*(cmds: openArray[string], err.add("\n") echo(err) result = max(waitForExit(q[r]), result) + if afterRunEvent != nil: afterRunEvent(r, q[r]) if q[r] != nil: close(q[r]) if beforeRunEvent != nil: beforeRunEvent(i) @@ -285,6 +293,7 @@ proc execProcesses*(cmds: openArray[string], if not running(q[r]): #echo(outputStream(q[r]).readLine()) result = max(waitForExit(q[r]), result) + if afterRunEvent != nil: afterRunEvent(r, q[r]) if q[r] != nil: close(q[r]) if beforeRunEvent != nil: beforeRunEvent(i) @@ -293,6 +302,7 @@ proc execProcesses*(cmds: openArray[string], if i > high(cmds): break for j in 0..m-1: result = max(waitForExit(q[j]), result) + if afterRunEvent != nil: afterRunEvent(j, q[j]) if q[j] != nil: close(q[j]) else: for i in 0..high(cmds): @@ -300,9 +310,10 @@ proc execProcesses*(cmds: openArray[string], beforeRunEvent(i) var p = startProcess(cmds[i], options=options + {poEvalCommand}) result = max(waitForExit(p), result) + if afterRunEvent != nil: afterRunEvent(i, p) close(p) -proc select*(readfds: var seq[Process], timeout = 500): int +proc select*(readfds: var seq[Process], timeout = 500): int {.benign.} ## `select` with a sensible Nim interface. `timeout` is in milliseconds. ## Specify -1 for no timeout. Returns the number of processes that are ## ready to read from. The processes that are ready to be read from are @@ -352,7 +363,7 @@ when defined(Windows) and not defined(useNimRtl): # TRUE and n (>0) bytes returned (good data). # FALSE and bytes returned undefined (system error). if a == 0 and br != 0: raiseOSError(osLastError()) - s.atTheEnd = br < bufLen + s.atTheEnd = br == 0 #< bufLen result = br proc hsWriteData(s: Stream, buffer: pointer, bufLen: int) = @@ -394,13 +405,68 @@ when defined(Windows) and not defined(useNimRtl): #var # O_WRONLY {.importc: "_O_WRONLY", header: "<fcntl.h>".}: int # O_RDONLY {.importc: "_O_RDONLY", header: "<fcntl.h>".}: int + proc myDup(h: Handle; inherit: WinBool=1): Handle = + let thisProc = getCurrentProcess() + if duplicateHandle(thisProc, h, + thisProc, addr result,0,inherit, + DUPLICATE_SAME_ACCESS) == 0: + raiseOSError(osLastError()) + + proc createAllPipeHandles(si: var STARTUPINFO; + stdin, stdout, stderr: var Handle) = + var sa: SECURITY_ATTRIBUTES + sa.nLength = sizeof(SECURITY_ATTRIBUTES).cint + sa.lpSecurityDescriptor = nil + sa.bInheritHandle = 1 + let pipeOutName = newWideCString(r"\\.\pipe\stdout") + let pipeInName = newWideCString(r"\\.\pipe\stdin") + let pipeOut = createNamedPipe(pipeOutName, + dwOpenMode=PIPE_ACCESS_INBOUND or FILE_FLAG_WRITE_THROUGH, + dwPipeMode=PIPE_NOWAIT, + nMaxInstances=1, + nOutBufferSize=1024, nInBufferSize=1024, + nDefaultTimeOut=0,addr sa) + if pipeOut == INVALID_HANDLE_VALUE: + raiseOSError(osLastError()) + let pipeIn = createNamedPipe(pipeInName, + dwOpenMode=PIPE_ACCESS_OUTBOUND or FILE_FLAG_WRITE_THROUGH, + dwPipeMode=PIPE_NOWAIT, + nMaxInstances=1, + nOutBufferSize=1024, nInBufferSize=1024, + nDefaultTimeOut=0,addr sa) + if pipeIn == INVALID_HANDLE_VALUE: + raiseOSError(osLastError()) + + si.hStdOutput = createFileW(pipeOutName, + FILE_WRITE_DATA or SYNCHRONIZE, 0, addr sa, + OPEN_EXISTING, # very important flag! + FILE_ATTRIBUTE_NORMAL, + 0 # no template file for OPEN_EXISTING + ) + if si.hStdOutput == INVALID_HANDLE_VALUE: + raiseOSError(osLastError()) + si.hStdError = myDup(si.hStdOutput) + si.hStdInput = createFileW(pipeInName, + FILE_READ_DATA or SYNCHRONIZE, 0, addr sa, + OPEN_EXISTING, # very important flag! + FILE_ATTRIBUTE_NORMAL, + 0 # no template file for OPEN_EXISTING + ) + if si.hStdOutput == INVALID_HANDLE_VALUE: + raiseOSError(osLastError()) + + stdin = myDup(pipeIn, 0) + stdout = myDup(pipeOut, 0) + discard closeHandle(pipeIn) + discard closeHandle(pipeOut) + stderr = stdout proc createPipeHandles(rdHandle, wrHandle: var Handle) = - var piInheritablePipe: SECURITY_ATTRIBUTES - piInheritablePipe.nLength = sizeof(SECURITY_ATTRIBUTES).cint - piInheritablePipe.lpSecurityDescriptor = nil - piInheritablePipe.bInheritHandle = 1 - if createPipe(rdHandle, wrHandle, piInheritablePipe, 1024) == 0'i32: + var sa: SECURITY_ATTRIBUTES + sa.nLength = sizeof(SECURITY_ATTRIBUTES).cint + sa.lpSecurityDescriptor = nil + sa.bInheritHandle = 1 + if createPipe(rdHandle, wrHandle, sa, 1024) == 0'i32: raiseOSError(osLastError()) proc fileClose(h: Handle) {.inline.} = @@ -417,16 +483,20 @@ when defined(Windows) and not defined(useNimRtl): success: int hi, ho, he: Handle new(result) + result.options = options si.cb = sizeof(si).cint if poParentStreams notin options: si.dwFlags = STARTF_USESTDHANDLES # STARTF_USESHOWWINDOW or - createPipeHandles(si.hStdInput, hi) - createPipeHandles(ho, si.hStdOutput) - if poStdErrToStdOut in options: - si.hStdError = si.hStdOutput - he = ho + if poInteractive notin options: + createPipeHandles(si.hStdInput, hi) + createPipeHandles(ho, si.hStdOutput) + if poStdErrToStdOut in options: + si.hStdError = si.hStdOutput + he = ho + else: + createPipeHandles(he, si.hStdError) else: - createPipeHandles(he, si.hStdError) + createAllPipeHandles(si, hi, ho, he) result.inHandle = FileHandle(hi) result.outHandle = FileHandle(ho) result.errHandle = FileHandle(he) @@ -469,6 +539,7 @@ when defined(Windows) and not defined(useNimRtl): if e != nil: dealloc(e) if success == 0: + if poInteractive in result.options: close(result) const errInvalidParameter = 87.int const errFileNotFound = 2.int if lastError.int in {errInvalidParameter, errFileNotFound}: @@ -482,12 +553,12 @@ when defined(Windows) and not defined(useNimRtl): result.id = procInfo.dwProcessId proc close(p: Process) = - when false: - # somehow this does not work on Windows: + if poInteractive in p.options: + # somehow this is not always required on Windows: discard closeHandle(p.inHandle) discard closeHandle(p.outHandle) discard closeHandle(p.errHandle) - discard closeHandle(p.FProcessHandle) + #discard closeHandle(p.FProcessHandle) proc suspend(p: Process) = discard suspendThread(p.fProcessHandle) @@ -564,7 +635,7 @@ when defined(Windows) and not defined(useNimRtl): assert readfds.len <= MAXIMUM_WAIT_OBJECTS var rfds: WOHandleArray for i in 0..readfds.len()-1: - rfds[i] = readfds[i].fProcessHandle + rfds[i] = readfds[i].outHandle #fProcessHandle var ret = waitForMultipleObjects(readfds.len.int32, addr(rfds), 0'i32, timeout.int32) @@ -578,6 +649,11 @@ when defined(Windows) and not defined(useNimRtl): readfds.del(i) return 1 + proc hasData*(p: Process): bool = + var x: int32 + if peekNamedPipe(p.outHandle, lpTotalBytesAvail=addr x): + result = x > 0 + elif not defined(useNimRtl): const readIdx = 0 @@ -635,6 +711,7 @@ elif not defined(useNimRtl): var pStdin, pStdout, pStderr: array [0..1, cint] new(result) + result.options = options result.exitCode = -3 # for ``waitForExit`` if poParentStreams notin options: if pipe(pStdin) != 0'i32 or pipe(pStdout) != 0'i32 or @@ -960,6 +1037,15 @@ elif not defined(useNimRtl): pruneProcessSet(readfds, (rd)) + proc hasData*(p: Process): bool = + var rd: TFdSet + + FD_ZERO(rd) + let m = max(0, int(p.outHandle)) + FD_SET(cint(p.outHandle), rd) + + result = int(select(cint(m+1), addr(rd), nil, nil, nil)) == 1 + proc execCmdEx*(command: string, options: set[ProcessOption] = { poStdErrToStdOut, poUsePath}): tuple[ diff --git a/lib/pure/rationals.nim b/lib/pure/rationals.nim index 60d09c71a..72c64befc 100644 --- a/lib/pure/rationals.nim +++ b/lib/pure/rationals.nim @@ -18,8 +18,9 @@ type Rational*[T] = object ## a rational number, consisting of a numerator and denominator num*, den*: T -proc initRational*[T](num, den: T): Rational[T] = +proc initRational*[T:SomeInteger](num, den: T): Rational[T] = ## Create a new rational number. + assert(den != 0, "a denominator of zero value is invalid") result.num = num result.den = den @@ -33,11 +34,68 @@ proc `$`*[T](x: Rational[T]): string = ## Turn a rational number into a string. result = $x.num & "/" & $x.den -proc toRational*[T](x: T): Rational[T] = +proc toRational*[T:SomeInteger](x: T): Rational[T] = ## Convert some integer `x` to a rational number. result.num = x result.den = 1 +proc toRationalSub(x: float, n: int): Rational[int] = + var + a = 0 + b, c, d = 1 + result = 0 // 1 # rational 0 + while b <= n and d <= n: + let ac = (a+c) + let bd = (b+d) + # scale by 1000 so not overflow for high precision + let mediant = (ac/1000) / (bd/1000) + if x == mediant: + if bd <= n: + result.num = ac + result.den = bd + return result + elif d > b: + result.num = c + result.den = d + return result + else: + result.num = a + result.den = b + return result + elif x > mediant: + a = ac + b = bd + else: + c = ac + d = bd + if (b > n): + return initRational(c, d) + return initRational(a, b) + +proc toRational*(x: float, n: int = high(int)): Rational[int] = + ## Calculate the best rational numerator and denominator + ## that approximates to `x`, where the denominator is + ## smaller than `n` (default is the largest possible + ## int to give maximum resolution) + ## + ## The algorithm is based on the Farey sequence named + ## after John Farey + ## + ## .. code-block:: Nim + ## import math, rationals + ## for i in 1..10: + ## let t = (10 ^ (i+3)).int + ## let x = toRational(PI, t) + ## let newPI = x.num / x.den + ## echo x, " ", newPI, " error: ", PI - newPI, " ", t + if x > 1: + result = toRationalSub(1.0/x, n) + swap(result.num, result.den) + elif x == 1.0: + result = 1 // 1 + else: + result = toRationalSub(x, n) + proc toFloat*[T](x: Rational[T]): float = ## Convert a rational number `x` to a float. x.num / x.den @@ -47,7 +105,7 @@ proc toInt*[T](x: Rational[T]): int = ## `x` does not contain an integer value. x.num div x.den -proc reduce*[T](x: var Rational[T]) = +proc reduce*[T:SomeInteger](x: var Rational[T]) = ## Reduce rational `x`. let common = gcd(x.num, x.den) if x.den > 0: @@ -287,3 +345,8 @@ when isMainModule: assert toRational(5) == 5//1 assert abs(toFloat(y) - 0.4814814814814815) < 1.0e-7 assert toInt(z) == 0 + + assert toRational(0.98765432) == 12345679 // 12500000 + assert toRational(0.1, 1000000) == 1 // 10 + assert toRational(0.9, 1000000) == 9 // 10 + assert toRational(PI) == 80143857 // 25510582 diff --git a/lib/pure/selectors.nim b/lib/pure/selectors.nim index bfc393a96..ca969c761 100644 --- a/lib/pure/selectors.nim +++ b/lib/pure/selectors.nim @@ -13,6 +13,8 @@ import os, unsigned, hashes when defined(linux): import posix, epoll +elif defined(macosx) or defined(freebsd) or defined(openbsd) or defined(netbsd): + import posix, kqueue, times elif defined(windows): import winlean else: @@ -79,7 +81,6 @@ when defined(nimdoc): proc `[]`*(s: Selector, fd: SocketHandle): SelectorKey = ## Retrieves the selector key for ``fd``. - elif defined(linux): type Selector* = object @@ -99,15 +100,13 @@ elif defined(linux): result.data.fd = fd.cint proc register*(s: var Selector, fd: SocketHandle, events: set[Event], - data: SelectorData) = + data: SelectorData) = var event = createEventStruct(events, fd) if events != {}: if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fd, addr(event)) != 0: raiseOSError(osLastError()) - var key = SelectorKey(fd: fd, events: events, data: data) - - s.fds[fd] = key + s.fds[fd] = SelectorKey(fd: fd, events: events, data: data) proc update*(s: var Selector, fd: SocketHandle, events: set[Event]) = if s.fds[fd].events != events: @@ -154,11 +153,6 @@ elif defined(linux): raiseOSError(err) proc select*(s: var Selector, timeout: int): seq[ReadyInfo] = - ## - ## The ``events`` field of the returned ``key`` contains the original events - ## for which the ``fd`` was bound. This is contrary to the ``events`` field - ## of the ``TReadyInfo`` tuple which determines which events are ready - ## on the ``fd``. result = @[] let evNum = epoll_wait(s.epollFD, addr s.events[0], 64.cint, timeout.cint) if evNum < 0: @@ -204,6 +198,86 @@ elif defined(linux): ## Retrieves the selector key for ``fd``. return s.fds[fd] +elif defined(macosx) or defined(freebsd) or defined(openbsd) or defined(netbsd): + type + Selector* = object + kqFD: cint + events: array[64, KEvent] + when MultiThreaded: + fds: SharedTable[SocketHandle, SelectorKey] + else: + fds: Table[SocketHandle, SelectorKey] + + template modifyKQueue(kqFD: cint, fd: SocketHandle, event: Event, + op: cushort) = + var kev = KEvent(ident: fd.cuint, + filter: if event == EvRead: EVFILT_READ else: EVFILT_WRITE, + flags: op) + if kevent(kqFD, addr kev, 1, nil, 0, nil) == -1: + raiseOSError(osLastError()) + + proc register*(s: var Selector, fd: SocketHandle, events: set[Event], + data: SelectorData) = + for event in events: + modifyKQueue(s.kqFD, fd, event, EV_ADD) + s.fds[fd] = SelectorKey(fd: fd, events: events, data: data) + + proc update*(s: var Selector, fd: SocketHandle, events: set[Event]) = + let previousEvents = s.fds[fd].events + if previousEvents != events: + for event in events-previousEvents: + modifyKQueue(s.kqFD, fd, event, EV_ADD) + for event in previousEvents-events: + modifyKQueue(s.kqFD, fd, event, EV_DELETE) + s.fds.mget(fd).events = events + + proc unregister*(s: var Selector, fd: SocketHandle) = + for event in s.fds[fd].events: + modifyKQueue(s.kqFD, fd, event, EV_DELETE) + s.fds.del(fd) + + proc close*(s: var Selector) = + when MultiThreaded: deinitSharedTable(s.fds) + if s.kqFD.close() != 0: raiseOSError(osLastError()) + + proc select*(s: var Selector, timeout: int): seq[ReadyInfo] = + result = @[] + var tv = Timespec(tv_sec: timeout.Time, tv_nsec: 0) + let evNum = kevent(s.kqFD, nil, 0, addr s.events[0], 64.cint, addr tv) + if evNum < 0: + let err = osLastError() + if err.cint == EINTR: + return @[] + raiseOSError(err) + if evNum == 0: return @[] + for i in 0 .. <evNum: + let fd = s.events[i].ident.SocketHandle + + var evSet: set[Event] = {} + if (s.events[i].flags and EV_EOF) != 0: evSet = evSet + {EvError} + if s.events[i].filter == EVFILT_READ: evSet = evSet + {EvRead} + elif s.events[i].filter == EVFILT_WRITE: evSet = evSet + {EvWrite} + let selectorKey = s.fds[fd] + assert selectorKey.fd != 0.SocketHandle + result.add((selectorKey, evSet)) + + proc newSelector*(): Selector = + result.kqFD = kqueue() + if result.kqFD < 0: + raiseOSError(osLastError()) + when MultiThreaded: + result.fds = initSharedTable[SocketHandle, SelectorKey]() + else: + result.fds = initTable[SocketHandle, SelectorKey]() + + proc contains*(s: Selector, fd: SocketHandle): bool = + ## Determines whether selector contains a file descriptor. + s.fds.hasKey(fd) # and s.fds[fd].events != {} + + proc `[]`*(s: Selector, fd: SocketHandle): SelectorKey = + ## Retrieves the selector key for ``fd``. + return s.fds[fd] + elif not defined(nimdoc): # TODO: kqueue for bsd/mac os x. type diff --git a/lib/pure/streams.nim b/lib/pure/streams.nim index 8aa8d35d8..38e91fee4 100644 --- a/lib/pure/streams.nim +++ b/lib/pure/streams.nim @@ -11,6 +11,26 @@ ## the `FileStream` and the `StringStream` which implement the stream ## interface for Nim file objects (`File`) and strings. Other modules ## may provide other implementations for this standard stream interface. +## +## Examples: +## +## .. code-block:: Nim +## +## import streams +## var +## ss = newStringStream("""The first line +## the second line +## the third line""") +## line = "" +## while ss.readLine(line): +## echo line +## ss.close() +## +## var fs = newFileStream("somefile.txt", fmRead) +## if not isNil(fs): +## while fs.readLine(line): +## echo line +## fs.close() include "system/inclrtl" @@ -81,6 +101,19 @@ proc readData*(s: Stream, buffer: pointer, bufLen: int): int = ## low level proc that reads data into an untyped `buffer` of `bufLen` size. result = s.readDataImpl(s, buffer, bufLen) +proc readAll*(s: Stream): string = + ## Reads all available data. + const bufferSize = 1000 + result = newString(bufferSize) + var r = 0 + while true: + let readBytes = readData(s, addr(result[r]), bufferSize) + if readBytes < bufferSize: + setLen(result, r+readBytes) + break + inc r, bufferSize + setLen(result, r+bufferSize) + proc readData*(s, unused: Stream, buffer: pointer, bufLen: int): int {.deprecated.} = ## low level proc that reads data into an untyped `buffer` of `bufLen` size. @@ -371,7 +404,7 @@ when not defined(js): result.writeDataImpl = fsWriteData result.flushImpl = fsFlush - proc newFileStream*(filename: string, mode: FileMode): FileStream = + proc newFileStream*(filename: string, mode: FileMode = fmRead): FileStream = ## creates a new stream from the file named `filename` with the mode `mode`. ## If the file cannot be opened, nil is returned. See the `system ## <system.html>`_ module for a list of available FileMode enums. diff --git a/lib/pure/strtabs.nim b/lib/pure/strtabs.nim index 86f81aa43..1ce9067a7 100644 --- a/lib/pure/strtabs.nim +++ b/lib/pure/strtabs.nim @@ -173,6 +173,9 @@ proc clear*(s: StringTableRef, mode: StringTableMode) = s.mode = mode s.counter = 0 s.data.setLen(startSize) + for i in 0..<s.data.len: + if not isNil(s.data[i].key): + s.data[i].key = nil proc newStringTable*(keyValuePairs: varargs[string], mode: StringTableMode): StringTableRef {. @@ -248,3 +251,6 @@ when isMainModule: x.mget("11") = "23" assert x["11"] == "23" + x.clear(modeCaseInsensitive) + x["11"] = "22" + assert x["11"] == "22" diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index d1c09f43d..a78fed4b9 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -60,6 +60,132 @@ const ## doAssert "01234".find(invalid) == -1 ## doAssert "01A34".find(invalid) == 2 +proc isAlpha*(c: char): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsAlphaChar".}= + ## Checks whether or not `c` is alphabetical. + ## + ## This checks a-z, A-Z ASCII characters only. + return c in Letters + +proc isAlphaNumeric*(c: char): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsAlphaNumericChar".}= + ## Checks whether or not `c` is alphanumeric. + ## + ## This checks a-z, A-Z, 0-9 ASCII characters only. + return c in Letters or c in Digits + +proc isDigit*(c: char): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsDigitChar".}= + ## Checks whether or not `c` is a number. + ## + ## This checks 0-9 ASCII characters only. + return c in Digits + +proc isSpace*(c: char): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsSpaceChar".}= + ## Checks whether or not `c` is a whitespace character. + return c in Whitespace + +proc isLower*(c: char): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsLowerChar".}= + ## Checks whether or not `c` is a lower case character. + ## + ## This checks ASCII characters only. + return c in {'a'..'z'} + +proc isUpper*(c: char): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsUpperChar".}= + ## Checks whether or not `c` is an upper case character. + ## + ## This checks ASCII characters only. + return c in {'A'..'Z'} + +proc isAlpha*(s: string): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsAlphaStr".}= + ## Checks whether or not `s` is alphabetical. + ## + ## This checks a-z, A-Z ASCII characters only. + ## Returns true if all characters in `s` are + ## alphabetic and there is at least one character + ## in `s`. + if s.len() == 0: + return false + + result = true + for c in s: + result = c.isAlpha() and result + +proc isAlphaNumeric*(s: string): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsAlphaNumericStr".}= + ## Checks whether or not `s` is alphanumeric. + ## + ## This checks a-z, A-Z, 0-9 ASCII characters only. + ## Returns true if all characters in `s` are + ## alpanumeric and there is at least one character + ## in `s`. + if s.len() == 0: + return false + + result = true + for c in s: + result = c.isAlphaNumeric() and result + +proc isDigit*(s: string): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsDigitStr".}= + ## Checks whether or not `s` is a numeric value. + ## + ## This checks 0-9 ASCII characters only. + ## Returns true if all characters in `s` are + ## numeric and there is at least one character + ## in `s`. + if s.len() == 0: + return false + + result = true + for c in s: + result = c.isDigit() and result + +proc isSpace*(s: string): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsSpaceStr".}= + ## Checks whether or not `s` is completely whitespace. + ## + ## Returns true if all characters in `s` are whitespace + ## characters and there is at least one character in `s`. + if s.len() == 0: + return false + + result = true + for c in s: + result = c.isSpace() and result + +proc isLower*(s: string): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsLowerStr".}= + ## Checks whether or not `s` contains all lower case characters. + ## + ## This checks ASCII characters only. + ## Returns true if all characters in `s` are lower case + ## and there is at least one character in `s`. + if s.len() == 0: + return false + + result = true + for c in s: + result = c.isLower() and result + +proc isUpper*(s: string): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsUpperStr".}= + ## Checks whether or not `s` contains all upper case characters. + ## + ## This checks ASCII characters only. + ## Returns true if all characters in `s` are upper case + ## and there is at least one character in `s`. + if s.len() == 0: + return false + + result = true + for c in s: + result = c.isUpper() and result + proc toLower*(c: char): char {.noSideEffect, procvar, rtl, extern: "nsuToLowerChar".} = ## Converts `c` into lower case. @@ -169,7 +295,8 @@ proc cmpIgnoreStyle*(a, b: string): int {.noSideEffect, inc(j) -proc strip*(s: string, leading = true, trailing = true, chars: set[char] = Whitespace): string +proc strip*(s: string, leading = true, trailing = true, + chars: set[char] = Whitespace): string {.noSideEffect, rtl, extern: "nsuStrip".} = ## Strips `chars` from `s` and returns the resulting string. ## @@ -504,7 +631,8 @@ proc repeat*(c: char, count: Natural): string {.noSideEffect, ## ## .. code-block:: nim ## proc tabexpand(indent: int, text: string, tabsize: int = 4) = - ## echo '\t'.repeat(indent div tabsize), ' '.repeat(indent mod tabsize), text + ## echo '\t'.repeat(indent div tabsize), ' '.repeat(indent mod tabsize), + ## text ## ## tabexpand(4, "At four") ## tabexpand(5, "At five") @@ -533,11 +661,13 @@ template spaces*(n: Natural): string = repeat(' ',n) ## echo text1 & spaces(max(0, width - text1.len)) & "|" ## echo text2 & spaces(max(0, width - text2.len)) & "|" -proc repeatChar*(count: Natural, c: char = ' '): string {.deprecated.} = repeat(c, count) +proc repeatChar*(count: Natural, c: char = ' '): string {.deprecated.} = ## deprecated: use repeat() or spaces() + repeat(c, count) -proc repeatStr*(count: Natural, s: string): string {.deprecated.} = repeat(s, count) +proc repeatStr*(count: Natural, s: string): string {.deprecated.} = ## deprecated: use repeat(string, count) or string.repeat(count) + repeat(s, count) proc align*(s: string, count: Natural, padding = ' '): string {. noSideEffect, rtl, extern: "nsuAlignString".} = @@ -850,8 +980,8 @@ proc rfind*(s: string, sub: char, start: int = -1): int {.noSideEffect, if sub == s[i]: return i return -1 -proc count*(s: string, sub: string, overlapping: bool = false): int {.noSideEffect, - rtl, extern: "nsuCountString".} = +proc count*(s: string, sub: string, overlapping: bool = false): int {. + noSideEffect, rtl, extern: "nsuCountString".} = ## Count the occurrences of a substring `sub` in the string `s`. ## Overlapping occurrences of `sub` only count when `overlapping` ## is set to true. @@ -1449,7 +1579,8 @@ proc removeSuffix*(s: var string, chars: set[char] = Newlines) {. s.setLen(last + 1) -proc removeSuffix*(s: var string, c: char) {.rtl, extern: "nsuRemoveSuffixChar".} = +proc removeSuffix*(s: var string, c: char) {. + rtl, extern: "nsuRemoveSuffixChar".} = ## Removes a single character (in-place) from a string. ## .. code-block:: nim ## var @@ -1515,8 +1646,61 @@ when isMainModule: doAssert strip("sfoofoofoos", chars = {'s'}) == "foofoofoo" doAssert strip("barfoofoofoobar", chars = {'b', 'a', 'r'}) == "foofoofoo" doAssert strip("stripme but don't strip this stripme", - chars = {'s', 't', 'r', 'i', 'p', 'm', 'e'}) == " but don't strip this " + chars = {'s', 't', 'r', 'i', 'p', 'm', 'e'}) == + " but don't strip this " doAssert strip("sfoofoofoos", leading = false, chars = {'s'}) == "sfoofoofoo" doAssert strip("sfoofoofoos", trailing = false, chars = {'s'}) == "foofoofoos" doAssert " foo\n bar".indent(4, "Q") == "QQQQ foo\nQQQQ bar" + + doAssert isAlpha('r') + doAssert isAlpha('A') + doAssert(not isAlpha('$')) + + doAssert isAlpha("Rasp") + doAssert isAlpha("Args") + doAssert(not isAlpha("$Tomato")) + + doAssert isAlphaNumeric('3') + doAssert isAlphaNumeric('R') + doAssert(not isAlphaNumeric('!')) + + doAssert isAlphaNumeric("34ABc") + doAssert isAlphaNumeric("Rad") + doAssert isAlphaNumeric("1234") + doAssert(not isAlphaNumeric("@nose")) + + doAssert isDigit('3') + doAssert(not isDigit('a')) + doAssert(not isDigit('%')) + + doAssert isDigit("12533") + doAssert(not isDigit("12.33")) + doAssert(not isDigit("A45b")) + + doAssert isSpace('\t') + doAssert isSpace('\l') + doAssert(not isSpace('A')) + + doAssert isSpace("\t\l \v\r\f") + doAssert isSpace(" ") + doAssert(not isSpace("ABc \td")) + + doAssert isLower('a') + doAssert isLower('z') + doAssert(not isLower('A')) + doAssert(not isLower('5')) + doAssert(not isLower('&')) + + doAssert isLower("abcd") + doAssert(not isLower("abCD")) + doAssert(not isLower("33aa")) + + doAssert isUpper('A') + doAssert(not isUpper('b')) + doAssert(not isUpper('5')) + doAssert(not isUpper('%')) + + doAssert isUpper("ABC") + doAssert(not isUpper("AAcc")) + doAssert(not isUpper("A#$")) diff --git a/lib/pure/subexes.nim b/lib/pure/subexes.nim index 2d1adc0eb..5824ace81 100644 --- a/lib/pure/subexes.nim +++ b/lib/pure/subexes.nim @@ -351,6 +351,7 @@ proc format*(formatstr: Subex, a: varargs[string, `$`]): string {.noSideEffect, {.pop.} when isMainModule: + from strutils import replace proc `%`(formatstr: string, a: openarray[string]): string = result = newStringOfCap(formatstr.len + a.len shl 4) @@ -382,18 +383,18 @@ when isMainModule: doAssert "${$1}" % "1" == "1" doAssert "${$$-1} $$1" % "1" == "1 $1" - doAssert "$#($', '10c'\n '{#..})" % ["doAssert", "longishA", "longish"] == + doAssert(("$#($', '10c'\n '{#..})" % ["doAssert", "longishA", "longish"]).replace(" \n", "\n") == """doAssert( longishA, - longish)""" + longish)""") - assert "type MyEnum* = enum\n $', '2i'\n '{..}" % ["fieldA", - "fieldB", "FiledClkad", "fieldD", "fieldE", "longishFieldName"] == + doAssert(("type MyEnum* = enum\n $', '2i'\n '{..}" % ["fieldA", + "fieldB", "FiledClkad", "fieldD", "fieldE", "longishFieldName"]).replace(" \n", "\n") == strutils.unindent """ type MyEnum* = enum fieldA, fieldB, FiledClkad, fieldD, - fieldE, longishFieldName""" + fieldE, longishFieldName""") doAssert subex"$1($', '{2..})" % ["f", "a", "b", "c"] == "f(a, b, c)" @@ -401,12 +402,10 @@ when isMainModule: doAssert subex"$['''|'|''''|']']#" % "0" == "'|" - assert subex("type\n Enum = enum\n $', '40c'\n '{..}") % [ - "fieldNameA", "fieldNameB", "fieldNameC", "fieldNameD"] == + doAssert((subex("type\n Enum = enum\n $', '40c'\n '{..}") % [ + "fieldNameA", "fieldNameB", "fieldNameC", "fieldNameD"]).replace(" \n", "\n") == strutils.unindent """ type Enum = enum fieldNameA, fieldNameB, fieldNameC, - fieldNameD""" - - + fieldNameD""") diff --git a/lib/pure/unicode.nim b/lib/pure/unicode.nim index 396957f6c..b059a7315 100644 --- a/lib/pure/unicode.nim +++ b/lib/pure/unicode.nim @@ -1319,15 +1319,43 @@ proc reversed*(s: string): string = reverseUntil(len(s)) +proc graphemeLen*(s: string; i: Natural): Natural = + ## The number of bytes belonging to 's[i]' including following combining + ## characters. + var j = i.int + var r, r2: Rune + if j < s.len: + fastRuneAt(s, j, r, true) + result = j-i + while j < s.len: + fastRuneAt(s, j, r2, true) + if not isCombining(r2): break + result = j-i + +proc lastRune*(s: string; last: int): (Rune, int) = + ## length of the last rune in 's[0..last]'. Returns the rune and its length + ## in bytes. + if s[last] <= chr(127): + result = (Rune(s[last]), 1) + else: + var L = 0 + while last-L >= 0 and ord(s[last-L]) shr 6 == 0b10: inc(L) + var r: Rune + fastRuneAt(s, last-L, r, false) + result = (r, L+1) + when isMainModule: let someString = "öÑ" someRunes = @[runeAt(someString, 0), runeAt(someString, 2)] compared = (someString == $someRunes) - assert compared == true + doAssert compared == true - assert reversed("Reverse this!") == "!siht esreveR" - assert reversed("先秦兩漢") == "漢兩秦先" - assert reversed("as⃝df̅") == "f̅ds⃝a" - assert reversed("a⃞b⃞c⃞") == "c⃞b⃞a⃞" - assert len(toRunes("as⃝df̅")) == runeLen("as⃝df̅") + doAssert reversed("Reverse this!") == "!siht esreveR" + doAssert reversed("先秦兩漢") == "漢兩秦先" + doAssert reversed("as⃝df̅") == "f̅ds⃝a" + doAssert reversed("a⃞b⃞c⃞") == "c⃞b⃞a⃞" + doAssert len(toRunes("as⃝df̅")) == runeLen("as⃝df̅") + const test = "as⃝" + doAssert lastRune(test, test.len-1)[1] == 3 + doAssert graphemeLen("è", 0) == 2 diff --git a/lib/pure/uri.nim b/lib/pure/uri.nim index 492de3b46..abb1a462d 100644 --- a/lib/pure/uri.nim +++ b/lib/pure/uri.nim @@ -142,6 +142,7 @@ proc parseUri*(uri: string): Uri = parseUri(uri, result) proc removeDotSegments(path: string): string = + if path.len == 0: return "" var collection: seq[string] = @[] let endsWithSlash = path[path.len-1] == '/' var i = 0 @@ -432,3 +433,12 @@ when isMainModule: block: let test = parseUri("http://example.com/foo/") / "/bar/asd" doAssert test.path == "/foo/bar/asd" + + # removeDotSegments tests + block: + # empty test + doAssert removeDotSegments("") == "" + + # bug #3207 + block: + doAssert parseUri("http://qq/1").combine(parseUri("https://qqq")).`$` == "https://qqq" diff --git a/lib/pure/xmltree.nim b/lib/pure/xmltree.nim index 1c8573986..7c97a0a56 100644 --- a/lib/pure/xmltree.nim +++ b/lib/pure/xmltree.nim @@ -104,10 +104,23 @@ proc tag*(n: XmlNode): string {.inline.} = assert n.k == xnElement result = n.fTag +proc `tag=`*(n: XmlNode, tag: string) {.inline.} = + ## sets the tag name of `n`. `n` has to be an ``xnElement`` node. + assert n.k == xnElement + n.fTag = tag + proc add*(father, son: XmlNode) {.inline.} = ## adds the child `son` to `father`. add(father.s, son) +proc insert*(father, son: XmlNode, index: int) {.inline.} = + ## insert the child `son` to a given position in `father`. + assert father.k == xnElement and son.k == xnElement + if len(father.s) > index: + insert(father.s, son, index) + else: + insert(father.s, son, len(father.s)) + proc len*(n: XmlNode): int {.inline.} = ## returns the number `n`'s children. if n.k == xnElement: result = len(n.s) diff --git a/lib/system.nim b/lib/system.nim index 8f529b8c0..89de08c6f 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -221,11 +221,21 @@ proc high*[T](x: T): T {.magic: "High", noSideEffect.} ## the highest possible value of an ordinal value `x`. As a special ## semantic rule, `x` may also be a type identifier. ## ``high(int)`` is Nim's way of writing `INT_MAX`:idx: or `MAX_INT`:idx:. + ## + ## .. code-block:: nim + ## var arr = [1,2,3,4,5,6,7] + ## high(arr) #=> 6 + ## high(2) #=> 9223372036854775807 proc low*[T](x: T): T {.magic: "Low", noSideEffect.} ## returns the lowest possible index of an array, a sequence, a string or ## the lowest possible value of an ordinal value `x`. As a special ## semantic rule, `x` may also be a type identifier. + ## + ## .. code-block:: nim + ## var arr = [1,2,3,4,5,6,7] + ## high(arr) #=> 0 + ## high(2) #=> -9223372036854775808 type range*{.magic: "Range".}[T] ## Generic type to construct range types. @@ -243,7 +253,7 @@ when defined(nimArrIdx): # :array|openarray|string|seq|cstring|tuple proc `[]`*[I: Ordinal;T](a: T; i: I): T {. noSideEffect, magic: "ArrGet".} - proc `[]=`*[I: Ordinal;T,S](a: var T; i: I; + proc `[]=`*[I: Ordinal;T,S](a: T; i: I; x: S) {.noSideEffect, magic: "ArrPut".} proc `=`*[T](dest: var T; src: T) {.noSideEffect, magic: "Asgn".} @@ -584,6 +594,10 @@ proc sizeof*[T](x: T): int {.magic: "SizeOf", noSideEffect.} ## its usage is discouraged - using ``new`` for the most cases suffices ## that one never needs to know ``x``'s size. As a special semantic rule, ## ``x`` may also be a type identifier (``sizeof(int)`` is valid). + ## + ## .. code-block:: nim + ## sizeof('A') #=> 1 + ## sizeof(2) #=> 8 when defined(nimtypedescfixed): proc sizeof*(x: typedesc): int {.magic: "SizeOf", noSideEffect.} @@ -610,11 +624,21 @@ proc inc*[T: Ordinal|uint|uint64](x: var T, y = 1) {.magic: "Inc", noSideEffect. ## increments the ordinal ``x`` by ``y``. If such a value does not ## exist, ``EOutOfRange`` is raised or a compile time error occurs. This is a ## short notation for: ``x = succ(x, y)``. + ## + ## .. code-block:: nim + ## var i = 2 + ## inc(i) #=> 3 + ## inc(i, 3) #=> 6 proc dec*[T: Ordinal|uint|uint64](x: var T, y = 1) {.magic: "Dec", noSideEffect.} ## decrements the ordinal ``x`` by ``y``. If such a value does not ## exist, ``EOutOfRange`` is raised or a compile time error occurs. This is a ## short notation for: ``x = pred(x, y)``. + ## + ## .. code-block:: nim + ## var i = 2 + ## dec(i) #=> 1 + ## dec(i, 3) #=> -2 proc newSeq*[T](s: var seq[T], len: Natural) {.magic: "NewSeq", noSideEffect.} ## creates a new sequence of type ``seq[T]`` with length ``len``. @@ -659,11 +683,22 @@ proc len*[T](x: seq[T]): int {.magic: "LengthSeq", noSideEffect.} ## returns the length of an array, an openarray, a sequence or a string. ## This is roughly the same as ``high(T)-low(T)+1``, but its resulting type is ## always an int. + ## + ## .. code-block:: nim + ## var arr = [1,1,1,1,1] + ## len(arr) #=> 5 + ## for i in 0..<arr.len: + ## echo arr[i] #=> 1,1,1,1,1 # set routines: proc incl*[T](x: var set[T], y: T) {.magic: "Incl", noSideEffect.} ## includes element ``y`` to the set ``x``. This is the same as ## ``x = x + {y}``, but it might be more efficient. + ## + ## .. code-block:: nim + ## var a = initSet[int](4) + ## a.incl(2) #=> {2} + ## a.incl(3) #=> {2, 3} template incl*[T](s: var set[T], flags: set[T]) = ## includes the set of flags to the set ``x``. @@ -672,6 +707,10 @@ template incl*[T](s: var set[T], flags: set[T]) = proc excl*[T](x: var set[T], y: T) {.magic: "Excl", noSideEffect.} ## excludes element ``y`` to the set ``x``. This is the same as ## ``x = x - {y}``, but it might be more efficient. + ## + ## .. code-block:: nim + ## var b = {2,3,5,6,12,545} + ## b.excl(5) #=> {2,3,6,12,545} template excl*[T](s: var set[T], flags: set[T]) = ## excludes the set of flags to ``x``. @@ -680,12 +719,22 @@ template excl*[T](s: var set[T], flags: set[T]) = proc card*[T](x: set[T]): int {.magic: "Card", noSideEffect.} ## returns the cardinality of the set ``x``, i.e. the number of elements ## in the set. + ## + ## .. code-block:: nim + ## var i = {1,2,3,4} + ## card(i) #=> 4 proc ord*[T](x: T): int {.magic: "Ord", noSideEffect.} ## returns the internal int value of an ordinal value ``x``. + ## + ## .. code-block:: nim + ## ord('A') #=> 65 proc chr*(u: range[0..255]): char {.magic: "Chr", noSideEffect.} ## converts an int in the range 0..255 to a character. + ## + ## .. code-block:: nim + ## chr(65) #=> A # -------------------------------------------------------------------------- # built-in operators @@ -1207,10 +1256,19 @@ proc compileOption*(option, arg: string): bool {. ## echo "compiled with optimization for size and uses Boehm's GC" const - hasThreadSupport = compileOption("threads") + hasThreadSupport = compileOption("threads") and not defined(nimscript) hasSharedHeap = defined(boehmgc) or defined(gogc) # don't share heaps; every thread has its own taintMode = compileOption("taintmode") +when defined(boehmgc): + when defined(windows): + const boehmLib = "boehmgc.dll" + elif defined(macosx): + const boehmLib = "libgc.dylib" + else: + const boehmLib = "libgc.so.1" + {.pragma: boehmGC, noconv, dynlib: boehmLib.} + when taintMode: type TaintedString* = distinct string ## a distinct string type that ## is `tainted`:idx:. It is an alias for @@ -1290,6 +1348,10 @@ proc add *[T](x: var seq[T], y: openArray[T]) {.noSideEffect.} = ## containers should also call their adding proc `add` for consistency. ## Generic code becomes much easier to write if the Nim naming scheme is ## respected. + ## + ## .. code-block:: nim + ## var s: seq[string] = @["test2","test2"] + ## s.add("test") #=> @[test2, test2, test] let xl = x.len setLen(x, xl + y.len) for i in 0..high(y): x[xl+i] = y[i] @@ -1304,6 +1366,10 @@ proc shallowCopy*[T](x: var T, y: T) {.noSideEffect, magic: "ShallowCopy".} proc del*[T](x: var seq[T], i: Natural) {.noSideEffect.} = ## deletes the item at index `i` by putting ``x[high(x)]`` into position `i`. ## This is an O(1) operation. + ## + ## .. code-block:: nim + ## var i = @[1,2,3,4,5] + ## i.del(2) #=> @[1, 2, 5, 4] let xl = x.len - 1 shallowCopy(x[i], x[xl]) setLen(x, xl) @@ -1311,6 +1377,10 @@ proc del*[T](x: var seq[T], i: Natural) {.noSideEffect.} = proc delete*[T](x: var seq[T], i: Natural) {.noSideEffect.} = ## deletes the item at index `i` by moving ``x[i+1..]`` by one position. ## This is an O(n) operation. + ## + ## .. code-block:: nim + ## var i = @[1,2,3,4,5] + ## i.delete(2) #=> @[1, 2, 4, 5] template defaultImpl = let xl = x.len for j in i..xl-2: shallowCopy(x[j], x[j+1]) @@ -1326,6 +1396,10 @@ proc delete*[T](x: var seq[T], i: Natural) {.noSideEffect.} = proc insert*[T](x: var seq[T], item: T, i = 0.Natural) {.noSideEffect.} = ## inserts `item` into `x` at position `i`. + ## + ## .. code-block:: nim + ## var i = @[1,2,3,4,5] + ## i.insert(2,4) #=> @[1, 2, 3, 4, 2, 5] template defaultImpl = let xl = x.len setLen(x, xl+1) @@ -1346,6 +1420,12 @@ proc repr*[T](x: T): string {.magic: "Repr", noSideEffect.} ## takes any Nim variable and returns its string representation. It ## works even for complex data graphs with cycles. This is a great ## debugging tool. + ## + ## .. code-block:: nim + ## var s: seq[string] = @["test2","test2"] + ## var i = @[1,2,3,4,5] + ## repr(s) #=> 0x1055eb050[0x1055ec050"test2", 0x1055ec078"test2"] + ## repr(i) #=> 0x1055ed050[1, 2, 3, 4, 5] type ByteAddress* = int @@ -2089,53 +2169,6 @@ proc pop*[T](s: var seq[T]): T {.inline, noSideEffect.} = result = s[L] setLen(s, L) -proc each*[T, S](data: openArray[T], op: proc (x: T): S {.closure.}): seq[S] {. - deprecated.} = - ## The well-known ``map`` operation from functional programming. Applies - ## `op` to every item in `data` and returns the result as a sequence. - ## - ## **Deprecated since version 0.9:** Use the ``map`` proc instead. - newSeq(result, data.len) - for i in 0..data.len-1: result[i] = op(data[i]) - -proc each*[T](data: var openArray[T], op: proc (x: var T) {.closure.}) {. - deprecated.} = - ## The well-known ``map`` operation from functional programming. Applies - ## `op` to every item in `data` modifying it directly. - ## - ## **Deprecated since version 0.9:** Use the ``map`` proc instead. - for i in 0..data.len-1: op(data[i]) - -proc map*[T, S](data: openArray[T], op: proc (x: T): S {.closure.}): seq[S] = - ## Returns a new sequence with the results of `op` applied to every item in - ## `data`. - ## - ## Since the input is not modified you can use this version of ``map`` to - ## transform the type of the elements in the input sequence. Example: - ## - ## .. code-block:: nim - ## let - ## a = @[1, 2, 3, 4] - ## b = map(a, proc(x: int): string = $x) - ## assert b == @["1", "2", "3", "4"] - newSeq(result, data.len) - for i in 0..data.len-1: result[i] = op(data[i]) - -proc map*[T](data: var openArray[T], op: proc (x: var T) {.closure.}) = - ## Applies `op` to every item in `data` modifying it directly. - ## - ## Note that this version of ``map`` requires your input and output types to - ## be the same, since they are modified in-place. Example: - ## - ## .. code-block:: nim - ## var a = @["1", "2", "3", "4"] - ## echo repr(a) - ## # --> ["1", "2", "3", "4"] - ## map(a, proc(x: var string) = x &= "42") - ## echo repr(a) - ## # --> ["142", "242", "342", "442"] - for i in 0..data.len-1: op(data[i]) - iterator fields*[T: tuple|object](x: T): RootObj {. magic: "Fields", noSideEffect.} ## iterates over every field of `x`. Warning: This really transforms @@ -2235,7 +2268,9 @@ proc `$`*[T: tuple|object](x: T): string = firstElement = false result.add(")") -proc collectionToString[T](x: T, b, e: string): string = +proc collectionToString[T: set | seq](x: T, b, e: string): string = + when x is seq: + if x.isNil: return "nil" result = b var firstElement = true for value in items(x): diff --git a/lib/system/alloc.nim b/lib/system/alloc.nim index 13a10e46f..3ebbc8c1e 100644 --- a/lib/system/alloc.nim +++ b/lib/system/alloc.nim @@ -27,10 +27,60 @@ sysAssert(roundup(65, 8) == 72, "roundup broken 2") # some platforms have really weird unmap behaviour: unmap(blockStart, PageSize) # really frees the whole block. Happens for Linux/PowerPC for example. Amd64 # and x86 are safe though; Windows is special because MEM_RELEASE can only be -# used with a size of 0: -const weirdUnmap = not (defined(amd64) or defined(i386)) or defined(windows) +# used with a size of 0. We also allow unmapping to be turned off with +# -d:nimAllocNoUnmap: +const doNotUnmap = not (defined(amd64) or defined(i386)) or + defined(windows) or defined(nimAllocNoUnmap) -when defined(posix): + +when defined(emscripten): + const + PROT_READ = 1 # page can be read + PROT_WRITE = 2 # page can be written + MAP_PRIVATE = 2'i32 # Changes are private + + var MAP_ANONYMOUS {.importc: "MAP_ANONYMOUS", header: "<sys/mman.h>".}: cint + type + PEmscriptenMMapBlock = ptr EmscriptenMMapBlock + EmscriptenMMapBlock {.pure, inheritable.} = object + realSize: int # size of previous chunk; for coalescing + realPointer: pointer # if < PageSize it is a small chunk + + proc mmap(adr: pointer, len: int, prot, flags, fildes: cint, + off: int): pointer {.header: "<sys/mman.h>".} + + proc munmap(adr: pointer, len: int) {.header: "<sys/mman.h>".} + + proc osAllocPages(block_size: int): pointer {.inline.} = + let realSize = block_size + sizeof(EmscriptenMMapBlock) + PageSize + 1 + result = mmap(nil, realSize, PROT_READ or PROT_WRITE, + MAP_PRIVATE or MAP_ANONYMOUS, -1, 0) + if result == nil or result == cast[pointer](-1): + raiseOutOfMem() + + let realPointer = result + let pos = cast[int](result) + + # Convert pointer to PageSize correct one. + var new_pos = cast[ByteAddress](pos) +% (PageSize - (pos %% PageSize)) + if (new_pos-pos)< sizeof(EmscriptenMMapBlock): + new_pos = new_pos +% PageSize + result = cast[pointer](new_pos) + + var mmapDescrPos = cast[ByteAddress](result) -% sizeof(EmscriptenMMapBlock) + + var mmapDescr = cast[EmscriptenMMapBlock](mmapDescrPos) + mmapDescr.realSize = realSize + mmapDescr.realPointer = realPointer + + c_fprintf(c_stdout, "[Alloc] size %d %d realSize:%d realPos:%d\n", block_size, cast[int](result), realSize, cast[int](realPointer)) + + proc osDeallocPages(p: pointer, size: int) {.inline} = + var mmapDescrPos = cast[ByteAddress](p) -% sizeof(EmscriptenMMapBlock) + var mmapDescr = cast[EmscriptenMMapBlock](mmapDescrPos) + munmap(mmapDescr.realPointer, mmapDescr.realSize) + +elif defined(posix): const PROT_READ = 1 # page can be read PROT_WRITE = 2 # page can be written @@ -478,7 +528,7 @@ proc freeBigChunk(a: var MemRegion, c: PBigChunk) = excl(a.chunkStarts, pageIndex(c)) c = cast[PBigChunk](le) - if c.size < ChunkOsReturn or weirdUnmap: + if c.size < ChunkOsReturn or doNotUnmap: incl(a, a.chunkStarts, pageIndex(c)) updatePrevSize(a, c, c.size) listAdd(a.freeChunksList, c) @@ -762,7 +812,7 @@ proc deallocOsPages(a: var MemRegion) = # we free every 'ordinarily' allocated page by iterating over the page bits: for p in elements(a.chunkStarts): var page = cast[PChunk](p shl PageShift) - when not weirdUnmap: + when not doNotUnmap: var size = if page.size < PageSize: PageSize else: page.size osDeallocPages(page, size) else: diff --git a/lib/system/gc_common.nim b/lib/system/gc_common.nim index ceb362378..47e8b4b1f 100644 --- a/lib/system/gc_common.nim +++ b/lib/system/gc_common.nim @@ -95,7 +95,9 @@ proc setupForeignThreadGc*() = # ----------------- stack management -------------------------------------- # inspired from Smart Eiffel -when defined(sparc): +when defined(emscripten): + const stackIncreases = true +elif defined(sparc): const stackIncreases = false elif defined(hppa) or defined(hp9000) or defined(hp9000s300) or defined(hp9000s700) or defined(hp9000s800) or defined(hp9000s820): @@ -162,9 +164,9 @@ elif stackIncreases: proc isOnStack(p: pointer): bool = var stackTop {.volatile.}: pointer stackTop = addr(stackTop) - var a = cast[TAddress](gch.stackBottom) - var b = cast[TAddress](stackTop) - var x = cast[TAddress](p) + var a = cast[ByteAddress](gch.stackBottom) + var b = cast[ByteAddress](stackTop) + var x = cast[ByteAddress](p) result = a <=% x and x <=% b var @@ -173,14 +175,14 @@ elif stackIncreases: # in a platform independent way template forEachStackSlot(gch, gcMark: expr) {.immediate, dirty.} = - var registers: C_JmpBuf + var registers {.noinit.}: C_JmpBuf if c_setjmp(registers) == 0'i32: # To fill the C stack with registers. - var max = cast[TAddress](gch.stackBottom) - var sp = cast[TAddress](addr(registers)) +% jmpbufSize -% sizeof(pointer) + var max = cast[ByteAddress](gch.stackBottom) + var sp = cast[ByteAddress](addr(registers)) +% jmpbufSize -% sizeof(pointer) # sp will traverse the JMP_BUF as well (jmp_buf size is added, # otherwise sp would be below the registers structure). while sp >=% max: - gcMark(gch, cast[ppointer](sp)[]) + gcMark(gch, cast[PPointer](sp)[]) sp = sp -% sizeof(pointer) else: diff --git a/lib/system/mmdisp.nim b/lib/system/mmdisp.nim index 8a946716d..1c13f3ff8 100644 --- a/lib/system/mmdisp.nim +++ b/lib/system/mmdisp.nim @@ -66,41 +66,34 @@ proc raiseOutOfMem() {.noinline.} = quit(1) when defined(boehmgc): - when defined(windows): - const boehmLib = "boehmgc.dll" - elif defined(macosx): - const boehmLib = "libgc.dylib" - else: - const boehmLib = "libgc.so.1" - - proc boehmGCinit {.importc: "GC_init", dynlib: boehmLib.} - proc boehmGC_disable {.importc: "GC_disable", dynlib: boehmLib.} - proc boehmGC_enable {.importc: "GC_enable", dynlib: boehmLib.} + proc boehmGCinit {.importc: "GC_init", boehmGC.} + proc boehmGC_disable {.importc: "GC_disable", boehmGC.} + proc boehmGC_enable {.importc: "GC_enable", boehmGC.} proc boehmGCincremental {. - importc: "GC_enable_incremental", dynlib: boehmLib.} - proc boehmGCfullCollect {.importc: "GC_gcollect", dynlib: boehmLib.} - proc boehmAlloc(size: int): pointer {. - importc: "GC_malloc", dynlib: boehmLib.} + importc: "GC_enable_incremental", boehmGC.} + proc boehmGCfullCollect {.importc: "GC_gcollect", boehmGC.} + proc boehmAlloc(size: int): pointer {.importc: "GC_malloc", boehmGC.} proc boehmAllocAtomic(size: int): pointer {. - importc: "GC_malloc_atomic", dynlib: boehmLib.} + importc: "GC_malloc_atomic", boehmGC.} proc boehmRealloc(p: pointer, size: int): pointer {. - importc: "GC_realloc", dynlib: boehmLib.} - proc boehmDealloc(p: pointer) {.importc: "GC_free", dynlib: boehmLib.} + importc: "GC_realloc", boehmGC.} + proc boehmDealloc(p: pointer) {.importc: "GC_free", boehmGC.} + when hasThreadSupport: + proc boehmGC_allow_register_threads {. + importc: "GC_allow_register_threads", boehmGC.} - proc boehmGetHeapSize: int {.importc: "GC_get_heap_size", dynlib: boehmLib.} + proc boehmGetHeapSize: int {.importc: "GC_get_heap_size", boehmGC.} ## Return the number of bytes in the heap. Excludes collector private ## data structures. Includes empty blocks and fragmentation loss. ## Includes some pages that were allocated but never written. - proc boehmGetFreeBytes: int {.importc: "GC_get_free_bytes", dynlib: boehmLib.} + proc boehmGetFreeBytes: int {.importc: "GC_get_free_bytes", boehmGC.} ## Return a lower bound on the number of free bytes in the heap. - proc boehmGetBytesSinceGC: int {.importc: "GC_get_bytes_since_gc", - dynlib: boehmLib.} + proc boehmGetBytesSinceGC: int {.importc: "GC_get_bytes_since_gc", boehmGC.} ## Return the number of bytes allocated since the last collection. - proc boehmGetTotalBytes: int {.importc: "GC_get_total_bytes", - dynlib: boehmLib.} + proc boehmGetTotalBytes: int {.importc: "GC_get_total_bytes", boehmGC.} ## Return the total number of bytes allocated in this process. ## Never decreases. @@ -157,7 +150,9 @@ when defined(boehmgc): proc setStackBottom(theStackBottom: pointer) = discard proc initGC() = - when defined(macosx): boehmGCinit() + boehmGCinit() + when hasThreadSupport: + boehmGC_allow_register_threads() proc newObj(typ: PNimType, size: int): pointer {.compilerproc.} = if ntfNoRefs in typ.flags: result = allocAtomic(size) @@ -204,9 +199,6 @@ elif defined(gogc): else: const goLib = "libgo.so" - proc `div`[T: SomeUnsignedInt](x, y: T): T {.magic: "DivU", noSideEffect.} - proc `-`[T: SomeUnsignedInt](x, y: T): T {.magic: "SubU", noSideEffect.} - proc roundup(x, v: int): int {.inline.} = result = (x + (v-1)) and not (v-1) diff --git a/lib/system/nimscript.nim b/lib/system/nimscript.nim index 4841749a9..22430348c 100644 --- a/lib/system/nimscript.nim +++ b/lib/system/nimscript.nim @@ -31,7 +31,8 @@ proc moveFile(src, dest: string) {. tags: [ReadIOEffect, WriteIOEffect], raises: [OSError].} = builtin proc copyFile(src, dest: string) {. tags: [ReadIOEffect, WriteIOEffect], raises: [OSError].} = builtin -proc createDir(dir: string) {.tags: [WriteIOEffect], raises: [OSError].} = builtin +proc createDir(dir: string) {.tags: [WriteIOEffect], raises: [OSError].} = + builtin proc getOsError: string = builtin proc setCurrentDir(dir: string) = builtin proc getCurrentDir(): string = builtin @@ -56,7 +57,7 @@ proc getCommand*(): string = ## "c", "js", "build", "help". builtin -proc setCommand*(cmd: string) = +proc setCommand*(cmd: string; project="") = ## Sets the Nim command that should be continued with after this Nimscript ## has finished. builtin diff --git a/lib/system/threads.nim b/lib/system/threads.nim index c7cb8d9df..c5de841f8 100644 --- a/lib/system/threads.nim +++ b/lib/system/threads.nim @@ -304,22 +304,53 @@ type when not defined(boehmgc) and not hasSharedHeap and not defined(gogc): proc deallocOsPages() +when defined(boehmgc): + type GCStackBaseProc = proc(sb: pointer, t: pointer) {.noconv.} + proc boehmGC_call_with_stack_base(sbp: GCStackBaseProc, p: pointer) + {.importc: "GC_call_with_stack_base", boehmGC.} + proc boehmGC_register_my_thread(sb: pointer) + {.importc: "GC_register_my_thread", boehmGC.} + proc boehmGC_unregister_my_thread() + {.importc: "GC_unregister_my_thread", boehmGC.} + + proc threadProcWrapDispatch[TArg](sb: pointer, thrd: pointer) {.noconv.} = + boehmGC_register_my_thread(sb) + let thrd = cast[ptr Thread[TArg]](thrd) + when TArg is void: + thrd.dataFn() + else: + thrd.dataFn(thrd.data) + boehmGC_unregister_my_thread() +else: + proc threadProcWrapDispatch[TArg](thrd: ptr Thread[TArg]) = + when TArg is void: + thrd.dataFn() + else: + thrd.dataFn(thrd.data) + +proc threadProcWrapStackFrame[TArg](thrd: ptr Thread[TArg]) = + when defined(boehmgc): + boehmGC_call_with_stack_base(threadProcWrapDispatch[TArg], thrd) + elif not defined(nogc) and not defined(gogc): + var p {.volatile.}: proc(a: ptr Thread[TArg]) {.nimcall.} = + threadProcWrapDispatch[TArg] + when not hasSharedHeap: + # init the GC for refc/markandsweep + setStackBottom(addr(p)) + initGC() + when declared(registerThread): + thrd.stackBottom = addr(thrd) + registerThread(thrd) + p(thrd) + when declared(registerThread): unregisterThread(thrd) + when declared(deallocOsPages): deallocOsPages() + else: + threadProcWrapDispatch(thrd) + template threadProcWrapperBody(closure: expr) {.immediate.} = when declared(globalsSlot): threadVarSetValue(globalsSlot, closure) - var t = cast[ptr Thread[TArg]](closure) - when useStackMaskHack: - var tls: ThreadLocalStorage - when not defined(boehmgc) and not defined(gogc) and not defined(nogc) and not hasSharedHeap: - # init the GC for this thread: - setStackBottom(addr(t)) - initGC() - when declared(registerThread): - t.stackBottom = addr(t) - registerThread(t) - when TArg is void: t.dataFn() - else: t.dataFn(t.data) - when declared(registerThread): unregisterThread(t) - when declared(deallocOsPages): deallocOsPages() + var thrd = cast[ptr Thread[TArg]](closure) + threadProcWrapStackFrame(thrd) # Since an unhandled exception terminates the whole process (!), there is # no need for a ``try finally`` here, nor would it be correct: The current # exception is tried to be re-raised by the code-gen after the ``finally``! @@ -327,7 +358,7 @@ template threadProcWrapperBody(closure: expr) {.immediate.} = # page! # mark as not running anymore: - t.dataFn = nil + thrd.dataFn = nil {.push stack_trace:off.} when defined(windows): diff --git a/lib/windows/winlean.nim b/lib/windows/winlean.nim index 84dac6d79..89d86c62a 100644 --- a/lib/windows/winlean.nim +++ b/lib/windows/winlean.nim @@ -18,7 +18,7 @@ const type Handle* = int LONG* = int32 - ULONG* = int + ULONG* = int32 PULONG* = ptr int WINBOOL* = int32 DWORD* = int32 @@ -108,6 +108,13 @@ const CREATE_UNICODE_ENVIRONMENT* = 1024'i32 + PIPE_ACCESS_DUPLEX* = 0x00000003'i32 + PIPE_ACCESS_INBOUND* = 1'i32 + PIPE_ACCESS_OUTBOUND* = 2'i32 + PIPE_NOWAIT* = 0x00000001'i32 + SYNCHRONIZE* = 0x00100000'i32 + FILE_FLAG_WRITE_THROUGH* = 0x80000000'i32 + proc closeHandle*(hObject: Handle): WINBOOL {.stdcall, dynlib: "kernel32", importc: "CloseHandle".} @@ -125,6 +132,19 @@ proc createPipe*(hReadPipe, hWritePipe: var Handle, nSize: int32): WINBOOL{. stdcall, dynlib: "kernel32", importc: "CreatePipe".} +proc createNamedPipe*(lpName: WideCString, + dwOpenMode, dwPipeMode, nMaxInstances, nOutBufferSize, + nInBufferSize, nDefaultTimeOut: int32, + lpSecurityAttributes: ptr SECURITY_ATTRIBUTES): Handle {. + stdcall, dynlib: "kernel32", importc: "CreateNamedPipeW".} + +proc peekNamedPipe*(hNamedPipe: Handle, lpBuffer: pointer=nil, + nBufferSize: int32 = 0, + lpBytesRead: ptr int32 = nil, + lpTotalBytesAvail: ptr int32 = nil, + lpBytesLeftThisMessage: ptr int32 = nil): bool {. + stdcall, dynlib: "kernel32", importc: "PeekNamedPipe".} + when useWinUnicode: proc createProcessW*(lpApplicationName, lpCommandLine: WideCString, lpProcessAttributes: ptr SECURITY_ATTRIBUTES, @@ -615,12 +635,24 @@ const FILE_FLAG_BACKUP_SEMANTICS* = 33554432'i32 FILE_FLAG_OPEN_REPARSE_POINT* = 0x00200000'i32 + DUPLICATE_SAME_ACCESS* = 2 + FILE_READ_DATA* = 0x00000001 # file & pipe + FILE_WRITE_DATA* = 0x00000002 # file & pipe # Error Constants const ERROR_ACCESS_DENIED* = 5 ERROR_HANDLE_EOF* = 38 +proc duplicateHandle*(hSourceProcessHandle: HANDLE, hSourceHandle: HANDLE, + hTargetProcessHandle: HANDLE, + lpTargetHandle: ptr HANDLE, + dwDesiredAccess: DWORD, bInheritHandle: WINBOOL, + dwOptions: DWORD): WINBOOL{.stdcall, dynlib: "kernel32", + importc: "DuplicateHandle".} +proc getCurrentProcess*(): HANDLE{.stdcall, dynlib: "kernel32", + importc: "GetCurrentProcess".} + when useWinUnicode: proc createFileW*(lpFileName: WideCString, dwDesiredAccess, dwShareMode: DWORD, lpSecurityAttributes: pointer, |