version0.7.10

This commit is contained in:
Andreas Rumpf 2009-06-08 08:06:25 +02:00
commit 4d4b3b1c04
193 changed files with 9099 additions and 17637 deletions

341
lib/pure/cgi.nim Normal file
View file

@ -0,0 +1,341 @@
#
#
# 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 helper procs for CGI applictions. Example:
##
## .. code-block:: Nimrod
##
## import strtabs, cgi
##
## # Fill the values when debugging:
## when debug:
## setTestData("name", "Klaus", "password", "123456")
## # read the data into `myData`
## var myData = readData()
## # check that the data's variable names are "name" or "passwort"
## validateData(myData, "name", "password")
## # start generating content:
## writeContentType()
## # generate content:
## write(stdout, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\">\n")
## write(stdout, "<html><head><title>Test</title></head><body>\n")
## writeln(stdout, "your name: " & myData["name"])
## writeln(stdout, "your password: " & myData["password"])
## writeln(stdout, "</body></html>")
import strutils, os, strtabs
proc URLencode*(s: string): string =
## Encodes a value to be HTTP safe: This means that characters in the set
## ``{'A'..'Z', 'a'..'z', '0'..'9', '_'}`` are carried over to the result,
## a space is converted to ``'+'`` and every other character is encoded as
## ``'%xx'`` where ``xx`` denotes its hexadecimal value.
result = ""
for i in 0..s.len-1:
case s[i]
of 'a'..'z', 'A'..'Z', '0'..'9', '_': add(result, s[i])
of ' ': add(result, '+')
else:
add(result, '%')
add(result, toHex(ord(s[i]), 2))
proc handleHexChar(c: char, x: var int) {.inline.} =
case c
of '0'..'9': x = (x shl 4) or (ord(c) - ord('0'))
of 'a'..'f': x = (x shl 4) or (ord(c) - ord('a') + 10)
of 'A'..'F': x = (x shl 4) or (ord(c) - ord('A') + 10)
else: assert(false)
proc URLdecode*(s: string): string =
## Decodes a value from its HTTP representation: This means that a ``'+'``
## is converted to a space, ``'%xx'`` (where ``xx`` denotes a hexadecimal
## value) is converted to the character with ordinal number ``xx``, and
## and every other character is carried over.
result = ""
var i = 0
while i < s.len:
case s[i]
of '%':
var x = 0
handleHexChar(s[i+1], x)
handleHexChar(s[i+2], x)
inc(i, 2)
add(result, chr(x))
of '+': add(result, ' ')
else: add(result, s[i])
inc(i)
proc addXmlChar(dest: var string, c: Char) {.inline.} =
case c
of '&': add(dest, "&amp;")
of '<': add(dest, "&lt;")
of '>': add(dest, "&gt;")
of '\"': add(dest, "&quot;")
else: add(dest, c)
proc XMLencode*(s: string): string =
## Encodes a value to be XML safe:
## * ``"`` is replaced by ``&quot;``
## * ``<`` is replaced by ``&lt;``
## * ``>`` is replaced by ``&gt;``
## * ``&`` is replaced by ``&amp;``
## * every other character is carried over.
result = ""
for i in 0..len(s)-1: addXmlChar(result, s[i])
type
ECgi* = object of EIO ## the exception that is raised, if a CGI error occurs
TRequestMethod* = enum ## the used request method
methodNone, ## no REQUEST_METHOD environment variable
methodPost, ## query uses the POST method
methodGet ## query uses the GET method
proc cgiError*(msg: string) {.noreturn.} =
## raises an ECgi exception with message `msg`.
var e: ref ECgi
new(e)
e.msg = msg
raise e
proc getEncodedData(allowedMethods: set[TRequestMethod]): string =
case getenv("REQUEST_METHOD")
of "POST":
if methodPost notin allowedMethods:
cgiError("'REQUEST_METHOD' 'POST' is not supported")
var L = parseInt(getenv("CONTENT_LENGTH"))
result = newString(L)
if readBuffer(stdin, addr(result[0]), L) != L:
cgiError("cannot read from stdin")
of "GET":
if methodGet notin allowedMethods:
cgiError("'REQUEST_METHOD' 'GET' is not supported")
result = getenv("QUERY_STRING")
else:
if methodNone notin allowedMethods:
cgiError("'REQUEST_METHOD' must be 'POST' or 'GET'")
iterator decodeData*(allowedMethods: set[TRequestMethod] =
{methodNone, methodPost, methodGet}): tuple[key, value: string] =
## Reads and decodes CGI data and yields the (name, value) pairs the
## data consists of. If the client does not use a method listed in the
## `allowedMethods` set, an `ECgi` exception is raised.
var enc = getEncodedData(allowedMethods)
if not isNil(enc):
# decode everything in one pass:
var i = 0
var name = ""
var value = ""
while enc[i] != '\0':
setLen(name, 0) # reuse memory
while true:
case enc[i]
of '\0': break
of '%':
var x = 0
handleHexChar(enc[i+1], x)
handleHexChar(enc[i+2], x)
inc(i, 2)
add(name, chr(x))
of '+': add(name, ' ')
of '=', '&': break
else: add(name, enc[i])
inc(i)
if enc[i] != '=': cgiError("'=' expected")
inc(i) # skip '='
setLen(value, 0) # reuse memory
while true:
case enc[i]
of '%':
var x = 0
handleHexChar(enc[i+1], x)
handleHexChar(enc[i+2], x)
inc(i, 2)
add(value, chr(x))
of '+': add(value, ' ')
of '&', '\0': break
else: add(value, enc[i])
inc(i)
yield (name, value)
if enc[i] == '&': inc(i)
elif enc[i] == '\0': break
else: cgiError("'&' expected")
proc readData*(allowedMethods: set[TRequestMethod] =
{methodNone, methodPost, methodGet}): PStringTable =
## Read CGI data. If the client does not use a method listed in the
## `allowedMethods` set, an `ECgi` exception is raised.
result = newStringTable()
for name, value in decodeData(allowedMethods):
result[name] = value
proc validateData*(data: PStringTable, validKeys: openarray[string]) =
## validates data; raises `ECgi` if this fails. This checks that each variable
## name of the CGI `data` occurs in the `validKeys` array.
for key, val in pairs(data):
if find(validKeys, key) < 0:
cgiError("unknown variable name: " & key)
proc getContentLength*(): string =
## returns contents of the ``CONTENT_LENGTH`` environment variable
return getenv("CONTENT_LENGTH")
proc getContentType*(): string =
## returns contents of the ``CONTENT_TYPE`` environment variable
return getenv("CONTENT_Type")
proc getDocumentRoot*(): string =
## returns contents of the ``DOCUMENT_ROOT`` environment variable
return getenv("DOCUMENT_ROOT")
proc getGatewayInterface*(): string =
## returns contents of the ``GATEWAY_INTERFACE`` environment variable
return getenv("GATEWAY_INTERFACE")
proc getHttpAccept*(): string =
## returns contents of the ``HTTP_ACCEPT`` environment variable
return getenv("HTTP_ACCEPT")
proc getHttpAcceptCharset*(): string =
## returns contents of the ``HTTP_ACCEPT_CHARSET`` environment variable
return getenv("HTTP_ACCEPT_CHARSET")
proc getHttpAcceptEncoding*(): string =
## returns contents of the ``HTTP_ACCEPT_ENCODING`` environment variable
return getenv("HTTP_ACCEPT_ENCODING")
proc getHttpAcceptLanguage*(): string =
## returns contents of the ``HTTP_ACCEPT_LANGUAGE`` environment variable
return getenv("HTTP_ACCEPT_LANGUAGE")
proc getHttpConnection*(): string =
## returns contents of the ``HTTP_CONNECTION`` environment variable
return getenv("HTTP_CONNECTION")
proc getHttpCookie*(): string =
## returns contents of the ``HTTP_COOKIE`` environment variable
return getenv("HTTP_COOKIE")
proc getHttpHost*(): string =
## returns contents of the ``HTTP_HOST`` environment variable
return getenv("HTTP_HOST")
proc getHttpReferer*(): string =
## returns contents of the ``HTTP_REFERER`` environment variable
return getenv("HTTP_REFERER")
proc getHttpUserAgent*(): string =
## returns contents of the ``HTTP_USER_AGENT`` environment variable
return getenv("HTTP_USER_AGENT")
proc getPathInfo*(): string =
## returns contents of the ``PATH_INFO`` environment variable
return getenv("PATH_INFO")
proc getPathTranslated*(): string =
## returns contents of the ``PATH_TRANSLATED`` environment variable
return getenv("PATH_TRANSLATED")
proc getQueryString*(): string =
## returns contents of the ``QUERY_STRING`` environment variable
return getenv("QUERY_STRING")
proc getRemoteAddr*(): string =
## returns contents of the ``REMOTE_ADDR`` environment variable
return getenv("REMOTE_ADDR")
proc getRemoteHost*(): string =
## returns contents of the ``REMOTE_HOST`` environment variable
return getenv("REMOTE_HOST")
proc getRemoteIdent*(): string =
## returns contents of the ``REMOTE_IDENT`` environment variable
return getenv("REMOTE_IDENT")
proc getRemotePort*(): string =
## returns contents of the ``REMOTE_PORT`` environment variable
return getenv("REMOTE_PORT")
proc getRemoteUser*(): string =
## returns contents of the ``REMOTE_USER`` environment variable
return getenv("REMOTE_USER")
proc getRequestMethod*(): string =
## returns contents of the ``REQUEST_METHOD`` environment variable
return getenv("REQUEST_METHOD")
proc getRequestURI*(): string =
## returns contents of the ``REQUEST_URI`` environment variable
return getenv("REQUEST_URI")
proc getScriptFilename*(): string =
## returns contents of the ``SCRIPT_FILENAME`` environment variable
return getenv("SCRIPT_FILENAME")
proc getScriptName*(): string =
## returns contents of the ``SCRIPT_NAME`` environment variable
return getenv("SCRIPT_NAME")
proc getServerAddr*(): string =
## returns contents of the ``SERVER_ADDR`` environment variable
return getenv("SERVER_ADDR")
proc getServerAdmin*(): string =
## returns contents of the ``SERVER_ADMIN`` environment variable
return getenv("SERVER_ADMIN")
proc getServerName*(): string =
## returns contents of the ``SERVER_NAME`` environment variable
return getenv("SERVER_NAME")
proc getServerPort*(): string =
## returns contents of the ``SERVER_PORT`` environment variable
return getenv("SERVER_PORT")
proc getServerProtocol*(): string =
## returns contents of the ``SERVER_PROTOCOL`` environment variable
return getenv("SERVER_PROTOCOL")
proc getServerSignature*(): string =
## returns contents of the ``SERVER_SIGNATURE`` environment variable
return getenv("SERVER_SIGNATURE")
proc getServerSoftware*(): string =
## returns contents of the ``SERVER_SOFTWARE`` environment variable
return getenv("SERVER_SOFTWARE")
proc setTestData*(keysvalues: openarray[string]) =
## fills the appropriate environment variables to test your CGI application.
## This can only simulate the 'GET' request method. `keysvalues` should
## provide embedded (name, value)-pairs. Example:
##
## .. code-block:: Nimrod
## setTestData("name", "Hanz", "password", "12345")
putenv("REQUEST_METHOD", "GET")
var i = 0
var query = ""
while i < keysvalues.len:
add(query, URLencode(keysvalues[i]))
add(query, '=')
add(query, URLencode(keysvalues[i+1]))
add(query, '&')
inc(i, 2)
putenv("QUERY_STRING", query)
proc writeContentType*() =
## call this before starting to send your HTML data to `stdout`. This
## implements this part of the CGI protocol:
##
## .. code-block:: Nimrod
## write(stdout, "Content-type: text/html\n\n")
##
## It also modifies the debug stack traces so that they contain
## ``<br />`` and are easily readable in a browser.
write(stdout, "Content-type: text/html\n\n")
system.stackTraceNewLine = "<br />\n"

106
lib/pure/complex.nim Normal file
View file

@ -0,0 +1,106 @@
#
#
# Nimrod's Runtime Library
# (c) Copyright 2006 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module implements complex numbers.
{.push checks:off, line_dir:off, stack_trace:off, debugger:off.}
# the user does not want to trace a part
# of the standard library!
import
math
type
TComplex* = tuple[re, im: float]
## a complex number, consisting of a real and an imaginary part
proc `==` *(x, y: TComplex): bool =
## Compare two complex numbers `x` and `y` for equality.
result = x.re == y.re and x.im == y.im
proc `+` *(x, y: TComplex): TComplex =
## Add two complex numbers.
result.re = x.re + y.re
result.im = x.im + y.im
proc `-` *(x, y: TComplex): TComplex =
## Subtract two complex numbers.
result.re = x.re - y.re
result.im = x.im - y.im
proc `-` *(z: TComplex): TComplex =
## Unary minus for complex numbers.
result.re = -z.re
result.im = -z.im
proc `/` *(x, y: TComplex): TComplex =
## Divide `x` by `y`.
var
r, den: float
if abs(y.re) < abs(y.im):
r = y.re / y.im
den = y.im + r * y.re
result.re = (x.re * r + x.im) / den
result.im = (x.im * r - x.re) / den
else:
r = y.im / y.re
den = y.re + r * y.im
result.re = (x.re + r * x.im) / den
result.im = (x.im - r * x.re) / den
proc `*` *(x, y: TComplex): TComplex =
## Multiply `x` with `y`.
result.re = x.re * y.re - x.im * y.im
result.im = x.im * y.re + x.re * y.im
proc abs*(z: TComplex): float =
## Return the distance from (0,0) to `z`.
# optimized by checking special cases (sqrt is expensive)
var x, y, temp: float
x = abs(z.re)
y = abs(z.im)
if x == 0.0:
result = y
elif y == 0.0:
result = x
elif x > y:
temp = y / x
result = x * sqrt(1.0 + temp * temp)
else:
temp = x / y
result = y * sqrt(1.0 + temp * temp)
proc sqrt*(z: TComplex): TComplex =
## Square root for a complex number `z`.
var x, y, w, r: float
if z.re == 0.0 and z.im == 0.0:
result = z
else:
x = abs(z.re)
y = abs(z.im)
if x >= y:
r = y / x
w = sqrt(x) * sqrt(0.5 * (1.0 + sqrt(1.0 + r * r)))
else:
r = x / y
w = sqrt(y) * sqrt(0.5 * (r + sqrt(1.0 + r * r)))
if z.re >= 0.0:
result.re = w
result.im = z.im / (w * 2)
else:
if z.im >= 0.0: result.im = w
else: result.im = -w
result.re = z.im / (c.im + c.im)
{.pop.}

84
lib/pure/dynlib.nim Normal file
View file

@ -0,0 +1,84 @@
#
#
# 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 the ability to access symbols from shared
## libraries. On POSIX this uses the ``dlsym`` mechanism, on
## Windows ``LoadLibrary``.
type
TLibHandle* = pointer ## a handle to a dynamically loaded library
proc LoadLib*(path: string): TLibHandle
## loads a library from `path`. Returns nil if the library could not
## be loaded.
proc UnloadLib*(lib: TLibHandle)
## unloads the library `lib`
proc symAddr*(lib: TLibHandle, name: string): pointer
## retrieves the address of a procedure/variable from `lib`. Returns nil
## if the symbol could not be found.
proc checkedSymAddr*(lib: TLibHandle, name: string): pointer =
## retrieves the address of a procedure/variable from `lib`. Raises
## `EInvalidLibrary` if the symbol could not be found.
result = symAddr(lib, name)
if result == nil:
var e: ref EInvalidLibrary
new(e)
e.msg = "could not find symbol: " & name
raise e
when defined(posix):
#
# =========================================================================
# This is an implementation based on the dlfcn interface.
# The dlfcn interface is available in Linux, SunOS, Solaris, IRIX, FreeBSD,
# NetBSD, AIX 4.2, HPUX 11, and probably most other Unix flavors, at least
# as an emulation layer on top of native functions.
# =========================================================================
#
var
RTLD_NOW {.importc: "RTLD_NOW", header: "<dlfcn.h>".}: int
proc dlclose(lib: TLibHandle) {.importc, header: "<dlfcn.h>".}
proc dlopen(path: CString, mode: int): TLibHandle {.
importc, header: "<dlfcn.h>".}
proc dlsym(lib: TLibHandle, name: cstring): pointer {.
importc, header: "<dlfcn.h>".}
proc LoadLib(path: string): TLibHandle = return dlopen(path, RTLD_NOW)
proc UnloadLib(lib: TLibHandle) = dlclose(lib)
proc symAddr(lib: TLibHandle, name: string): pointer =
return dlsym(lib, name)
elif defined(windows) or defined(dos):
#
# =======================================================================
# Native Windows Implementation
# =======================================================================
#
type
THINSTANCE {.importc: "HINSTANCE".} = pointer
proc FreeLibrary(lib: THINSTANCE) {.importc, header: "<windows.h>", stdcall.}
proc winLoadLibrary(path: cstring): THINSTANCE {.
importc: "LoadLibraryA", header: "<windows.h>", stdcall.}
proc GetProcAddress(lib: THINSTANCE, name: cstring): pointer {.
importc: "GetProcAddress", header: "<windows.h>", stdcall.}
proc LoadLib(path: string): TLibHandle =
result = cast[TLibHandle](winLoadLibrary(path))
proc UnloadLib(lib: TLibHandle) = FreeLibrary(cast[THINSTANCE](lib))
proc symAddr(lib: TLibHandle, name: string): pointer =
result = GetProcAddress(cast[THINSTANCE](lib), name)
else:
{.error: "no implementation for dynlib".}

97
lib/pure/hashes.nim Normal file
View file

@ -0,0 +1,97 @@
#
#
# Nimrod's Runtime Library
# (c) Copyright 2008 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module implements efficient computations of hash values for diverse
## Nimrod types.
import
strutils
type
THash* = int ## a hash value; hash tables using these values should
## always have a size of a power of two and can use the ``and``
## operator instead of ``mod`` for truncation of the hash value.
proc concHash(h: THash, val: int): THash {.inline.} =
result = h +% val
result = result +% result shl 10
result = result xor (result shr 6)
proc finishHash(h: THash): THash {.inline.} =
result = h +% h shl 3
result = result xor (result shr 11)
result = result +% result shl 15
proc hashData*(Data: Pointer, Size: int): THash =
## hashes an array of bytes of size `size`
var
h: THash
p: cstring
i, s: int
h = 0
p = cast[cstring](Data)
i = 0
s = size
while s > 0:
h = concHash(h, ord(p[i]))
Inc(i)
Dec(s)
result = finishHash(h)
proc hash*(x: Pointer): THash {.inline.} =
## efficient hashing of pointers
result = (cast[THash](x)) shr 3 # skip the alignment
proc hash*(x: int): THash {.inline.} =
## efficient hashing of integers
result = x
proc hash*(x: int64): THash {.inline.} =
## efficient hashing of integers
result = toU32(x)
proc hash*(x: char): THash {.inline.} =
## efficient hashing of characters
result = ord(x)
proc hash*(x: string): THash =
## efficient hashing of strings
var h: THash
h = 0
for i in 0..x.len-1:
h = concHash(h, ord(x[i]))
result = finishHash(h)
proc hashIgnoreStyle*(x: string): THash =
## efficient hashing of strings; style is ignored
var
h: THash
c: Char
h = 0
for i in 0..x.len-1:
c = x[i]
if c == '_':
continue # skip _
if c in {'A'..'Z'}:
c = chr(ord(c) + (ord('a') - ord('A'))) # toLower()
h = concHash(h, ord(c))
result = finishHash(h)
proc hashIgnoreCase*(x: string): THash =
## efficient hashing of strings; case is ignored
var
h: THash
c: Char
h = 0
for i in 0..x.len-1:
c = x[i]
if c in {'A'..'Z'}:
c = chr(ord(c) + (ord('a') - ord('A'))) # toLower()
h = concHash(h, ord(c))
result = finishHash(h)

166
lib/pure/lexbase.nim Normal file
View file

@ -0,0 +1,166 @@
#
#
# The Nimrod Compiler
# (c) Copyright 2009 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module implements a base object of a lexer with efficient buffer
## handling. Only at line endings checks are necessary if the buffer
## needs refilling.
import
strutils, streams
const
EndOfFile* = '\0' ## end of file marker
NewLines* = {'\c', '\L'}
# Buffer handling:
# buf:
# "Example Text\n ha!" bufLen = 17
# ^pos = 0 ^ sentinel = 12
#
type
TBaseLexer* = object of TObject ## the base lexer. Inherit your lexer from
## this object.
bufpos*: int ## the current position within the buffer
buf*: cstring ## the buffer itself
bufLen*: int ## length of buffer in characters
input: PStream ## the input stream
LineNumber*: int ## the current line number
sentinel: int
lineStart: int # index of last line start in buffer
fileOpened: bool
proc open*(L: var TBaseLexer, input: PStream, bufLen: int = 8192)
## inits the TBaseLexer with a stream to read from
proc close*(L: var TBaseLexer)
## closes the base lexer. This closes `L`'s associated stream too.
proc getCurrentLine*(L: TBaseLexer, marker: bool = true): string
## retrieves the current line.
proc getColNumber*(L: TBaseLexer, pos: int): int
## retrieves the current column.
proc HandleCR*(L: var TBaseLexer, pos: int): int
## Call this if you scanned over '\c' in the buffer; it returns the the
## position to continue the scanning from. `pos` must be the position
## of the '\c'.
proc HandleLF*(L: var TBaseLexer, pos: int): int
## Call this if you scanned over '\L' in the buffer; it returns the the
## position to continue the scanning from. `pos` must be the position
## of the '\L'.
# implementation
const
chrSize = sizeof(char)
proc close(L: var TBaseLexer) =
dealloc(L.buf)
L.input.close(L.input)
proc FillBuffer(L: var TBaseLexer) =
var
charsRead, toCopy, s: int # all are in characters,
# not bytes (in case this
# is not the same)
oldBufLen: int
# we know here that pos == L.sentinel, but not if this proc
# is called the first time by initBaseLexer()
assert(L.sentinel < L.bufLen)
toCopy = L.BufLen - L.sentinel - 1
assert(toCopy >= 0)
if toCopy > 0:
MoveMem(L.buf, addr(L.buf[L.sentinel + 1]), toCopy * chrSize) # "moveMem" handles overlapping regions
charsRead = L.input.readData(L.input, addr(L.buf[toCopy]),
(L.sentinel + 1) * chrSize) div chrSize
s = toCopy + charsRead
if charsRead < L.sentinel + 1:
L.buf[s] = EndOfFile # set end marker
L.sentinel = s
else:
# compute sentinel:
dec(s) # BUGFIX (valgrind)
while true:
assert(s < L.bufLen)
while (s >= 0) and not (L.buf[s] in NewLines): Dec(s)
if s >= 0:
# we found an appropriate character for a sentinel:
L.sentinel = s
break
else:
# rather than to give up here because the line is too long,
# double the buffer's size and try again:
oldBufLen = L.BufLen
L.bufLen = L.BufLen * 2
L.buf = cast[cstring](realloc(L.buf, L.bufLen * chrSize))
assert(L.bufLen - oldBuflen == oldBufLen)
charsRead = L.input.ReadData(L.input, addr(L.buf[oldBufLen]),
oldBufLen * chrSize) div chrSize
if charsRead < oldBufLen:
L.buf[oldBufLen + charsRead] = EndOfFile
L.sentinel = oldBufLen + charsRead
break
s = L.bufLen - 1
proc fillBaseLexer(L: var TBaseLexer, pos: int): int =
assert(pos <= L.sentinel)
if pos < L.sentinel:
result = pos + 1 # nothing to do
else:
fillBuffer(L)
L.bufpos = 0 # XXX: is this really correct?
result = 0
L.lineStart = result
proc HandleCR(L: var TBaseLexer, pos: int): int =
assert(L.buf[pos] == '\c')
inc(L.linenumber)
result = fillBaseLexer(L, pos)
if L.buf[result] == '\L':
result = fillBaseLexer(L, result)
proc HandleLF(L: var TBaseLexer, pos: int): int =
assert(L.buf[pos] == '\L')
inc(L.linenumber)
result = fillBaseLexer(L, pos) #L.lastNL := result-1; // BUGFIX: was: result;
proc skip_UTF_8_BOM(L: var TBaseLexer) =
if (L.buf[0] == '\xEF') and (L.buf[1] == '\xBB') and (L.buf[2] == '\xBF'):
inc(L.bufpos, 3)
inc(L.lineStart, 3)
proc open(L: var TBaseLexer, input: PStream, bufLen: int = 8192) =
assert(bufLen > 0)
assert(input != nil)
L.input = input
L.bufpos = 0
L.bufLen = bufLen
L.buf = cast[cstring](alloc(bufLen * chrSize))
L.sentinel = bufLen - 1
L.lineStart = 0
L.linenumber = 1 # lines start at 1
fillBuffer(L)
skip_UTF_8_BOM(L)
proc getColNumber(L: TBaseLexer, pos: int): int =
result = abs(pos - L.lineStart)
proc getCurrentLine(L: TBaseLexer, marker: bool = true): string =
var i: int
result = ""
i = L.lineStart
while not (L.buf[i] in {'\c', '\L', EndOfFile}):
add(result, L.buf[i])
inc(i)
add(result, "\n")
if marker:
add(result, RepeatChar(getColNumber(L, L.bufpos)) & "^\n")

