diff --git a/config/nimrod.cfg b/config/nimrod.cfg index 22dc35f5af..122980348c 100755 --- a/config/nimrod.cfg +++ b/config/nimrod.cfg @@ -57,6 +57,7 @@ hint[LineTooLong]=off @if unix: @if not bsd: gcc.options.linker = "-ldl" + tcc.options.linker = "-ldl" @end @end diff --git a/doc/manual.txt b/doc/manual.txt index 9a60602641..5038e292b5 100755 --- a/doc/manual.txt +++ b/doc/manual.txt @@ -2542,10 +2542,10 @@ cannot be inherited from. Pure pragma ----------- The `pure`:idx: pragma serves two completely different purposes: -1) To mark a procedure that Nimrod should not generate any exit statements like +1. To mark a procedure that Nimrod should not generate any exit statements like ``return result;`` in the generated code. This is useful for procs that only consist of an assembler statement. -2) To mark an object type so that its type field should be omitted. This is +2. To mark an object type so that its type field should be omitted. This is necessary for binary compatibility with other compiled languages. @@ -2795,7 +2795,7 @@ a dynamic library. The pragma then has no argument and has to be used in conjunction with the ``exportc`` pragma: .. code-block:: Nimrod - proc exportme(): int {.cdecl, export, dynlib.} + proc exportme(): int {.cdecl, exportc, dynlib.} This is only useful if the program is compiled as a dynamic library via the ``--app:lib`` command line option. diff --git a/doc/pegdocs.txt b/doc/pegdocs.txt index 922b998c74..6dde731113 100755 --- a/doc/pegdocs.txt +++ b/doc/pegdocs.txt @@ -98,6 +98,11 @@ macro meaning ``\i`` ignore case for matching; use this at the start of the PEG ``\y`` ignore style for matching; use this at the start of the PEG ``\ident`` a standard ASCII identifier: ``[a-zA-Z_][a-zA-Z_0-9]*`` +``\letter`` any Unicode letter +``\upper`` any Unicode uppercase letter +``\lower`` any Unicode lowercase letter +``\title`` any Unicode title letter +``\white`` any Unicode whitespace character ============== ============================================================ A backslash followed by a letter is a built-in macro, otherwise it diff --git a/doc/tut2.txt b/doc/tut2.txt index a139fb5deb..bd9769af31 100755 --- a/doc/tut2.txt +++ b/doc/tut2.txt @@ -75,9 +75,9 @@ Inheritance is done with the ``object of`` syntax. Multiple inheritance is currently not supported. If an object type has no suitable ancestor, ``TObject`` can be used as its ancestor, but this is only a convention. -**Note**: Aggregation (*has-a* relation) is often preferable to inheritance +**Note**: Composition (*has-a* relation) is often preferable to inheritance (*is-a* relation) for simple code reuse. Since objects are value types in -Nimrod, aggregation is as efficient as inheritance. +Nimrod, composition is as efficient as inheritance. Mutually recursive types @@ -487,7 +487,7 @@ The example shows a generic binary tree. Depending on context, the brackets are used either to introduce type parameters or to instantiate a generic proc, iterator or type. As the example shows, generics work with overloading: the best match of ``add`` is used. The built-in ``add`` procedure for sequences -is not hidden and used in the ``preorder`` iterator. +is not hidden and is used in the ``preorder`` iterator. Templates diff --git a/examples/httpserver2.nim b/examples/httpserver2.nim new file mode 100644 index 0000000000..0604e6a83c --- /dev/null +++ b/examples/httpserver2.nim @@ -0,0 +1,245 @@ +import strutils, os, osproc, strtabs, streams, sockets + +const + wwwNL* = "\r\L" + ServerSig = "Server: httpserver.nim/1.0.0" & wwwNL + +type + TRequestMethod = enum reqGet, reqPost + TServer* = object ## contains the current server state + socket: TSocket + job: seq[TJob] + TJob* = object + client: TSocket + process: PProcess + +# --------------- output messages -------------------------------------------- + +proc sendTextContentType(client: TSocket) = + send(client, "Content-type: text/html" & wwwNL) + send(client, wwwNL) + +proc badRequest(client: TSocket) = + # Inform the client that a request it has made has a problem. + send(client, "HTTP/1.0 400 BAD REQUEST" & wwwNL) + sendTextContentType(client) + send(client, "
Your browser sent a bad request, " & + "such as a POST without a Content-Length.
" & wwwNL) + + +proc cannotExec(client: TSocket) = + send(client, "HTTP/1.0 500 Internal Server Error" & wwwNL) + sendTextContentType(client) + send(client, "Error prohibited CGI execution.
" & wwwNL) + + +proc headers(client: TSocket, filename: string) = + # XXX could use filename to determine file type + send(client, "HTTP/1.0 200 OK" & wwwNL) + send(client, ServerSig) + sendTextContentType(client) + +proc notFound(client: TSocket, path: string) = + send(client, "HTTP/1.0 404 NOT FOUND" & wwwNL) + send(client, ServerSig) + sendTextContentType(client) + send(client, "The server could not fulfill" & wwwNL) + send(client, "your request because the resource " & path & "" & wwwNL) + send(client, "is unavailable or nonexistent.
" & wwwNL) + send(client, "" & wwwNL) + + +proc unimplemented(client: TSocket) = + send(client, "HTTP/1.0 501 Method Not Implemented" & wwwNL) + send(client, ServerSig) + sendTextContentType(client) + send(client, "HTTP request method not supported.
" & + "" & wwwNL) + + +# ----------------- file serving --------------------------------------------- + +proc discardHeaders(client: TSocket) = skip(client) + +proc serveFile(client: TSocket, filename: string) = + discardHeaders(client) + + var f: TFile + if open(f, filename): + headers(client, filename) + const bufSize = 8000 # != 8K might be good for memory manager + var buf = alloc(bufsize) + while True: + var bytesread = readBuffer(f, buf, bufsize) + if bytesread > 0: + var byteswritten = send(client, buf, bytesread) + if bytesread != bytesWritten: + dealloc(buf) + close(f) + OSError() + if bytesread != bufSize: break + dealloc(buf) + close(f) + client.close() + else: + notFound(client, filename) + +# ------------------ CGI execution ------------------------------------------- + +proc executeCgi(server: var TServer, client: TSocket, path, query: string, + meth: TRequestMethod) = + var env = newStringTable(modeCaseInsensitive) + var contentLength = -1 + case meth + of reqGet: + discardHeaders(client) + + env["REQUEST_METHOD"] = "GET" + env["QUERY_STRING"] = query + of reqPost: + var buf = "" + var dataAvail = true + while dataAvail: + dataAvail = recvLine(client, buf) + if buf.len == 0: + break + var L = toLower(buf) + if L.startsWith("content-length:"): + var i = len("content-length:") + while L[i] in Whitespace: inc(i) + contentLength = parseInt(copy(L, i)) + + if contentLength < 0: + badRequest(client) + return + + env["REQUEST_METHOD"] = "POST" + env["CONTENT_LENGTH"] = $contentLength + + send(client, "HTTP/1.0 200 OK" & wwwNL) + + var process = startProcess(command=path, env=env) + + var job: TJob + job.process = process + job.client = client + server.job.add(job) + + if meth == reqPost: + # get from client and post to CGI program: + var buf = alloc(contentLength) + if recv(client, buf, contentLength) != contentLength: + dealloc(buf) + OSError() + var inp = process.inputStream + inp.writeData(inp, buf, contentLength) + dealloc(buf) + +proc animate(server: var TServer) = + # checks list of jobs, removes finished ones (pretty sloppy by seq copying) + var active_jobs: seq[TJob] = @[] + for i in 0..server.job.len-1: + var job = server.job[i] + if running(job.process): + active_jobs.add(job) + else: + # read process output stream and send it to client + var outp = job.process.outputStream + while true: + var line = outp.readstr(1024) + if line.len == 0: + break + else: + try: + send(job.client, line) + except: + echo("send failed, client diconnected") + close(job.client) + + server.job = active_jobs + +# --------------- Server Setup ----------------------------------------------- + +proc acceptRequest(server: var TServer, client: TSocket) = + var cgi = false + var query = "" + var buf = "" + discard recvLine(client, buf) + var path = "" + var data = buf.split() + var meth = reqGet + var q = find(data[1], '?') + + # extract path + if q >= 0: + # strip "?..." from path, this may be found in both POST and GET + path = data[1].copy(0, q-1) + else: + path = data[1] + # path starts with "/", by adding "." in front of it we serve files from cwd + path = "." & path + + echo("accept: " & path) + + if cmpIgnoreCase(data[0], "GET") == 0: + if q >= 0: + cgi = true + query = data[1].copy(q+1) + elif cmpIgnoreCase(data[0], "POST") == 0: + cgi = true + meth = reqPost + else: + unimplemented(client) + + if path[path.len-1] == '/' or existsDir(path): + path = path / "index.html" + + if not ExistsFile(path): + discardHeaders(client) + notFound(client, path) + client.close() + else: + when defined(Windows): + var ext = splitFile(path).ext.toLower + if ext == ".exe" or ext == ".cgi": + # XXX: extract interpreter information here? + cgi = true + else: + if {fpUserExec, fpGroupExec, fpOthersExec} * path.getFilePermissions != {}: + cgi = true + if not cgi: + serveFile(client, path) + else: + executeCgi(server, client, path, query, meth) + + + +when isMainModule: + var port = 80 + + var server: TServer + server.job = @[] + server.socket = socket(AF_INET) + if server.socket == InvalidSocket: OSError() + server.socket.bindAddr(port=TPort(port)) + listen(server.socket) + echo("server up on port " & $port) + + while true: + # check for new new connection & handle it + var list: seq[TSocket] = @[server.socket] + if select(list, 10) > 0: + var client = accept(server.socket) + try: + acceptRequest(server, client) + except: + echo("failed to accept client request") + + # pooling events + animate(server) + # some slack for CPU + sleep(10) + server.socket.close() diff --git a/lib/nimbase.h b/lib/nimbase.h index 983bb112dc..0251364d14 100755 --- a/lib/nimbase.h +++ b/lib/nimbase.h @@ -22,6 +22,10 @@ __TINYC__ #ifndef NIMBASE_H #define NIMBASE_H +#if defined(__GNUC__) +# define _GNU_SOURCE 1 +#endif + #if !defined(__TINYC__) # include