diff options
Diffstat (limited to 'lib')
50 files changed, 1585 insertions, 881 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/parseopt.nim b/lib/deprecated/pure/parseopt.nim index 218f5ab81..218f5ab81 100644 --- a/lib/pure/parseopt.nim +++ b/lib/deprecated/pure/parseopt.nim 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 d264dc02a..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>".} @@ -2316,7 +2316,7 @@ proc timer_settime*(a1: Timer, a2: cint, a3: var Itimerspec, proc tzset*() {.importc, header: "<time.h>".} -proc wait*(a1: var cint): Pid {.importc, header: "<sys/wait.h>".} +proc wait*(a1: ptr cint): Pid {.importc, discardable, header: "<sys/wait.h>".} proc waitid*(a1: cint, a2: Id, a3: var SigInfo, a4: cint): cint {. importc, header: "<sys/wait.h>".} proc waitpid*(a1: Pid, a2: var cint, a3: cint): Pid {. @@ -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 f49388b17..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 @@ -126,6 +126,7 @@ export Port, SocketFlag ## * Can't await in a ``except`` body ## * Forward declarations for async procs are broken, ## link includes workaround: https://github.com/nim-lang/Nim/issues/3182. +## * FutureVar[T] needs to be completed manually. # TODO: Check if yielded future is nil and throw a more meaningful exception @@ -145,10 +146,15 @@ type Future*[T] = ref object of FutureBase ## Typed future. value: T ## Stored value -{.deprecated: [PFutureBase: FutureBase, PFuture: Future].} + FutureVar*[T] = distinct Future[T] + + FutureError* = object of Exception + cause*: FutureBase +{.deprecated: [PFutureBase: FutureBase, PFuture: Future].} -var currentID = 0 +when not defined(release): + var currentID = 0 proc newFuture*[T](fromProc: string = "unspecified"): Future[T] = ## Creates a new future. ## @@ -162,18 +168,39 @@ proc newFuture*[T](fromProc: string = "unspecified"): Future[T] = result.fromProc = fromProc currentID.inc() +proc newFutureVar*[T](fromProc = "unspecified"): FutureVar[T] = + ## Create a new ``FutureVar``. This Future type is ideally suited for + ## situations where you want to avoid unnecessary allocations of Futures. + ## + ## Specifying ``fromProc``, which is a string specifying the name of the proc + ## that this future belongs to, is a good habit as it helps with debugging. + result = FutureVar[T](newFuture[T](fromProc)) + +proc clean*[T](future: FutureVar[T]) = + ## Resets the ``finished`` status of ``future``. + Future[T](future).finished = false + Future[T](future).error = nil + proc checkFinished[T](future: Future[T]) = + ## Checks whether `future` is finished. If it is then raises a + ## ``FutureError``. when not defined(release): if future.finished: - echo("<-----> ", future.id, " ", future.fromProc) - echo(future.stackTrace) - echo("-----") + var msg = "" + msg.add("An attempt was made to complete a Future more than once. ") + msg.add("Details:") + msg.add("\n Future ID: " & $future.id) + msg.add("\n Created in proc: " & future.fromProc) + msg.add("\n Stack trace to moment of creation:") + msg.add("\n" & indent(future.stackTrace.strip(), 4)) when T is string: - echo("Contents: ", future.value.repr) - echo("<----->") - echo("Future already finished, cannot finish twice.") - echo getStackTrace() - assert false + msg.add("\n Contents (string): ") + msg.add("\n" & indent(future.value.repr, 4)) + msg.add("\n Stack trace to moment of secondary completion:") + msg.add("\n" & indent(getStackTrace().strip(), 4)) + var err = newException(FutureError, msg) + err.cause = future + raise err proc complete*[T](future: Future[T], val: T) = ## Completes ``future`` with value ``val``. @@ -194,6 +221,15 @@ proc complete*(future: Future[void]) = if future.cb != nil: future.cb() +proc complete*[T](future: FutureVar[T]) = + ## Completes a ``FutureVar``. + template fut: expr = Future[T](future) + checkFinished(fut) + assert(fut.error == nil) + fut.finished = true + if fut.cb != nil: + fut.cb() + proc fail*[T](future: Future[T], error: ref Exception) = ## Completes ``future`` with ``error``. #assert(not future.finished, "Future already finished, cannot finish twice.") @@ -230,15 +266,17 @@ proc `callback=`*[T](future: Future[T], ## If future has already completed then ``cb`` will be called immediately. future.callback = proc () = cb(future) -proc echoOriginalStackTrace[T](future: Future[T]) = +proc injectStacktrace[T](future: Future[T]) = # TODO: Come up with something better. when not defined(release): - echo("Original stack trace in ", future.fromProc, ":") + var msg = "" + msg.add("\n " & future.fromProc & "'s lead up to read of failed Future:") + if not future.errorStackTrace.isNil and future.errorStackTrace != "": - echo(future.errorStackTrace) + msg.add("\n" & indent(future.errorStackTrace.strip(), 4)) else: - echo("Empty or nil stack trace.") - echo("Continuing...") + msg.add("\n Empty or nil stack trace.") + future.error.msg.add(msg) proc read*[T](future: Future[T]): T = ## Retrieves the value of ``future``. Future must be finished otherwise @@ -247,7 +285,7 @@ proc read*[T](future: Future[T]): T = ## If the result of the future is an error then that error will be raised. if future.finished: if future.error != nil: - echoOriginalStackTrace(future) + injectStacktrace(future) raise future.error when T isnot void: return future.value @@ -264,6 +302,13 @@ proc readError*[T](future: Future[T]): ref Exception = else: raise newException(ValueError, "No error in future.") +proc mget*[T](future: FutureVar[T]): var T = + ## Returns a mutable value stored in ``future``. + ## + ## Unlike ``read``, this function will not raise an exception if the + ## Future has not been finished. + result = Future[T](future).value + proc finished*[T](future: Future[T]): bool = ## Determines whether ``future`` has completed. ## @@ -282,7 +327,7 @@ proc asyncCheck*[T](future: Future[T]) = future.callback = proc () = if future.failed: - echoOriginalStackTrace(future) + injectStacktrace(future) raise future.error proc `and`*[T, Y](fut1: Future[T], fut2: Future[Y]): Future[void] = @@ -430,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): @@ -483,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 @@ -566,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 @@ -661,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 @@ -732,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() @@ -782,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 @@ -855,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) @@ -928,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) @@ -947,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) @@ -1423,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]) @@ -1440,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( @@ -1464,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.}) @@ -1505,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 f9085e4bf..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", @@ -151,7 +157,8 @@ proc processClient(client: AsyncSocket, address: string, var request: Request request.url = initUri() request.headers = newStringTable(modeCaseInsensitive) - var line = newStringOfCap(80) + var lineFut = newFutureVar[string]("asynchttpserver.processClient") + lineFut.mget() = newStringOfCap(80) var key, value = "" while not client.isClosed: @@ -165,14 +172,15 @@ proc processClient(client: AsyncSocket, address: string, request.client = client # First line - GET /path HTTP/1.1 - line.setLen(0) - await client.recvLineInto(addr line) # TODO: Timeouts. - if line == "": + lineFut.mget().setLen(0) + lineFut.clean() + await client.recvLineInto(lineFut) # TODO: Timeouts. + if lineFut.mget == "": client.close() return var i = 0 - for linePart in line.split(' '): + for linePart in lineFut.mget.split(' '): case i of 0: request.reqMethod.shallowCopy(linePart.normalize) of 1: parseUri(linePart, request.url) @@ -184,20 +192,21 @@ proc processClient(client: AsyncSocket, address: string, "Invalid request protocol. Got: " & linePart) continue else: - await request.respond(Http400, "Invalid request. Got: " & line) + await request.respond(Http400, "Invalid request. Got: " & lineFut.mget) continue inc i # Headers while true: i = 0 - line.setLen(0) - await client.recvLineInto(addr line) + lineFut.mget.setLen(0) + lineFut.clean() + await client.recvLineInto(lineFut) - if line == "": + if lineFut.mget == "": client.close(); return - if line == "\c\L": break - let (key, value) = parseHeader(line) + if lineFut.mget == "\c\L": break + let (key, value) = parseHeader(lineFut.mget) request.headers[key] = value if request.reqMethod == "post": diff --git a/lib/pure/asyncnet.nim b/lib/pure/asyncnet.nim index 9139200f3..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 = @@ -316,7 +317,7 @@ proc accept*(socket: AsyncSocket, retFut.complete(future.read.client) return retFut -proc recvLineInto*(socket: AsyncSocket, resString: ptr string, +proc recvLineInto*(socket: AsyncSocket, resString: FutureVar[string], flags = {SocketFlag.SafeDisconn}) {.async.} = ## Reads a line of data from ``socket`` into ``resString``. ## @@ -338,16 +339,23 @@ proc recvLineInto*(socket: AsyncSocket, resString: ptr string, ## **Warning**: ``recvLineInto`` currently uses a raw pointer to a string for ## performance reasons. This will likely change soon to use FutureVars. assert SocketFlag.Peek notin flags ## TODO: + assert(not resString.mget.isNil(), + "String inside resString future needs to be initialised") result = newFuture[void]("asyncnet.recvLineInto") + # TODO: Make the async transformation check for FutureVar params and complete + # them when the result future is completed. + # Can we replace the result future with the FutureVar? + template addNLIfEmpty(): stmt = - if resString[].len == 0: - resString[].add("\c\L") + if resString.mget.len == 0: + resString.mget.add("\c\L") if socket.isBuffered: if socket.bufLen == 0: let res = socket.readIntoBuf(flags) if res == 0: + resString.complete() return var lastR = false @@ -355,7 +363,8 @@ proc recvLineInto*(socket: AsyncSocket, resString: ptr string, if socket.currPos >= socket.bufLen: let res = socket.readIntoBuf(flags) if res == 0: - resString[].setLen(0) + resString.mget.setLen(0) + resString.complete() return case socket.buffer[socket.currPos] @@ -365,13 +374,15 @@ proc recvLineInto*(socket: AsyncSocket, resString: ptr string, of '\L': addNLIfEmpty() socket.currPos.inc() + resString.complete() return else: if lastR: socket.currPos.inc() + resString.complete() return else: - resString[].add socket.buffer[socket.currPos] + resString.mget.add socket.buffer[socket.currPos] socket.currPos.inc() else: var c = "" @@ -379,18 +390,22 @@ proc recvLineInto*(socket: AsyncSocket, resString: ptr string, let recvFut = recv(socket, 1, flags) c = recvFut.read() if c.len == 0: - resString[].setLen(0) + resString.mget.setLen(0) + resString.complete() return if c == "\r": let recvFut = recv(socket, 1, flags) # Skip \L c = recvFut.read() assert c == "\L" addNLIfEmpty() + resString.complete() return elif c == "\L": addNLIfEmpty() + resString.complete() return - resString[].add c + resString.mget.add c + resString.complete() proc recvLine*(socket: AsyncSocket, flags = {SocketFlag.SafeDisconn}): Future[string] {.async.} = @@ -416,8 +431,11 @@ proc recvLine*(socket: AsyncSocket, result.add("\c\L") assert SocketFlag.Peek notin flags ## TODO: - result = "" - await socket.recvLineInto(addr result, flags) + # TODO: Optimise this + var resString = newFutureVar[string]("asyncnet.recvLine") + resString.mget() = "" + await socket.recvLineInto(resString, flags) + result = resString.mget() proc listen*(socket: AsyncSocket, backlog = SOMAXCONN) {.tags: [ReadIOEffect].} = ## Marks ``socket`` as accepting connections. 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/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 540a1a8eb..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`. @@ -1074,9 +1077,9 @@ when not defined(js): ## for nice error messages. var p: JsonParser p.open(s, filename) + defer: p.close() discard getTok(p) # read first token result = p.parseJson() - p.close() proc parseJson*(buffer: string): JsonNode = ## Parses JSON from `buffer`. @@ -1203,6 +1206,17 @@ when isMainModule: testJson{["c", "d"]} = %true assert(testJson["c"]["d"].bval) + # make sure no memory leek when parsing invalid string + let startMemory = getOccupiedMem() + for i in 0 .. 10000: + try: + discard parseJson"""{ invalid""" + except: + discard + # memory diff should less than 2M + assert(abs(getOccupiedMem() - startMemory) < 2 * 1024 * 1024) + + # test `$` let stringified = $testJson let parsedAgain = parseJson(stringified) 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/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 7873e7226..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,79 @@ 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. + ## + ## Similar to POSIX's `getsockname`:idx:. + case domain + of AF_INET: + var name: Sockaddr_in + when useWinVersion: + name.sin_family = int16(ord(AF_INET)) + else: + name.sin_family = posix.AF_INET + var namelen = sizeof(name).SockLen + if getsockname(socket, cast[ptr SockAddr](addr(name)), + addr(namelen)) == -1'i32: + raiseOSError(osLastError()) + result = ($inet_ntoa(name.sin_addr), + Port(nativesockets.ntohs(name.sin_port))) + of AF_INET6: + var name: Sockaddr_in6 + when useWinVersion: + name.sin6_family = int16(ord(AF_INET6)) + else: + name.sin6_family = posix.AF_INET6 + var namelen = sizeof(name).SockLen + if getsockname(socket, cast[ptr SockAddr](addr(name)), + addr(namelen)) == -1'i32: + raiseOSError(osLastError()) + # Cannot use INET6_ADDRSTRLEN here, because it's a C define. + var buf: array[64, char] + if inet_ntop(name.sin6_family.cint, + addr name, buf.cstring, sizeof(buf).int32).isNil: + raiseOSError(osLastError()) + result = ($buf, Port(nativesockets.ntohs(name.sin6_port))) + else: + raiseOSError(OSErrorCode(-1), "invalid socket family in getLocalAddr") + +proc getPeerAddr*(socket: SocketHandle, domain: Domain): (string, Port) = + ## returns the socket's peer address and port number. + ## + ## Similar to POSIX's `getpeername`:idx: + case domain + of AF_INET: + var name: Sockaddr_in + when useWinVersion: + name.sin_family = int16(ord(AF_INET)) + else: + name.sin_family = posix.AF_INET + var namelen = sizeof(name).SockLen + if getpeername(socket, cast[ptr SockAddr](addr(name)), + addr(namelen)) == -1'i32: + raiseOSError(osLastError()) + result = ($inet_ntoa(name.sin_addr), + Port(nativesockets.ntohs(name.sin_port))) + of AF_INET6: + var name: Sockaddr_in6 + when useWinVersion: + name.sin6_family = int16(ord(AF_INET6)) + else: + name.sin6_family = posix.AF_INET6 + var namelen = sizeof(name).SockLen + if getpeername(socket, cast[ptr SockAddr](addr(name)), + addr(namelen)) == -1'i32: + raiseOSError(osLastError()) + # Cannot use INET6_ADDRSTRLEN here, because it's a C define. + var buf: array[64, char] + if inet_ntop(name.sin6_family.cint, + addr name, buf.cstring, sizeof(buf).int32).isNil: + raiseOSError(osLastError()) + result = ($buf, Port(nativesockets.ntohs(name.sin6_port))) + else: + raiseOSError(OSErrorCode(-1), "invalid socket family in getLocalAddr") proc getSockOptInt*(socket: SocketHandle, level, optname: int): int {. tags: [ReadIOEffect].} = diff --git a/lib/pure/net.nim b/lib/pure/net.nim index 141543c70..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 = "") {. @@ -533,6 +530,18 @@ proc getSockOpt*(socket: Socket, opt: SOBool, level = SOL_SOCKET): bool {. var res = getSockOptInt(socket.fd, cint(level), toCInt(opt)) result = res != 0 +proc getLocalAddr*(socket: Socket): (string, Port) = + ## Get the socket's local address and port number. + ## + ## This is high-level interface for `getsockname`:idx:. + getLocalAddr(socket.fd, socket.domain) + +proc getPeerAddr*(socket: Socket): (string, Port) = + ## Get the socket's peer address and port number. + ## + ## This is high-level interface for `getpeername`:idx:. + getPeerAddr(socket.fd, socket.domain) + proc setSockOpt*(socket: Socket, opt: SOBool, value: bool, level = SOL_SOCKET) {. tags: [WriteIOEffect].} = ## Sets option ``opt`` to a boolean value specified by ``value``. 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 ae3bd7f63..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".} = @@ -630,6 +760,22 @@ proc wordWrap*(s: string, maxLineWidth = 80, result.add(lastSep & word) lastSep.setLen(0) +proc indent*(s: string, count: Natural, padding: string = " "): string + {.noSideEffect, rtl, extern: "nsuIndent".} = + ## Indents each line in ``s`` by ``count`` amount of ``padding``. + ## + ## **Note:** This currently does not preserve the specific new line characters + ## used. + result = "" + var i = 0 + for line in s.splitLines(): + if i != 0: + result.add("\n") + for j in 1..count: + result.add(padding) + result.add(line) + i.inc + proc unindent*(s: string, eatAllIndent = false): string {. noSideEffect, rtl, extern: "nsuUnindent".} = ## Unindents `s`. @@ -834,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. @@ -1433,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 @@ -1499,6 +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/times.nim b/lib/pure/times.nim index aa4ae5ace..3142952e7 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -11,6 +11,26 @@ ## This module contains routines and types for dealing with time. ## This module is available for the `JavaScript target ## <backends.html#the-javascript-target>`_. +## +## Examples: +## +## .. code-block:: nim +## +## import times, os +## var +## t = cpuTime() +## +## sleep(100) # replace this with something to be timed +## echo "Time taken: ",cpuTime() - t +## +## echo "My formatted time: ", format(getLocalTime(getTime()), "d MMMM yyyy HH:mm") +## echo "Using predefined formats: ", getClockStr(), " ", getDateStr() +## +## echo "epochTime() float value: ", epochTime() +## echo "getTime() float value: ", toSeconds(getTime()) +## echo "cpuTime() float value: ", cpuTime() +## echo "An hour from now : ", getLocalTime(getTime()) + initInterval(0,0,0,1) +## echo "An hour from (UTC) now: ", getGmTime(getTime()) + initInterval(0,0,0,1) {.push debugger:off.} # the user does not want to trace a part # of the standard library! @@ -288,10 +308,10 @@ proc `+`*(a: TimeInfo, interval: TimeInterval): TimeInfo = ## very accurate. let t = toSeconds(timeInfoToTime(a)) let secs = toSeconds(a, interval) - if a.tzname == "UTC": - result = getGMTime(fromSeconds(t + secs)) - else: - result = getLocalTime(fromSeconds(t + secs)) + #if a.tzname == "UTC": + # result = getGMTime(fromSeconds(t + secs)) + #else: + result = getLocalTime(fromSeconds(t + secs)) proc `-`*(a: TimeInfo, interval: TimeInterval): TimeInfo = ## subtracts ``interval`` time. @@ -300,10 +320,10 @@ proc `-`*(a: TimeInfo, interval: TimeInterval): TimeInfo = ## when you subtract so much that you reach the Julian calendar. let t = toSeconds(timeInfoToTime(a)) let secs = toSeconds(a, interval) - if a.tzname == "UTC": - result = getGMTime(fromSeconds(t - secs)) - else: - result = getLocalTime(fromSeconds(t - secs)) + #if a.tzname == "UTC": + # result = getGMTime(fromSeconds(t - secs)) + #else: + result = getLocalTime(fromSeconds(t - secs)) when not defined(JS): proc epochTime*(): float {.rtl, extern: "nt$1", tags: [TimeEffect].} @@ -1269,3 +1289,28 @@ when isMainModule: assert getDayOfWeekJulian(21, 9, 1970) == dMon assert getDayOfWeekJulian(1, 1, 2000) == dSat assert getDayOfWeekJulian(1, 1, 2021) == dFri + + # toSeconds tests with GM and Local timezones + #var t4 = getGMTime(fromSeconds(876124714)) # Mon 6 Oct 08:58:34 BST 1997 + var t4L = getLocalTime(fromSeconds(876124714)) + assert toSeconds(timeInfoToTime(t4L)) == 876124714 # fromSeconds is effectively "localTime" + assert toSeconds(timeInfoToTime(t4L)) + t4L.timezone.float == toSeconds(timeInfoToTime(t4)) + + assert toSeconds(t4, initInterval(seconds=0)) == 0.0 + assert toSeconds(t4L, initInterval(milliseconds=1)) == toSeconds(t4, initInterval(milliseconds=1)) + assert toSeconds(t4L, initInterval(seconds=1)) == toSeconds(t4, initInterval(seconds=1)) + assert toSeconds(t4L, initInterval(minutes=1)) == toSeconds(t4, initInterval(minutes=1)) + assert toSeconds(t4L, initInterval(hours=1)) == toSeconds(t4, initInterval(hours=1)) + assert toSeconds(t4L, initInterval(days=1)) == toSeconds(t4, initInterval(days=1)) + assert toSeconds(t4L, initInterval(months=1)) == toSeconds(t4, initInterval(months=1)) + assert toSeconds(t4L, initInterval(years=1)) == toSeconds(t4, initInterval(years=1)) + + # adding intervals + var + a1L = toSeconds(timeInfoToTime(t4L + initInterval(hours = 1))) + t4L.timezone.float + a1G = toSeconds(timeInfoToTime(t4)) + 60.0 * 60.0 + assert a1L == a1G + # subtracting intervals + a1L = toSeconds(timeInfoToTime(t4L - initInterval(hours = 1))) + t4L.timezone.float + a1G = toSeconds(timeInfoToTime(t4)) - (60.0 * 60.0) + assert a1L == a1G 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 e0bfbe8ea..f7178adcc 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -78,7 +78,7 @@ type stmt* {.magic: Stmt.} ## meta type to denote a statement (for templates) typedesc* {.magic: TypeDesc.} ## meta type to denote a type description void* {.magic: "VoidType".} ## meta type to denote the absence of any type - auto* = expr ## meta type for automatic type determination + auto* {.magic: Expr.} ## meta type for automatic type determination any* = distinct auto ## meta type for any supported type untyped* {.magic: Expr.} ## meta type to denote an expression that ## is not resolved (for templates) @@ -104,7 +104,7 @@ type SomeNumber* = SomeInteger|SomeReal ## type class matching all number types -proc defined*(x: expr): bool {.magic: "Defined", noSideEffect.} +proc defined*(x: expr): bool {.magic: "Defined", noSideEffect, compileTime.} ## Special compile-time procedure that checks whether `x` is ## defined. ## `x` is an external symbol introduced through the compiler's @@ -125,7 +125,7 @@ when defined(nimalias): TNumber: SomeNumber, TOrdinal: SomeOrdinal].} -proc declared*(x: expr): bool {.magic: "Defined", noSideEffect.} +proc declared*(x: expr): bool {.magic: "Defined", noSideEffect, compileTime.} ## Special compile-time procedure that checks whether `x` is ## declared. `x` has to be an identifier or a qualified identifier. ## This can be used to check whether a library provides a certain @@ -140,11 +140,11 @@ when defined(useNimRtl): {.deadCodeElim: on.} proc definedInScope*(x: expr): bool {. - magic: "DefinedInScope", noSideEffect, deprecated.} + magic: "DefinedInScope", noSideEffect, deprecated, compileTime.} ## **Deprecated since version 0.9.6**: Use ``declaredInScope`` instead. proc declaredInScope*(x: expr): bool {. - magic: "DefinedInScope", noSideEffect.} + magic: "DefinedInScope", noSideEffect, compileTime.} ## Special compile-time procedure that checks whether `x` is ## declared in the current scope. `x` has to be an identifier. @@ -160,7 +160,7 @@ proc unsafeAddr*[T](x: var T): ptr T {.magic: "Addr", noSideEffect.} = ## Cannot be overloaded. discard -proc `type`*(x: expr): typeDesc {.magic: "TypeOf", noSideEffect.} = +proc `type`*(x: expr): typeDesc {.magic: "TypeOf", noSideEffect, compileTime.} = ## Builtin 'type' operator for accessing the type of an expression. ## Cannot be overloaded. discard @@ -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. @@ -239,6 +249,14 @@ type seq*{.magic: "Seq".}[T] ## Generic type to construct sequences. set*{.magic: "Set".}[T] ## Generic type to construct bit sets. +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: T; i: I; + x: S) {.noSideEffect, magic: "ArrPut".} + proc `=`*[T](dest: var T; src: T) {.noSideEffect, magic: "Asgn".} + type Slice*[T] = object ## builtin slice type a*, b*: T ## the bounds @@ -576,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.} @@ -602,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``. @@ -651,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``. @@ -664,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``. @@ -672,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 @@ -1175,9 +1232,12 @@ const seqShallowFlag = low(int) -let nimvm* {.magic: "Nimvm".}: bool = false +when defined(nimKnowsNimvm): + let nimvm* {.magic: "Nimvm".}: bool = false ## may be used only in "when" expression. ## It is true in Nim VM context and false otherwise +else: + const nimvm*: bool = false proc compileOption*(option: string): bool {. magic: "CompileOption", noSideEffect.} @@ -1196,7 +1256,7 @@ 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") @@ -1288,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] @@ -1302,31 +1366,66 @@ 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. - let xl = x.len - shallowCopy(x[i], x[xl-1]) - setLen(x, xl-1) + ## + ## .. 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) 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. - let xl = x.len - for j in i..xl-2: shallowCopy(x[j], x[j+1]) - setLen(x, xl-1) + ## + ## .. 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]) + setLen(x, xl-1) + + when nimvm: + defaultImpl() + else: + when defined(js): + {.emit: "`x`[`x`_Idx].splice(`i`, 1);".} + else: + defaultImpl() proc insert*[T](x: var seq[T], item: T, i = 0.Natural) {.noSideEffect.} = ## inserts `item` into `x` at position `i`. - let xl = x.len - setLen(x, xl+1) - var j = xl-1 - while j >= i: - shallowCopy(x[j+1], x[j]) - dec(j) + ## + ## .. 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) + var j = xl-1 + while j >= i: + shallowCopy(x[j+1], x[j]) + dec(j) + when nimvm: + defaultImpl() + else: + when defined(js): + {.emit: "`x`[`x`_Idx].splice(`i`, 0, null);".} + else: + defaultImpl() x[i] = item 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 @@ -2216,7 +2315,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): @@ -3373,7 +3474,7 @@ when hasAlloc: x[j+i] = item[j] inc(j) -proc compiles*(x: expr): bool {.magic: "Compiles", noSideEffect.} = +proc compiles*(x: expr): bool {.magic: "Compiles", noSideEffect, compileTime.} = ## Special compile-time procedure that checks whether `x` can be compiled ## without any semantic error. ## This can be used to check whether a type supports some operation: @@ -3437,7 +3538,7 @@ when hasAlloc and not defined(nimscript) and not defined(JS): include "system/deepcopy" -proc procCall*(x: expr) {.magic: "ProcCall".} = +proc procCall*(x: expr) {.magic: "ProcCall", compileTime.} = ## special magic to prohibit dynamic binding for `method`:idx: calls. ## This is similar to `super`:idx: in ordinary OO languages. ## @@ -3446,6 +3547,7 @@ proc procCall*(x: expr) {.magic: "ProcCall".} = ## procCall someMethod(a, b) discard +proc `^`*[T](x: int; y: openArray[T]): int {.noSideEffect, magic: "Roof".} proc `^`*(x: int): int {.noSideEffect, magic: "Roof".} = ## builtin `roof`:idx: operator that can be used for convenient array access. ## ``a[^x]`` is rewritten to ``a[a.len-x]``. However currently the ``a`` 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/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/repr.nim b/lib/system/repr.nim index b4188527f..1f81a0813 100644 --- a/lib/system/repr.nim +++ b/lib/system/repr.nim @@ -21,9 +21,22 @@ proc reprPointer(x: pointer): string {.compilerproc.} = return $buf proc `$`(x: uint64): string = - var buf: array [0..59, char] - discard c_sprintf(buf, "%llu", x) - return $buf + if x == 0: + result = "0" + else: + var buf: array [60, char] + var i = 0 + var n = x + while n != 0: + let nn = n div 10'u64 + buf[i] = char(n - 10'u64 * nn + ord('0')) + inc i + n = nn + + let half = i div 2 + # Reverse + for t in 0 .. < half: swap(buf[t], buf[i-t-1]) + result = $buf proc reprStrAux(result: var string, s: string) = if cast[pointer](s) == nil: @@ -294,4 +307,3 @@ when not defined(useNimRtl): reprAux(result, addr(p), typ, cl) add result, "\n" deinitReprClosure(cl) - diff --git a/lib/windows/winlean.nim b/lib/windows/winlean.nim index 24015dd3a..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, @@ -409,7 +429,7 @@ type bytes*: array[0..15, char] Sockaddr_in6* {.importc: "SOCKADDR_IN6", - header: "winsock2.h".} = object + header: "ws2tcpip.h".} = object sin6_family*: int16 sin6_port*: int16 # unsigned sin6_flowinfo*: int32 # unsigned @@ -511,6 +531,9 @@ proc connect*(s: SocketHandle, name: ptr SockAddr, namelen: SockLen): cint {. proc getsockname*(s: SocketHandle, name: ptr SockAddr, namelen: ptr SockLen): cint {. stdcall, importc: "getsockname", dynlib: ws2dll.} +proc getpeername*(s: SocketHandle, name: ptr SockAddr, + namelen: ptr SockLen): cint {. + stdcall, importc, dynlib: ws2dll.} proc getsockopt*(s: SocketHandle, level, optname: cint, optval: pointer, optlen: ptr SockLen): cint {. stdcall, importc: "getsockopt", dynlib: ws2dll.} @@ -572,6 +595,9 @@ proc freeaddrinfo*(ai: ptr AddrInfo) {. proc inet_ntoa*(i: InAddr): cstring {. stdcall, importc, dynlib: ws2dll.} +proc inet_ntop*(family: cint, paddr: pointer, pStringBuffer: cstring, + stringBufSize: int32): cstring {.stdcall, importc, dynlib: ws2dll.} + const MAXIMUM_WAIT_OBJECTS* = 0x00000040 @@ -609,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, |