better subscript overloading

This commit is contained in:
rumpf_a@web.de 2010-01-03 12:31:21 +01:00
commit a58a2f3823
85 changed files with 55936 additions and 2319 deletions

146
lib/pure/logging.nim Executable file
View file

@ -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)

View file

@ -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)

375
lib/pure/ropes.nim Executable file
View file

@ -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.}

206
lib/pure/sockets.nim Normal file
View file

@ -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)

View file

@ -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