249
lib/pure/macros.nim Normal file
View file

@ -0,0 +1,249 @@
#
#
# 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 the interface to the compiler's abstract syntax
## tree (`AST`:idx:). Macros operate on this tree.
## .. include:: ../doc/astspec.txt
#[[[cog
#def toEnum(name, elems):
# body = ""
# counter = 0
# for e in elems:
# if counter % 4 == 0: p = "\n "
# else: p = ""
# body = body + p + 'n' + e + ', '
# counter = counter + 1
#
# return (" TNimrod%s* = enum%s\n TNim%ss* = set[TNimrod%s]\n" %
# (name, body[:-2], name, name))
#
#enums = eval(open("data/ast.yml").read())
#cog.out("type\n")
#for key, val in enums.items():
# if key[-4:] == "Flag": continue
# cog.out(toEnum(key, val))
#]]]
type
TNimrodNodeKind* = enum
nnkNone, nnkEmpty, nnkIdent, nnkSym,
nnkType, nnkCharLit, nnkIntLit, nnkInt8Lit,
nnkInt16Lit, nnkInt32Lit, nnkInt64Lit, nnkFloatLit,
nnkFloat32Lit, nnkFloat64Lit, nnkStrLit, nnkRStrLit,
nnkTripleStrLit, nnkMetaNode, nnkNilLit, nnkDotCall,
nnkCommand, nnkCall, nnkGenericCall, nnkExplicitTypeListCall,
nnkExprEqExpr, nnkExprColonExpr, nnkIdentDefs, nnkVarTuple,
nnkInfix, nnkPrefix, nnkPostfix, nnkPar,
nnkCurly, nnkBracket, nnkBracketExpr, nnkPragmaExpr,
nnkRange, nnkDotExpr, nnkCheckedFieldExpr, nnkDerefExpr,
nnkIfExpr, nnkElifExpr, nnkElseExpr, nnkLambda,
nnkAccQuoted, nnkHeaderQuoted, nnkTableConstr, nnkQualified,
nnkHiddenStdConv, nnkHiddenSubConv, nnkHiddenCallConv, nnkConv,
nnkCast, nnkAddr, nnkHiddenAddr, nnkHiddenDeref,
nnkObjDownConv, nnkObjUpConv, nnkChckRangeF, nnkChckRange64,
nnkChckRange, nnkStringToCString, nnkCStringToString, nnkPassAsOpenArray,
nnkAsgn, nnkFastAsgn, nnkDefaultTypeParam, nnkGenericParams,
nnkFormalParams, nnkOfInherit, nnkModule, nnkProcDef,
nnkConverterDef, nnkMacroDef, nnkTemplateDef, nnkIteratorDef,
nnkOfBranch, nnkElifBranch, nnkExceptBranch, nnkElse,
nnkMacroStmt, nnkAsmStmt, nnkPragma, nnkIfStmt,
nnkWhenStmt, nnkForStmt, nnkWhileStmt, nnkCaseStmt,
nnkVarSection, nnkConstSection, nnkConstDef, nnkTypeSection,
nnkTypeDef, nnkYieldStmt, nnkTryStmt, nnkFinally,
nnkRaiseStmt, nnkReturnStmt, nnkBreakStmt, nnkContinueStmt,
nnkBlockStmt, nnkDiscardStmt, nnkStmtList, nnkImportStmt,
nnkFromStmt, nnkImportAs, nnkIncludeStmt, nnkCommentStmt,
nnkStmtListExpr, nnkBlockExpr, nnkStmtListType, nnkBlockType,
nnkVm, nnkTypeOfExpr, nnkObjectTy, nnkTupleTy,
nnkRecList, nnkRecCase, nnkRecWhen, nnkRefTy,
nnkPtrTy, nnkVarTy, nnkAbstractTy, nnkProcTy,
nnkEnumTy, nnkEnumFieldDef, nnkReturnToken
TNimNodeKinds* = set[TNimrodNodeKind]
TNimrodTypeKind* = enum
ntyNone, ntyBool, ntyChar, ntyEmpty,
ntyArrayConstr, ntyNil, ntyGeneric, ntyGenericInst,
ntyGenericParam, ntyAbstract, ntyEnum, ntyOrdinal,
ntyArray, ntyObject, ntyTuple, ntySet,
ntyRange, ntyPtr, ntyRef, ntyVar,
ntySequence, ntyProc, ntyPointer, ntyOpenArray,
ntyString, ntyCString, ntyForward, ntyInt,
ntyInt8, ntyInt16, ntyInt32, ntyInt64,
ntyFloat, ntyFloat32, ntyFloat64, ntyFloat128
TNimTypeKinds* = set[TNimrodTypeKind]
TNimrodSymKind* = enum
nskUnknownSym, nskConditional, nskDynLib, nskParam,
nskTypeParam, nskTemp, nskType, nskConst,
nskVar, nskProc, nskIterator, nskConverter,
nskMacro, nskTemplate, nskField, nskEnumField,
nskForVar, nskModule, nskLabel, nskStub
TNimSymKinds* = set[TNimrodSymKind]
#[[[end]]]
type
TNimrodIdent = object of TObject
## represents a Nimrod identifier in the AST
TNimrodNode {.final.} = object # hidden
TNimrodSymbol {.final.} = object # hidden
TNimrodType {.final.} = object # hidden
PNimrodType* {.compilerproc.} = ref TNimrodType
## represents a Nimrod type in the compiler; currently this is not very
## useful as there is no API to deal with Nimrod types.
PNimrodSymbol* {.compilerproc.} = ref TNimrodSymbol
## represents a Nimrod *symbol* in the compiler; a *symbol* is a looked-up
## *ident*.
PNimrodNode* {.compilerproc.} = ref TNimrodNode
## represents a Nimrod AST node. Macros operate on this type.
expr* = PNimrodNode
stmt* = PNimrodNode
# Nodes should be reference counted to make the `copy` operation very fast!
# However, this is difficult to achieve: modify(n[0][1]) should propagate to
# its father. How to do this without back references?
proc `[]`* (n: PNimrodNode, i: int): PNimrodNode {.magic: "NChild".}
## get `n`'s `i`'th child.
proc `[]=`* (n: PNimrodNode, i: int, child: PNimrodNode) {.magic: "NSetChild".}
## set `n`'s `i`'th child to `child`.
proc `!` *(s: string): TNimrodIdent {.magic: "StrToIdent".}
## constructs an identifier from the string `s`
proc `$`*(i: TNimrodIdent): string {.magic: "IdentToStr".}
## converts a Nimrod identifier to a string
proc `==`* (a, b: TNimrodIdent): bool {.magic: "EqIdent".}
## compares two Nimrod identifiers
proc len*(n: PNimrodNode): int {.magic: "NLen".}
## returns the number of children of `n`.
proc add*(father, child: PNimrodNode) {.magic: "NAdd".}
## adds the `child` to the `father` node
proc add*(father: PNimrodNode, children: openArray[PNimrodNode]) {.
magic: "NAddMultiple".}
## adds each `children` to the `father` node
proc del*(father: PNimrodNode, idx = 0, n = 1) {.magic: "NDel".}
## deletes `n` children of `father` starting at index `idx`.
proc kind*(n: PNimrodNode): TNimrodNodeKind {.magic: "NKind".}
## returns the `kind` of the node `n`.
proc intVal*(n: PNimrodNode): biggestInt {.magic: "NIntVal".}
proc floatVal*(n: PNimrodNode): biggestFloat {.magic: "NFloatVal".}
proc symbol*(n: PNimrodNode): PNimrodSymbol {.magic: "NSymbol".}
proc ident*(n: PNimrodNode): TNimrodIdent {.magic: "NIdent".}
proc typ*(n: PNimrodNode): PNimrodType {.magic: "NGetType".}
proc strVal*(n: PNimrodNode): string {.magic: "NStrVal".}
proc `intVal=`*(n: PNimrodNode, val: biggestInt) {.magic: "NSetIntVal".}
proc `floatVal=`*(n: PNimrodNode, val: biggestFloat) {.magic: "NSetFloatVal".}
proc `symbol=`*(n: PNimrodNode, val: PNimrodSymbol) {.magic: "NSetSymbol".}
proc `ident=`*(n: PNimrodNode, val: TNimrodIdent) {.magic: "NSetIdent".}
proc `typ=`*(n: PNimrodNode, typ: PNimrodType) {.magic: "NSetType".}
proc `strVal=`*(n: PNimrodNode, val: string) {.magic: "NSetStrVal".}
proc newNimNode*(kind: TNimrodNodeKind,
n: PNimrodNode=nil): PNimrodNode {.magic: "NNewNimNode".}
proc copyNimNode*(n: PNimrodNode): PNimrodNode {.magic: "NCopyNimNode".}
proc copyNimTree*(n: PNimrodNode): PNimrodNode {.magic: "NCopyNimTree".}
proc error*(msg: string) {.magic: "NError".}
## writes an error message at compile time
proc warning*(msg: string) {.magic: "NWarning".}
## writes a warning message at compile time
proc hint*(msg: string) {.magic: "NHint".}
## writes a hint message at compile time
proc newStrLitNode*(s: string): PNimrodNode {.compileTime.} =
## creates a string literal node from `s`
result = newNimNode(nnkStrLit)
result.strVal = s
proc newIntLitNode*(i: biggestInt): PNimrodNode {.compileTime.} =
## creates a int literal node from `i`
result = newNimNode(nnkIntLit)
result.intVal = i
proc newFloatLitNode*(f: biggestFloat): PNimrodNode {.compileTime.} =
## creates a float literal node from `f`
result = newNimNode(nnkFloatLit)
result.floatVal = f
proc newIdentNode*(i: TNimrodIdent): PNimrodNode {.compileTime.} =
## creates an identifier node from `i`
result = newNimNode(nnkIdent)
result.ident = i
proc newIdentNode*(i: string): PNimrodNode {.compileTime.} =
## creates an identifier node from `i`
result = newNimNode(nnkIdent)
result.ident = !i
proc toStrLit*(n: PNimrodNode): PNimrodNode {.compileTime.} =
## converts the AST `n` to the concrete Nimrod code and wraps that
## in a string literal node
return newStrLitNode(repr(n))
proc expectKind*(n: PNimrodNode, k: TNimrodNodeKind) {.compileTime.} =
## checks that `n` is of kind `k`. If this is not the case,
## compilation aborts with an error message. This is useful for writing
## macros that check the AST that is passed to them.
if n.kind != k: error("macro expects a node of kind: " & repr(k))
proc expectMinLen*(n: PNimrodNode, min: int) {.compileTime.} =
## checks that `n` has at least `min` children. If this is not the case,
## compilation aborts with an error message. This is useful for writing
## macros that check its number of arguments.
if n.len < min: error("macro expects a node with " & $min & " children")
proc expectLen*(n: PNimrodNode, len: int) {.compileTime.} =
## checks that `n` has exactly `len` children. If this is not the case,
## compilation aborts with an error message. This is useful for writing
## macros that check its number of arguments.
if n.len != len: error("macro expects a node with " & $len & " children")
proc newCall*(theProc: TNimrodIdent,
args: openArray[PNimrodNode]): PNimrodNode {.compileTime.} =
## produces a new call node. `theProc` is the proc that is called with
## the arguments ``args[0..]``.
result = newNimNode(nnkCall)
result.add(newIdentNode(theProc))
result.add(args)
proc newCall*(theProc: string,
args: openArray[PNimrodNode]): PNimrodNode {.compileTime.} =
## produces a new call node. `theProc` is the proc that is called with
## the arguments ``args[0..]``.
result = newNimNode(nnkCall)
result.add(newIdentNode(theProc))
result.add(args)
proc nestList*(theProc: TNimrodIdent,
x: PNimrodNode): PNimrodNode {.compileTime.} =
## nests the list `x` into a tree of call expressions:
## ``[a, b, c]`` is transformed into ``theProc(a, theProc(c, d))``
var L = x.len
result = newCall(theProc, x[L-2], x[L-1])
var a = result
for i in countdown(L-3, 0):
a = newCall(theProc, x[i], copyNimTree(a))

247
lib/pure/math.nim Normal file
View file

@ -0,0 +1,247 @@
#
#
# Nimrod's Runtime Library
# (c) Copyright 2008 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Basic math routines for Nimrod.
## This module is available for the ECMAScript target.
{.push debugger:off .} # the user does not want to trace a part
# of the standard library!
{.push checks:off, line_dir:off, stack_trace:off.}
when defined(Posix):
{.passl: "-lm".}
const
PI* = 3.1415926535897932384626433 ## the circle constant PI (Ludolph's number)
E* = 2.71828182845904523536028747 ## Euler's number
type
TFloatClass* = enum ## describes the class a floating point value belongs to.
## This is the type that is returned by `classify`.
fcNormal, ## value is an ordinary nonzero floating point value
fcSubnormal, ## value is a subnormal (a very small) floating point value
fcZero, ## value is zero
fcNegZero, ## value is the negative zero
fcNan, ## value is Not-A-Number (NAN)
fcInf, ## value is positive infinity
fcNegInf ## value is negative infinity
proc classify*(x: float): TFloatClass =
## classifies a floating point value. Returns `x`'s class as specified by
## `TFloatClass`.
# ECMAScript and most C compilers have no classify:
if x == 0.0:
if 1.0/x == Inf:
return fcZero
else:
return fcNegZero
if x*0.5 == x:
if x > 0.0: return fcInf
else: return fcNegInf
if x != x: return fcNan
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 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.
## Negative numbers are not a power of two.
return (x and -x) == x
proc nextPowerOfTwo*(x: int): int =
## returns the nearest power of two, so that
## result**2 >= x > (result-1)**2.
result = x - 1
when defined(cpu64):
result = result or (result shr 32)
result = result or (result shr 16)
result = result or (result shr 8)
result = result or (result shr 4)
result = result or (result shr 2)
result = result or (result shr 1)
Inc(result)
proc countBits*(n: int32): int {.noSideEffect.}
## counts the set bits in `n`.
include "system/cntbits"
proc sum*[T](x: openarray[T]): T {.noSideEffect.} =
## computes the sum of the elements in `x`.
## If `x` is empty, 0 is returned.
for i in items(x): result = result + i
proc mean*(x: openarray[float]): float {.noSideEffect.} =
## computes the mean of the elements in `x`.
## If `x` is empty, NaN is returned.
result = sum(x) / toFloat(len(x))
proc variance*(x: openarray[float]): float {.noSideEffect.} =
## computes the mean of the elements in `x`.
## If `x` is empty, NaN is returned.
result = 0.0
var m = mean(x)
for i in 0 .. high(x):
var diff = x[i] - m
result = result + diff*diff
result = result / toFloat(len(x))
when not defined(ECMAScript):
proc random*(max: int): int
## returns a random number in the range 0..max-1. The sequence of
## random number is always the same, unless `randomize` is called
## which initializes the random number generator with a "random"
## number, i.e. a tickcount.
proc randomize*()
## initializes the random number generator with a "random"
## number, i.e. a tickcount. Note: Does nothing for the ECMAScript target,
## as ECMAScript does not support this.
proc sqrt*(x: float): float {.importc: "sqrt", header: "<math.h>".}
## computes the square root of `x`.
proc ln*(x: float): float {.importc: "log", header: "<math.h>".}
## computes ln(x).
proc log10*(x: float): float {.importc: "log10", header: "<math.h>".}
proc log2*(x: float): float = return ln(x) / ln(2.0)
proc exp*(x: float): float {.importc: "exp", header: "<math.h>".}
## computes e**x.
proc frexp*(x: float, exponent: var int): float {.
importc: "frexp", header: "<math.h>".}
## Split a number into mantissa and exponent.
## `frexp` calculates the mantissa m (a float greater than or equal to 0.5
## and less than 1) and the integer value n such that `x` (the original
## float value) equals m * 2**n. frexp stores n in `exponent` and returns
## m.
proc round*(x: float): int {.importc: "lrint", nodecl.}
## converts a float to an int by rounding.
proc arccos*(x: float): float {.importc: "acos", header: "<math.h>".}
proc arcsin*(x: float): float {.importc: "asin", header: "<math.h>".}
proc arctan*(x: float): float {.importc: "atan", header: "<math.h>".}
proc arctan2*(y, x: float): float {.importc: "atan2", header: "<math.h>".}
## Calculate the arc tangent of `y` / `x`.
## `atan2` returns the arc tangent of `y` / `x`; it produces correct
## results even when the resulting angle is near pi/2 or -pi/2
## (`x` near 0).
proc cos*(x: float): float {.importc: "cos", header: "<math.h>".}
proc cosh*(x: float): float {.importc: "cosh", header: "<math.h>".}
proc hypot*(x, y: float): float {.importc: "hypot", header: "<math.h>".}
## same as ``sqrt(x*x + y*y)``.
proc sinh*(x: float): float {.importc: "sinh", header: "<math.h>".}
proc tan*(x: float): float {.importc: "tan", header: "<math.h>".}
proc tanh*(x: float): float {.importc: "tanh", header: "<math.h>".}
proc pow*(x, y: float): float {.importc: "pow", header: "<math.h>".}
## computes x to power raised of y.
# C procs:
proc gettime(dummy: ptr cint): cint {.importc: "time", header: "<time.h>".}
proc srand(seed: cint) {.importc: "srand", nodecl.}
proc rand(): cint {.importc: "rand", nodecl.}
proc randomize() = srand(gettime(nil))
proc random(max: int): int = return int(rand()) mod max
else:
proc mathrandom(): float {.importc: "Math.random", nodecl.}
proc mathfloor(x: float): float {.importc: "Math.floor", nodecl.}
proc random*(max: int): int = return mathfloor(mathrandom() * max)
proc randomize*() = nil
proc sqrt*(x: float): float {.importc: "Math.sqrt", nodecl.}
proc ln*(x: float): float {.importc: "Math.log", nodecl.}
proc log10*(x: float): float = return ln(x) / ln(10.0)
proc log2*(x: float): float = return ln(x) / ln(2.0)
proc exp*(x: float): float {.importc: "Math.exp", nodecl.}
proc round*(x: float): int {.importc: "Math.round", nodecl.}
proc pow*(x, y: float): float {.importc: "Math.pow", nodecl.}
proc frexp*(x: float, exponent: var int): float =
if x == 0.0:
exponent = 0.0
result = 0.0
elif x < 0.0:
result = -frexp(-x, exponent)
else:
var ex = mathfloor(log2(x))
exponent = round(ex)
result = x / pow(2.0, ex)
proc arccos*(x: float): float {.importc: "Math.acos", nodecl.}
proc arcsin*(x: float): float {.importc: "Math.asin", nodecl.}
proc arctan*(x: float): float {.importc: "Math.atan", nodecl.}
proc arctan2*(y, x: float): float {.importc: "Math.atan2", nodecl.}
proc cos*(x: float): float {.importc: "Math.cos", nodecl.}
proc cosh*(x: float): float = return (exp(x)+exp(-x))*0.5
proc hypot*(x, y: float): float = return sqrt(x*x + y*y)
proc sinh*(x: float): float = return (exp(x)-exp(-x))*0.5
proc tan*(x: float): float {.importc: "Math.tan", nodecl.}
proc tanh*(x: float): float =
var y = exp(2.0*x)
return (y-1.0)/(y+1.0)
type
TRunningStat* = object ## an accumulator for statistical data
n*: int ## number of pushed data
sum*, min*, max*, mean*: float ## self-explaining
oldM, oldS, newS: float
proc push*(s: var TRunningStat, x: float) =
## pushes a value `x` for processing
inc(s.n)
# See Knuth TAOCP vol 2, 3rd edition, page 232
if s.n == 1:
s.oldM = x
s.mean = x
s.oldS = 0.0
else:
s.mean = s.oldM + (x - s.oldM)/toFloat(s.n)
s.newS = s.oldS + (x - s.oldM)*(x - s.mean)
# set up for next iteration:
s.oldM = s.mean
s.oldS = s.newS
s.sum = s.sum + x
if s.min > x: s.min = x
if s.max < x: s.max = x
proc variance*(s: TRunningStat): float =
## computes the current variance of `s`
if s.n > 1: result = s.newS / (toFloat(s.n - 1))
proc standardDeviation*(s: TRunningStat): float =
## computes the current standard deviation of `s`
result = sqrt(variance(s))
{.pop.}
{.pop.}

245
lib/pure/md5.nim Normal file
View file

