diff options
Diffstat (limited to 'lib/pure')
-rwxr-xr-x | lib/pure/logging.nim | 146 | ||||
-rwxr-xr-x | lib/pure/os.nim | 21 | ||||
-rwxr-xr-x | lib/pure/ropes.nim | 375 | ||||
-rw-r--r-- | lib/pure/sockets.nim | 206 | ||||
-rwxr-xr-x | lib/pure/strtabs.nim | 8 |
5 files changed, 742 insertions, 14 deletions
diff --git a/lib/pure/logging.nim b/lib/pure/logging.nim new file mode 100755 index 000000000..6df39f50b --- /dev/null +++ b/lib/pure/logging.nim @@ -0,0 +1,146 @@ +# +# +# Nimrod's Runtime Library +# (c) Copyright 2009 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module implements a simple logger. It is based on the following design: +## * Runtime log formating is a bug: Sooner or later ever log file is parsed. +## * Keep it simple: If this library does not fullfill your needs, write your +## own. Trying to support every logging feature just leads to bloat. +## +## Format is:: +## +## DEBUG|INFO|... (2009-11-02 00:00:00)? (Component: )? Message +## +## + +type + TLevel* = enum ## logging level + lvlAll, ## all levels active + lvlDebug, ## debug level (and any above) active + lvlInfo, ## info level (and any above) active + lvlWarn, ## warn level (and any above) active + lvlError, ## error level (and any above) active + lvlFatal ## fatal level (and any above) active + +const + LevelNames*: array [TLevel, string] = [ + "DEBUG", "DEBUG", "INFO", "WARN", "ERROR", "FATAL" + ] + +type + TLogger* = object of TObject ## abstract logger; the base type of all loggers + levelThreshold*: TLevel ## only messages of level >= levelThreshold + ## should be processed + TConsoleLogger* = object of TLogger ## logger that writes the messages to the + ## console + + TFileLogger* = object of TLogger ## logger that writes the messages to a file + f: TFile + + TRollingFileLogger* = object of + TFileLogger ## logger that writes the message to a file + maxlines: int # maximum number of lines + lines: seq[string] + +method log*(L: ref TLogger, level: TLevel, + frmt: string, args: openArray[string]) = + ## override this method in custom loggers. Default implementation does + ## nothing. + nil + +method log*(L: ref TConsoleLogger, level: TLevel, + frmt: string, args: openArray[string]) = + Writeln(stdout, LevelNames[level], " ", frmt % args) + +method log*(L: ref TFileLogger, level: TLevel, + frmt: string, args: openArray[string]) = + Writeln(L.f, LevelNames[level], " ", frmt % args) + +proc defaultFilename*(): string = + ## returns the default filename for a logger + var (path, name, ext) = splitFile(getApplicationFilename()) + result = changeFileExt(path / name & "_" & getDateStr(), "log") + +proc substituteLog*(frmt: string): string = + ## converts $date to the current date + ## converts $time to the current time + ## converts $app to getApplicationFilename() + ## converts + result = "" + var i = 0 + while i < frmt.len: + if frmt[i] != '$': + result.add(frmt[i]) + inc(i) + else: + inc(i) + var v = "" + var app = getApplicationFilename() + while frmt[i] in IdentChars: + v.add(toLower(frmt[i])) + inc(i) + case v + of "date": result.add(getDateStr()) + of "time": result.add(getClockStr()) + of "app": result.add(app) + of "appdir": result.add(app.splitFile.dir) + of "appname": result.add(app.splitFile.name) + + +proc newFileLogger(filename = defaultFilename(), + mode: TFileMode = fmAppend, + levelThreshold = lvlNone): ref TFileLogger = + new(result) + result.levelThreshold = levelThreshold + if not open(result.f, filename, mode): + raiseException(EIO, "cannot open for writing: " & filename) + +proc newRollingFileLogger(filename = defaultFilename(), + mode: TFileMode = fmAppend, + levelThreshold = lvlNone, + maxLines = 1000): ref TFileLogger = + new(result) + result.levelThreshold = levelThreshold + result.maxLines = maxLines + if not open(result.f, filename, mode): + raiseException(EIO, "cannot open for writing: " & filename) + +var + level* = lvlNone + handlers*: seq[ref TLogger] = @[] + +proc logLoop(level: TLevel, msg: string) = + for logger in items(handlers): + if level >= logger.levelThreshold: + log(logger, level, msg) + +template log*(level: TLevel, msg: string) = + ## logs a message of the given level + if level >= logging.Level: + (bind logLoop)(level, frmt, args) + +template debug*(msg: string) = + ## logs a debug message + log(lvlDebug, msg) + +template info*(msg: string) = + ## logs an info message + log(lvlInfo, msg) + +template warn*(msg: string) = + ## logs a warning message + log(lvlWarn, msg) + +template error*(msg: string) = + ## logs an error message + log(lvlError, msg) + +template fatal*(msg: string) = + ## logs a fatal error message and calls ``quit(msg)`` + log(lvlFatal, msg) + diff --git a/lib/pure/os.nim b/lib/pure/os.nim index afa145e9f..85ac9c83c 100755 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -635,22 +635,23 @@ proc copyFile*(dest, source: string) = if CopyFileA(source, dest, 0'i32) == 0'i32: OSError() else: # generic version of copyFile which works for any platform: - const - bufSize = 8192 # 8K buffer - var - d, s: TFile + const bufSize = 8000 # better for memory manager + var d, s: TFile if not open(s, source): OSError() if not open(d, dest, fmWrite): close(s) OSError() - var - buf: Pointer = alloc(bufsize) - bytesread, byteswritten: int + var buf = alloc(bufsize) while True: - bytesread = readBuffer(s, buf, bufsize) - byteswritten = writeBuffer(d, buf, bytesread) + var bytesread = readBuffer(s, buf, bufsize) + if bytesread > 0: + var byteswritten = writeBuffer(d, buf, bytesread) + if bytesread != bytesWritten: + dealloc(buf) + close(s) + close(d) + OSError() if bytesread != bufSize: break - if bytesread != bytesWritten: OSError() dealloc(buf) close(s) close(d) diff --git a/lib/pure/ropes.nim b/lib/pure/ropes.nim new file mode 100755 index 000000000..6655a9fda --- /dev/null +++ b/lib/pure/ropes.nim @@ -0,0 +1,375 @@ +# +# +# Nimrod's Runtime Library +# (c) Copyright 2009 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module contains support for a `rope`:idx: data type. +## Ropes can represent very long strings efficiently; especially concatenation +## is done in O(1) instead of O(n). They are essentially concatenation +## trees that are only flattened when converting to a native Nimrod +## string. The empty string is represented by ``nil``. Ropes are immutable and +## subtrees can be shared without copying. +## Leaves can be cached for better memory efficiency at the cost of a bit of +## runtime efficiency. + +{.deadCodeElim: on.} + +{.push debugger:off .} # the user does not want to trace a part + # of the standard library! + +# copied from excpt.nim, because I don't want to make this template public +template newException(exceptn, message: expr): expr = + block: # open a new scope + var + e: ref exceptn + new(e) + e.msg = message + e + +const + countCacheMisses = false + +var + cacheEnabled = false + +type + PRope* = ref TRope ## empty rope is represented by nil + TRope {.acyclic, final, pure.} = object + left, right: PRope + length: int + data: string # != nil if a leaf + +proc isConc(r: PRope): bool {.inline.} = return isNil(r.data) + +# Note that the left and right pointers are not needed for leafs. +# Leaves have relatively high memory overhead (~30 bytes on a 32 +# bit machine) and we produce many of them. This is why we cache and +# share leafs accross different rope trees. +# To cache them they are inserted in another tree, a splay tree for best +# performance. But for the caching tree we use the leaf's left and right +# pointers. + +proc len*(a: PRope): int = + ## the rope's length + if a == nil: result = 0 + else: result = a.length + +proc newRope(): PRope = new(result) +proc newRope(data: string): PRope = + new(result) + result.length = len(data) + result.data = data + +var + cache: PRope # the root of the cache tree + N: PRope # dummy rope needed for splay algorithm + +when countCacheMisses: + var misses, hits: int + +proc splay(s: string, tree: PRope, cmpres: var int): PRope = + var c: int + var t = tree + N.left = nil + N.right = nil # reset to nil + var le = N + var r = N + while true: + c = cmp(s, t.data) + if c < 0: + if (t.left != nil) and (s < t.left.data): + var y = t.left + t.left = y.right + y.right = t + t = y + if t.left == nil: break + r.left = t + r = t + t = t.left + elif c > 0: + if (t.right != nil) and (s > t.right.data): + var y = t.right + t.right = y.left + y.left = t + t = y + if t.right == nil: break + le.right = t + le = t + t = t.right + else: + break + cmpres = c + le.right = t.left + r.left = t.right + t.left = N.right + t.right = N.left + result = t + +proc insertInCache(s: string, tree: PRope): PRope = + var t = tree + if t == nil: + result = newRope(s) + when countCacheMisses: inc(misses) + return + var cmp: int + t = splay(s, t, cmp) + if cmp == 0: + # We get here if it's already in the Tree + # Don't add it again + result = t + when countCacheMisses: inc(hits) + else: + when countCacheMisses: inc(misses) + result = newRope(s) + if cmp < 0: + result.left = t.left + result.right = t + t.left = nil + else: + # i > t.item: + result.right = t.right + result.left = t + t.right = nil + +proc rope*(s: string): PRope = + ## Converts a string to a rope. + if s.len == 0: + result = nil + elif cacheEnabled: + result = insertInCache(s, cache) + cache = result + else: + result = newRope(s) + +proc rope*(i: BiggestInt): PRope = + ## Converts an int to a rope. + result = rope($i) + +proc rope*(f: BiggestFloat): PRope = + ## Converts a float to a rope. + result = rope($f) + +proc disableCache*() = + ## the cache is discarded and disabled. The GC will reuse its used memory. + cache = nil + cacheEnabled = false + +proc enableCache*() = + ## Enables the caching of leaves. This reduces the memory footprint at + ## the cost of runtime efficiency. + cacheEnabled = true + +proc `&`*(a, b: PRope): PRope = + ## the concatenation operator for ropes. + if a == nil: + result = b + elif b == nil: + result = a + else: + result = newRope() + result.length = a.length + b.length + when false: + # XXX rebalancing would be nice, but is too expensive. + result.left = a.left + var x = newRope() + x.left = a.right + x.right = b + result.right = x + else: + result.left = a + result.right = b + +proc `&`*(a: PRope, b: string): PRope = + ## the concatenation operator for ropes. + result = a & rope(b) + +proc `&`*(a: string, b: PRope): PRope = + ## the concatenation operator for ropes. + result = rope(a) & b + +proc `&`*(a: openarray[PRope]): PRope = + ## the concatenation operator for an openarray of ropes. + for i in countup(0, high(a)): result = result & a[i] + +proc add*(a: var PRope, b: PRope) = + ## adds `b` to the rope `a`. + a = a & b + +proc add*(a: var PRope, b: string) = + ## adds `b` to the rope `a`. + a = a & b + +proc `[]`*(r: PRope, i: int): char = + ## returns the character at position `i` in the rope `r`. This is quite + ## expensive! Worst-case: O(n). If ``i >= r.len``, ``\0`` is returned. + var x = r + var j = i + if x == nil: return + while true: + if not isConc(x): + if x.data.len <% j: return x.data[j] + return '\0' + else: + if x.left.len >% j: + x = x.left + else: + x = x.right + dec(j, x.len) + +iterator leaves*(r: PRope): string = + ## iterates over any leaf string in the rope `r`. + if r != nil: + var stack = @[r] + while stack.len > 0: + var it = stack.pop + while isConc(it): + stack.add(it.right) + it = it.left + assert(it != nil) + assert(it.data != nil) + yield it.data + +iterator items*(r: PRope): char = + ## iterates over any character in the rope `r`. + for s in leaves(r): + for c in items(s): yield c + +proc write*(f: TFile, r: PRope) = + ## writes a rope to a file. + for s in leaves(r): write(f, s) + +proc `$`*(r: PRope): string = + ## converts a rope back to a string. + result = newString(r.len) + setLen(result, 0) + for s in leaves(r): add(result, s) + +when false: + # Format string caching seems reasonable: All leaves can be shared and format + # string parsing has to be done only once. A compiled format string is stored + # as a rope. A negative length is used for the index into the args array. + proc compiledArg(idx: int): PRope = + new(result) + result.length = -idx + + proc compileFrmt(frmt: string): PRope = + var i = 0 + var length = len(frmt) + result = nil + var num = 0 + while i < length: + if frmt[i] == '$': + inc(i) + case frmt[i] + of '$': + add(result, "$") + inc(i) + of '#': + inc(i) + add(result, compiledArg(num+1)) + inc(num) + of '0'..'9': + var j = 0 + while true: + j = j * 10 + ord(frmt[i]) - ord('0') + inc(i) + if frmt[i] notin {'0'..'9'}: break + num = j + add(s, compiledArg(j)) + of '{': + inc(i) + var j = 0 + while frmt[i] in {'0'..'9'}: + j = j * 10 + ord(frmt[i]) - ord('0') + inc(i) + if frmt[i] == '}': inc(i) + else: raise newException(EInvalidValue, "invalid format string") + num = j + add(s, compiledArg(j)) + else: raise newException(EInvalidValue, "invalid format string") + var start = i + while i < length: + if frmt[i] != '$': inc(i) + else: break + if i - 1 >= start: + add(result, copy(frmt, start, i-1)) + +proc `%`*(frmt: string, args: openarray[PRope]): PRope = + ## `%` substitution operator for ropes. Does not support the ``$identifier`` + ## nor ``${identifier}`` notations. + var i = 0 + var length = len(frmt) + result = nil + var num = 0 + while i < length: + if frmt[i] == '$': + inc(i) + case frmt[i] + of '$': + add(result, "$") + inc(i) + of '#': + inc(i) + add(result, args[num]) + inc(num) + of '0'..'9': + var j = 0 + while true: + j = j * 10 + ord(frmt[i]) - ord('0') + inc(i) + if frmt[i] notin {'0'..'9'}: break + num = j + add(result, args[j-1]) + of '{': + inc(i) + var j = 0 + while frmt[i] in {'0'..'9'}: + j = j * 10 + ord(frmt[i]) - ord('0') + inc(i) + if frmt[i] == '}': inc(i) + else: raise newException(EInvalidValue, "invalid format string") + num = j + add(result, args[j-1]) + else: raise newException(EInvalidValue, "invalid format string") + var start = i + while i < length: + if frmt[i] != '$': inc(i) + else: break + if i - 1 >= start: + add(result, copy(frmt, start, i - 1)) + +proc addf*(c: var PRope, frmt: string, args: openarray[PRope]) = + ## shortcut for ``add(c, frmt % args)``. + add(c, frmt % args) + +proc equalsFile*(r: PRope, f: TFile): bool = + ## returns true if the contents of the file `f` equal `r`. + var bufSize = 1024 # reasonable start value + var buf = alloc(BufSize) + for s in leaves(r): + if s.len > bufSize: + bufSize = max(bufSize * 2, s.len) + buf = realloc(buf, bufSize) + var readBytes = readBuffer(f, buf, s.len) + result = readBytes == s.len and equalMem(buf, cstring(s), s.len) + if not result: break + if result: + result = readBuffer(f, buf, 1) == 0 # really at the end of file? + dealloc(buf) + +proc equalsFile*(r: PRope, f: string): bool = + ## returns true if the contents of the file `f` equal `r`. If `f` does not + ## exist, false is returned. + var bin: TFile + result = open(bin, f) + if result: + result = equalsFile(r, bin) + close(bin) + +new(N) # init dummy node for splay algorithm + +{.pop.} \ No newline at end of file diff --git a/lib/pure/sockets.nim b/lib/pure/sockets.nim new file mode 100644 index 000000000..0f9221b37 --- /dev/null +++ b/lib/pure/sockets.nim @@ -0,0 +1,206 @@ +# +# +# Nimrod's Runtime Library +# (c) Copyright 2010 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module implements a simple portable type-safe sockets layer. **Note**: +## This module is incomplete and probably buggy. It does not work on Windows +## yet. Help if you are interested. + +# TODO: +# getservbyname(name, proto) +# getservbyport(port, proto) +# gethostbyname(name) +# gethostbyaddr(addr) +# shutdown(sock, how) +# connect(sock, address, port) +# select({ socket, ... }, timeout) + +# sendto +# recvfrom + +# bind(socket, address, port) + +# getsockopt(socket, level, optname) +# setsockopt(socket, level, optname, value) + +import os + +when defined(Windows): + import winlean +else: + import posix + +type + TSocket* = distinct cint ## socket type + TPort* = distinct int16 ## port type + + TDomain* = enum ## domain, which specifies the protocol family of the + ## created socket. Other domains than those that are listed + ## here are unsupported. + AF_UNIX, ## for local socket (using a file). + AF_INET, ## for network protocol IPv4 or + AF_INET6 ## for network protocol IPv6. + + TType* = enum ## second argument to `socket` proc + SOCK_STREAM, ## reliable stream-oriented service or Stream Sockets + SOCK_DGRAM, ## datagram service or Datagram Sockets + SOCK_SEQPACKET, ## reliable sequenced packet service, or + SOCK_RAW ## raw protocols atop the network layer. + + TProtocol* = enum ## third argument to `socket` proc + IPPROTO_TCP, ## Transmission control protocol. + IPPROTO_UDP, ## User datagram protocol. + IPPROTO_IP, ## Internet protocol. + IPPROTO_IPV6, ## Internet Protocol Version 6. + IPPROTO_RAW, ## Raw IP Packets Protocol. + IPPROTO_ICMP ## Control message protocol. + +const + InvalidSocket* = TSocket(-1'i32) ## invalid socket number + +proc `==`*(a, b: TSocket): bool {.borrow.} +proc `==`*(a, b: TPort): bool {.borrow.} + +proc ToInt(domain: TDomain): cint = + case domain + of AF_UNIX: result = posix.AF_UNIX + of AF_INET: result = posix.AF_INET + of AF_INET6: result = posix.AF_INET6 + +proc ToInt(typ: TType): cint = + case typ + of SOCK_STREAM: result = posix.SOCK_STREAM + of SOCK_DGRAM: result = posix.SOCK_DGRAM + of SOCK_SEQPACKET: result = posix.SOCK_SEQPACKET + of SOCK_RAW: result = posix.SOCK_RAW + +proc ToInt(p: TProtocol): cint = + case p + of IPPROTO_TCP: result = posix.IPPROTO_TCP + of IPPROTO_UDP: result = posix.IPPROTO_UDP + of IPPROTO_IP: result = posix.IPPROTO_IP + of IPPROTO_IPV6: result = posix.IPPROTO_IPV6 + of IPPROTO_RAW: result = posix.IPPROTO_RAW + of IPPROTO_ICMP: result = posix.IPPROTO_ICMP + +proc socket*(domain: TDomain = AF_INET6, typ: TType = SOCK_STREAM, + protocol: TProtocol = IPPROTO_TCP): TSocket = + ## creates a new socket; returns `InvalidSocket` if an error occurs. + result = TSocket(posix.socket(ToInt(domain), ToInt(typ), ToInt(protocol))) + +proc listen*(socket: TSocket, attempts = 5) = + ## listens to socket. + if posix.listen(cint(socket), cint(attempts)) < 0'i32: OSError() + +proc bindAddr*(socket: TSocket, port = TPort(0)) = + var name: Tsockaddr_in + name.sin_family = posix.AF_INET + name.sin_port = htons(int16(port)) + name.sin_addr.s_addr = htonl(INADDR_ANY) + if bindSocket(cint(socket), cast[ptr TSockAddr](addr(name)), + sizeof(name)) < 0'i32: + OSError() + +proc getSockName*(socket: TSocket): TPort = + var name: Tsockaddr_in + name.sin_family = posix.AF_INET + #name.sin_port = htons(cint16(port)) + #name.sin_addr.s_addr = htonl(INADDR_ANY) + var namelen: cint = sizeof(name) + if getsockname(cint(socket), cast[ptr TSockAddr](addr(name)), + addr(namelen)) == -1'i32: + OSError() + result = TPort(ntohs(name.sin_port)) + +proc accept*(server: TSocket): TSocket = + ## waits for a client and returns its socket + var client: Tsockaddr_in + var clientLen: TsockLen = sizeof(client) + result = TSocket(accept(cint(server), cast[ptr TSockAddr](addr(client)), + addr(clientLen))) + +proc close*(socket: TSocket) = + ## closes a socket. + when defined(windows): + discard winlean.closeSocket(cint(socket)) + else: + discard posix.close(cint(socket)) + +proc recvLine*(socket: TSocket, line: var string): bool = + ## returns false if no further data is available. + setLen(line, 0) + while true: + var c: char + var n = recv(cint(socket), addr(c), 1, 0'i32) + if n <= 0: return + if c == '\r': + n = recv(cint(socket), addr(c), 1, MSG_PEEK) + if n > 0 and c == '\L': + discard recv(cint(socket), addr(c), 1, 0'i32) + elif n <= 0: return false + return true + elif c == '\L': return true + add(line, c) + +proc recv*(socket: TSocket, data: pointer, size: int): int = + ## receive data from a socket + result = posix.recv(cint(socket), data, size, 0'i32) + +proc recv*(socket: TSocket): string = + ## receive all the data from the socket + const bufSize = 200 + var buf = newString(bufSize) + result = "" + while true: + var bytesRead = recv(socket, cstring(buf), bufSize-1) + buf[bytesRead] = '\0' # might not be necessary + setLen(buf, bytesRead) + add(result, buf) + if bytesRead != bufSize-1: break + +proc skip*(socket: TSocket) = + ## skips all the data that is pending for the socket + const bufSize = 200 + var buf = alloc(bufSize) + while recv(socket, buf, bufSize) == bufSize: nil + dealloc(buf) + +proc send*(socket: TSocket, data: pointer, size: int): int = + result = posix.send(cint(socket), data, size, 0'i32) + +proc send*(socket: TSocket, data: string) = + if send(socket, cstring(data), data.len) != data.len: OSError() + +proc ntohl*(x: int32): int32 = + ## Convert 32-bit integers from network to host 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. + when cpuEndian == bigEndian: result = x + else: result = (x shr 24'i32) or + (x shr 8'i32 and 0xff00'i32) or + (x shl 8'i32 and 0xff0000'i32) or + (x shl 24'i32) + +proc ntohs*(x: int16): int16 = + ## Convert 16-bit integers from network to host 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. + when cpuEndian == bigEndian: result = x + else: result = (x shr 8'i16) or (x shl 8'i16) + +proc htonl*(x: int32): int32 = + ## Convert 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 = sockets.ntohl(x) + +proc htons*(x: int16): int16 = + ## Convert 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 = sockets.ntohs(x) diff --git a/lib/pure/strtabs.nim b/lib/pure/strtabs.nim index 10cd0b933..8ea59637a 100755 --- a/lib/pure/strtabs.nim +++ b/lib/pure/strtabs.nim @@ -7,10 +7,10 @@ # distribution, for details about the copyright. # -## The ``strtabs`` module implements an efficient hash table that is a mapping -## from strings to strings. Supports a case-sensitive, case-insensitive and -## style-insensitive mode. An efficient string substitution operator ``%`` -## for the string table is also provided. +## The ``strtabs`` module implements an efficient hash table that is a mapping +## from strings to strings. Supports a case-sensitive, case-insensitive and +## style-insensitive mode. An efficient string substitution operator ``%`` +## for the string table is also provided. import os, hashes, strutils |