@ -0,0 +1,245 @@
#
#
# Nimrod's Runtime Library
# (c) Copyright 2009 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Module for computing MD5 checksums.
type
MD5State = array[0..3, int32]
MD5Block = array[0..15, int32]
MD5CBits = array[0..7, int8]
MD5Digest* = array[0..15, int8]
MD5Buffer = array[0..63, int8]
MD5Context* {.final.} = object
State: MD5State
Count: array[0..1, int32]
Buffer: MD5Buffer
const
padding: cstring = "\x80\0\0\0" &
"\0\0\0\0\0\0\0\0" &
"\0\0\0\0\0\0\0\0" &
"\0\0\0\0\0\0\0\0" &
"\0\0\0\0\0\0\0\0" &
"\0\0\0\0\0\0\0\0" &
"\0\0\0\0\0\0\0\0" &
"\0\0\0\0\0\0\0\0" &
"\0\0\0\0"
proc F(x, y, z: int32): int32 {.inline.} =
Result = (x and y) or ((not x) and z)
proc G(x, y, z: int32): int32 {.inline.} =
Result = (x and z) or (y and (not z))
proc H(x, y, z: int32): int32 {.inline.} =
Result = x xor y xor z
proc I(x, y, z: int32): int32 {.inline.} =
Result = y xor (x or (not z))
proc rot(x: var int32, n: int8) {.inline.} =
x = toU32(x shl ze(n)) or (x shr toU32(32 -% ze(n)))
proc FF(a: var int32, b, c, d, x: int32, s: int8, ac: int32) =
a = a +% F(b, c, d) +% x +% ac
rot(a, s)
a = a +% b
proc GG(a: var int32, b, c, d, x: int32, s: int8, ac: int32) =
a = a +% G(b, c, d) +% x +% ac
rot(a, s)
a = a +% b
proc HH(a: var int32, b, c, d, x: int32, s: int8, ac: int32) =
a = a +% H(b, c, d) +% x +% ac
rot(a, s)
a = a +% b
proc II(a: var int32, b, c, d, x: int32, s: int8, ac: int32) =
a = a +% I(b, c, d) +% x +% ac
rot(a, s)
a = a +% b
proc encode(dest: var MD5Block, src: cstring) =
var j = 0
for i in 0..high(dest):
dest[i] = toU32(ord(src[j]) or
ord(src[j+1]) shl 8 or
ord(src[j+2]) shl 16 or
ord(src[j+3]) shl 24)
inc(j, 4)
proc decode(dest: var openarray[int8], src: openarray[int32]) =
var i = 0
for j in 0..high(src):
dest[i] = toU8(src[j] and 0xff'i32)
dest[i+1] = toU8(src[j] shr 8'i32 and 0xff'i32)
dest[i+2] = toU8(src[j] shr 16'i32 and 0xff'i32)
dest[i+3] = toU8(src[j] shr 24'i32 and 0xff'i32)
inc(i, 4)
proc transform(Buffer: pointer, State: var MD5State) =
var
myBlock: MD5Block
encode(myBlock, cast[cstring](buffer))
var a = State[0]
var b = State[1]
var c = State[2]
var d = State[3]
FF(a, b, c, d, myBlock[0], 7'i8, 0xD76AA478'i32)
FF(d, a, b, c, myBlock[1], 12'i8, 0xE8C7B756'i32)
FF(c, d, a, b, myBlock[2], 17'i8, 0x242070DB'i32)
FF(b, c, d, a, myBlock[3], 22'i8, 0xC1BDCEEE'i32)
FF(a, b, c, d, myBlock[4], 7'i8, 0xF57C0FAF'i32)
FF(d, a, b, c, myBlock[5], 12'i8, 0x4787C62A'i32)
FF(c, d, a, b, myBlock[6], 17'i8, 0xA8304613'i32)
FF(b, c, d, a, myBlock[7], 22'i8, 0xFD469501'i32)
FF(a, b, c, d, myBlock[8], 7'i8, 0x698098D8'i32)
FF(d, a, b, c, myBlock[9], 12'i8, 0x8B44F7AF'i32)
FF(c, d, a, b, myBlock[10], 17'i8, 0xFFFF5BB1'i32)
FF(b, c, d, a, myBlock[11], 22'i8, 0x895CD7BE'i32)
FF(a, b, c, d, myBlock[12], 7'i8, 0x6B901122'i32)
FF(d, a, b, c, myBlock[13], 12'i8, 0xFD987193'i32)
FF(c, d, a, b, myBlock[14], 17'i8, 0xA679438E'i32)
FF(b, c, d, a, myBlock[15], 22'i8, 0x49B40821'i32)
GG(a, b, c, d, myBlock[1], 5'i8, 0xF61E2562'i32)
GG(d, a, b, c, myBlock[6], 9'i8, 0xC040B340'i32)
GG(c, d, a, b, myBlock[11], 14'i8, 0x265E5A51'i32)
GG(b, c, d, a, myBlock[0], 20'i8, 0xE9B6C7AA'i32)
GG(a, b, c, d, myBlock[5], 5'i8, 0xD62F105D'i32)
GG(d, a, b, c, myBlock[10], 9'i8, 0x02441453'i32)
GG(c, d, a, b, myBlock[15], 14'i8, 0xD8A1E681'i32)
GG(b, c, d, a, myBlock[4], 20'i8, 0xE7D3FBC8'i32)
GG(a, b, c, d, myBlock[9], 5'i8, 0x21E1CDE6'i32)
GG(d, a, b, c, myBlock[14], 9'i8, 0xC33707D6'i32)
GG(c, d, a, b, myBlock[3], 14'i8, 0xF4D50D87'i32)
GG(b, c, d, a, myBlock[8], 20'i8, 0x455A14ED'i32)
GG(a, b, c, d, myBlock[13], 5'i8, 0xA9E3E905'i32)
GG(d, a, b, c, myBlock[2], 9'i8, 0xFCEFA3F8'i32)
GG(c, d, a, b, myBlock[7], 14'i8, 0x676F02D9'i32)
GG(b, c, d, a, myBlock[12], 20'i8, 0x8D2A4C8A'i32)
HH(a, b, c, d, myBlock[5], 4'i8, 0xFFFA3942'i32)
HH(d, a, b, c, myBlock[8], 11'i8, 0x8771F681'i32)
HH(c, d, a, b, myBlock[11], 16'i8, 0x6D9D6122'i32)
HH(b, c, d, a, myBlock[14], 23'i8, 0xFDE5380C'i32)
HH(a, b, c, d, myBlock[1], 4'i8, 0xA4BEEA44'i32)
HH(d, a, b, c, myBlock[4], 11'i8, 0x4BDECFA9'i32)
HH(c, d, a, b, myBlock[7], 16'i8, 0xF6BB4B60'i32)
HH(b, c, d, a, myBlock[10], 23'i8, 0xBEBFBC70'i32)
HH(a, b, c, d, myBlock[13], 4'i8, 0x289B7EC6'i32)
HH(d, a, b, c, myBlock[0], 11'i8, 0xEAA127FA'i32)
HH(c, d, a, b, myBlock[3], 16'i8, 0xD4EF3085'i32)
HH(b, c, d, a, myBlock[6], 23'i8, 0x04881D05'i32)
HH(a, b, c, d, myBlock[9], 4'i8, 0xD9D4D039'i32)
HH(d, a, b, c, myBlock[12], 11'i8, 0xE6DB99E5'i32)
HH(c, d, a, b, myBlock[15], 16'i8, 0x1FA27CF8'i32)
HH(b, c, d, a, myBlock[2], 23'i8, 0xC4AC5665'i32)
II(a, b, c, d, myBlock[0], 6'i8, 0xF4292244'i32)
II(d, a, b, c, myBlock[7], 10'i8, 0x432AFF97'i32)
II(c, d, a, b, myBlock[14], 15'i8, 0xAB9423A7'i32)
II(b, c, d, a, myBlock[5], 21'i8, 0xFC93A039'i32)
II(a, b, c, d, myBlock[12], 6'i8, 0x655B59C3'i32)
II(d, a, b, c, myBlock[3], 10'i8, 0x8F0CCC92'i32)
II(c, d, a, b, myBlock[10], 15'i8, 0xFFEFF47D'i32)
II(b, c, d, a, myBlock[1], 21'i8, 0x85845DD1'i32)
II(a, b, c, d, myBlock[8], 6'i8, 0x6FA87E4F'i32)
II(d, a, b, c, myBlock[15], 10'i8, 0xFE2CE6E0'i32)
II(c, d, a, b, myBlock[6], 15'i8, 0xA3014314'i32)
II(b, c, d, a, myBlock[13], 21'i8, 0x4E0811A1'i32)
II(a, b, c, d, myBlock[4], 6'i8, 0xF7537E82'i32)
II(d, a, b, c, myBlock[11], 10'i8, 0xBD3AF235'i32)
II(c, d, a, b, myBlock[2], 15'i8, 0x2AD7D2BB'i32)
II(b, c, d, a, myBlock[9], 21'i8, 0xEB86D391'i32)
State[0] = State[0] +% a
State[1] = State[1] +% b
State[2] = State[2] +% c
State[3] = State[3] +% d
proc MD5Init*(c: var MD5Context) =
## initializes a MD5Context
c.State[0] = 0x67452301'i32
c.State[1] = 0xEFCDAB89'i32
c.State[2] = 0x98BADCFE'i32
c.State[3] = 0x10325476'i32
c.Count[0] = 0'i32
c.Count[1] = 0'i32
ZeroMem(addr(c.Buffer), SizeOf(MD5Buffer))
proc MD5Update*(c: var MD5Context, input: cstring, len: int) =
## updates the MD5Context with the `input` data of length `len`
var input = input
var Index = (c.Count[0] shr 3) and 0x3F
c.Count[0] = c.count[0] +% toU32(len shl 3)
if c.Count[0] < (len shl 3): c.Count[1] = c.count[1] +% 1'i32
c.Count[1] = c.count[1] +% toU32(len shr 29)
var PartLen = 64 - Index
if len >= PartLen:
CopyMem(addr(c.Buffer[Index]), Input, PartLen)
transform(addr(c.Buffer), c.State)
var i = PartLen
while i + 63 < len:
Transform(addr(Input[I]), c.State)
inc(i, 64)
CopyMem(addr(c.Buffer[0]), addr(Input[i]), len-i)
else:
CopyMem(addr(c.Buffer[Index]), addr(Input[0]), len)
proc MD5Final*(c: var MD5Context, digest: var MD5Digest) =
## finishes the MD5Context and stores the result in `digest`
var
Bits: MD5CBits
PadLen: int
decode(bits, c.Count)
var Index = (c.Count[0] shr 3) and 0x3F
if Index < 56: PadLen = 56 - Index
else: PadLen = 120 - Index
MD5Update(c, padding, PadLen)
MD5Update(c, cast[cstring](addr(Bits)), 8)
decode(digest, c.State)
ZeroMem(addr(c), SizeOf(MD5Context))
proc toMD5*(s: string): MD5Digest =
## computes the MD5Digest value for a string `s`
var c: MD5Context
MD5Init(c)
MD5Update(c, cstring(s), len(s))
MD5Final(c, result)
proc `$`*(D: MD5Digest): string =
## converts a MD5Digest value into its string representation
const digits = "0123456789abcdef"
result = ""
for i in 0..15:
add(result, Digits[(D[I] shr 4) and 0xF])
add(result, Digits[D[I] and 0xF])
proc getMD5*(s: string): string =
## computes an MD5 value of `s` and returns its string representation
var
c: MD5Context
d: MD5Digest
MD5Init(c)
MD5Update(c, cstring(s), len(s))
MD5Final(c, d)
result = $d
proc `==`*(D1, D2: MD5Digest): bool =
## checks if two MD5Digest values are identical
for i in 0..15:
if D1[i] != D2[i]: return false
return true
when isMainModule:
assert(getMD5("Franz jagt im komplett verwahrlosten Taxi quer durch Bayern") ==
"a3cca2b2aa1e3b5b3b5aad99a8529074")
assert(getMD5("Frank jagt im komplett verwahrlosten Taxi quer durch Bayern") ==
"7e716d0e702df0505fc72e2b89467910")
assert($toMD5("") == "d41d8cd98f00b204e9800998ecf8427e")

1049
lib/pure/os.nim Normal file

File diff suppressed because it is too large Load diff

427
lib/pure/osproc.nim Normal file
View file

@ -0,0 +1,427 @@
#
#
# 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 an advanced facility for executing OS processes
## and process communication.
## **On Windows this module does not work properly. Please help!**
import
os, strtabs, streams
when defined(windows):
import winlean
type
TProcess = object of TObject
when defined(windows):
FProcessHandle: Thandle
FThreadHandle: Thandle
inputHandle, outputHandle, errorHandle: TFileHandle
else:
inputHandle, outputHandle, errorHandle: TFileHandle
id: cint
exitCode: cint
PProcess* = ref TProcess ## represents an operating system process
TProcessOption* = enum ## options that can be passed `startProcess`
poNone, ## none option
poUseShell, ## use the shell to execute the command; NOTE: This
## often creates a security whole!
poStdErrToStdOut ## merge stdout and stderr to the stdout stream
proc executeProcess*(command: string,
options: set[TProcessOption] = {poStdErrToStdOut,
poUseShell}): string
## A convience procedure that executes ``command`` with ``startProcess``
## and returns its output as a string.
proc executeCommand*(command: string): int
## Executes ``command`` and returns its error code. Standard input, output,
## error streams are inherited from the calling process.
proc startProcess*(command: string,
workingDir: string = "",
args: openarray[string] = [],
env: PStringTable = nil,
options: set[TProcessOption] = {poStdErrToStdOut}): PProcess
## Starts a process. `Command` is the executable file, `workingDir` is the
## process's working directory. If ``workingDir == ""`` the current directory
## is used. `args` are the command line arguments that are passed to the
## process. On many operating systems, the first command line argument is the
## name of the executable. `args` should not contain this argument!
## `startProcess` takes care of that. `env` is the environment that will be
## passed to the process. If ``env == nil`` the environment is inherited of
## the parent process. `options` are additional flags that may be passed
## to `startProcess`. See the documentation of ``TProcessOption`` for the
## meaning of these flags.
## Return value: The newly created process object. Nil is never returned,
## but ``EOS`` is raised in case of an error.
when true:
nil
else:
proc startGUIProcess*(command: string,
workingDir: string = "",
args: openarray[string] = [],
env: PStringTable = nil,
x = -1,
y = -1,
width = -1,
height = -1): PProcess
proc suspend*(p: PProcess)
## Suspends the process `p`.
proc resume*(p: PProcess)
## Resumes the process `p`.
proc terminate*(p: PProcess)
## Terminates the process `p`.
proc running*(p: PProcess): bool
## Returns true iff the process `p` is still running. Returns immediately.
proc processID*(p: PProcess): int =
## returns `p`'s process ID.
return p.id
proc waitForExit*(p: PProcess): int
## waits for the process to finish and returns `p`'s error code.
proc inputStream*(p: PProcess): PStream
## returns ``p``'s input stream for writing to
proc outputStream*(p: PProcess): PStream
## returns ``p``'s output stream for reading from
proc errorStream*(p: PProcess): PStream
## returns ``p``'s output stream for reading from
proc executeProcess*(command: string,
options: set[TProcessOption] = {poStdErrToStdOut,
poUseShell}): string =
var c = parseCmdLine(command)
var a: seq[string] = @[] # slicing is not yet implemented :-(
for i in 1 .. c.len-1: add(a, c[i])
var p = startProcess(command=c[0], args=a, options=options)
var outp = outputStream(p)
result = ""
while running(p) or not outp.atEnd(outp):
result.add(outp.readLine())
result.add("\n")
when false:
proc deallocCStringArray(a: cstringArray) =
var i = 0
while a[i] != nil:
dealloc(a[i])
inc(i)
dealloc(a)
when defined(Windows):
# We need to implement a handle stream for Windows:
type
PFileHandleStream = ref TFileHandleStream
TFileHandleStream = object of TStream
handle: THandle
atTheEnd: bool
proc hsClose(s: PFileHandleStream) = nil # nothing to do here
proc hsAtEnd(s: PFileHandleStream): bool = return true
proc hsReadData(s: PFileHandleStream, buffer: pointer, bufLen: int): int =
var br: int32
var a = winlean.ReadFile(s.handle, buffer, bufLen, br, nil)
if a == 0: OSError()
result = br
#atEnd = bytesRead < bufLen
proc hsWriteData(s: PFileHandleStream, buffer: pointer, bufLen: int) =
var bytesWritten: int32
var a = winlean.writeFile(s.handle, buffer, bufLen, bytesWritten, nil)
if a == 0: OSError()
proc newFileHandleStream(handle: THandle): PFileHandleStream =
new(result)
result.handle = handle
result.close = hsClose
result.atEnd = hsAtEnd
result.readData = hsReadData
result.writeData = hsWriteData
proc buildCommandLine(a: string, args: openarray[string]): cstring =
var L = a.len
for i in 0..high(args): inc(L, args[i].len+1)
result = cast[cstring](alloc0(L+1))
copyMem(result, cstring(a), a.len)
L = a.len
for i in 0..high(args):
result[L] = ' '
inc(L)
copyMem(addr(result[L]), cstring(args[i]), args[i].len)
inc(L, args[i].len)
proc buildEnv(env: PStringTable): cstring =
var L = 0
for key, val in pairs(env): inc(L, key.len + val.len + 2)
result = cast[cstring](alloc0(L+2))
L = 0
for key, val in pairs(env):
var x = key & "=" & val
copyMem(addr(result[L]), cstring(x), x.len+1) # copy \0
inc(L, x.len+1)
#proc open_osfhandle(osh: THandle, mode: int): int {.
# importc: "_open_osfhandle", header: "<fcntl.h>".}
#var
# O_WRONLY {.importc: "_O_WRONLY", header: "<fcntl.h>".}: int
# O_RDONLY {.importc: "_O_RDONLY", header: "<fcntl.h>".}: int
proc CreatePipeHandles(Inhandle, OutHandle: var THandle) =
var piInheritablePipe: TSecurityAttributes
piInheritablePipe.nlength = SizeOF(TSecurityAttributes)
piInheritablePipe.lpSecurityDescriptor = nil
piInheritablePipe.Binherithandle = 1
if CreatePipe(Inhandle, Outhandle, piInheritablePipe, 0) == 0'i32:
OSError()
proc startProcess*(command: string,
workingDir: string = "",
args: openarray[string] = [],
env: PStringTable = nil,
options: set[TProcessOption] = {poStdErrToStdOut}): PProcess =
new(result)
var
SI: TStartupInfo
ProcInfo: TProcessInformation
success: int
hi, ho, he: THandle
SI.cb = SizeOf(SI)
SI.dwFlags = STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES
CreatePipeHandles(SI.hStdInput, HI)
CreatePipeHandles(HO, Si.hStdOutput)
#SI.hStdInput = GetStdHandle(STD_INPUT_HANDLE())
#SI.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE())
if poStdErrToStdOut in options:
SI.hStdError = SI.hStdOutput
HE = HO
else:
CreatePipeHandles(HE, Si.hStdError)
#SI.hStdError = GetStdHandle(STD_ERROR_HANDLE())
#result.inputHandle = open_osfhandle(HI, O_WRONLY)
#if result.inputHandle == -1'i32: OSError()
result.inputHandle = hi
result.outputHandle = ho
result.errorHandle = he
#result.outputHandle = open_osfhandle(HO, O_RDONLY)
#if result.outputHandle == -1'i32: OSError()
#result.errorHandle = open_osfhandle(HE, O_RDONLY)
#if result.errorHandle == -1'i32: OSError()
var cmdl = buildCommandLine(command, args)
var wd: cstring = nil
if len(workingDir) > 0: wd = workingDir
if env == nil:
success = winlean.CreateProcess(nil,
cmdl, nil, nil, 0, NORMAL_PRIORITY_CLASS, nil, wd, SI, ProcInfo)
else:
var e = buildEnv(env)
success = winlean.CreateProcess(nil,
cmdl, nil, nil, 0, NORMAL_PRIORITY_CLASS, e, wd, SI, ProcInfo)
dealloc(e)
dealloc(cmdl)
if success == 0:
OSError()
result.FProcessHandle = procInfo.hProcess
result.FThreadHandle = procInfo.hThread
result.id = procInfo.dwProcessID
proc suspend(p: PProcess) =
discard SuspendThread(p.FThreadHandle)
proc resume(p: PProcess) =
discard ResumeThread(p.FThreadHandle)
proc running(p: PProcess): bool =
var x = waitForSingleObject(p.FProcessHandle, 50)
return x == WAIT_TIMEOUT
proc terminate(p: PProcess) =
if running(p):
discard TerminateProcess(p.FProcessHandle, 0)
proc waitForExit(p: PProcess): int =
discard CloseHandle(p.FThreadHandle)
discard WaitForSingleObject(p.FProcessHandle, Infinite)
var res: int32
discard GetExitCodeProcess(p.FProcessHandle, res)
result = res
discard CloseHandle(p.FProcessHandle)
proc inputStream(p: PProcess): PStream =
result = newFileHandleStream(p.inputHandle)
proc outputStream(p: PProcess): PStream =
result = newFileHandleStream(p.outputHandle)
proc errorStream(p: PProcess): PStream =
result = newFileHandleStream(p.errorHandle)
proc executeCommand(command: string): int =
var
SI: TStartupInfo
ProcInfo: TProcessInformation
process: THandle
L: int32
SI.cb = SizeOf(SI)
SI.hStdError = GetStdHandle(STD_ERROR_HANDLE)
SI.hStdInput = GetStdHandle(STD_INPUT_HANDLE)
SI.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE)
if winlean.CreateProcess(nil, command, nil, nil, 0,
NORMAL_PRIORITY_CLASS, nil, nil, SI, ProcInfo) == 0:
OSError()
else:
Process = ProcInfo.hProcess
discard CloseHandle(ProcInfo.hThread)
if WaitForSingleObject(Process, INFINITE) != -1:
discard GetExitCodeProcess(Process, L)
result = int(L)
else:
result = -1
discard CloseHandle(Process)
else:
import posix
const
readIdx = 0
writeIdx = 1
proc addCmdArgs(command: string, args: openarray[string]): string =
result = command
for i in 0 .. high(args):
add(result, " ")
add(result, args[i])
proc toCStringArray(b, a: openarray[string]): cstringArray =
result = cast[cstringArray](alloc0((a.len + b.len + 1) * sizeof(cstring)))
for i in 0..high(b):
result[i] = cast[cstring](alloc(b[i].len+1))
copyMem(result[i], cstring(b[i]), b[i].len+1)
for i in 0..high(a):
result[i+b.len] = cast[cstring](alloc(a[i].len+1))
copyMem(result[i+b.len], cstring(a[i]), a[i].len+1)
proc ToCStringArray(t: PStringTable): cstringArray =
result = cast[cstringArray](alloc0((t.len + 1) * sizeof(cstring)))
var i = 0
for key, val in pairs(t):
var x = key & "=" & val
result[i] = cast[cstring](alloc(x.len+1))
copyMem(result[i], addr(x[0]), x.len+1)
inc(i)
proc startProcess*(command: string,
workingDir: string = "",
args: openarray[string] = [],
env: PStringTable = nil,
options: set[TProcessOption] = {poStdErrToStdOut}): PProcess =
new(result)
var
p_stdin, p_stdout, p_stderr: array [0..1, cint]
if pipe(p_stdin) != 0'i32 or pipe(p_stdout) != 0'i32:
OSError("failed to create a pipe")
var Pid = fork()
if Pid < 0:
OSError("failed to fork process")
if pid == 0:
## child process:
discard close(p_stdin[writeIdx])
if dup2(p_stdin[readIdx], readIdx) < 0: OSError()
discard close(p_stdout[readIdx])
if dup2(p_stdout[writeIdx], writeIdx) < 0: OSError()
if poStdErrToStdOut in options:
if dup2(p_stdout[writeIdx], 2) < 0: OSError()
else:
if pipe(p_stderr) != 0'i32: OSError("failed to create a pipe")
discard close(p_stderr[readIdx])
if dup2(p_stderr[writeIdx], 2) < 0: OSError()
if workingDir.len > 0:
os.setCurrentDir(workingDir)
if poUseShell notin options:
var a = toCStringArray([extractFilename(command)], args)
if env == nil:
discard execv(command, a)
else:
discard execve(command, a, ToCStringArray(env))
else:
var x = addCmdArgs(command, args)
var a = toCStringArray(["sh", "-c"], [x])
if env == nil:
discard execv("/bin/sh", a)
else:
discard execve("/bin/sh", a, ToCStringArray(env))
# too risky to raise an exception here:
quit("execve call failed: " & $strerror(errno))
# Parent process. Copy process information.
result.id = pid
result.inputHandle = p_stdin[writeIdx]
result.outputHandle = p_stdout[readIdx]
if poStdErrToStdOut in options:
result.errorHandle = result.outputHandle
else:
result.errorHandle = p_stderr[readIdx]
discard close(p_stderr[writeIdx])
discard close(p_stdin[readIdx])
discard close(p_stdout[writeIdx])
proc suspend(p: PProcess) =
discard kill(p.id, SIGSTOP)
proc resume(p: PProcess) =
discard kill(p.id, SIGCONT)
proc running(p: PProcess): bool =
result = waitPid(p.id, p.exitCode, WNOHANG) == int(p.id)
proc terminate(p: PProcess) =
if kill(p.id, SIGTERM) == 0'i32:
if running(p): discard kill(p.id, SIGKILL)
proc waitForExit(p: PProcess): int =
result = 1
if waitPid(p.id, p.exitCode, 0) == int(p.id):
result = p.exitCode
proc inputStream(p: PProcess): PStream =
var f: TFile
if not openFile(f, p.inputHandle, fmWrite): OSError()
result = newFileStream(f)
proc outputStream(p: PProcess): PStream =
var f: TFile
if not openFile(f, p.outputHandle, fmRead): OSError()
result = newFileStream(f)
proc errorStream(p: PProcess): PStream =
var f: TFile
if not openFile(f, p.errorHandle, fmRead): OSError()
result = newFileStream(f)
proc csystem(cmd: cstring): cint {.nodecl, importc: "system".}
proc executeCommand(command: string): int =
result = csystem(command)
when isMainModule:
echo executeCommand("gcc -v")

352
lib/pure/parsecfg.nim Normal file
View file

@ -0,0 +1,352 @@
#
#
# Nimrod's Runtime Library
# (c) Copyright 2008 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## The ``parsecfg`` module implements a high performance configuration file
## parser. The configuration file's syntax is similar to the Windows ``.ini``
## format, but much more powerful, as it is not a line based parser. String
## literals, raw string literals and triple quoted string literals are supported
## as in the Nimrod programming language.
## This is an example of how a configuration file may look like:
##
## .. include:: doc/mytest.cfg
## :literal:
## The file ``tests/tparscfg.nim`` demonstrates how to use the
## configuration file parser:
##
## .. code-block:: nimrod
## :file: tests/tparscfg.nim
import
hashes, strutils, lexbase, streams
type
TCfgEventKind* = enum ## enumation of all events that may occur when parsing
cfgEof, ## end of file reached
cfgSectionStart, ## a ``[section]`` has been parsed
cfgKeyValuePair, ## a ``key=value`` pair has been detected
cfgOption, ## a ``--key=value`` command line option
cfgError ## an error ocurred during parsing
TCfgEvent* = object of TObject ## describes a parsing event
case kind*: TCfgEventKind ## the kind of the event
of cfgEof: nil
of cfgSectionStart:
section*: string ## `section` contains the name of the
## parsed section start (syntax: ``[section]``)
of cfgKeyValuePair, cfgOption:
key*, value*: string ## contains the (key, value) pair if an option
## of the form ``--key: value`` or an ordinary
## ``key= value`` pair has been parsed.
## ``value==""`` if it was not specified in the
## configuration file.
of cfgError: ## the parser encountered an error: `msg`
msg*: string ## contains the error message. No exceptions
## are thrown if a parse error occurs.
TTokKind = enum
tkInvalid, tkEof,
tkSymbol, tkEquals, tkColon, tkBracketLe, tkBracketRi, tkDashDash
TToken {.final.} = object # a token
kind: TTokKind # the type of the token
literal: string # the parsed (string) literal
TParserState = enum
startState # , commaState # not yet used
TCfgParser* = object of TBaseLexer ## the parser object.
tok: TToken
state: TParserState
filename: string
proc open*(c: var TCfgParser, input: PStream, filename: string)
## initializes the parser with an input stream. `Filename` is only used
## for nice error messages.
proc close*(c: var TCfgParser)
## closes the parser `c` and its associated input stream.
proc next*(c: var TCfgParser): TCfgEvent
## retrieves the first/next event. This controls the parser.
proc getColumn*(c: TCfgParser): int
## get the current column the parser has arrived at.
proc getLine*(c: TCfgParser): int
## get the current line the parser has arrived at.
proc getFilename*(c: TCfgParser): string
## get the filename of the file that the parser processes.
proc errorStr*(c: TCfgParser, msg: string): string
## returns a properly formated error message containing current line and
## column information.
# implementation
const
SymChars: TCharSet = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\x80'..'\xFF'}
proc rawGetTok(c: var TCfgParser, tok: var TToken)
proc open(c: var TCfgParser, input: PStream, filename: string) =
lexbase.open(c, input)
c.filename = filename
c.state = startState
c.tok.kind = tkInvalid
c.tok.literal = ""
rawGetTok(c, c.tok)
proc close(c: var TCfgParser) =
lexbase.close(c)
proc getColumn(c: TCfgParser): int =
result = getColNumber(c, c.bufPos)
proc getLine(c: TCfgParser): int =
result = c.linenumber
proc getFilename(c: TCfgParser): string =
result = c.filename
proc handleHexChar(c: var TCfgParser, xi: var int) =
case c.buf[c.bufpos]
of '0'..'9':
xi = (xi shl 4) or (ord(c.buf[c.bufpos]) - ord('0'))
inc(c.bufpos)
of 'a'..'f':
xi = (xi shl 4) or (ord(c.buf[c.bufpos]) - ord('a') + 10)
inc(c.bufpos)
of 'A'..'F':
xi = (xi shl 4) or (ord(c.buf[c.bufpos]) - ord('A') + 10)
inc(c.bufpos)
else:
nil
proc handleDecChars(c: var TCfgParser, xi: var int) =
while c.buf[c.bufpos] in {'0'..'9'}:
xi = (xi * 10) + (ord(c.buf[c.bufpos]) - ord('0'))
inc(c.bufpos)
proc getEscapedChar(c: var TCfgParser, tok: var TToken) =
inc(c.bufpos) # skip '\'
case c.buf[c.bufpos]
of 'n', 'N':
add(tok.literal, nl)
Inc(c.bufpos)
of 'r', 'R', 'c', 'C':
add(tok.literal, '\c')
Inc(c.bufpos)
of 'l', 'L':
add(tok.literal, '\L')
Inc(c.bufpos)
of 'f', 'F':
add(tok.literal, '\f')
inc(c.bufpos)
of 'e', 'E':
add(tok.literal, '\e')
Inc(c.bufpos)
of 'a', 'A':
add(tok.literal, '\a')
Inc(c.bufpos)
of 'b', 'B':
add(tok.literal, '\b')
Inc(c.bufpos)
of 'v', 'V':
add(tok.literal, '\v')
Inc(c.bufpos)
of 't', 'T':
add(tok.literal, '\t')
Inc(c.bufpos)
of '\'', '\"':
add(tok.literal, c.buf[c.bufpos])
Inc(c.bufpos)
of '\\':
add(tok.literal, '\\')
Inc(c.bufpos)
of 'x', 'X':
inc(c.bufpos)
var xi = 0
handleHexChar(c, xi)
handleHexChar(c, xi)
add(tok.literal, Chr(xi))
of '0'..'9':
var xi = 0
handleDecChars(c, xi)
if (xi <= 255): add(tok.literal, Chr(xi))
else: tok.kind = tkInvalid
else: tok.kind = tkInvalid
proc HandleCRLF(c: var TCfgParser, pos: int): int =
case c.buf[pos]
of '\c': result = lexbase.HandleCR(c, pos)
of '\L': result = lexbase.HandleLF(c, pos)
else: result = pos
proc getString(c: var TCfgParser, tok: var TToken, rawMode: bool) =
var pos = c.bufPos + 1 # skip "
var buf = c.buf # put `buf` in a register
tok.kind = tkSymbol
if (buf[pos] == '\"') and (buf[pos + 1] == '\"'):
# long string literal:
inc(pos, 2) # skip ""
# skip leading newline:
pos = HandleCRLF(c, pos)
buf = c.buf
while true:
case buf[pos]
of '\"':
if (buf[pos + 1] == '\"') and (buf[pos + 2] == '\"'): break
add(tok.literal, '\"')
Inc(pos)
of '\c', '\L':
pos = HandleCRLF(c, pos)
buf = c.buf
add(tok.literal, nl)
of lexbase.EndOfFile:
tok.kind = tkInvalid
break
else:
add(tok.literal, buf[pos])
Inc(pos)
c.bufpos = pos + 3 # skip the three """
else:
# ordinary string literal
while true:
var ch = buf[pos]
if ch == '\"':
inc(pos) # skip '"'
break
if ch in {'\c', '\L', lexbase.EndOfFile}:
tok.kind = tkInvalid
break
if (ch == '\\') and not rawMode:
c.bufPos = pos
getEscapedChar(c, tok)
pos = c.bufPos
else:
add(tok.literal, ch)
Inc(pos)
c.bufpos = pos
proc getSymbol(c: var TCfgParser, tok: var TToken) =
var pos = c.bufpos
var buf = c.buf
while true:
add(tok.literal, buf[pos])
Inc(pos)
if not (buf[pos] in SymChars): break
c.bufpos = pos
tok.kind = tkSymbol
proc skip(c: var TCfgParser) =
var pos = c.bufpos
var buf = c.buf
while true:
case buf[pos]
of ' ', '\t':
Inc(pos)
of '#', ';':
while not (buf[pos] in {'\c', '\L', lexbase.EndOfFile}): inc(pos)
of '\c', '\L':
pos = HandleCRLF(c, pos)
buf = c.buf
else:
break # EndOfFile also leaves the loop
c.bufpos = pos
proc rawGetTok(c: var TCfgParser, tok: var TToken) =
tok.kind = tkInvalid
setlen(tok.literal, 0)
skip(c)
case c.buf[c.bufpos]
of '=':
tok.kind = tkEquals
inc(c.bufpos)
tok.literal = "="
of '-':
inc(c.bufPos)
if c.buf[c.bufPos] == '-': inc(c.bufPos)
tok.kind = tkDashDash
tok.literal = "--"
of ':':
tok.kind = tkColon
inc(c.bufpos)
tok.literal = ":"
of 'r', 'R':
if c.buf[c.bufPos + 1] == '\"':
Inc(c.bufPos)
getString(c, tok, true)
else:
getSymbol(c, tok)
of '[':
tok.kind = tkBracketLe
inc(c.bufpos)
tok.literal = "]"
of ']':
tok.kind = tkBracketRi
Inc(c.bufpos)
tok.literal = "]"
of '\"':
getString(c, tok, false)
of lexbase.EndOfFile:
tok.kind = tkEof
tok.literal = "[EOF]"
else: getSymbol(c, tok)
proc errorStr(c: TCfgParser, msg: string): string =
result = `%`("$1($2, $3) Error: $4",
[c.filename, toString(getLine(c)), toString(getColumn(c)), msg])
proc getKeyValPair(c: var TCfgParser, kind: TCfgEventKind): TCfgEvent =
if c.tok.kind == tkSymbol:
result.kind = kind
result.key = c.tok.literal
result.value = ""
rawGetTok(c, c.tok)
if c.tok.kind in {tkEquals, tkColon}:
rawGetTok(c, c.tok)
if c.tok.kind == tkSymbol:
result.value = c.tok.literal
else:
result.kind = cfgError
result.msg = errorStr(c, "symbol expected, but found: " & c.tok.literal)
rawGetTok(c, c.tok)
else:
result.kind = cfgError
result.msg = errorStr(c, "symbol expected, but found: " & c.tok.literal)
rawGetTok(c, c.tok)
proc next(c: var TCfgParser): TCfgEvent =
case c.tok.kind
of tkEof:
result.kind = cfgEof
of tkDashDash:
rawGetTok(c, c.tok)
result = getKeyValPair(c, cfgOption)
of tkSymbol:
result = getKeyValPair(c, cfgKeyValuePair)
of tkBracketLe:
rawGetTok(c, c.tok)
if c.tok.kind == tkSymbol:
result.kind = cfgSectionStart
result.section = c.tok.literal
else:
result.kind = cfgError
result.msg = errorStr(c, "symbol expected, but found: " & c.tok.literal)
rawGetTok(c, c.tok)
if c.tok.kind == tkBracketRi:
rawGetTok(c, c.tok)
else:
result.kind = cfgError
result.msg = errorStr(c, "\']\' expected, but found: " & c.tok.literal)
of tkInvalid, tkEquals, tkColon, tkBracketRi:
result.kind = cfgError
result.msg = errorStr(c, "invalid token: " & c.tok.literal)
rawGetTok(c, c.tok)

178
lib/pure/parsecsv.nim Normal file
View file

@ -0,0 +1,178 @@
#
#
# 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 high performance `CSV`:idx:
## (`comma separated value`:idx:) parser.
##
## Example: How to use the parser
## ==============================
##
## .. code-block:: nimrod
## import os, parsecsv, streams
## var s = newFileStream(ParamStr(1), fmRead)
## if s == nil: quit("cannot open the file" & ParamStr(1))
## var x: TCsvParser
## open(x, s, ParamStr(1))
## while readRow(x):
## Echo "new row: "
## for val in items(x.row):
## Echo "##", val, "##"
## close(x)
##
import
lexbase, streams
type
TCsvRow* = seq[string] ## a row in a CSV file
TCsvParser* = object of TBaseLexer ## the parser object.
row*: TCsvRow ## the current row
filename: string
sep, quote, esc: char
skipWhite: bool
currRow: int
EInvalidCsv* = object of EIO ## exception that is raised if
## a parsing error occurs
proc raiseEInvalidCsv(filename: string, line, col: int,
msg: string) {.noreturn.} =
var e: ref EInvalidCsv
new(e)
e.msg = filename & "(" & $line & ", " & $col & ") Error: " & msg
raise e
proc error(my: TCsvParser, pos: int, msg: string) =
raiseEInvalidCsv(my.filename, my.LineNumber, getColNumber(my, pos), msg)
proc open*(my: var TCsvParser, input: PStream, filename: string,
separator = ',', quote = '"', escape = '\0',
skipInitialSpace = false) =
## initializes the parser with an input stream. `Filename` is only used
## for nice error messages. The parser's behaviour can be controlled by
## the diverse optional parameters:
## - `separator`: character used to separate fields
## - `quote`: Used to quote fields containing special characters like
## `separator`, `quote` or new-line characters. '\0' disables the parsing
## of quotes.
## - `escape`: removes any special meaning from the following character;
## '\0' disables escaping; if escaping is disabled and `quote` is not '\0',
## two `quote` characters are parsed one literal `quote` character.
## - `skipInitialSpace`: If true, whitespace immediately following the
## `separator` is ignored.
lexbase.open(my, input)
my.filename = filename
my.sep = separator
my.quote = quote
my.esc = escape
my.skipWhite = skipInitialSpace
my.row = @[]
my.currRow = 0
proc parseField(my: var TCsvParser, a: var string) =
var pos = my.bufpos
var buf = my.buf
if my.skipWhite:
while buf[pos] in {' ', '\t'}: inc(pos)
setLen(a, 0) # reuse memory
if buf[pos] == my.quote and my.quote != '\0':
inc(pos)
while true:
var c = buf[pos]
if c == '\0':
my.bufpos = pos # can continue after exception?
error(my, pos, my.quote & " expected")
break
elif c == my.quote:
if my.esc == '\0' and buf[pos+1] == my.quote:
add(a, my.quote)
inc(pos, 2)
else:
inc(pos)
break
elif c == my.esc:
add(a, buf[pos+1])
inc(pos, 2)
else:
case c
of '\c':
pos = handleCR(my, pos)
buf = my.buf
add(a, "\n")
of '\l':
pos = handleLF(my, pos)
buf = my.buf
add(a, "\n")
else:
add(a, c)
inc(pos)
else:
while true:
var c = buf[pos]
if c == my.sep: break
if c in {'\c', '\l', '\0'}: break
add(a, c)
inc(pos)
my.bufpos = pos
proc processedRows*(my: var TCsvParser): int =
## returns number of the processed rows
return my.currRow
proc readRow*(my: var TCsvParser, columns = 0): bool =
## reads the next row; if `columns` > 0, it expects the row to have
## exactly this many columns. Returns false if the end of the file
## has been encountered else true.
var col = 0 # current column
var oldpos = my.bufpos
while my.buf[my.bufpos] != '\0':
var oldlen = my.row.len
if oldlen < col+1:
setLen(my.row, col+1)
my.row[col] = ""
parseField(my, my.row[col])
inc(col)
if my.buf[my.bufpos] == my.sep:
inc(my.bufpos)
else:
case my.buf[my.bufpos]
of '\c', '\l':
# skip empty lines:
while true:
case my.buf[my.bufpos]
of '\c': my.bufpos = handleCR(my, my.bufpos)
of '\l': my.bufpos = handleLF(my, my.bufpos)
else: break
of '\0': nil
else: error(my, my.bufpos, my.sep & " expected")
break
setlen(my.row, col)
result = col > 0
if result and col != columns and columns > 0:
error(my, oldpos+1, $columns & " columns expected, but found " &
$col & " columns")
inc(my.currRow)
proc close*(my: var TCsvParser) {.inline.} =
## closes the parser `my` and its associated input stream.
lexbase.close(my)
when isMainModule:
import os
var s = newFileStream(ParamStr(1), fmRead)
if s == nil: quit("cannot open the file" & ParamStr(1))
var x: TCsvParser
open(x, s, ParamStr(1))
while readRow(x):
Echo "new row: "
for val in items(x.row):
Echo "##", val, "##"
close(x)

152
lib/pure/parseopt.nim Normal file
View file

@ -0,0 +1,152 @@
#
#
# 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 provides the standard Nimrod command line parser.
## It supports one convenience iterator over all command line options and some
## lower-level features.
{.push debugger: off.}
import
os, strutils
type
TCmdLineKind* = enum ## the detected command line token
cmdEnd, ## end of command line reached
cmdArgument, ## argument detected
cmdLongoption, ## a long option ``--option`` detected
cmdShortOption ## a short option ``-c`` detected
TOptParser* =
object of TObject ## this object implements the command line parser
cmd: string
pos: int
inShortState: bool
kind*: TCmdLineKind ## the dected command line token
key*, val*: string ## key and value pair; ``key`` is the option
## or the argument, ``value`` is not "" if
## the option was given a value
proc init*(cmdline: string = ""): TOptParser
## inits the option parser. If ``cmdline == ""``, the real command line
## (as provided by the ``OS`` module) is taken.
proc next*(p: var TOptParser)
## parses the first or next option; ``p.kind`` describes what token has been
## parsed. ``p.key`` and ``p.val`` are set accordingly.
proc getRestOfCommandLine*(p: TOptParser): string
## retrieves the rest of the command line that has not been parsed yet.
# implementation
proc init(cmdline: string = ""): TOptParser =
result.pos = strStart
result.inShortState = false
if cmdline != "":
result.cmd = cmdline
else:
result.cmd = ""
for i in countup(1, ParamCount()):
result.cmd = result.cmd & quoteIfContainsWhite(paramStr(i)) & ' '
result.kind = cmdEnd
result.key = ""
result.val = ""
proc parseWord(s: string, i: int, w: var string,
delim: TCharSet = {'\x09', ' ', '\0'}): int =
result = i
if s[result] == '\"':
inc(result)
while not (s[result] in {'\0', '\"'}):
add(w, s[result])
inc(result)
if s[result] == '\"': inc(result)
else:
while not (s[result] in delim):
add(w, s[result])
inc(result)
proc handleShortOption(p: var TOptParser) =
var i = p.pos
p.kind = cmdShortOption
add(p.key, p.cmd[i])
inc(i)
p.inShortState = true
while p.cmd[i] in {'\x09', ' '}:
inc(i)
p.inShortState = false
if p.cmd[i] in {':', '='}:
inc(i)
p.inShortState = false
while p.cmd[i] in {'\x09', ' '}: inc(i)
i = parseWord(p.cmd, i, p.val)
if p.cmd[i] == '\0': p.inShortState = false
p.pos = i
proc next(p: var TOptParser) =
var i = p.pos
while p.cmd[i] in {'\x09', ' '}: inc(i)
p.pos = i
setlen(p.key, 0)
setlen(p.val, 0)
if p.inShortState:
handleShortOption(p)
return
case p.cmd[i]
of '\0':
p.kind = cmdEnd
of '-':
inc(i)
if p.cmd[i] == '-':
p.kind = cmdLongOption
inc(i)
i = parseWord(p.cmd, i, p.key, {'\0', ' ', '\x09', ':', '='})
while p.cmd[i] in {'\x09', ' '}: inc(i)
if p.cmd[i] in {':', '='}:
inc(i)
while p.cmd[i] in {'\x09', ' '}: inc(i)
p.pos = parseWord(p.cmd, i, p.val)
else:
p.pos = i
else:
p.pos = i
handleShortOption(p)
else:
p.kind = cmdArgument
p.pos = parseWord(p.cmd, i, p.key)
proc getRestOfCommandLine(p: TOptParser): string =
result = strip(copy(p.cmd, p.pos + strStart, len(p.cmd) - 1))
iterator getopt*(): tuple[kind: TCmdLineKind, key, val: string] =
##this is an convenience iterator for iterating over the command line.
##This uses the TOptParser object. Example:
##
## .. code-block:: nimrod
## var
## filename = ""
## for kind, key, val in getopt():
## case kind
## of cmdArgument:
## filename = key
## of cmdLongOption, cmdShortOption:
## case key
## of "help", "h": writeHelp()
## of "version", "v": writeVersion()
## of cmdEnd: assert(false) # cannot happen
## if filename == "":
## # no filename has been given, so we show the help:
## writeHelp()
var p = init()
while true:
next(p)
if p.kind == cmdEnd: break
yield (p.kind, p.key, p.val)
{.pop.}

1333
lib/pure/parsesql.nim Normal file

File diff suppressed because it is too large Load diff

635
lib/pure/parsexml.nim Normal file
View file

@ -0,0 +1,635 @@
#
#
# 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 high performance `XML`:idx: / `HTML`:idx:
## parser.
## The only encoding that is supported is UTF-8. The parser has been designed
## to be somewhat error correcting, so that even most "wild HTML" found on the
## web can be parsed with it. **Note:** This parser does not check that each
## ``<tag>`` has a corresponding ``</tag>``! These checks have do be
## implemented by the client code for various reasons:
##
## * Old HTML contains tags that have no end tag: ``<br>`` for example.
## * HTML tags are case insensitive, XML tags are case sensitive. Since this
## library can parse both, only the client knows which comparison is to be
## used.
## * Thus the checks would have been very difficult to implement properly with
## little benefit, especially since they are simple to implement in the
## client. The client should use the `errorMsgExpected` proc to generate
## a nice error message that fits the other error messages this library
## creates.
##
##
## Example 1: Retrieve HTML title
## ==============================
##
## The file ``examples/htmltitle.nim`` demonstrates how to use the
## XML parser to accomplish a simple task: To determine the title of an HTML
## document.
##
## .. code-block:: nimrod
## :file: examples/htmltitle.nim
##
##
## Example 2: Retrieve all HTML links
## ==================================
##
## The file ``examples/htmlrefs.nim`` demonstrates how to use the
## XML parser to accomplish another simple task: To determine all the links
## an HTML document contains.
##
## .. code-block:: nimrod
## :file: examples/htmlrefs.nim
##
import
hashes, strutils, lexbase, streams, unicode
# the parser treats ``<br />`` as ``<br></br>``
type
TXmlEventKind* = enum ## enumation of all events that may occur when parsing
xmlError, ## an error ocurred during parsing
xmlEof, ## end of file reached
xmlCharData, ## character data
xmlWhitespace, ## whitespace has been parsed
xmlComment, ## a comment has been parsed
xmlPI, ## processing instruction (``<?name something ?>``)
xmlElementStart, ## ``<elem>``
xmlElementEnd, ## ``</elem>``
xmlElementOpen, ## ``<elem
xmlAttribute, ## ``key = "value"`` pair
xmlElementClose, ## ``>``
xmlCData, ## ``<![CDATA[`` ... data ... ``]]>``
xmlEntity, ## &entity;
xmlSpecial ## ``<! ... data ... >``
TXmlError* = enum ## enumeration that lists all errors that can occur
errNone, ## no error
errEndOfCDataExpected, ## ``]]>`` expected
errNameExpected, ## name expected
errSemicolonExpected, ## ``;`` expected
errQmGtExpected, ## ``?>`` expected
errGtExpected, ## ``>`` expected
errEqExpected, ## ``=`` expected
errQuoteExpected, ## ``"`` or ``'`` expected
errEndOfCommentExpected ## ``-->`` expected
TParserState = enum
stateStart, stateNormal, stateAttr, stateEmptyElementTag, stateError
TXmlParseOption* = enum ## options for the XML parser
reportWhitespace, ## report whitespace
reportComments ## report comments
TXmlParser* = object of TBaseLexer ## the parser object.
a, b: string
kind: TXmlEventKind
err: TXmlError
state: TParserState
filename: string
options: set[TXmlParseOption]
const
errorMessages: array [TXmlError, string] = [
"no error",
"']]>' expected",
"name expected",
"';' expected",
"'?>' expected",
"'>' expected",
"'=' expected",
"'\"' or \"'\" expected",
"'-->' expected"
]
proc open*(my: var TXmlParser, input: PStream, filename: string,
options: set[TXmlParseOption] = {}) =
## initializes the parser with an input stream. `Filename` is only used
## for nice error messages. The parser's behaviour can be controlled by
## the `options` parameter: If `options` contains ``reportWhitespace``
## a whitespace token is reported as an ``xmlWhitespace`` event.
## If `options` contains ``reportComments`` a comment token is reported as an
## ``xmlComment`` event.
lexbase.open(my, input)
my.filename = filename
my.state = stateStart
my.kind = xmlError
my.a = ""
my.b = ""
my.options = options
proc close*(my: var TXmlParser) {.inline.} =
## closes the parser `my` and its associated input stream.
lexbase.close(my)
proc charData*(my: TXmlParser): string {.inline.} =
## returns the character data for the events: ``xmlCharData``,
## ``xmlWhitespace``, ``xmlComment``, ``xmlCData``, ``xmlSpecial``
assert(my.kind in {xmlCharData, xmlWhitespace, xmlComment, xmlCData,
xmlSpecial})
return my.a
proc kind*(my: TXmlParser): TXmlEventKind {.inline.} =
## returns the current event type for the XML parser
return my.kind
proc elementName*(my: TXmlParser): string {.inline.} =
## returns the element name for the events: ``xmlElementStart``,
## ``xmlElementEnd``, ``xmlElementOpen``
assert(my.kind in {xmlElementStart, xmlElementEnd, xmlElementOpen})
return my.a
proc entityName*(my: TXmlParser): string {.inline.} =
## returns the entity name for the event: ``xmlEntity``
assert(my.kind == xmlEntity)
return my.a
proc attrKey*(my: TXmlParser): string {.inline.} =
## returns the attribute key for the event ``xmlAttribute``
assert(my.kind == xmlAttribute)
return my.a
proc attrValue*(my: TXmlParser): string {.inline.} =
## returns the attribute value for the event ``xmlAttribute``
assert(my.kind == xmlAttribute)
return my.b
proc PIName*(my: TXmlParser): string {.inline.} =
## returns the processing instruction name for the event ``xmlPI``
assert(my.kind == xmlPI)
return my.a
proc PIRest*(my: TXmlParser): string {.inline.} =
## returns the rest of the processing instruction for the event ``xmlPI``
assert(my.kind == xmlPI)
return my.b
proc getColumn*(my: TXmlParser): int {.inline.} =
## get the current column the parser has arrived at.
result = getColNumber(my, my.bufPos)
proc getLine*(my: TXmlParser): int {.inline.} =
## get the current line the parser has arrived at.
result = my.linenumber
proc getFilename*(my: TXmlParser): string {.inline.} =
## get the filename of the file that the parser processes.
result = my.filename
proc errorMsg*(my: TXmlParser): string =
## returns a helpful error message for the event ``xmlError``
assert(my.kind == xmlError)
result = "$1($2, $3) Error: $4" % [
my.filename, $getLine(my), $getColumn(my), errorMessages[my.err]]
proc errorMsgExpected*(my: TXmlParser, tag: string): string =
## returns an error message "<tag> expected" in the same format as the
## other error messages
result = "$1($2, $3) Error: $4" % [
my.filename, $getLine(my), $getColumn(my), "<$1> expected" % tag]
proc markError(my: var TXmlParser, kind: TXmlError) {.inline.} =
my.err = kind
my.state = stateError
proc parseCDATA(my: var TXMLParser) =
var pos = my.bufpos + len("<![CDATA[")
var buf = my.buf
while true:
case buf[pos]
of ']':
if buf[pos+1] == ']' and buf[pos+2] == '>':
inc(pos, 3)
break
add(my.a, ']')
inc(pos)
of '\0':
markError(my, errEndOfCDataExpected)
break
of '\c':
pos = lexbase.HandleCR(my, pos)
buf = my.buf
add(my.a, '\L')
of '\L':
pos = lexbase.HandleLF(my, pos)
buf = my.buf
add(my.a, '\L')
else:
add(my.a, buf[pos])
inc(pos)
my.bufpos = pos # store back
my.kind = xmlCDATA
proc parseComment(my: var TXMLParser) =
var pos = my.bufpos + len("<!--")
var buf = my.buf
while true:
case buf[pos]
of '-':
if buf[pos+1] == '-' and buf[pos+2] == '>':
inc(pos, 3)
break
if my.options.contains(reportComments): add(my.a, '-')
inc(pos)
of '\0':
markError(my, errEndOfCommentExpected)
break
of '\c':
pos = lexbase.HandleCR(my, pos)
buf = my.buf
if my.options.contains(reportComments): add(my.a, '\L')
of '\L':
pos = lexbase.HandleLF(my, pos)
buf = my.buf
if my.options.contains(reportComments): add(my.a, '\L')
else:
if my.options.contains(reportComments): add(my.a, buf[pos])
inc(pos)
my.bufpos = pos
my.kind = xmlComment
proc parseWhitespace(my: var TXmlParser, skip=False) =
var pos = my.bufpos
var buf = my.buf
while true:
case buf[pos]
of ' ', '\t':
if not skip: add(my.a, buf[pos])
Inc(pos)
of '\c':
# the specification says that CR-LF, CR are to be transformed to LF
pos = lexbase.HandleCR(my, pos)
buf = my.buf
if not skip: add(my.a, '\L')
of '\L':
pos = lexbase.HandleLF(my, pos)
buf = my.buf
if not skip: add(my.a, '\L')
else:
break
my.bufpos = pos
const
NameStartChar = {'A'..'Z', 'a'..'z', '_', ':', '\128'..'\255'}
NameChar = {'A'..'Z', 'a'..'z', '0'..'9', '.', '-', '_', ':', '\128'..'\255'}
proc parseName(my: var TXmlParser, dest: var string) =
var pos = my.bufpos
var buf = my.buf
if buf[pos] in nameStartChar:
while true:
add(dest, buf[pos])
inc(pos)
if buf[pos] notin NameChar: break
my.bufpos = pos
else:
markError(my, errNameExpected)
proc parseEntity(my: var TXmlParser, dest: var string) =
var pos = my.bufpos+1
var buf = my.buf
my.kind = xmlCharData
if buf[pos] == '#':
var r: TRune
inc(pos)
if buf[pos] == 'x':
inc(pos)
while true:
case buf[pos]
of '0'..'9': r = (r shl 4) or (ord(buf[pos]) - ord('0'))
of 'a'..'f': r = (r shl 4) or (ord(buf[pos]) - ord('a') + 10)
of 'A'..'F': r = (r shl 4) or (ord(buf[pos]) - ord('A') + 10)
else: break
inc(pos)
else:
while buf[pos] in {'0'..'9'}:
r = r * 10 + (ord(buf[pos]) - ord('0'))
inc(pos)
add(dest, toUTF8(r))
elif buf[pos] == 'l' and buf[pos+1] == 't':
add(dest, '<')
inc(pos, 2)
elif buf[pos] == 'g' and buf[pos+1] == 't':
add(dest, '>')
inc(pos, 2)
elif buf[pos] == 'a' and buf[pos+1] == 'm' and buf[pos+2] == 'p':
add(dest, '&')
inc(pos, 3)
elif buf[pos] == 'a' and buf[pos+1] == 'p' and buf[pos+2] == 'o' and
buf[pos+3] == 's':
add(dest, '\'')
inc(pos, 4)
elif buf[pos] == 'q' and buf[pos+1] == 'u' and buf[pos+2] == 'o' and
buf[pos+3] == 't':
add(dest, '"')
inc(pos, 4)
else:
my.bufpos = pos
parseName(my, dest)
pos = my.bufpos
if my.err != errNameExpected:
my.kind = xmlEntity
else:
add(dest, '&')
if buf[pos] == ';':
inc(pos)
else:
markError(my, errSemiColonExpected)
my.bufpos = pos
proc parsePI(my: var TXmlParser) =
inc(my.bufpos, "<?".len)
parseName(my, my.a)
var pos = my.bufpos
var buf = my.buf
setLen(my.b, 0)
while true:
case buf[pos]
of '\0':
markError(my, errQmGtExpected)
break
of '?':
if buf[pos+1] == '>':
inc(pos, 2)
break
add(my.b, '?')
inc(pos)
of '\c':
# the specification says that CR-LF, CR are to be transformed to LF
pos = lexbase.HandleCR(my, pos)
buf = my.buf
add(my.b, '\L')
of '\L':
pos = lexbase.HandleLF(my, pos)
buf = my.buf
add(my.b, '\L')
else:
add(my.b, buf[pos])
inc(pos)
my.bufpos = pos
my.kind = xmlPI
proc parseSpecial(my: var TXmlParser) =
# things that start with <!
var pos = my.bufpos + 2
var buf = my.buf
var opentags = 0
while true:
case buf[pos]
of '\0':
markError(my, errGtExpected)
break
of '<':
inc(opentags)
inc(pos)
add(my.a, '<')
of '>':
if opentags <= 0:
inc(pos)
break
dec(opentags)
inc(pos)
add(my.a, '>')
of '\c':
pos = lexbase.HandleCR(my, pos)
buf = my.buf
add(my.a, '\L')
of '\L':
pos = lexbase.HandleLF(my, pos)
buf = my.buf
add(my.a, '\L')
else:
add(my.a, buf[pos])
inc(pos)
my.bufpos = pos
my.kind = xmlSpecial
proc parseTag(my: var TXmlParser) =
inc(my.bufpos)
parseName(my, my.a)
# if we have no name, do not interpret the '<':
if my.a.len == 0:
my.kind = xmlCharData
add(my.a, '<')
return
parseWhitespace(my, skip=True)
if my.buf[my.bufpos] in NameStartChar:
# an attribute follows:
my.kind = xmlElementOpen
my.state = stateAttr
else:
my.kind = xmlElementStart
if my.buf[my.bufpos] == '/' and my.buf[my.bufpos+1] == '>':
inc(my.bufpos, 2)
my.state = stateEmptyElementTag
elif my.buf[my.bufpos] == '>':
inc(my.bufpos)
else:
markError(my, errGtExpected)
proc parseEndTag(my: var TXmlParser) =
inc(my.bufpos, 2)
parseName(my, my.a)
parseWhitespace(my, skip=True)
if my.buf[my.bufpos] == '>':
inc(my.bufpos)
else:
markError(my, errGtExpected)
my.kind = xmlElementEnd
proc parseAttribute(my: var TXmlParser) =
my.kind = xmlAttribute
setLen(my.a, 0)
setLen(my.b, 0)
parseName(my, my.a)
# if we have no name, we have '<tag attr= key %&$$%':
if my.a.len == 0:
markError(my, errGtExpected)
return
parseWhitespace(my, skip=True)
if my.buf[my.bufpos] != '=':
markError(my, errEqExpected)
return
inc(my.bufpos)
parseWhitespace(my, skip=True)
var pos = my.bufpos
var buf = my.buf
if buf[pos] in {'\'', '"'}:
var quote = buf[pos]
var pendingSpace = false
inc(pos)
while true:
case buf[pos]
of '\0':
markError(my, errQuoteExpected)
break
of '&':
if pendingSpace:
add(my.b, ' ')
pendingSpace = false
my.bufpos = pos
parseEntity(my, my.b)
my.kind = xmlAttribute # parseEntity overwrites my.kind!
pos = my.bufpos
of ' ', '\t':
pendingSpace = true
inc(pos)
of '\c':
pos = lexbase.HandleCR(my, pos)
buf = my.buf
pendingSpace = true
of '\L':
pos = lexbase.HandleLF(my, pos)
buf = my.buf
pendingSpace = true
else:
if buf[pos] == quote:
inc(pos)
break
else:
if pendingSpace:
add(my.b, ' ')
pendingSpace = false
add(my.b, buf[pos])
inc(pos)
else:
markError(my, errQuoteExpected)
my.bufpos = pos
parseWhitespace(my, skip=True)
proc parseCharData(my: var TXmlParser) =
var pos = my.bufpos
var buf = my.buf
while true:
case buf[pos]
of '\0', '<', '&': break
of '\c':
# the specification says that CR-LF, CR are to be transformed to LF
pos = lexbase.HandleCR(my, pos)
buf = my.buf
add(my.a, '\L')
of '\L':
pos = lexbase.HandleLF(my, pos)
buf = my.buf
add(my.a, '\L')
else:
add(my.a, buf[pos])
inc(pos)
my.bufpos = pos
my.kind = xmlCharData
proc rawGetTok(my: var TXmlParser) =
my.kind = xmlError
setLen(my.a, 0)
var pos = my.bufpos
var buf = my.buf
case buf[pos]
of '<':
case buf[pos+1]
of '/':
parseEndTag(my)
of '!':
if buf[pos+2] == '[' and buf[pos+3] == 'C' and buf[pos+4] == 'D' and
buf[pos+5] == 'A' and buf[pos+6] == 'T' and buf[pos+7] == 'A' and
buf[pos+8] == '[':
parseCDATA(my)
elif buf[pos+2] == '-' and buf[pos+3] == '-':
parseComment(my)
else:
parseSpecial(my)
of '?':
parsePI(my)
else:
parseTag(my)
of ' ', '\t', '\c', '\l':
parseWhiteSpace(my)
my.kind = xmlWhitespace
of '\0':
my.kind = xmlEof
of '&':
parseEntity(my, my.a)
else:
parseCharData(my)
assert my.kind != xmlError
proc getTok(my: var TXmlParser) =
while true:
rawGetTok(my)
case my.kind
of xmlComment:
if my.options.contains(reportComments): break
of xmlWhitespace:
if my.options.contains(reportWhitespace): break
else: break
proc next*(my: var TXmlParser) =
## retrieves the first/next event. This controls the parser.
case my.state
of stateNormal:
getTok(my)
of stateStart:
getTok(my)
if my.kind == xmlPI and my.a == "xml":
# just skip the first ``<?xml >`` processing instruction
getTok(my)
my.state = stateNormal
of stateAttr:
# parse an attribute key-value pair:
if my.buf[my.bufpos] == '>':
my.kind = xmlElementClose
inc(my.bufpos)
my.state = stateNormal
elif my.buf[my.bufpos] == '/' and my.buf[my.bufpos+1] == '>':
my.kind = xmlElementClose
inc(my.bufpos, 2)
my.state = stateEmptyElementTag
else:
parseAttribute(my)
# state remains the same
of stateEmptyElementTag:
my.state = stateNormal
my.kind = xmlElementEnd
of stateError:
my.kind = xmlError
my.state = stateNormal
when isMainModule:
import os
var s = newFileStream(ParamStr(1), fmRead)
if s == nil: quit("cannot open the file" & ParamStr(1))
var x: TXmlParser
open(x, s, ParamStr(1))
while true:
next(x)
case x.kind
of xmlError: Echo(x.errorMsg())
of xmlEof: break
of xmlCharData: echo(x.charData)
of xmlWhitespace: echo("|$1|" % x.charData)
of xmlComment: echo("<!-- $1 -->" % x.charData)
of xmlPI: echo("<? $1 ## $2 ?>" % [x.PIName, x.PIRest])
of xmlElementStart: echo("<$1>" % x.elementName)
of xmlElementEnd: echo("</$1>" % x.elementName)
of xmlElementOpen: echo("<$1" % x.elementName)
of xmlAttribute:
echo("Key: " & x.attrKey)
echo("Value: " & x.attrValue)
of xmlElementClose: echo(">")
of xmlCData:
echo("<![CDATA[$1]]>" % x.charData)
of xmlEntity:
echo("&$1;" % x.entityName)
of xmlSpecial:
echo("SPECIAL: " & x.charData)
close(x)

154
lib/pure/regexprs.nim Normal file
View file

@ -0,0 +1,154 @@
#
#
# Nimrod's Runtime Library
# (c) Copyright 2009 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Regular expression support for Nimrod.
## Currently this module is implemented by providing a wrapper around the
## `PRCE (Perl-Compatible Regular Expressions) <http://www.pcre.org>`_
## C library. This means that your application will depend on the PRCE
## library's licence when using this module, which should not be a problem
## though.
## PRCE's licence follows:
##
## .. include:: ../doc/regexprs.txt
##
# This is not just a convenient wrapper for the pcre library; the
# API will stay the same if the implementation should change.
import
pcre, strutils
type
EInvalidRegEx* = object of EInvalidValue
## is raised if the pattern is no valid regular expression.
const
MaxSubpatterns* = 10
## defines the maximum number of subpatterns that can be captured.
## More subpatterns cannot be captured!
proc match*(s, pattern: string, matches: var openarray[string],
start: int = 0): bool
## returns ``true`` if ``s[start..]`` matches the ``pattern`` and
## the captured substrings in the array ``matches``. If it does not
## match, nothing is written into ``matches`` and ``false`` is
## returned.
proc match*(s, pattern: string, start: int = 0): bool
## returns ``true`` if ``s`` matches the ``pattern`` beginning from ``start``.
proc matchLen*(s, pattern: string, matches: var openarray[string],
start: int = 0): int
## the same as ``match``, but it returns the length of the match,
## if there is no match, -1 is returned. Note that a match length
## of zero can happen.
proc find*(s, pattern: string, matches: var openarray[string],
start: int = 0): bool
## returns ``true`` if ``pattern`` occurs in ``s`` and the captured
## substrings in the array ``matches``. If it does not match, nothing
## is written into ``matches``.
proc find*(s, pattern: string, start: int = 0): bool
## returns ``true`` if ``pattern`` occurs in ``s``.
proc rawCompile(pattern: string, flags: cint): PPcre =
var
msg: CString
offset: int
com = pcreCompile(pattern, flags, addr(msg), addr(offset), nil)
if com == nil:
var e: ref EInvalidRegEx
new(e)
e.msg = $msg & "\n" & pattern & "\n" & repeatChar(offset) & "^\n"
raise e
return com
proc matchOrFind(s: string, pattern: PPcre, matches: var openarray[string],
start: cint): cint =
var
rawMatches: array [0..maxSubpatterns * 3 - 1, cint]
res = int(pcreExec(pattern, nil, s, len(s), start, 0,
cast[ptr cint](addr(rawMatches)), maxSubpatterns * 3))
dealloc(pattern)
if res < 0: return res
for i in 0..res-1:
var
a = rawMatches[i * 2]
b = rawMatches[i * 2 + 1]
if a >= 0'i32: matches[i] = copy(s, a, int(b)-1)
else: matches[i] = ""
return res
proc matchOrFind(s: string, pattern: PPcre, start: cint): cint =
var
rawMatches: array [0..maxSubpatterns * 3 - 1, cint]
res = pcreExec(pattern, nil, s, len(s), start, 0,
cast[ptr cint](addr(rawMatches)), maxSubpatterns * 3)
dealloc(pattern)
return res
proc match(s, pattern: string, matches: var openarray[string],
start: int = 0): bool =
return matchOrFind(s, rawCompile(pattern, PCRE_ANCHORED),
matches, start) >= 0'i32
proc matchLen(s, pattern: string, matches: var openarray[string],
start: int = 0): int =
return matchOrFind(s, rawCompile(pattern, PCRE_ANCHORED), matches, start)
proc find(s, pattern: string, matches: var openarray[string],
start: int = 0): bool =
return matchOrFind(s, rawCompile(pattern, PCRE_MULTILINE),
matches, start) >= 0'i32
proc match(s, pattern: string, start: int = 0): bool =
return matchOrFind(s, rawCompile(pattern, PCRE_ANCHORED), start) >= 0'i32
proc find(s, pattern: string, start: int = 0): bool =
return matchOrFind(s, rawCompile(pattern, PCRE_MULTILINE), start) >= 0'i32
template `=~` *(s, pattern: expr): expr =
## This calls ``match`` with an implicit declared ``matches`` array that
## can be used in the scope of the ``=~`` call:
##
## .. code-block:: nimrod
##
## if line =~ r"\s*(\w+)\s*\=\s*(\w+)":
## # matches a key=value pair:
## echo("Key: ", matches[1])
## echo("Value: ", matches[2])
## elif line =~ r"\s*(\#.*)":
## # matches a comment
## # note that the implicit ``matches`` array is different from the
## # ``matches`` array of the first branch
## echo("comment: ", matches[1])
## else:
## echo("syntax error")
##
var matches: array[0..maxSubPatterns-1, string]
match(s, pattern, matches)
const ## common regular expressions
reIdentifier* = r"\b[a-zA-Z_]+[a-zA-Z_0-9]*\b" ## describes an identifier
reNatural* = r"\b\d+\b" ## describes a natural number
reInteger* = r"\b[-+]?\d+\b" ## describes an integer
reHex* = r"\b0[xX][0-9a-fA-F]+\b" ## describes a hexadecimal number
reBinary* = r"\b0[bB][01]+\b" ## describes a binary number (example: 0b11101)
reOctal* = r"\b0[oO][0-7]+\b" ## describes an octal number (example: 0o777)
reFloat* = r"\b[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?\b"
## describes a floating point number
reEmail* = r"\b[a-zA-Z0-9!#$%&'*+/=?^_`{|}~\-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)" &
r"*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+(?:[a-zA-Z]{2}|com|org|" &
r"net|gov|mil|biz|info|mobi|name|aero|jobs|museum)\b"
## describes a common email address
reURL* = r"\b(http(s)?|ftp|gopher|telnet|file|notes|ms\-help):" &
r"((//)|(\\\\))+[\w\d:#@%/;$()~_?\+\-\=\\\.\&]*\b"
## describes an URL

244
lib/pure/streams.nim Normal file
View file

@ -0,0 +1,244 @@
#
#
# Nimrod's Runtime Library
# (c) Copyright 2008 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module provides a stream interface and two implementations thereof:
## the `PFileStream` and the `PStringStream` which implement the stream
## interface for Nimrod file objects (`TFile`) and strings. Other modules
## may provide other implementations for this standard stream interface.
proc newEIO(msg: string): ref EIO =
new(result)
result.msg = msg
type
PStream* = ref TStream
TStream* = object of TObject ## Stream interface that supports
## writing or reading.
close*: proc (s: PStream)
atEnd*: proc (s: PStream): bool
setPosition*: proc (s: PStream, pos: int)
getPosition*: proc (s: PStream): int
readData*: proc (s: PStream, buffer: pointer, bufLen: int): int
writeData*: proc (s: PStream, buffer: pointer, bufLen: int)
proc write*[T](s: PStream, x: T) =
## generic write procedure. Writes `x` to the stream `s`. Implementation:
##
## .. code-block:: Nimrod
##
## s.writeData(s, addr(x), sizeof(x))
var x = x
s.writeData(s, addr(x), sizeof(x))
proc write*(s: PStream, x: string) =
## writes the string `x` to the the stream `s`. No length field or
## terminating zero is written.
s.writeData(s, cstring(x), x.len)
proc read[T](s: PStream, result: var T) =
## generic write procedure. Reads `result` from the stream `s`.
if s.readData(s, addr(result), sizeof(T)) != sizeof(T):
raise newEIO("cannot read from stream")
proc readChar*(s: PStream): char =
## reads a char from the stream `s`. Raises `EIO` if an error occured.
read(s, result)
proc readBool*(s: PStream): bool =
## reads a bool from the stream `s`. Raises `EIO` if an error occured.
read(s, result)
proc readInt8*(s: PStream): int8 =
## reads an int8 from the stream `s`. Raises `EIO` if an error occured.
read(s, result)
proc readInt16*(s: PStream): int16 =
## reads an int16 from the stream `s`. Raises `EIO` if an error occured.
read(s, result)
proc readInt32*(s: PStream): int32 =
## reads an int32 from the stream `s`. Raises `EIO` if an error occured.
read(s, result)
proc readInt64*(s: PStream): int64 =
## reads an int64 from the stream `s`. Raises `EIO` if an error occured.
read(s, result)
proc readFloat32*(s: PStream): float32 =
## reads a float32 from the stream `s`. Raises `EIO` if an error occured.
read(s, result)
proc readFloat64*(s: PStream): float64 =
## reads a float64 from the stream `s`. Raises `EIO` if an error occured.
read(s, result)
proc readStr*(s: PStream, length: int): string =
## reads a string of length `length` from the stream `s`. Raises `EIO` if
## an error occured.
result = newString(length)
var L = s.readData(s, addr(result[0]), length)
if L != length: setLen(result, L)
proc readLine*(s: PStream): string =
## Reads a line from a stream `s`. Note: This is not very efficient. Raises
## `EIO` if an error occured.
result = ""
while not s.atEnd(s):
var c = readChar(s)
if c == '\c':
c = readChar(s)
break
elif c == '\L': break
result.add(c)
type
PStringStream* = ref TStringStream ## a stream that encapsulates a string
TStringStream* = object of TStream
data*: string
pos: int
proc ssAtEnd(s: PStringStream): bool =
return s.pos >= s.data.len
proc ssSetPosition(s: PStringStream, pos: int) =
s.pos = min(pos, s.data.len-1)
proc ssGetPosition(s: PStringStream): int =
return s.pos
proc ssReadData(s: PStringStream, buffer: pointer, bufLen: int): int =
result = min(bufLen, s.data.len - s.pos)
if result > 0:
copyMem(buffer, addr(s.data[s.pos]), result)
inc(s.pos, result)
proc ssWriteData(s: PStringStream, buffer: pointer, bufLen: int) =
if bufLen > 0:
setLen(s.data, s.data.len + bufLen)
copyMem(addr(s.data[s.pos]), buffer, bufLen)
inc(s.pos, bufLen)
proc ssClose(s: PStringStream) =
s.data = nil
proc newStringStream*(s: string = ""): PStringStream =
## creates a new stream from the string `s`.
new(result)
result.data = s
result.pos = 0
result.close = ssClose
result.atEnd = ssAtEnd
result.setPosition = ssSetPosition
result.getPosition = ssGetPosition
result.readData = ssReadData
result.writeData = ssWriteData
type
PFileStream* = ref TFileStream ## a stream that encapsulates a `TFile`
TFileStream* = object of TStream
f: TFile
proc fsClose(s: PFileStream) = closeFile(s.f)
proc fsAtEnd(s: PFileStream): bool = return EndOfFile(s.f)
proc fsSetPosition(s: PFileStream, pos: int) = setFilePos(s.f, pos)
proc fsGetPosition(s: PFileStream): int = return int(getFilePos(s.f))
proc fsReadData(s: PFileStream, buffer: pointer, bufLen: int): int =
result = readBuffer(s.f, buffer, bufLen)
proc fsWriteData(s: PFileStream, buffer: pointer, bufLen: int) =
if writeBuffer(s.f, buffer, bufLen) != bufLen:
raise newEIO("cannot write to stream")
proc newFileStream*(f: TFile): PFileStream =
## creates a new stream from the file `f`.
new(result)
result.f = f
result.close = fsClose
result.atEnd = fsAtEnd
result.setPosition = fsSetPosition
result.getPosition = fsGetPosition
result.readData = fsReadData
result.writeData = fsWriteData
proc newFileStream*(filename: string, mode: TFileMode): PFileStream =
## creates a new stream from the file named `filename` with the mode `mode`.
## If the file cannot be opened, nil is returned.
var f: TFile
if OpenFile(f, filename, mode): result = newFileStream(f)
when true:
nil
else:
type
TFileHandle* = cint ## Operating system file handle
PFileHandleStream* = ref TFileHandleStream
TFileHandleStream* = object of TStream
handle*: TFileHandle
pos: int
proc newEOS(msg: string): ref EOS =
new(result)
result.msg = msg
proc hsGetPosition(s: PFileHandleStream): int =
return s.pos
when defined(windows):
# do not import windows as this increases compile times:
nil
else:
import posix
proc hsSetPosition(s: PFileHandleStream, pos: int) =
discard lseek(s.handle, pos, SEEK_SET)
proc hsClose(s: PFileHandleStream) = discard close(s.handle)
proc hsAtEnd(s: PFileHandleStream): bool =
var pos = hsGetPosition(s)
var theEnd = lseek(s.handle, 0, SEEK_END)
result = pos >= theEnd
hsSetPosition(s, pos) # set position back
proc hsReadData(s: PFileHandleStream, buffer: pointer, bufLen: int): int =
result = posix.read(s.handle, buffer, bufLen)
inc(s.pos, result)
proc hsWriteData(s: PFileHandleStream, buffer: pointer, bufLen: int) =
if posix.write(s.handle, buffer, bufLen) != bufLen:
raise newEIO("cannot write to stream")
inc(s.pos, bufLen)
proc newFileHandleStream*(handle: TFileHandle): PFileHandleStream =
new(result)
result.handle = handle
result.pos = 0
result.close = hsClose
result.atEnd = hsAtEnd
result.setPosition = hsSetPosition
result.getPosition = hsGetPosition
result.readData = hsReadData
result.writeData = hsWriteData
proc newFileHandleStream*(filename: string,
mode: TFileMode): PFileHandleStream =
when defined(windows):
nil
else:
var flags: cint
case mode
of fmRead: flags = posix.O_RDONLY
of fmWrite: flags = O_WRONLY or int(O_CREAT)
of fmReadWrite: flags = O_RDWR or int(O_CREAT)
of fmReadWriteExisting: flags = O_RDWR
of fmAppend: flags = O_WRONLY or int(O_CREAT) or O_APPEND
var handle = open(filename, flags)
if handle < 0: raise newEOS("posix.open() call failed")
result = newFileHandleStream(handle)

198
lib/pure/strtabs.nim Normal file
View file

@ -0,0 +1,198 @@
#
#
# Nimrod's Runtime Library
# (c) Copyright 2008 Andreas Rumpf
#
# See the file "copying.txt", included in this
# 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.
import
os, hashes, strutils
type
TStringTableMode* = enum ## describes the tables operation mode
modeCaseSensitive, ## the table is case sensitive
modeCaseInsensitive, ## the table is case insensitive
modeStyleInsensitive ## the table is style insensitive
TKeyValuePair = tuple[key, val: string]
TKeyValuePairSeq = seq[TKeyValuePair]
TStringTable* = object of TObject
counter: int
data: TKeyValuePairSeq
mode: TStringTableMode
PStringTable* = ref TStringTable ## use this type to declare string tables
proc newStringTable*(keyValuePairs: openarray[string],
mode: TStringTableMode = modeCaseSensitive): PStringTable
## creates a new string table with given key value pairs.
## Example::
## var mytab = newStringTable("key1", "val1", "key2", "val2",
## modeCaseInsensitive)
proc newStringTable*(mode: TStringTableMode = modeCaseSensitive): PStringTable
## creates a new string table that is empty.
proc `[]=`*(t: PStringTable, key, val: string)
## puts a (key, value)-pair into `t`.
proc `[]`*(t: PStringTable, key: string): string
## retrieves the value at ``t[key]``. If `key` is not in `t`, "" is returned
## and no exception is raised. One can check with ``hasKey`` whether the key
## exists.
proc hasKey*(t: PStringTable, key: string): bool
## returns true iff `key` is in the table `t`.
proc len*(t: PStringTable): int =
## returns the number of keys in `t`.
result = t.counter
iterator pairs*(t: PStringTable): tuple[key, value: string] =
## iterates over any (key, value) pair in the table `t`.
for h in 0..high(t.data):
if not isNil(t.data[h].key):
yield (t.data[h].key, t.data[h].val)
type
TFormatFlag* = enum ## flags for the `%` operator
useEnvironment, ## use environment variable if the ``$key``
## is not found in the table
useEmpty, ## use the empty string as a default, thus it
## won't throw an exception if ``$key`` is not
## in the table
useKey ## do not replace ``$key`` if it is not found
## in the table (or in the environment)
proc `%`*(f: string, t: PStringTable, flags: set[TFormatFlag] = {}): string
## The `%` operator for string tables.
# implementation
const
growthFactor = 2
startSize = 64
proc newStringTable(mode: TStringTableMode = modeCaseSensitive): PStringTable =
new(result)
result.mode = mode
result.counter = 0
newSeq(result.data, startSize)
proc newStringTable(keyValuePairs: openarray[string],
mode: TStringTableMode = modeCaseSensitive): PStringTable =
result = newStringTable(mode)
var i = 0
while i < high(keyValuePairs):
result[keyValuePairs[i]] = keyValuePairs[i + 1]
inc(i, 2)
proc myhash(t: PStringTable, key: string): THash =
case t.mode
of modeCaseSensitive: result = hashes.hash(key)
of modeCaseInsensitive: result = hashes.hashIgnoreCase(key)
of modeStyleInsensitive: result = hashes.hashIgnoreStyle(key)
proc myCmp(t: PStringTable, a, b: string): bool =
case t.mode
of modeCaseSensitive: result = cmp(a, b) == 0
of modeCaseInsensitive: result = cmpIgnoreCase(a, b) == 0
of modeStyleInsensitive: result = cmpIgnoreStyle(a, b) == 0
proc mustRehash(length, counter: int): bool =
assert(length > counter)
result = (length * 2 < counter * 3) or (length - counter < 4)
proc nextTry(h, maxHash: THash): THash =
result = ((5 * h) + 1) and maxHash
proc RawGet(t: PStringTable, key: string): int =
var h: THash
h = myhash(t, key) and high(t.data) # start with real hash value
while not isNil(t.data[h].key):
if mycmp(t, t.data[h].key, key):
return h
h = nextTry(h, high(t.data))
result = - 1
proc `[]`(t: PStringTable, key: string): string =
var index: int
index = RawGet(t, key)
if index >= 0: result = t.data[index].val
else: result = ""
proc hasKey(t: PStringTable, key: string): bool =
result = rawGet(t, key) >= 0
proc RawInsert(t: PStringTable, data: var TKeyValuePairSeq, key, val: string) =
var h: THash
h = myhash(t, key) and high(data)
while not isNil(data[h].key):
h = nextTry(h, high(data))
data[h].key = key
data[h].val = val
proc Enlarge(t: PStringTable) =
var n: TKeyValuePairSeq
newSeq(n, len(t.data) * growthFactor)
for i in countup(0, high(t.data)):
if not isNil(t.data[i].key): RawInsert(t, n, t.data[i].key, t.data[i].val)
swap(t.data, n)
proc `[]=`(t: PStringTable, key, val: string) =
var index = RawGet(t, key)
if index >= 0:
t.data[index].val = val
else:
if mustRehash(len(t.data), t.counter): Enlarge(t)
RawInsert(t, t.data, key, val)
inc(t.counter)
proc RaiseFormatException(s: string) =
var e: ref EInvalidValue
new(e)
e.msg = "format string: key not found: " & s
raise e
proc getValue(t: PStringTable, flags: set[TFormatFlag], key: string): string =
if hasKey(t, key): return t[key]
if useEnvironment in flags: result = os.getEnv(key)
else: result = ""
if result.len == 0:
if useKey in flags: result = '$' & key
elif not (useEmpty in flags): raiseFormatException(key)
proc `%`(f: string, t: PStringTable, flags: set[TFormatFlag] = {}): string =
const
PatternChars = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\x80'..'\xFF'}
result = ""
var i = 0
while i < len(f):
if f[i] == '$':
case f[i+1]
of '$':
add(result, '$')
inc(i, 2)
of '{':
var j = i + 1
while j < f.len and f[j] != '}': inc(j)
add(result, getValue(t, flags, copy(f, i+2, j-1)))
i = j + 1
of 'a'..'z', 'A'..'Z', '\x80'..'\xFF', '_':
var j = i + 1
while j < f.len and f[j] in PatternChars: inc(j)
add(result, getValue(t, flags, copy(f, i+1, j-1)))
i = j
else:
add(result, f[i])
inc(i)
else:
add(result, f[i])
inc(i)

975
lib/pure/strutils.nim Normal file
View file

@ -0,0 +1,975 @@
#
#
# 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 various string utility routines.
## See the module `regexprs` for regular expression support.
## All the routines here are avaiable for the EMCAScript target too!
{.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
type
TCharSet* = set[char] # for compability for Nim
const
Whitespace* = {' ', '\t', '\v', '\r', '\l', '\f'}
## All the characters that count as whitespace.
Letters* = {'A'..'Z', 'a'..'z'}
## the set of letters
Digits* = {'0'..'9'}
## the set of digits
IdentChars* = {'a'..'z', 'A'..'Z', '0'..'9', '_'}
## the set of characters an identifier can consist of
IdentStartChars* = {'a'..'z', 'A'..'Z', '_'}
## the set of characters an identifier can start with
strStart* = 0 # this is only for bootstraping
# XXX: remove this someday
nl* = "\n" # this is only for bootstraping XXX: remove this somehow
proc `%` *(formatstr: string, a: openarray[string]): string {.noSideEffect.}
## The `substitution`:idx: operator performs string substitutions in
## `formatstr` and returns a modified `formatstr`. This is often called
## `string interpolation`:idx:.
##
## This is best explained by an example:
##
## .. code-block:: nimrod
## "$1 eats $2." % ["The cat", "fish"]
##
## Results in:
##
## .. code-block:: nimrod
## "The cat eats fish."
##
## The substitution variables (the thing after the ``$``)
## are enumerated from 1 to 9.
## Substitution variables can also be words (that is
## ``[A-Za-z_]+[A-Za-z0-9_]*``) in which case the arguments in `a` with even
## indices are keys and with odd indices are the corresponding values.
## An example:
##
## .. code-block:: nimrod
## "$animal eats $food." % ["animal", "The cat", "food", "fish"]
##
## Results in:
##
## .. code-block:: nimrod
## "The cat eats fish."
##
## The variables are compared with `cmpIgnoreStyle`. `EInvalidValue` is
## raised if an ill-formed format string has been passed to the `%` operator.
proc `%` *(formatstr, a: string): string {.noSideEffect.}
## This is the same as ``formatstr % [a]``.
proc addf*(s: var string, formatstr: string, a: openarray[string])
## The same as ``add(s, formatstr % a)``, but more efficient.
proc strip*(s: string, leading = true, trailing = true): string {.noSideEffect.}
## Strips whitespace from `s` and returns the resulting string.
## If `leading` is true, leading whitespace is stripped.
## If `trailing` is true, trailing whitespace is stripped.
proc toLower*(s: string): string {.noSideEffect.}
## Converts `s` into lower case. This works only for the letters A-Z.
## See `unicode.toLower` for a version that works for any Unicode character.
proc toLower*(c: Char): Char {.noSideEffect.}
## Converts `c` into lower case. This works only for the letters A-Z.
## See `unicode.toLower` for a version that works for any Unicode character.
proc toUpper*(s: string): string {.noSideEffect.}
## Converts `s` into upper case. This works only for the letters a-z.
## See `unicode.toUpper` for a version that works for any Unicode character.
proc toUpper*(c: Char): Char {.noSideEffect.}
## Converts `c` into upper case. This works only for the letters a-z.
## See `unicode.toUpper` for a version that works for any Unicode character.
proc capitalize*(s: string): string {.noSideEffect.}
## Converts the first character of `s` into upper case.
## This works only for the letters a-z.
proc normalize*(s: string): string {.noSideEffect.}
## Normalizes the string `s`. That means to convert it to lower case and
## remove any '_'. This is needed for Nimrod identifiers for example.
proc findSubStr*(sub, s: string, start: int = 0): int {.
noSideEffect, deprecated.}
## Searches for `sub` in `s` starting at position `start`. Searching is
## case-sensitive. If `sub` is not in `s`, -1 is returned.
## **Deprecated since version 0.7.6**: Use `find` instead, but beware that
## this has a different parameter order.
proc findSubStr*(sub: char, s: string, start: int = 0): int {.
noSideEffect, deprecated.}
## Searches for `sub` in `s` starting at position `start`. Searching is
## case-sensitive. If `sub` is not in `s`, -1 is returned.
## **Deprecated since version 0.7.6**: Use `find` instead, but beware that
## this has a different parameter order.
proc findChars*(chars: set[char], s: string, start: int = 0): int {.
noSideEffect, deprecated.}
## Searches for `chars` in `s` starting at position `start`. If `s` contains
## none of the characters in `chars`, -1 is returned.
## **Deprecated since version 0.7.6**: Use `find` instead, but beware that
## this has a different parameter order.
proc find*(s, sub: string, start: int = 0): int {.noSideEffect.}
## Searches for `sub` in `s` starting at position `start`. Searching is
## case-sensitive. If `sub` is not in `s`, -1 is returned.
proc find*(s: string, sub: char, start: int = 0): int {.noSideEffect.}
## Searches for `sub` in `s` starting at position `start`. Searching is
## case-sensitive. If `sub` is not in `s`, -1 is returned.
proc find*(s: string, chars: set[char], start: int = 0): int {.noSideEffect.}
## Searches for `chars` in `s` starting at position `start`. If `s` contains
## none of the characters in `chars`, -1 is returned.
proc replaceStr*(s, sub, by: string): string {.noSideEffect.}
## Replaces `sub` in `s` by the string `by`.
proc replaceStr*(s: string, sub, by: char): string {.noSideEffect.}
## optimized version for characters.
proc deleteStr*(s: var string, first, last: int)
## Deletes in `s` the characters at position `first`..`last`. This modifies
## `s` itself, it does not return a copy.
proc toOctal*(c: char): string
## Converts a character `c` to its octal representation. The resulting
## string may not have a leading zero. Its length is always exactly 3.
iterator split*(s: string, seps: set[char] = Whitespace): string =
## Splits the string `s` into substrings.
##
## Substrings are separated by a substring containing only `seps`.
## Examples:
##
## .. code-block:: nimrod
## for word in split(" this is an example "):
## writeln(stdout, word)
##
## Results in:
##
## .. code-block:: nimrod
## "this"
## "is"
## "an"
## "example"
##
## for word in split(";;this;is;an;;example;;;", {';'}):
## writeln(stdout, word)
##
## produces in the same output.
var
first: int = 0
last: int = 0
assert(not ('\0' in seps))
while last < len(s):
while s[last] in seps: inc(last)
first = last
while last < len(s) and s[last] not_in seps: inc(last) # BUGFIX!
yield copy(s, first, last-1)
iterator split*(s: string, sep: char): string =
## Splits the string `s` into substrings.
##
## Substrings are separated by the character `sep`.
## Example:
##
## .. code-block:: nimrod
## for word in split(";;this;is;an;;example;;;", ';'):
## writeln(stdout, word)
##
## Results in:
##
## .. code-block:: nimrod
## ""
## ""
## "this"
## "is"
## "an"
## ""
## "example"
## ""
## ""
## ""
##
var last = 0
assert('\0' != sep)
if len(s) > 0:
# `<=` is correct here for the edge cases!
while last <= len(s):
var first = last
while last < len(s) and s[last] != sep: inc(last)
yield copy(s, first, last-1)
inc(last)
iterator splitLines*(s: string): string =
## Splits the string `s` into its containing lines. Each newline
## combination (CR, LF, CR-LF) is supported. The result strings contain
## no trailing ``\n``.
##
## Example:
##
## .. code-block:: nimrod
## for line in lines("\nthis\nis\nan\n\nexample\n"):
## writeln(stdout, line)
##
## Results in:
##
## .. code-block:: nimrod
## ""
## "this"
## "is"
## "an"
## ""
## "example"
## ""
var first = 0
var last = 0
while true:
while s[last] notin {'\0', '\c', '\l'}: inc(last)
yield copy(s, first, last-1)
# skip newlines:
if s[last] == '\l': inc(last)
elif s[last] == '\c':
inc(last)
if s[last] == '\l': inc(last)
else: break # was '\0'
first = last
proc splitLinesSeq*(s: string): seq[string] {.noSideEffect.} =
## The same as `split`, but is a proc that returns a sequence of substrings.
result = @[]
for line in splitLines(s): add(result, line)
proc splitSeq*(s: string, seps: set[char] = Whitespace): seq[string] {.
noSideEffect.}
## The same as `split`, but is a proc that returns a sequence of substrings.
proc splitSeq*(s: string, sep: char): seq[string] {.noSideEffect.} =
## The same as `split`, but is a proc that returns a sequence of substrings.
result = @[]
for sub in split(s, sep): add(result, sub)
proc cmpIgnoreCase*(a, b: string): int {.noSideEffect.}
## Compares two strings in a case insensitive manner. Returns:
##
## | 0 iff a == b
## | < 0 iff a < b
## | > 0 iff a > b
proc cmpIgnoreStyle*(a, b: string): int {.noSideEffect.}
## Compares two strings normalized (i.e. case and
## underscores do not matter). Returns:
##
## | 0 iff a == b
## | < 0 iff a < b
## | > 0 iff a > b
proc contains*(s: string, c: char): bool {.noSideEffect.}
## Same as ``findSubStr(c, s) >= 0``.
proc contains*(s, sub: string): bool {.noSideEffect.}
## Same as ``findSubStr(sub, s) >= 0``.
proc contains*(s: string, chars: set[char]): bool {.noSideEffect.}
## Same as ``findChars(s, chars) >= 0``.
proc toHex*(x: BiggestInt, len: int): string {.noSideEffect.}
## Converts `x` to its hexadecimal representation. The resulting string
## will be exactly `len` characters long. No prefix like ``0x``
## is generated. `x` is treated as unsigned value.
proc intToStr*(x: int, minchars: int = 1): string
## Converts `x` to its decimal representation. The resulting string
## will be minimally `minchars` characters long. This is achieved by
## adding leading zeros.
proc ParseInt*(s: string): int {.noSideEffect.}
## Parses a decimal integer value contained in `s`. If `s` is not
## a valid integer, `EInvalidValue` is raised.
# XXX: make this biggestint!
proc ParseBiggestInt*(s: string): biggestInt {.noSideEffect.}
## Parses a decimal integer value contained in `s`. If `s` is not
## a valid integer, `EInvalidValue` is raised.
proc ParseFloat*(s: string, start = 0): float {.noSideEffect.}
## Parses a decimal floating point value contained in `s`. If `s` is not
## a valid floating point number, `EInvalidValue` is raised. ``NAN``,
## ``INF``, ``-INF`` are also supported (case insensitive comparison).
# XXX: make this biggestfloat.
# the stringify and format operators:
proc toString*[Ty](x: Ty): string
## This generic proc is the same as the stringify operator `$`.
proc repeatChar*(count: int, c: Char = ' '): string
## Returns a string of length `count` consisting only of
## the character `c`.
proc startsWith*(s, prefix: string): bool {.noSideEffect.}
## Returns true iff ``s`` starts with ``prefix``.
## If ``prefix == ""`` true is returned.
proc endsWith*(s, suffix: string): bool {.noSideEffect.}
## Returns true iff ``s`` ends with ``suffix``.
## If ``suffix == ""`` true is returned.
proc addSep*(dest: var string, sep = ", ", startLen = 0) {.noSideEffect,
inline.} =
## A shorthand for:
##
## .. code-block:: nimrod
## if dest.len > startLen: add(dest, sep)
##
## This is often useful for generating some code where the items need to
## be *separated* by `sep`. `sep` is only added if `dest` is longer than
## `startLen`. The following example creates a string describing
## an array of integers:
##
## .. code-block:: nimrod
## var arr = "["
## for x in items([2, 3, 5, 7, 11]):
## addSep(arr, startLen=len("["))
## add(arr, $x)
## add(arr, "]")
if dest.len > startLen: add(dest, sep)
proc allCharsInSet*(s: string, theSet: TCharSet): bool =
## returns true iff each character of `s` is in the set `theSet`.
for c in items(s):
if not (c in theSet): return false
return true
proc quoteIfContainsWhite*(s: string): string =
## returns ``'"' & s & '"'`` if `s` contains a space and does not
## start with a quote, else returns `s`
if find(s, {' ', '\t'}) >= 0 and s[0] != '"':
result = '"' & s & '"'
else:
result = s
proc startsWith(s, prefix: string): bool =
var i = 0
while true:
if prefix[i] == '\0': return true
if s[i] != prefix[i]: return false
inc(i)
proc endsWith(s, suffix: string): bool =
var
i = 0
j = len(s) - len(suffix)
while true:
if suffix[i] == '\0': return true
if s[i+j] != suffix[i]: return false
inc(i)
when false:
proc abbrev(s: string, possibilities: openarray[string]): int =
## returns the index of the first item in `possibilities` if not
## ambigious; -1 if no item has been found; -2 if multiple items
## match.
result = -1 # none found
for i in 0..possibilities.len-1:
if possibilities[i].startsWith(s):
if result >= 0: return -2 # ambigious
result = i
proc repeatChar(count: int, c: Char = ' '): string =
result = newString(count)
for i in 0..count-1:
result[i] = c
proc intToStr(x: int, minchars: int = 1): string =
result = $abs(x)
for i in 1 .. minchars - len(result):
result = '0' & result
if x < 0:
result = '-' & result
proc toString[Ty](x: Ty): string = return $x
proc toOctal(c: char): string =
result = newString(3)
var val = ord(c)
for i in countdown(2, 0):
result[i] = Chr(val mod 8 + ord('0'))
val = val div 8
proc `%`(formatstr: string, a: string): string =
return formatstr % [a]
proc findNormalized(x: string, inArray: openarray[string]): int =
var i = 0
while i < high(inArray):
if cmpIgnoreStyle(x, inArray[i]) == 0: return i
inc(i, 2) # incrementing by 1 would probably result in a
# security whole ...
return -1
proc addf(s: var string, formatstr: string, a: openarray[string]) =
const PatternChars = {'a'..'z', 'A'..'Z', '0'..'9', '\128'..'\255', '_'}
var i = 0
while i < len(formatstr):
if formatstr[i] == '$':
case formatstr[i+1] # again we use the fact that strings
# are zero-terminated here
of '$':
add s, '$'
inc(i, 2)
of '1'..'9':
var j = 0
inc(i) # skip $
while formatstr[i] in {'0'..'9'}:
j = j * 10 + ord(formatstr[i]) - ord('0')
inc(i)
add s, a[j - 1]
of '{':
var j = i+1
while formatstr[j] notin {'\0', '}'}: inc(j)
var x = findNormalized(copy(formatstr, i+2, j-1), a)
if x >= 0 and x < high(a): add s, a[x+1]
else: raise newException(EInvalidValue, "invalid format string")
i = j+1
of 'a'..'z', 'A'..'Z', '\128'..'\255', '_':
var j = i+1
while formatstr[j] in PatternChars: inc(j)
var x = findNormalized(copy(formatstr, i+1, j-1), a)
if x >= 0 and x < high(a): add s, a[x+1]
else: raise newException(EInvalidValue, "invalid format string")
i = j
else: raise newException(EInvalidValue, "invalid format string")
else:
add s, formatstr[i]
inc(i)
proc `%`(formatstr: string, a: openarray[string]): string =
result = ""
addf(result, formatstr, a)
proc cmpIgnoreCase(a, b: string): int =
# makes usage of the fact that strings are zero-terminated
for i in 0..len(a)-1:
var aa = toLower(a[i])
var bb = toLower(b[i])
result = ord(aa) - ord(bb)
if result != 0: break
{.push checks: off, line_trace: off .} # this is a hot-spot in the compiler!
# thus we compile without checks here
proc cmpIgnoreStyle(a, b: string): int =
var i = 0
var j = 0
while True:
while a[i] == '_': inc(i)
while b[j] == '_': inc(j) # BUGFIX: typo
var aa = toLower(a[i])
var bb = toLower(b[j])
result = ord(aa) - ord(bb)
if result != 0 or aa == '\0': break
inc(i)
inc(j)
{.pop.}
# ---------- splitting -----------------------------------------------------
proc splitSeq(s: string, seps: set[char]): seq[string] =
result = @[]
for sub in split(s, seps): add result, sub
# ---------------------------------------------------------------------------
proc strip(s: string, leading = true, trailing = true): string =
const
chars: set[Char] = Whitespace
var
first = 0
last = len(s)-1
if leading:
while s[first] in chars: inc(first)
if trailing:
while last >= 0 and s[last] in chars: dec(last)
result = copy(s, first, last)
proc toLower(c: Char): Char =
if c in {'A'..'Z'}:
result = chr(ord(c) + (ord('a') - ord('A')))
else:
result = c
proc toLower(s: string): string =
result = newString(len(s))
for i in 0..len(s) - 1:
result[i] = toLower(s[i])
proc toUpper(c: Char): Char =
if c in {'a'..'z'}:
result = Chr(Ord(c) - (Ord('a') - Ord('A')))
else:
result = c
proc toUpper(s: string): string =
result = newString(len(s))
for i in 0..len(s) - 1:
result[i] = toUpper(s[i])
proc capitalize(s: string): string =
result = toUpper(s[0]) & copy(s, 1)
proc normalize(s: string): string =
result = ""
for i in 0..len(s) - 1:
if s[i] in {'A'..'Z'}:
add result, Chr(Ord(s[i]) + (Ord('a') - Ord('A')))
elif s[i] != '_':
add result, s[i]
type
TSkipTable = array[Char, int]
proc preprocessSub(sub: string, a: var TSkipTable) =
var m = len(sub)
for i in 0..0xff: a[chr(i)] = m+1
for i in 0..m-1: a[sub[i]] = m-i
proc findSubStrAux(s, sub: string, start: int, a: TSkipTable): int =
# fast "quick search" algorithm:
var
m = len(sub)
n = len(s)
# search:
var j = start
while j <= n - m:
block match:
for k in 0..m-1:
if sub[k] != s[k+j]: break match
return j
inc(j, a[s[j+m]])
return -1
proc findSubStr(sub, s: string, start: int = 0): int =
var a: TSkipTable
preprocessSub(sub, a)
result = findSubStrAux(s, sub, start, a)
# slow linear search:
#var
# i, j, M, N: int
#M = len(sub)
#N = len(s)
#i = start
#j = 0
#if i >= N:
# result = -1
#else:
# while True:
# if s[i] == sub[j]:
# Inc(i)
# Inc(j)
# else:
# i = i - j + 1
# j = 0
# if (j >= M):
# return i - M
# elif (i >= N):
# return -1
proc find(s, sub: string, start: int = 0): int =
var a: TSkipTable
preprocessSub(sub, a)
result = findSubStrAux(s, sub, start, a)
proc find(s: string, sub: char, start: int = 0): int =
for i in start..len(s)-1:
if sub == s[i]: return i
return -1
proc find(s: string, chars: set[char], start: int = 0): int =
for i in start..s.len-1:
if s[i] in chars: return i
return -1
proc findSubStr(sub: char, s: string, start: int = 0): int =
for i in start..len(s)-1:
if sub == s[i]: return i
return -1
proc findChars(chars: set[char], s: string, start: int = 0): int =
for i in start..s.len-1:
if s[i] in chars: return i
return -1
proc contains(s: string, chars: set[char]): bool =
return find(s, chars) >= 0
proc contains(s: string, c: char): bool =
return find(s, c) >= 0
proc contains(s, sub: string): bool =
return find(s, sub) >= 0
proc replaceStr(s, sub, by: string): string =
var a: TSkipTable
result = ""
preprocessSub(sub, a)
var i = 0
while true:
var j = findSubStrAux(s, sub, i, a)
if j < 0: break
add result, copy(s, i, j - 1)
add result, by
i = j + len(sub)
# copy the rest:
add result, copy(s, i)
proc replaceStr(s: string, sub, by: char): string =
result = newString(s.len)
var i = 0
while i < s.len:
if s[i] == sub: result[i] = by
else: result[i] = s[i]
inc(i)
proc deleteStr(s: var string, first, last: int) =
# example: "abc___uvwxyz\0" (___ is to be deleted)
# --> first == 3, last == 5
# s[first..] = s[last+1..]
var
i = first
while last+i+1 < len(s):
s[i] = s[last+i+1]
inc(i)
setlen(s, len(s)-(last-first+1))
# parsing numbers:
proc toHex(x: BiggestInt, len: int): string =
const
HexChars = "0123456789ABCDEF"
var
shift: BiggestInt
result = newString(len)
for j in countdown(len-1, 0):
result[j] = HexChars[toU32(x shr shift) and 0xF'i32]
shift = shift + 4
{.push overflowChecks: on.}
# this must be compiled with overflow checking turned on:
proc rawParseInt(s: string, index: var int): BiggestInt =
# index contains the start position at proc entry; end position will be
# an index before the proc returns; index = -1 on error (no number at all)
# the problem here is that integers have an asymmetrical range: there is
# one more valid negative than prositive integer. Thus we perform the
# computation as a negative number and then change the sign at the end.
var
i: int = index # a local i is more efficient than accessing a var parameter
sign: BiggestInt = -1
if s[i] == '+':
inc(i)
elif s[i] == '-':
inc(i)
sign = 1
if s[i] in {'0'..'9'}:
result = 0
while s[i] in {'0'..'9'}:
result = result * 10 - (ord(s[i]) - ord('0'))
inc(i)
while s[i] == '_':
inc(i) # underscores are allowed and ignored
result = result * sign
if s[i] == '\0':
index = i # store index back
else:
index = -1 # BUGFIX: error!
else:
index = -1
{.pop.} # overflowChecks
proc parseInt(s: string): int =
var
index: int = 0
res = rawParseInt(s, index)
if index == -1:
raise newException(EInvalidValue, "invalid integer: " & s)
elif (sizeof(int) <= 4) and
((res < low(int)) or (res > high(int))):
raise newException(EOverflow, "overflow")
else:
result = int(res) # convert to smaller integer type
proc ParseBiggestInt(s: string): biggestInt =
var index = 0
result = rawParseInt(s, index)
if index == -1:
raise newException(EInvalidValue, "invalid integer: " & s)
proc ParseFloat(s: string, start = 0): float =
var
esign = 1.0
sign = 1.0
i = start
exponent: int
flags: int
result = 0.0
if s[i] == '+': inc(i)
elif s[i] == '-':
sign = -1.0
inc(i)
if s[i] == 'N' or s[i] == 'n':
if s[i+1] == 'A' or s[i+1] == 'a':
if s[i+2] == 'N' or s[i+2] == 'n':
if s[i+3] == '\0': return NaN
raise newException(EInvalidValue, "invalid float: " & s)
if s[i] == 'I' or s[i] == 'i':
if s[i+1] == 'N' or s[i+1] == 'n':
if s[i+2] == 'F' or s[i+2] == 'f':
if s[i+3] == '\0': return Inf*sign
raise newException(EInvalidValue, "invalid float: " & s)
while s[i] in {'0'..'9'}:
# Read integer part
flags = flags or 1
result = result * 10.0 + toFloat(ord(s[i]) - ord('0'))
inc(i)
while s[i] == '_': inc(i)
# Decimal?
if s[i] == '.':
var hd = 1.0
inc(i)
while s[i] in {'0'..'9'}:
# Read fractional part
flags = flags or 2
result = result * 10.0 + toFloat(ord(s[i]) - ord('0'))
hd = hd * 10.0
inc(i)
while s[i] == '_': inc(i)
result = result / hd # this complicated way preserves precision
# Again, read integer and fractional part
if flags == 0:
raise newException(EInvalidValue, "invalid float: " & s)
# Exponent?
if s[i] in {'e', 'E'}:
inc(i)
if s[i] == '+':
inc(i)
elif s[i] == '-':
esign = -1.0
inc(i)
if s[i] notin {'0'..'9'}:
raise newException(EInvalidValue, "invalid float: " & s)
while s[i] in {'0'..'9'}:
exponent = exponent * 10 + ord(s[i]) - ord('0')
inc(i)
while s[i] == '_': inc(i)
# Calculate Exponent
var hd = 1.0
for j in 1..exponent:
hd = hd * 10.0
if esign > 0.0: result = result * hd
else: result = result / hd
# Not all characters are read?
if s[i] != '\0': raise newException(EInvalidValue, "invalid float: " & s)
# evaluate sign
result = result * sign
proc toOct*(x: BiggestInt, len: int): string =
## converts `x` into its octal representation. The resulting string is
## always `len` characters long. No leading ``0o`` prefix is generated.
var
mask: BiggestInt = 7
shift: BiggestInt = 0
assert(len > 0)
result = newString(len)
for j in countdown(len-1, 0):
result[j] = chr(int((x and mask) shr shift) + ord('0'))
shift = shift + 3
mask = mask shl 3
proc toBin*(x: BiggestInt, len: int): string =
## converts `x` into its binary representation. The resulting string is
## always `len` characters long. No leading ``0b`` prefix is generated.
var
mask: BiggestInt = 1
shift: BiggestInt = 0
assert(len > 0)
result = newString(len)
for j in countdown(len-1, 0):
result[j] = chr(int((x and mask) shr shift) + ord('0'))
shift = shift + 1
mask = mask shl 1
proc escape*(s: string, prefix = "\"", suffix = "\""): string =
## Escapes a string `s`. This does these operations (at the same time):
## * replaces any ``\`` by ``\\``
## * replaces any ``'`` by ``\'``
## * replaces any ``"`` by ``\"``
## * replaces any other character in the set ``{'\0'..'\31', '\128'..'\255'}``
## by ``\xHH`` where ``HH`` is its hexadecimal value.
## The procedure has been designed so that its output is usable for many
## different common syntaxes. The resulting string is prefixed with
## ``prefix`` and suffixed with ``suffix``. Both may be empty strings.
result = prefix
for c in items(s):
case c
of '\0'..'\31', '\128'..'\255':
add(result, '\\')
add(result, toHex(ord(c), 2))
of '\\': add(result, "\\\\")
of '\'': add(result, "\\'")
of '\"': add(result, "\\\"")
else: add(result, c)
add(result, suffix)
proc validEmailAddress*(s: string): bool =
## returns true if `s` seems to be a valid e-mail address.
## The checking also uses a domain list.
const
chars = Letters + Digits + {'!','#','$','%','&',
'\'','*','+','/','=','?','^','_','`','{','}','|','~','-','.'}
var i = 0
if s[i] notin chars or s[i] == '.': return false
while s[i] in chars:
if s[i] == '.' and s[i+1] == '.': return false
inc(i)
if s[i] != '@': return false
var j = len(s)-1
if s[j] notin letters: return false
while j >= i and s[j] in letters: dec(j)
inc(i) # skip '@'
while s[i] in {'0'..'9', 'a'..'z', '-', '.'}: inc(i)
if s[i] != '\0': return false
var x = copy(s, j+1)
if len(x) == 2 and x[0] in Letters and x[1] in Letters: return true
case toLower(x)
of "com", "org", "net", "gov", "mil", "biz", "info", "mobi", "name",
"aero", "jobs", "museum": return true
return false
proc validIdentifier*(s: string): bool =
## returns true if `s` is a valid identifier. A valid identifier starts
## with a character of the set `IdentStartChars` and is followed by any
## number of characters of the set `IdentChars`.
if s[0] in IdentStartChars:
for i in 1..s.len-1:
if s[i] notin IdentChars: return false
return true
proc editDistance*(a, b: string): int =
## returns the edit distance between `a` and `b`. This uses the Levenshtein
## distance algorithm with only a linear memory overhead. This implementation
## is highly optimized!
var len1 = a.len
var len2 = b.len
if len1 > len2:
# make `b` the longer string
return editDistance(b, a)
# strip common prefix:
var s = 0
while a[s] == b[s] and a[s] != '\0':
inc(s)
dec(len1)
dec(len2)
# strip common suffix:
while len1 > 0 and len2 > 0 and a[s+len1-1] == b[s+len2-1]:
dec(len1)
dec(len2)
# trivial cases:
if len1 == 0: return len2
if len2 == 0: return len1
# another special case:
if len1 == 1:
for j in s..len2-1:
if a[s] == b[j]: return len2 - 1
return len2
inc(len1)
inc(len2)
var half = len1 shr 1
# initalize first row:
#var row = cast[ptr array[0..high(int) div 8, int]](alloc(len2 * sizeof(int)))
var row: seq[int]
newSeq(row, len2)
var e = s + len2 - 1 # end marker
for i in 1..len2 - half - 1: row[i] = i
row[0] = len1 - half - 1
for i in 1 .. len1 - 1:
var char1 = a[i + s - 1]
var char2p: int
var D, x: int
var p: int
if i >= len1 - half:
# skip the upper triangle:
var offset = i - len1 + half
char2p = offset
p = offset
var c3 = row[p] + ord(char1 != b[s + char2p])
inc(p)
inc(char2p)
x = row[p] + 1
D = x
if x > c3: x = c3
row[p] = x
inc(p)
else:
p = 1
char2p = 0
D = i
x = i
if i <= half + 1:
# skip the lower triangle:
e = len2 + i - half - 2
# main:
while p <= e:
dec(D)
var c3 = D + ord(char1 != b[char2p + s])
inc(char2p)
inc(x)
if x > c3: x = c3
D = row[p] + 1
if x > D: x = D
row[p] = x
inc(p)
# lower triangle sentinel:
if i <= half:
dec(D)
var c3 = D + ord(char1 != b[char2p + s])
inc(x)
if x > c3: x = c3
row[p] = x
result = row[e]
#dealloc(row)
{.pop.}

310
lib/pure/terminal.nim Normal file
View file

@ -0,0 +1,310 @@
#
#
# 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 a few procedures to control the *terminal*
## (also called *console*). On UNIX, the implementation simply uses ANSI escape
## sequences and does not depend on any other module, on Windows it uses the
## Windows API.
## Changing the style is permanent even after program termination! Use the
## code ``system.addQuitProc(resetAttributes)`` to restore the defaults.
when defined(windows):
import windows, os
var
conHandle: THandle
# = createFile("CONOUT$", GENERIC_WRITE, 0, nil, OPEN_ALWAYS, 0, 0)
block:
var hTemp = GetStdHandle(STD_OUTPUT_HANDLE())
if DuplicateHandle(GetCurrentProcess(), hTemp, GetCurrentProcess(),
addr(conHandle), 0, 1, DUPLICATE_SAME_ACCESS) == 0:
OSError()
proc getCursorPos(): tuple [x,y: int] =
var c: TCONSOLE_SCREEN_BUFFER_INFO
if GetConsoleScreenBufferInfo(conHandle, addr(c)) == 0: OSError()
return (int(c.dwCursorPosition.x), int(c.dwCursorPosition.y))
proc getAttributes(): int16 =
var c: TCONSOLE_SCREEN_BUFFER_INFO
# workaround Windows bugs: try several times
if GetConsoleScreenBufferInfo(conHandle, addr(c)) != 0:
return c.wAttributes
else:
OSError()
return 0x70'i16 # ERROR: return white background, black text
var
oldAttr = getAttributes()
proc setCursorPos*(x, y: int) =
## sets the terminal's cursor to the (x,y) position. (0,0) is the
## upper left of the screen.
when defined(windows):
var c: TCoord
c.x = int16(x)
c.y = int16(y)
if SetConsoleCursorPosition(conHandle, c) == 0: OSError()
else:
stdout.write("\e[" & $y & ';' & $x & 'f')
proc setCursorXPos*(x: int) =
## sets the terminal's cursor to the x position. The y position is
## not changed.
when defined(windows):
var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO
var hStdout = conHandle
if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError()
var origin = scrbuf.dwCursorPosition
origin.x = int16(x)
if SetConsoleCursorPosition(conHandle, origin) == 0: OSError()
else:
stdout.write("\e[" & $x & 'G')
when defined(windows):
proc setCursorYPos*(y: int) =
## sets the terminal's cursor to the y position. The x position is
## not changed. **Warning**: This is not supported on UNIX!
when defined(windows):
var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO
var hStdout = conHandle
if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError()
var origin = scrbuf.dwCursorPosition
origin.y = int16(y)
if SetConsoleCursorPosition(conHandle, origin) == 0: OSError()
else:
nil
proc CursorUp*(count=1) =
## Moves the cursor up by `count` rows.
when defined(windows):
var p = getCursorPos()
dec(p.y, count)
setCursorPos(p.x, p.y)
else:
stdout.write("\e[" & $count & 'A')
proc CursorDown*(count=1) =
## Moves the cursor down by `count` rows.
when defined(windows):
var p = getCursorPos()
inc(p.y, count)
setCursorPos(p.x, p.y)
else:
stdout.write("\e[" & $count & 'B')
proc CursorForward*(count=1) =
## Moves the cursor forward by `count` columns.
when defined(windows):
var p = getCursorPos()
inc(p.x, count)
setCursorPos(p.x, p.y)
else:
stdout.write("\e[" & $count & 'C')
proc CursorBackward*(count=1) =
## Moves the cursor backward by `count` columns.
when defined(windows):
var p = getCursorPos()
dec(p.x, count)
setCursorPos(p.x, p.y)
else:
stdout.write("\e[" & $count & 'D')
when true:
nil
else:
proc EraseLineEnd* =
## Erases from the current cursor position to the end of the current line.
when defined(windows):
nil
else:
stdout.write("\e[K")
proc EraseLineStart* =
## Erases from the current cursor position to the start of the current line.
when defined(windows):
nil
else:
stdout.write("\e[1K")
proc EraseDown* =
## Erases the screen from the current line down to the bottom of the screen.
when defined(windows):
nil
else:
stdout.write("\e[J")
proc EraseUp* =
## Erases the screen from the current line up to the top of the screen.
when defined(windows):
nil
else:
stdout.write("\e[1J")
proc EraseLine* =
## Erases the entire current line.
when defined(windows):
var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO
var numwrote: DWORD
var hStdout = conHandle
if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError()
var origin = scrbuf.dwCursorPosition
origin.x = 0'i16
if SetConsoleCursorPosition(conHandle, origin) == 0: OSError()
var ht = scrbuf.dwSize.Y - origin.Y
var wt = scrbuf.dwSize.X - origin.X
if FillConsoleOutputCharacter(hStdout,' ', ht*wt,
origin, addr(numwrote)) == 0:
OSError()
if FillConsoleOutputAttribute(hStdout, scrbuf.wAttributes, ht * wt,
scrbuf.dwCursorPosition, addr(numwrote)) == 0:
OSError()
else:
stdout.write("\e[2K")
setCursorXPos(0)
proc EraseScreen* =
## Erases the screen with the background colour and moves the cursor to home.
when defined(windows):
var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO
var numwrote: DWORD
var origin: TCoord # is inititalized to 0, 0
var hStdout = conHandle
if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError()
if FillConsoleOutputCharacter(hStdout, ' ', scrbuf.dwSize.X*scrbuf.dwSize.Y,
origin, addr(numwrote)) == 0:
OSError()
if FillConsoleOutputAttribute(hStdout, scrbuf.wAttributes,
scrbuf.dwSize.X * scrbuf.dwSize.Y,
origin, addr(numwrote)) == 0:
OSError()
setCursorXPos(0)
else:
stdout.write("\e[2J")
proc ResetAttributes* {.noconv.} =
## resets all attributes; it is advisable to register this as a quit proc
## with ``system.addQuitProc(resetAttributes)``.
when defined(windows):
discard SetConsoleTextAttribute(conHandle, oldAttr)
else:
stdout.write("\e[0m")
type
TStyle* = enum ## different styles for text output
styleBright = 1, ## bright text
styleDim, ## dim text
styleUnknown, ## unknown
styleUnderscore = 4, ## underscored text
styleBlink, ## blinking/bold text
styleReverse, ## unknown
styleHidden ## hidden text
when not defined(windows):
var
gFG = 0
gBG = 0
proc WriteStyled*(txt: string, style: set[TStyle] = {styleBright}) =
## writes the text `txt` in a given `style`.
when defined(windows):
var a = 0'i16
if styleBright in style: a = a or int16(FOREGROUND_INTENSITY)
if styleBlink in style: a = a or int16(BACKGROUND_INTENSITY)
if styleReverse in style: a = a or 0x4000'i16 # COMMON_LVB_REVERSE_VIDEO
if styleUnderscore in style: a = a or 0x8000'i16 # COMMON_LVB_UNDERSCORE
var old = getAttributes()
discard SetConsoleTextAttribute(conHandle, old or a)
stdout.write(txt)
discard SetConsoleTextAttribute(conHandle, old)
else:
for s in items(style):
stdout.write("\e[" & $ord(s) & 'm')
stdout.write(txt)
resetAttributes()
if gFG != 0:
stdout.write("\e[" & $ord(gFG) & 'm')
if gBG != 0:
stdout.write("\e[" & $ord(gBG) & 'm')
type
TForegroundColor* = enum ## terminal's foreground colors
fgBlack = 30, ## black
fgRed, ## red
fgGreen, ## green
fgYellow, ## yellow
fgBlue, ## blue
fgMagenta, ## magenta
fgCyan, ## cyan
fgWhite ## white
TBackgroundColor* = enum ## terminal's background colors
bgBlack = 40, ## black
bgRed, ## red
bgGreen, ## green
bgYellow, ## yellow
bgBlue, ## blue
bgMagenta, ## magenta
bgCyan, ## cyan
bgWhite ## white
proc setForegroundColor*(fg: TForegroundColor, bright=false) =
## sets the terminal's foreground color
when defined(windows):
var old = getAttributes() and not 0x0007
if bright:
old = old or FOREGROUND_INTENSITY
const lookup: array [TForegroundColor, int] = [
0,
(FOREGROUND_RED),
(FOREGROUND_GREEN),
(FOREGROUND_RED or FOREGROUND_GREEN),
(FOREGROUND_BLUE),
(FOREGROUND_RED or FOREGROUND_BLUE),
(FOREGROUND_BLUE or FOREGROUND_GREEN),
(FOREGROUND_BLUE or FOREGROUND_GREEN or FOREGROUND_RED)]
discard SetConsoleTextAttribute(conHandle, toU16(old or lookup[fg]))
else:
gFG = ord(fg)
if bright: inc(gFG, 60)
stdout.write("\e[" & $gFG & 'm')
proc setBackgroundColor*(bg: TBackgroundColor, bright=false) =
## sets the terminal's background color
when defined(windows):
var old = getAttributes() and not 0x0070
if bright:
old = old or BACKGROUND_INTENSITY
const lookup: array [TBackgroundColor, int] = [
0,
(BACKGROUND_RED),
(BACKGROUND_GREEN),
(BACKGROUND_RED or BACKGROUND_GREEN),
(BACKGROUND_BLUE),
(BACKGROUND_RED or BACKGROUND_BLUE),
(BACKGROUND_BLUE or BACKGROUND_GREEN),
(BACKGROUND_BLUE or BACKGROUND_GREEN or BACKGROUND_RED)]
discard SetConsoleTextAttribute(conHandle, toU16(old or lookup[bg]))
else:
gBG = ord(bg)
if bright: inc(gBG, 60)
stdout.write("\e[" & $gBG & 'm')
when isMainModule:
system.addQuitProc(resetAttributes)
write(stdout, "never mind")
eraseLine()
#setCursorPos(2, 2)
writeStyled("styled text ", {styleBright, styleBlink, styleUnderscore})
setBackGroundColor(bgCyan, true)
setForeGroundColor(fgBlue)
writeln(stdout, "ordinary text")

307
lib/pure/times.nim Normal file
View file

@ -0,0 +1,307 @@
#
#
# 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 routines and types for dealing with time.
## This module is available for the ECMAScript target.
{.push debugger:off .} # the user does not want to trace a part
# of the standard library!
import
strutils
type
TMonth* = enum ## represents a month
mJan, mFeb, mMar, mApr, mMay, mJun, mJul, mAug, mSep, mOct, mNov, mDec
TWeekDay* = enum ## represents a weekday
dMon, dTue, dWed, dThu, dFri, dSat, dSun
when defined(posix):
type
TTime* = abstract int ## abstract type that represents a time
elif defined(windows):
when defined(vcc):
# newest version of Visual C++ defines time_t to be of 64 bits
type TTime* = abstract int64
else:
type TTime* = abstract int32
elif defined(ECMAScript):
type
TTime* {.final.} = object
getDay: proc (): int
getFullYear: proc (): int
getHours: proc (): int
getMilliseconds: proc (): int
getMinutes: proc (): int
getMonth: proc (): int
getSeconds: proc (): int
getTime: proc (): int
getTimezoneOffset: proc (): int
getUTCDate: proc (): int
getUTCFullYear: proc (): int
getUTCHours: proc (): int
getUTCMilliseconds: proc (): int
getUTCMinutes: proc (): int
getUTCMonth: proc (): int
getUTCSeconds: proc (): int
getYear: proc (): int
parse: proc (s: cstring): TTime
setDate: proc (x: int)
setFullYear: proc (x: int)
setHours: proc (x: int)
setMilliseconds: proc (x: int)
setMinutes: proc (x: int)
setMonth: proc (x: int)
setSeconds: proc (x: int)
setTime: proc (x: int)
setUTCDate: proc (x: int)
setUTCFullYear: proc (x: int)
setUTCHours: proc (x: int)
setUTCMilliseconds: proc (x: int)
setUTCMinutes: proc (x: int)
setUTCMonth: proc (x: int)
setUTCSeconds: proc (x: int)
setYear: proc (x: int)
toGMTString: proc (): cstring
toLocaleString: proc (): cstring
UTC: proc (): int
type
TTimeInfo* = object of TObject ## represents a time in different parts
second*: range[0..61] ## The number of seconds after the minute,
## normally in the range 0 to 59, but can
## be up to 61 to allow for leap seconds.
minute*: range[0..59] ## The number of minutes after the hour,
## in the range 0 to 59.
hour*: range[0..23] ## The number of hours past midnight,
## in the range 0 to 23.
monthday*: range[1..31] ## The day of the month, in the range 1 to 31.
month*: TMonth ## The current month.
year*: int ## The current year.
weekday*: TWeekDay ## The current day of the week.
yearday*: range[0..365] ## The number of days since January 1,
## in the range 0 to 365.
## Always 0 if the target is ECMAScript.
proc getTime*(): TTime ## gets the current calendar time
proc getLocalTime*(t: TTime): TTimeInfo
## converts the calendar time `t` to broken-time representation,
## expressed relative to the user's specified time zone.
proc getGMTime*(t: TTime): TTimeInfo
## converts the calendar time `t` to broken-down time representation,
## expressed in Coordinated Universal Time (UTC).
proc TimeInfoToTime*(timeInfo: TTimeInfo): TTime
## converts a broken-down time structure, expressed as local time, to
## calendar time representation. The function ignores the specified
## contents of the structure members `weekday` and `yearday` and recomputes
## them from the other information in the broken-down time structure.
proc `$` *(timeInfo: TTimeInfo): string
## converts a `TTimeInfo` object to a string representation.
proc `$` *(time: TTime): string
## converts a calendar time to a string representation.
proc getDateStr*(): string
## gets the current date as a string of the format
## ``YYYY-MM-DD``.
proc getClockStr*(): string
## gets the current clock time as a string of the format ``HH:MM:SS``.
proc `-` *(a, b: TTime): int64
## computes the difference of two calendar times. Result is in seconds.
proc `<` * (a, b: TTime): bool =
## returns true iff ``a < b``, that is iff a happened before b.
result = a - b < 0
proc `<=` * (a, b: TTime): bool =
## returns true iff ``a <= b``.
result = a - b <= 0
proc getStartMilsecs*(): int
## get the miliseconds from the start of the program
when not defined(ECMAScript):
# C wrapper:
type
structTM {.importc: "struct tm", final.} = object
second {.importc: "tm_sec".},
minute {.importc: "tm_min".},
hour {.importc: "tm_hour".},
monthday {.importc: "tm_mday".},
month {.importc: "tm_mon".},
year {.importc: "tm_year".},
weekday {.importc: "tm_wday".},
yearday {.importc: "tm_yday".},
isdst {.importc: "tm_isdst".}: cint
PTimeInfo = ptr structTM
PTime = ptr TTime
TClock {.importc: "clock_t".} = range[low(int)..high(int)]
proc localtime(timer: PTime): PTimeInfo {.
importc: "localtime", header: "<time.h>".}
proc gmtime(timer: PTime): PTimeInfo {.importc: "gmtime", header: "<time.h>".}
proc timec(timer: PTime): TTime {.importc: "time", header: "<time.h>".}
proc mktime(t: structTM): TTime {.importc: "mktime", header: "<time.h>".}
proc asctime(tblock: structTM): CString {.
importc: "asctime", header: "<time.h>".}
proc ctime(time: PTime): CString {.importc: "ctime", header: "<time.h>".}
# strftime(s: CString, maxsize: int, fmt: CString, t: tm): int {.
# importc: "strftime", header: "<time.h>".}
proc clock(): TClock {.importc: "clock", header: "<time.h>".}
proc difftime(a, b: TTime): float {.importc: "difftime", header: "<time.h>".}
var
clocksPerSec {.importc: "CLOCKS_PER_SEC", nodecl.}: int
# our own procs on top of that:
proc tmToTimeInfo(tm: structTM): TTimeInfo =
const
weekDays: array [0..6, TWeekDay] = [
dSun, dMon, dTue, dWed, dThu, dFri, dSat]
result.second = int(tm.second)
result.minute = int(tm.minute)
result.hour = int(tm.hour)
result.monthday = int(tm.monthday)
result.month = TMonth(tm.month)
result.year = tm.year + 1900'i32
result.weekday = weekDays[int(tm.weekDay)]
result.yearday = int(tm.yearday)
proc timeInfoToTM(t: TTimeInfo): structTM =
const
weekDays: array [TWeekDay, int] = [1, 2, 3, 4, 5, 6, 0]
result.second = t.second
result.minute = t.minute
result.hour = t.hour
result.monthday = t.monthday
result.month = ord(t.month)
result.year = t.year - 1900
result.weekday = weekDays[t.weekDay]
result.yearday = t.yearday
result.isdst = -1
proc `-` (a, b: TTime): int64 =
return toBiggestInt(difftime(a, b))
proc getStartMilsecs(): int = return clock() div (clocksPerSec div 1000)
proc getTime(): TTime = return timec(nil)
proc getLocalTime(t: TTime): TTimeInfo =
var a = t
result = tmToTimeInfo(localtime(addr(a))^)
# copying is needed anyway to provide reentrancity; thus
# the convertion is not expensive
proc getGMTime(t: TTime): TTimeInfo =
var a = t
result = tmToTimeInfo(gmtime(addr(a))^)
# copying is needed anyway to provide reentrancity; thus
# the convertion is not expensive
proc TimeInfoToTime(timeInfo: TTimeInfo): TTime =
var cTimeInfo = timeInfo # for C++ we have to make a copy,
# because the header of mktime is broken in my version of libc
return mktime(timeInfoToTM(cTimeInfo))
proc toStringTillNL(p: cstring): string =
result = ""
var i = 0
while p[i] != '\0' and p[i] != '\10' and p[i] != '\13':
add(result, p[i])
inc(i)
return result
proc `$`(timeInfo: TTimeInfo): string =
# BUGFIX: asctime returns a newline at the end!
var p = asctime(timeInfoToTM(timeInfo))
result = toStringTillNL(p)
proc `$`(time: TTime): string =
# BUGFIX: ctime returns a newline at the end!
var a = time
return toStringTillNL(ctime(addr(a)))
const
epochDiff = 116444736000000000'i64
rateDiff = 10000000'i64 # 100 nsecs
proc unixTimeToWinTime*(t: TTime): int64 =
## converts a UNIX `TTime` (``time_t``) to a Windows file time
result = int64(t) * rateDiff + epochDiff
proc winTimeToUnixTime*(t: int64): TTime =
## converts a Windows time to a UNIX `TTime` (``time_t``)
result = TTime((t - epochDiff) div rateDiff)
else:
proc getTime(): TTime {.importc: "new Date", nodecl.}
const
weekDays: array [0..6, TWeekDay] = [
dSun, dMon, dTue, dWed, dThu, dFri, dSat]
proc getLocalTime(t: TTime): TTimeInfo =
result.second = t.getSeconds()
result.minute = t.getMinutes()
result.hour = t.getHours()
result.monthday = t.getDate()
result.month = TMonth(t.getMonth())
result.year = t.getFullYear()
result.weekday = weekDays[t.getDay()]
result.yearday = 0
proc getGMTime(t: TTime): TTimeInfo =
result.second = t.getUTCSeconds()
result.minute = t.getUTCMinutes()
result.hour = t.getUTCHours()
result.monthday = t.getUTCDate()
result.month = TMonth(t.getUTCMonth())
result.year = t.getUTCFullYear()
result.weekday = weekDays[t.getDay()]
result.yearday = 0
proc TimeInfoToTime*(timeInfo: TTimeInfo): TTime =
result = getTime()
result.setSeconds(timeInfo.second)
result.setMinutes(timeInfo.minute)
result.setHours(timeInfo.hour)
result.setMonth(ord(timeInfo.month))
result.setFullYear(timeInfo.year)
result.setDate(timeInfo.monthday)
proc `$`(timeInfo: TTimeInfo): string = return $(TimeInfoToTIme(timeInfo))
proc `$`(time: TTime): string = $time.toLocaleString()
proc `-` (a, b: TTime): int64 =
return a.getTime() - b.getTime()
var
startMilsecs = getTime()
proc getStartMilsecs(): int =
## get the miliseconds from the start of the program
return int(getTime() - startMilsecs)
proc getDateStr(): string =
var ti = getLocalTime(getTime())
result = $ti.year & '-' & intToStr(ord(ti.month)+1, 2) &
'-' & intToStr(ti.monthDay, 2)
proc getClockStr(): string =
var ti = getLocalTime(getTime())
result = intToStr(ti.hour, 2) & ':' & intToStr(ti.minute, 2) &
':' & intToStr(ti.second, 2)
{.pop.}

1170
lib/pure/unicode.nim Normal file

File diff suppressed because it is too large Load diff

406
lib/pure/xmlgen.nim Normal file
View file

@ -0,0 +1,406 @@
#
#
# 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 `XML`:idx: and `HTML`:idx: code
## generator. Each commonly used HTML tag has a corresponding macro
## that generates a string with its HTML representation.
##
## Example:
##
## .. code-block:: nimrod
## var nim = "Nimrod"
## echo h1(a(href="http://force7.de/nimrod", nim))
##
## Writes the string::
##
## <h1><a href="http://force7.de/nimrod">Nimrod</a></h1>
##
import
macros, strutils
const
coreAttr* = " id class title style "
eventAttr* = " onclick ondblclick onmousedown onmouseup " &
"onmouseover onmousemove onmouseout onkeypress onkeydown onkeyup "
commonAttr* = coreAttr & eventAttr
proc getIdent(e: PNimrodNode): string {.compileTime.} =
case e.kind
of nnkIdent: result = normalize($e.ident)
of nnkAccQuoted: result = getIdent(e[0])
else: error("cannot extract identifier from node: " & toStrLit(e).strVal)
proc delete[T](s: var seq[T], attr: T): bool =
var idx = find(s, attr)
if idx >= 0:
var L = s.len
s[idx] = s[L-1]
setLen(s, L-1)
result = true
proc xmlCheckedTag*(e: PNimrodNode, tag: string,
optAttr = "", reqAttr = "",
isLeaf = false): PNimrodNode {.compileTime.} =
## use this procedure to define a new XML tag
# copy the attributes; when iterating over them these lists
# will be modified, so that each attribute is only given one value
var req = splitSeq(reqAttr)
var opt = splitSeq(optAttr)
result = newNimNode(nnkBracket, e)
result.add(newStrLitNode("<"))
result.add(newStrLitNode(tag))
# first pass over attributes:
for i in 1..e.len-1:
if e[i].kind == nnkExprEqExpr:
var name = getIdent(e[i][0])
if delete(req, name) or delete(opt, name):
result.add(newStrLitNode(" "))
result.add(newStrLitNode(name))
result.add(newStrLitNode("=\""))
result.add(e[i][1])
result.add(newStrLitNode("\""))
else:
error("invalid attribute for '" & tag & "' element: " & name)
# check each required attribute exists:
if req.len > 0:
error(req[0] & " attribute for '" & tag & "' element expected")
if isLeaf:
for i in 1..e.len-1:
if e[i].kind != nnkExprEqExpr:
error("element " & tag & " cannot be nested")
result.add(newStrLitNode(" />"))
else:
result.add(newStrLitNode(">"))
# second pass over elements:
for i in 1..e.len-1:
if e[i].kind != nnkExprEqExpr: result.add(e[i])
result.add(newStrLitNode("</"))
result.add(newStrLitNode(tag))
result.add(newStrLitNode(">"))
result = NestList(!"&", result)
macro a*(e: expr): expr =
## generates the HTML ``a`` element.
result = xmlCheckedTag(e, "a", "href charset type hreflang rel rev " &
"accesskey tabindex" & commonAttr)
macro acronym*(e: expr): expr =
## generates the HTML ``acronym`` element.
result = xmlCheckedTag(e, "acronym", commonAttr)
macro address*(e: expr): expr =
## generates the HTML ``address`` element.
result = xmlCheckedTag(e, "address", commonAttr)
macro area*(e: expr): expr =
## generates the HTML ``area`` element.
result = xmlCheckedTag(e, "area", "shape coords href nohref" &
" accesskey tabindex" & commonAttr, "alt", true)
macro b*(e: expr): expr =
## generates the HTML ``b`` element.
result = xmlCheckedTag(e, "b", commonAttr)
macro base*(e: expr): expr =
## generates the HTML ``base`` element.
result = xmlCheckedTag(e, "base", "", "href", true)
macro big*(e: expr): expr =
## generates the HTML ``big`` element.
result = xmlCheckedTag(e, "big", commonAttr)
macro blockquote*(e: expr): expr =
## generates the HTML ``blockquote`` element.
result = xmlCheckedTag(e, "blockquote", " cite" & commonAttr)
macro body*(e: expr): expr =
## generates the HTML ``body`` element.
result = xmlCheckedTag(e, "body", commonAttr)
macro br*(e: expr): expr =
## generates the HTML ``br`` element.
result = xmlCheckedTag(e, "br", "", "", true)
macro button*(e: expr): expr =
## generates the HTML ``button`` element.
result = xmlCheckedTag(e, "button", "accesskey tabindex " &
"disabled name type value" & commonAttr)
macro caption*(e: expr): expr =
## generates the HTML ``caption`` element.
result = xmlCheckedTag(e, "caption", commonAttr)
macro cite*(e: expr): expr =
## generates the HTML ``cite`` element.
result = xmlCheckedTag(e, "cite", commonAttr)
macro code*(e: expr): expr =
## generates the HTML ``code`` element.
result = xmlCheckedTag(e, "code", commonAttr)
macro col*(e: expr): expr =
## generates the HTML ``col`` element.
result = xmlCheckedTag(e, "col", "span align valign" & commonAttr, "", true)
macro colgroup*(e: expr): expr =
## generates the HTML ``colgroup`` element.
result = xmlCheckedTag(e, "colgroup", "span align valign" & commonAttr)
macro dd*(e: expr): expr =
## generates the HTML ``dd`` element.
result = xmlCheckedTag(e, "dd", commonAttr)
macro del*(e: expr): expr =
## generates the HTML ``del`` element.
result = xmlCheckedTag(e, "del", "cite datetime" & commonAttr)
macro dfn*(e: expr): expr =
## generates the HTML ``dfn`` element.
result = xmlCheckedTag(e, "dfn", commonAttr)
macro `div`*(e: expr): expr =
## generates the HTML ``div`` element.
result = xmlCheckedTag(e, "div", commonAttr)
macro dl*(e: expr): expr =
## generates the HTML ``dl`` element.
result = xmlCheckedTag(e, "dl", commonAttr)
macro dt*(e: expr): expr =
## generates the HTML ``dt`` element.
result = xmlCheckedTag(e, "dt", commonAttr)
macro em*(e: expr): expr =
## generates the HTML ``em`` element.
result = xmlCheckedTag(e, "em", commonAttr)
macro fieldset*(e: expr): expr =
## generates the HTML ``fieldset`` element.
result = xmlCheckedTag(e, "fieldset", commonAttr)
macro form*(e: expr): expr =
## generates the HTML ``form`` element.
result = xmlCheckedTag(e, "form", "method encype accept accept-charset" &
commonAttr, "action")
macro h1*(e: expr): expr =
## generates the HTML ``h1`` element.
result = xmlCheckedTag(e, "h1", commonAttr)
macro h2*(e: expr): expr =
## generates the HTML ``h2`` element.
result = xmlCheckedTag(e, "h2", commonAttr)
macro h3*(e: expr): expr =
## generates the HTML ``h3`` element.
result = xmlCheckedTag(e, "h3", commonAttr)
macro h4*(e: expr): expr =
## generates the HTML ``h4`` element.
result = xmlCheckedTag(e, "h4", commonAttr)
macro h5*(e: expr): expr =
## generates the HTML ``h5`` element.
result = xmlCheckedTag(e, "h5", commonAttr)
macro h6*(e: expr): expr =
## generates the HTML ``h6`` element.
result = xmlCheckedTag(e, "h6", commonAttr)
macro head*(e: expr): expr =
## generates the HTML ``head`` element.
result = xmlCheckedTag(e, "head", "profile")
macro html*(e: expr): expr =
## generates the HTML ``html`` element.
result = xmlCheckedTag(e, "html", "", "xmlns")
macro hr*(e: expr): expr =
## generates the HTML ``hr`` element.
result = xmlCheckedTag(e, "hr", commonAttr, "", true)
macro i*(e: expr): expr =
## generates the HTML ``i`` element.
result = xmlCheckedTag(e, "i", commonAttr)
macro img*(e: expr): expr =
## generates the HTML ``img`` element.
result = xmlCheckedTag(e, "img", "longdesc height width", "src alt", true)
macro input*(e: expr): expr =
## generates the HTML ``input`` element.
result = xmlCheckedTag(e, "input", "name type value checked maxlength src" &
" alt accept disabled readonly accesskey tabindex" & commonAttr, "", true)
macro ins*(e: expr): expr =
## generates the HTML ``ins`` element.
result = xmlCheckedTag(e, "ins", "cite datetime" & commonAttr)
macro kbd*(e: expr): expr =
## generates the HTML ``kbd`` element.
result = xmlCheckedTag(e, "kbd", commonAttr)
macro label*(e: expr): expr =
## generates the HTML ``label`` element.
result = xmlCheckedTag(e, "label", "for accesskey" & commonAttr)
macro legend*(e: expr): expr =
## generates the HTML ``legend`` element.
result = xmlCheckedTag(e, "legend", "accesskey" & commonAttr)
macro li*(e: expr): expr =
## generates the HTML ``li`` element.
result = xmlCheckedTag(e, "li", commonAttr)
macro link*(e: expr): expr =
## generates the HTML ``link`` element.
result = xmlCheckedTag(e, "link", "href charset hreflang type rel rev media" &
commonAttr, "", true)
macro map*(e: expr): expr =
## generates the HTML ``map`` element.
result = xmlCheckedTag(e, "map", "class title" & eventAttr, "id", false)
macro meta*(e: expr): expr =
## generates the HTML ``meta`` element.
result = xmlCheckedTag(e, "meta", "name http-equiv scheme", "content", true)
macro noscript*(e: expr): expr =
## generates the HTML ``noscript`` element.
result = xmlCheckedTag(e, "noscript", commonAttr)
macro `object`*(e: expr): expr =
## generates the HTML ``object`` element.
result = xmlCheckedTag(e, "object", "classid data codebase declare type " &
"codetype archive standby width height name tabindex" & commonAttr)
macro ol*(e: expr): expr =
## generates the HTML ``ol`` element.
result = xmlCheckedTag(e, "ol", commonAttr)
macro optgroup*(e: expr): expr =
## generates the HTML ``optgroup`` element.
result = xmlCheckedTag(e, "optgroup", "disabled" & commonAttr, "label", false)
macro option*(e: expr): expr =
## generates the HTML ``option`` element.
result = xmlCheckedTag(e, "option", "selected value" & commonAttr)
macro p*(e: expr): expr =
## generates the HTML ``p`` element.
result = xmlCheckedTag(e, "p", commonAttr)
macro param*(e: expr): expr =
## generates the HTML ``param`` element.
result = xmlCheckedTag(e, "param", "value id type valuetype", "name", true)
macro pre*(e: expr): expr =
## generates the HTML ``pre`` element.
result = xmlCheckedTag(e, "pre", commonAttr)
macro q*(e: expr): expr =
## generates the HTML ``q`` element.
result = xmlCheckedTag(e, "q", "cite" & commonAttr)
macro samp*(e: expr): expr =
## generates the HTML ``samp`` element.
result = xmlCheckedTag(e, "samp", commonAttr)
macro script*(e: expr): expr =
## generates the HTML ``script`` element.
result = xmlCheckedTag(e, "script", "src charset defer", "type", false)
macro select*(e: expr): expr =
## generates the HTML ``select`` element.
result = xmlCheckedTag(e, "select", "name size multiple disabled tabindex" &
commonAttr)
macro small*(e: expr): expr =
## generates the HTML ``small`` element.
result = xmlCheckedTag(e, "small", commonAttr)
macro span*(e: expr): expr =
## generates the HTML ``span`` element.
result = xmlCheckedTag(e, "span", commonAttr)
macro strong*(e: expr): expr =
## generates the HTML ``strong`` element.
result = xmlCheckedTag(e, "strong", commonAttr)
macro style*(e: expr): expr =
## generates the HTML ``style`` element.
result = xmlCheckedTag(e, "style", "media title", "type")
macro sub*(e: expr): expr =
## generates the HTML ``sub`` element.
result = xmlCheckedTag(e, "sub", commonAttr)
macro sup*(e: expr): expr =
## generates the HTML ``sup`` element.
result = xmlCheckedTag(e, "sup", commonAttr)
macro table*(e: expr): expr =
## generates the HTML ``table`` element.
result = xmlCheckedTag(e, "table", "summary border cellpadding cellspacing" &
" frame rules width" & commonAttr)
macro tbody*(e: expr): expr =
## generates the HTML ``tbody`` element.
result = xmlCheckedTag(e, "tbody", "align valign" & commonAttr)
macro td*(e: expr): expr =
## generates the HTML ``td`` element.
result = xmlCheckedTag(e, "td", "colspan rowspan abbr axis headers scope" &
" align valign" & commonAttr)
macro textarea*(e: expr): expr =
## generates the HTML ``textarea`` element.
result = xmlCheckedTag(e, "textarea", " name disabled readonly accesskey" &
" tabindex" & commonAttr, "rows cols", false)
macro tfoot*(e: expr): expr =
## generates the HTML ``tfoot`` element.
result = xmlCheckedTag(e, "tfoot", "align valign" & commonAttr)
macro th*(e: expr): expr =
## generates the HTML ``th`` element.
result = xmlCheckedTag(e, "th", "colspan rowspan abbr axis headers scope" &
" align valign" & commonAttr)
macro thead*(e: expr): expr =
## generates the HTML ``thead`` element.
result = xmlCheckedTag(e, "thead", "align valign" & commonAttr)
macro title*(e: expr): expr =
## generates the HTML ``title`` element.
result = xmlCheckedTag(e, "title")
macro tr*(e: expr): expr =
## generates the HTML ``tr`` element.
result = xmlCheckedTag(e, "tr", "align valign" & commonAttr)
macro tt*(e: expr): expr =
## generates the HTML ``tt`` element.
result = xmlCheckedTag(e, "tt", commonAttr)
macro ul*(e: expr): expr =
## generates the HTML ``ul`` element.
result = xmlCheckedTag(e, "ul", commonAttr)
macro `var`*(e: expr): expr =
## generates the HTML ``var`` element.
result = xmlCheckedTag(e, "var", commonAttr)
when isMainModule:
var nim = "Nimrod"
echo h1(a(href="http://force7.de/nimrod", nim))