diff --git a/lib/system.nim b/lib/system.nim index b9be52308db0..7febde12742b 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -1630,7 +1630,9 @@ else: template sysAssert(cond: bool, msg: string) = when defined(useSysAssert): if not cond: - echo "[SYSASSERT] ", msg + cstderr.rawWrite "[SYSASSERT] " + cstderr.rawWrite msg + cstderr.rawWrite "\n" quit 1 const hasAlloc = (hostOS != "standalone" or not defined(nogc)) and not defined(nimscript) @@ -3153,257 +3155,45 @@ when not defined(JS): #and not defined(nimscript): strDesc = TNimType(size: sizeof(string), kind: tyString, flags: {ntfAcyclic}) {.pop.} - - # ----------------- IO Part ------------------------------------------------ - type - CFile {.importc: "FILE", header: "", - incompletestruct.} = object - File* = ptr CFile ## The type representing a file handle. - - FileMode* = enum ## The file mode when opening a file. - fmRead, ## Open the file for read access only. - fmWrite, ## Open the file for write access only. - ## If the file does not exist, it will be - ## created. Existing files will be cleared! - fmReadWrite, ## Open the file for read and write access. - ## If the file does not exist, it will be - ## created. Existing files will be cleared! - fmReadWriteExisting, ## Open the file for read and write access. - ## If the file does not exist, it will not be - ## created. The existing file will not be cleared. - fmAppend ## Open the file for writing only; append data - ## at the end. - - FileHandle* = cint ## type that represents an OS file handle; this is - ## useful for low-level file access - - include "system/ansi_c" - include "system/memory" - - proc zeroMem(p: pointer, size: Natural) = - nimZeroMem(p, size) - when declared(memTrackerOp): - memTrackerOp("zeroMem", p, size) - proc copyMem(dest, source: pointer, size: Natural) = - nimCopyMem(dest, source, size) - when declared(memTrackerOp): - memTrackerOp("copyMem", dest, size) - proc moveMem(dest, source: pointer, size: Natural) = - c_memmove(dest, source, size) - when declared(memTrackerOp): - memTrackerOp("moveMem", dest, size) - proc equalMem(a, b: pointer, size: Natural): bool = - nimCmpMem(a, b, size) == 0 + when not defined(nimscript): + include "system/ansi_c" + include "system/memory" + + proc zeroMem(p: pointer, size: Natural) = + nimZeroMem(p, size) + when declared(memTrackerOp): + memTrackerOp("zeroMem", p, size) + proc copyMem(dest, source: pointer, size: Natural) = + nimCopyMem(dest, source, size) + when declared(memTrackerOp): + memTrackerOp("copyMem", dest, size) + proc moveMem(dest, source: pointer, size: Natural) = + c_memmove(dest, source, size) + when declared(memTrackerOp): + memTrackerOp("moveMem", dest, size) + proc equalMem(a, b: pointer, size: Natural): bool = + nimCmpMem(a, b, size) == 0 proc cmp(x, y: string): int = - when nimvm: + when defined(nimscript): if x < y: result = -1 elif x > y: result = 1 else: result = 0 else: - let minlen = min(x.len, y.len) - result = int(nimCmpMem(x.cstring, y.cstring, minlen.csize)) - if result == 0: - result = x.len - y.len - - when defined(nimscript): - proc readFile*(filename: string): TaintedString {.tags: [ReadIOEffect], benign.} - ## Opens a file named `filename` for reading, calls `readAll - ## <#readAll>`_ and closes the file afterwards. Returns the string. - ## Raises an IO exception in case of an error. If # you need to call - ## this inside a compile time macro you can use `staticRead - ## <#staticRead>`_. - - proc writeFile*(filename, content: string) {.tags: [WriteIOEffect], benign.} - ## Opens a file named `filename` for writing. Then writes the - ## `content` completely to the file and closes the file afterwards. - ## Raises an IO exception in case of an error. + when nimvm: + if x < y: result = -1 + elif x > y: result = 1 + else: result = 0 + else: + let minlen = min(x.len, y.len) + result = int(nimCmpMem(x.cstring, y.cstring, minlen.csize)) + if result == 0: + result = x.len - y.len when not defined(nimscript) and hostOS != "standalone": - - # text file handling: - var - stdin* {.importc: "stdin", header: "".}: File - ## The standard input stream. - stdout* {.importc: "stdout", header: "".}: File - ## The standard output stream. - stderr* {.importc: "stderr", header: "".}: File - ## The standard error stream. - - when defined(windows): - # work-around C's sucking abstraction: - # BUGFIX: stdin and stdout should be binary files! - proc c_setmode(handle, mode: cint) {. - importc: when defined(bcc): "setmode" else: "_setmode", - header: "".} - var - O_BINARY {.importc: "_O_BINARY", header:"".}: cint - - # we use binary mode on Windows: - c_setmode(c_fileno(stdin), O_BINARY) - c_setmode(c_fileno(stdout), O_BINARY) - c_setmode(c_fileno(stderr), O_BINARY) - when defined(endb): proc endbStep() - when defined(useStdoutAsStdmsg): - template stdmsg*: File = stdout - else: - template stdmsg*: File = stderr - ## Template which expands to either stdout or stderr depending on - ## `useStdoutAsStdmsg` compile-time switch. - - proc open*(f: var File, filename: string, - mode: FileMode = fmRead, bufSize: int = -1): bool {.tags: [], - raises: [], benign.} - ## Opens a file named `filename` with given `mode`. - ## - ## Default mode is readonly. Returns true iff the file could be opened. - ## This throws no exception if the file could not be opened. - - proc open*(f: var File, filehandle: FileHandle, - mode: FileMode = fmRead): bool {.tags: [], raises: [], - benign.} - ## Creates a ``File`` from a `filehandle` with given `mode`. - ## - ## Default mode is readonly. Returns true iff the file could be opened. - - proc open*(filename: string, - mode: FileMode = fmRead, bufSize: int = -1): File = - ## Opens a file named `filename` with given `mode`. - ## - ## Default mode is readonly. Raises an ``IOError`` if the file - ## could not be opened. - if not open(result, filename, mode, bufSize): - sysFatal(IOError, "cannot open: ", filename) - - proc reopen*(f: File, filename: string, mode: FileMode = fmRead): bool {. - tags: [], benign.} - ## reopens the file `f` with given `filename` and `mode`. This - ## is often used to redirect the `stdin`, `stdout` or `stderr` - ## file variables. - ## - ## Default mode is readonly. Returns true iff the file could be reopened. - - proc setStdIoUnbuffered*() {.tags: [], benign.} - ## Configures `stdin`, `stdout` and `stderr` to be unbuffered. - - proc close*(f: File) {.tags: [], gcsafe.} - ## Closes the file. - - proc endOfFile*(f: File): bool {.tags: [], benign.} - ## Returns true iff `f` is at the end. - - proc readChar*(f: File): char {.tags: [ReadIOEffect].} - ## Reads a single character from the stream `f`. Should not be used in - ## performance sensitive code. - - proc flushFile*(f: File) {.tags: [WriteIOEffect].} - ## Flushes `f`'s buffer. - - proc readAll*(file: File): TaintedString {.tags: [ReadIOEffect], benign.} - ## Reads all data from the stream `file`. - ## - ## Raises an IO exception in case of an error. It is an error if the - ## current file position is not at the beginning of the file. - - proc readFile*(filename: string): TaintedString {.tags: [ReadIOEffect], benign.} - ## Opens a file named `filename` for reading. - ## - ## Then calls `readAll <#readAll>`_ and closes the file afterwards. - ## Returns the string. Raises an IO exception in case of an error. If - ## you need to call this inside a compile time macro you can use - ## `staticRead <#staticRead>`_. - - proc writeFile*(filename, content: string) {.tags: [WriteIOEffect], benign.} - ## Opens a file named `filename` for writing. Then writes the - ## `content` completely to the file and closes the file afterwards. - ## Raises an IO exception in case of an error. - - proc write*(f: File, r: float32) {.tags: [WriteIOEffect], benign.} - proc write*(f: File, i: int) {.tags: [WriteIOEffect], benign.} - proc write*(f: File, i: BiggestInt) {.tags: [WriteIOEffect], benign.} - proc write*(f: File, r: BiggestFloat) {.tags: [WriteIOEffect], benign.} - proc write*(f: File, s: string) {.tags: [WriteIOEffect], benign.} - proc write*(f: File, b: bool) {.tags: [WriteIOEffect], benign.} - proc write*(f: File, c: char) {.tags: [WriteIOEffect], benign.} - proc write*(f: File, c: cstring) {.tags: [WriteIOEffect], benign.} - proc write*(f: File, a: varargs[string, `$`]) {.tags: [WriteIOEffect], benign.} - ## Writes a value to the file `f`. May throw an IO exception. - - proc readLine*(f: File): TaintedString {.tags: [ReadIOEffect], benign.} - ## reads a line of text from the file `f`. May throw an IO exception. - ## A line of text may be delimited by ``LF`` or ``CRLF``. The newline - ## character(s) are not part of the returned string. - - proc readLine*(f: File, line: var TaintedString): bool {.tags: [ReadIOEffect], - benign.} - ## reads a line of text from the file `f` into `line`. May throw an IO - ## exception. - ## A line of text may be delimited by ``LF`` or ``CRLF``. The newline - ## character(s) are not part of the returned string. Returns ``false`` - ## if the end of the file has been reached, ``true`` otherwise. If - ## ``false`` is returned `line` contains no new data. - - proc writeLine*[Ty](f: File, x: varargs[Ty, `$`]) {.inline, - tags: [WriteIOEffect], benign.} - ## writes the values `x` to `f` and then writes "\\n". - ## May throw an IO exception. - - proc getFileSize*(f: File): int64 {.tags: [ReadIOEffect], benign.} - ## retrieves the file size (in bytes) of `f`. - - proc readBytes*(f: File, a: var openArray[int8|uint8], start, len: Natural): int {. - tags: [ReadIOEffect], benign.} - ## reads `len` bytes into the buffer `a` starting at ``a[start]``. Returns - ## the actual number of bytes that have been read which may be less than - ## `len` (if not as many bytes are remaining), but not greater. - - proc readChars*(f: File, a: var openArray[char], start, len: Natural): int {. - tags: [ReadIOEffect], benign.} - ## reads `len` bytes into the buffer `a` starting at ``a[start]``. Returns - ## the actual number of bytes that have been read which may be less than - ## `len` (if not as many bytes are remaining), but not greater. - ## - ## **Warning:** The buffer `a` must be pre-allocated. This can be done - ## using, for example, ``newString``. - - proc readBuffer*(f: File, buffer: pointer, len: Natural): int {. - tags: [ReadIOEffect], benign.} - ## reads `len` bytes into the buffer pointed to by `buffer`. Returns - ## the actual number of bytes that have been read which may be less than - ## `len` (if not as many bytes are remaining), but not greater. - - proc writeBytes*(f: File, a: openArray[int8|uint8], start, len: Natural): int {. - tags: [WriteIOEffect], benign.} - ## writes the bytes of ``a[start..start+len-1]`` to the file `f`. Returns - ## the number of actual written bytes, which may be less than `len` in case - ## of an error. - - proc writeChars*(f: File, a: openArray[char], start, len: Natural): int {. - tags: [WriteIOEffect], benign.} - ## writes the bytes of ``a[start..start+len-1]`` to the file `f`. Returns - ## the number of actual written bytes, which may be less than `len` in case - ## of an error. - - proc writeBuffer*(f: File, buffer: pointer, len: Natural): int {. - tags: [WriteIOEffect], benign.} - ## writes the bytes of buffer pointed to by the parameter `buffer` to the - ## file `f`. Returns the number of actual written bytes, which may be less - ## than `len` in case of an error. - - proc setFilePos*(f: File, pos: int64, relativeTo: FileSeekPos = fspSet) {.benign.} - ## sets the position of the file pointer that is used for read/write - ## operations. The file's first byte has the index zero. - - proc getFilePos*(f: File): int64 {.benign.} - ## retrieves the current position of the file pointer that is used to - ## read from the file `f`. The file's first byte has the index zero. - - proc getFileHandle*(f: File): FileHandle - ## returns the OS file handle of the file ``f``. This is only useful for - ## platform specific programming. when defined(gcDestructors) and not defined(nimscript): include "core/strs" @@ -3553,47 +3343,8 @@ when not defined(JS): #and not defined(nimscript): {.pop.} when hasAlloc: include "system/strmantle" - when hostOS != "standalone": include "system/sysio" when hasThreadSupport: when hostOS != "standalone": include "system/channels" - else: - include "system/sysio" - - when not defined(nimscript) and hostOS != "standalone": - iterator lines*(filename: string): TaintedString {.tags: [ReadIOEffect].} = - ## Iterates over any line in the file named `filename`. - ## - ## If the file does not exist `IOError` is raised. The trailing newline - ## character(s) are removed from the iterated lines. Example: - ## - ## .. code-block:: nim - ## import strutils - ## - ## proc transformLetters(filename: string) = - ## var buffer = "" - ## for line in filename.lines: - ## buffer.add(line.replace("a", "0") & '\x0A') - ## writeFile(filename, buffer) - var f = open(filename, bufSize=8000) - defer: close(f) - var res = TaintedString(newStringOfCap(80)) - while f.readLine(res): yield res - - iterator lines*(f: File): TaintedString {.tags: [ReadIOEffect].} = - ## Iterate over any line in the file `f`. - ## - ## The trailing newline character(s) are removed from the iterated lines. - ## Example: - ## - ## .. code-block:: nim - ## proc countZeros(filename: File): tuple[lines, zeros: int] = - ## for line in filename.lines: - ## for letter in line: - ## if letter == '0': - ## result.zeros += 1 - ## result.lines += 1 - var res = TaintedString(newStringOfCap(80)) - while f.readLine(res): yield res when not defined(nimscript) and hasAlloc: when not defined(gcDestructors): @@ -3684,9 +3435,6 @@ elif defined(JS): if x < y: return -1 return 1 - when defined(nimffi): - include "system/sysio" - when not defined(nimNoArrayToString): proc `$`*[T, IDX](x: array[IDX, T]): string = ## generic ``$`` operator for arrays that is lifted from the components @@ -3702,7 +3450,11 @@ proc `$`*[T](x: openarray[T]): string = proc quit*(errormsg: string, errorcode = QuitFailure) {.noReturn.} = ## a shorthand for ``echo(errormsg); quit(errorcode)``. - echo(errormsg) + when defined(nimscript) or defined(js) or (hostOS == "standalone"): + echo errormsg + else: + cstderr.rawWrite(errormsg) + cstderr.rawWrite("\n") quit(errorcode) {.pop.} # checks @@ -4430,7 +4182,7 @@ when defined(cpp) and appType != "lib" and echo trace & "Error: unhandled exception: " & ex.msg & " [" & $ex.name & "]\n" else: - stderr.write trace & "Error: unhandled exception: " & ex.msg & + cstderr.rawWrite trace & "Error: unhandled exception: " & ex.msg & " [" & $ex.name & "]\n" quit 1 @@ -4479,3 +4231,9 @@ proc `$`*(t: typedesc): string {.magic: "TypeTrait".} = doAssert $(type(42)) == "int" doAssert $(type("Foo")) == "string" static: doAssert $(type(@['A', 'B'])) == "seq[char]" + +import system/widestrs +export widestrs + +import system/io +export io diff --git a/lib/system/ansi_c.nim b/lib/system/ansi_c.nim index af34060d8098..3afef0bfb95e 100644 --- a/lib/system/ansi_c.nim +++ b/lib/system/ansi_c.nim @@ -111,22 +111,27 @@ type c_sighandler_t = proc (a: cint) {.noconv.} proc c_signal(sign: cint, handler: proc (a: cint) {.noconv.}): c_sighandler_t {. importc: "signal", header: "", discardable.} -proc c_fprintf(f: File, frmt: cstring): cint {. +type + CFile {.importc: "FILE", header: "", + incompletestruct.} = object + CFileStar* = ptr CFile ## The type representing a file handle. + +var + cstderr* {.importc: "stderr", header: "".}: CFileStar + cstdout* {.importc: "stdout", header: "".}: CFileStar + +proc c_fprintf(f: CFileStar, frmt: cstring): cint {. importc: "fprintf", header: "", varargs, discardable.} proc c_printf(frmt: cstring): cint {. importc: "printf", header: "", varargs, discardable.} +proc c_fputs(c: cstring, f: CFileStar): cint {. + importc: "fputs", header: "", discardable.} + proc c_sprintf(buf, frmt: cstring): cint {. importc: "sprintf", header: "", varargs, noSideEffect.} # we use it only in a way that cannot lead to security issues -when defined(windows): - proc c_fileno(f: File): cint {. - importc: "_fileno", header: "".} -else: - proc c_fileno(f: File): cint {. - importc: "fileno", header: "".} - proc c_malloc(size: csize): pointer {. importc: "malloc", header: "".} proc c_free(p: pointer) {. diff --git a/lib/system/dyncalls.nim b/lib/system/dyncalls.nim index 70bdc429bf94..528587d05d5f 100644 --- a/lib/system/dyncalls.nim +++ b/lib/system/dyncalls.nim @@ -19,11 +19,11 @@ const proc nimLoadLibraryError(path: string) = # carefully written to avoid memory allocation: - stderr.rawWrite("could not load: ") - stderr.rawWrite(path) - stderr.rawWrite("\n") + cstderr.rawWrite("could not load: ") + cstderr.rawWrite(path) + cstderr.rawWrite("\n") when not defined(nimDebugDlOpen) and not defined(windows): - stderr.rawWrite("compile with -d:nimDebugDlOpen for more information\n") + cstderr.rawWrite("compile with -d:nimDebugDlOpen for more information\n") when defined(windows) and defined(guiapp): # Because console output is not shown in GUI apps, display error as message box: const prefix = "could not load: " @@ -35,9 +35,9 @@ proc nimLoadLibraryError(path: string) = proc procAddrError(name: cstring) {.noinline.} = # carefully written to avoid memory allocation: - stderr.rawWrite("could not import: ") - stderr.rawWrite(name) - stderr.rawWrite("\n") + cstderr.rawWrite("could not import: ") + cstderr.rawWrite(name) + cstderr.rawWrite("\n") quit(1) # this code was inspired from Lua's source code: @@ -79,8 +79,8 @@ when defined(posix): when defined(nimDebugDlOpen): let error = dlerror() if error != nil: - stderr.rawWrite(error) - stderr.rawWrite("\n") + cstderr.rawWrite(error) + cstderr.rawWrite("\n") proc nimGetProcAddr(lib: LibHandle, name: cstring): ProcAddr = result = dlsym(lib, name) @@ -162,20 +162,20 @@ elif defined(genode): elif defined(nintendoswitch): proc nimUnloadLibrary(lib: LibHandle) = - stderr.rawWrite("nimUnLoadLibrary not implemented") - stderr.rawWrite("\n") + cstderr.rawWrite("nimUnLoadLibrary not implemented") + cstderr.rawWrite("\n") quit(1) proc nimLoadLibrary(path: string): LibHandle = - stderr.rawWrite("nimLoadLibrary not implemented") - stderr.rawWrite("\n") + cstderr.rawWrite("nimLoadLibrary not implemented") + cstderr.rawWrite("\n") quit(1) proc nimGetProcAddr(lib: LibHandle, name: cstring): ProcAddr = - stderr.rawWrite("nimGetProAddr not implemented") - stderr.rawWrite(name) - stderr.rawWrite("\n") + cstderr.rawWrite("nimGetProAddr not implemented") + cstderr.rawWrite(name) + cstderr.rawWrite("\n") quit(1) else: diff --git a/lib/system/endb.nim b/lib/system/endb.nim index 257ee3fea2fc..b1f91295cbbb 100644 --- a/lib/system/endb.nim +++ b/lib/system/endb.nim @@ -76,29 +76,52 @@ proc `==`(a, b: StaticStr): bool = proc `==`(a: StaticStr, b: cstring): bool = result = c_strcmp(unsafeAddr a.data, b) == 0 -proc write(f: File, s: StaticStr) = +proc write(f: CFileStar, s: cstring) = c_fputs(s, f) +proc writeLine(f: CFileStar, s: cstring) = + c_fputs(s, f) + c_fputs("\n", f) + +proc write(f: CFileStar, s: StaticStr) = write(f, cstring(unsafeAddr s.data)) +proc write(f: CFileStar, i: int) = + when sizeof(int) == 8: + discard c_fprintf(f, "%lld", i) + else: + discard c_fprintf(f, "%ld", i) + +proc close(f: CFileStar): cint {. + importc: "fclose", header: "", discardable.} + +proc c_fgetc(stream: CFileStar): cint {. + importc: "fgetc", header: "".} +proc c_ungetc(c: cint, f: CFileStar): cint {. + importc: "ungetc", header: "", discardable.} + +var + cstdin* {.importc: "stdin", header: "".}: CFileStar + proc listBreakPoints() = - write(stdout, EndbBeg) - write(stdout, "| Breakpoints:\n") + write(cstdout, EndbBeg) + write(cstdout, "| Breakpoints:\n") for b in listBreakpoints(): - write(stdout, abs(b.low)) + write(cstdout, abs(b.low)) if b.high != b.low: - write(stdout, "..") - write(stdout, abs(b.high)) - write(stdout, " ") - write(stdout, b.filename) + write(cstdout, "..") + write(cstdout, abs(b.high)) + write(cstdout, " ") + write(cstdout, b.filename) if b.isActive: - write(stdout, " [disabled]\n") + write(cstdout, " [disabled]\n") else: - write(stdout, "\n") - write(stdout, EndbEnd) + write(cstdout, "\n") + write(cstdout, EndbEnd) + +proc openAppend(filename: cstring): CFileStar = + proc fopen(filename, mode: cstring): CFileStar {.importc: "fopen", header: "".} -proc openAppend(filename: cstring): File = - var p: pointer = fopen(filename, "ab") - if p != nil: - result = cast[File](p) + result = fopen(filename, "ab") + if result != nil: write(result, "----------------------------------------\n") proc dbgRepr(p: pointer, typ: PNimType): string = @@ -112,12 +135,12 @@ proc dbgRepr(p: pointer, typ: PNimType): string = # dec(recGcLock) deinitReprClosure(cl) -proc writeVariable(stream: File, slot: VarSlot) = +proc writeVariable(stream: CFileStar, slot: VarSlot) = write(stream, slot.name) write(stream, " = ") writeLine(stream, dbgRepr(slot.address, slot.typ)) -proc listFrame(stream: File, f: PFrame) = +proc listFrame(stream: CFileStar, f: PFrame) = write(stream, EndbBeg) write(stream, "| Frame (") write(stream, f.len) @@ -126,7 +149,7 @@ proc listFrame(stream: File, f: PFrame) = writeLine(stream, getLocal(f, i).name) write(stream, EndbEnd) -proc listLocals(stream: File, f: PFrame) = +proc listLocals(stream: CFileStar, f: PFrame) = write(stream, EndbBeg) write(stream, "| Frame (") write(stream, f.len) @@ -135,7 +158,7 @@ proc listLocals(stream: File, f: PFrame) = writeVariable(stream, getLocal(f, i)) write(stream, EndbEnd) -proc listGlobals(stream: File) = +proc listGlobals(stream: CFileStar) = write(stream, EndbBeg) write(stream, "| Globals:\n") for i in 0 .. getGlobalLen()-1: @@ -145,10 +168,10 @@ proc listGlobals(stream: File) = proc debugOut(msg: cstring) = # the *** *** markers are for easy recognition of debugger # output for external frontends. - write(stdout, EndbBeg) - write(stdout, "| ") - write(stdout, msg) - write(stdout, EndbEnd) + write(cstdout, EndbBeg) + write(cstdout, "| ") + write(cstdout, msg) + write(cstdout, EndbEnd) proc dbgFatal(msg: cstring) = debugOut(msg) @@ -157,20 +180,20 @@ proc dbgFatal(msg: cstring) = proc dbgShowCurrentProc(dbgFramePointer: PFrame) = if dbgFramePointer != nil: - write(stdout, "*** endb| now in proc: ") - write(stdout, dbgFramePointer.procname) - write(stdout, " ***\n") + write(cstdout, "*** endb| now in proc: ") + write(cstdout, dbgFramePointer.procname) + write(cstdout, " ***\n") else: - write(stdout, "*** endb| (proc name not available) ***\n") + write(cstdout, "*** endb| (proc name not available) ***\n") proc dbgShowExecutionPoint() = - write(stdout, "*** endb| ") - write(stdout, framePtr.filename) - write(stdout, "(") - write(stdout, framePtr.line) - write(stdout, ") ") - write(stdout, framePtr.procname) - write(stdout, " ***\n") + write(cstdout, "*** endb| ") + write(cstdout, framePtr.filename) + write(cstdout, "(") + write(cstdout, framePtr.line) + write(cstdout, ") ") + write(cstdout, framePtr.procname) + write(cstdout, " ***\n") proc scanAndAppendWord(src: cstring, a: var StaticStr, start: int): int = result = start @@ -279,7 +302,7 @@ proc breakpointToggle(s: cstring, start: int) = if not b.isNil: b.flip else: debugOut("[Warning] unknown breakpoint ") -proc dbgEvaluate(stream: File, s: cstring, start: int, f: PFrame) = +proc dbgEvaluate(stream: CFileStar, s: cstring, start: int, f: PFrame) = var dbgTemp: StaticStr var i = scanWord(s, dbgTemp, start) while s[i] in {' ', '\t'}: inc(i) @@ -315,8 +338,8 @@ proc dbgStackFrame(s: cstring, start: int, currFrame: PFrame) = var dbgTemp: StaticStr var i = scanFilename(s, dbgTemp, start) if dbgTemp.len == 0: - # just write it to stdout: - listFrame(stdout, currFrame) + # just write it to cstdout: + listFrame(cstdout, currFrame) else: var stream = openAppend(addr dbgTemp.data) if stream == nil: @@ -325,7 +348,7 @@ proc dbgStackFrame(s: cstring, start: int, currFrame: PFrame) = listFrame(stream, currFrame) close(stream) -proc readLine(f: File, line: var StaticStr): bool = +proc readLine(f: CFileStar, line: var StaticStr): bool = while true: var c = c_fgetc(f) if c < 0'i32: @@ -340,16 +363,16 @@ proc readLine(f: File, line: var StaticStr): bool = result = true proc listFilenames() = - write(stdout, EndbBeg) - write(stdout, "| Files:\n") + write(cstdout, EndbBeg) + write(cstdout, "| Files:\n") var i = 0 while true: let x = dbgFilenames[i] if x.isNil: break - write(stdout, x) - write(stdout, "\n") + write(cstdout, x) + write(cstdout, "\n") inc i - write(stdout, EndbEnd) + write(cstdout, EndbEnd) proc dbgWriteStackTrace(f: PFrame) proc commandPrompt() = @@ -361,10 +384,10 @@ proc commandPrompt() = dbgTemp: StaticStr while again: - write(stdout, "*** endb| >>") + write(cstdout, "*** endb| >>") let oldLen = dbgUser.len dbgUser.len = 0 - if not readLine(stdin, dbgUser): break + if not readLine(cstdin, dbgUser): break if dbgUser.len == 0: dbgUser.len = oldLen # now look what we have to do: var i = scanWord(addr dbgUser.data, dbgTemp, 0) @@ -398,7 +421,7 @@ proc commandPrompt() = prevState = dbgState prevSkipFrame = dbgSkipToFrame dbgState = dbSkipCurrent - dbgEvaluate(stdout, addr dbgUser.data, i, dbgFramePtr) + dbgEvaluate(cstdout, addr dbgUser.data, i, dbgFramePtr) dbgState = prevState dbgSkipToFrame = prevSkipFrame elif ?"o" or ?"out": @@ -412,7 +435,7 @@ proc commandPrompt() = prevState = dbgState prevSkipFrame = dbgSkipToFrame dbgState = dbSkipCurrent - listLocals(stdout, dbgFramePtr) + listLocals(cstdout, dbgFramePtr) dbgState = prevState dbgSkipToFrame = prevSkipFrame elif ?"g" or ?"globals": @@ -420,7 +443,7 @@ proc commandPrompt() = prevState = dbgState prevSkipFrame = dbgSkipToFrame dbgState = dbSkipCurrent - listGlobals(stdout) + listGlobals(cstdout) dbgState = prevState dbgSkipToFrame = prevSkipFrame elif ?"u" or ?"up": @@ -501,29 +524,29 @@ proc dbgWriteStackTrace(f: PFrame) = b = b.prev for j in countdown(i-1, 0): if tempFrames[j] == nil: - write(stdout, "(") - write(stdout, skipped) - write(stdout, " calls omitted) ...") + write(cstdout, "(") + write(cstdout, skipped) + write(cstdout, " calls omitted) ...") else: - write(stdout, tempFrames[j].filename) + write(cstdout, tempFrames[j].filename) if tempFrames[j].line > 0: - write(stdout, '(') - write(stdout, tempFrames[j].line) - write(stdout, ')') - write(stdout, ' ') - write(stdout, tempFrames[j].procname) - write(stdout, "\n") + write(cstdout, "(") + write(cstdout, tempFrames[j].line) + write(cstdout, ")") + write(cstdout, " ") + write(cstdout, tempFrames[j].procname) + write(cstdout, "\n") proc checkForBreakpoint = let b = checkBreakpoints(framePtr.filename, framePtr.line) if b != nil: - write(stdout, "*** endb| reached ") - write(stdout, framePtr.filename) - write(stdout, "(") - write(stdout, framePtr.line) - write(stdout, ") ") - write(stdout, framePtr.procname) - write(stdout, " ***\n") + write(cstdout, "*** endb| reached ") + write(cstdout, framePtr.filename) + write(cstdout, "(") + write(cstdout, framePtr.line) + write(cstdout, ") ") + write(cstdout, framePtr.procname) + write(cstdout, " ***\n") commandPrompt() proc lineHookImpl() {.nimcall.} = diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim index f2f82c3b8d91..0025d9088415 100644 --- a/lib/system/excpt.nim +++ b/lib/system/excpt.nim @@ -17,15 +17,15 @@ var ## instead of stdmsg.write when printing stacktrace. ## Unstable API. -proc c_fwrite(buf: pointer, size, n: csize, f: File): cint {. +proc c_fwrite(buf: pointer, size, n: csize, f: CFileStar): cint {. importc: "fwrite", header: "".} -proc rawWrite(f: File, s: string|cstring) = +proc rawWrite(f: CFileStar, s: string|cstring) = # we cannot throw an exception here! discard c_fwrite(cstring(s), 1, s.len, f) when not defined(windows) or not defined(guiapp): - proc writeToStdErr(msg: cstring) = rawWrite(stdmsg, msg) + proc writeToStdErr(msg: cstring) = rawWrite(cstderr, msg) else: proc MessageBoxA(hWnd: cint, lpText, lpCaption: cstring, uType: int): int32 {. diff --git a/lib/system/gc.nim b/lib/system/gc.nim index 018197c1eb98..d6d7da66e6ef 100644 --- a/lib/system/gc.nim +++ b/lib/system/gc.nim @@ -104,9 +104,11 @@ when not defined(useNimRtl): template gcAssert(cond: bool, msg: string) = when defined(useGcAssert): if not cond: - echo "[GCASSERT] ", msg + cstderr.rawWrite "[GCASSERT] " + cstderr.rawWrite msg when defined(logGC): - echo "[GCASSERT] statistics:\L", GC_getStatistics() + cstderr.rawWrite "[GCASSERT] statistics:\L" + cstderr.rawWrite GC_getStatistics() GC_disable() writeStackTrace() #var x: ptr int diff --git a/lib/system/gc_common.nim b/lib/system/gc_common.nim index ebd3dada258b..5af64ae20816 100644 --- a/lib/system/gc_common.nim +++ b/lib/system/gc_common.nim @@ -457,7 +457,7 @@ proc nimRegisterGlobalMarker(markerProc: GlobalMarkerProc) {.compilerProc.} = globalMarkers[globalMarkersLen] = markerProc inc globalMarkersLen else: - echo "[GC] cannot register global variable; too many global variables" + cstderr.rawWrite("[GC] cannot register global variable; too many global variables") quit 1 proc nimRegisterThreadLocalMarker(markerProc: GlobalMarkerProc) {.compilerProc.} = @@ -465,5 +465,5 @@ proc nimRegisterThreadLocalMarker(markerProc: GlobalMarkerProc) {.compilerProc.} threadLocalMarkers[threadLocalMarkersLen] = markerProc inc threadLocalMarkersLen else: - echo "[GC] cannot register thread local variable; too many thread local variables" + cstderr.rawWrite("[GC] cannot register thread local variable; too many thread local variables") quit 1 diff --git a/lib/system/gc_ms.nim b/lib/system/gc_ms.nim index aa5fb6aea134..bd08eedf0013 100644 --- a/lib/system/gc_ms.nim +++ b/lib/system/gc_ms.nim @@ -88,7 +88,8 @@ when not defined(useNimRtl): template gcAssert(cond: bool, msg: string) = when defined(useGcAssert): if not cond: - echo "[GCASSERT] ", msg + cstderr.rawWrite "[GCASSERT] " + cstderr.rawWrite msg quit 1 proc cellToUsr(cell: PCell): pointer {.inline.} = diff --git a/lib/system/io.nim b/lib/system/io.nim new file mode 100644 index 000000000000..408631db5f77 --- /dev/null +++ b/lib/system/io.nim @@ -0,0 +1,640 @@ + +include inclrtl + +# ----------------- IO Part ------------------------------------------------ +type + CFile {.importc: "FILE", header: "", + incompletestruct.} = object + File* = ptr CFile ## The type representing a file handle. + + FileMode* = enum ## The file mode when opening a file. + fmRead, ## Open the file for read access only. + fmWrite, ## Open the file for write access only. + ## If the file does not exist, it will be + ## created. Existing files will be cleared! + fmReadWrite, ## Open the file for read and write access. + ## If the file does not exist, it will be + ## created. Existing files will be cleared! + fmReadWriteExisting, ## Open the file for read and write access. + ## If the file does not exist, it will not be + ## created. The existing file will not be cleared. + fmAppend ## Open the file for writing only; append data + ## at the end. + + FileHandle* = cint ## type that represents an OS file handle; this is + ## useful for low-level file access + +# text file handling: +when not defined(nimscript) and not defined(js): + var + stdin* {.importc: "stdin", header: "".}: File + ## The standard input stream. + stdout* {.importc: "stdout", header: "".}: File + ## The standard output stream. + stderr* {.importc: "stderr", header: "".}: File + ## The standard error stream. + +when defined(useStdoutAsStdmsg): + template stdmsg*: File = stdout +else: + template stdmsg*: File = stderr + ## Template which expands to either stdout or stderr depending on + ## `useStdoutAsStdmsg` compile-time switch. + +when defined(windows): + proc c_fileno(f: File): cint {. + importc: "_fileno", header: "".} +else: + proc c_fileno(f: File): cint {. + importc: "fileno", header: "".} + +when defined(windows): + proc c_fdopen(filehandle: cint, mode: cstring): File {. + importc: "_fdopen", header: "".} +else: + proc c_fdopen(filehandle: cint, mode: cstring): File {. + importc: "fdopen", header: "".} +proc c_fputs(c: cstring, f: File): cint {. + importc: "fputs", header: "", tags: [WriteIOEffect].} +proc c_fgets(c: cstring, n: cint, f: File): cstring {. + importc: "fgets", header: "", tags: [ReadIOEffect].} +proc c_fgetc(stream: File): cint {. + importc: "fgetc", header: "", tags: [ReadIOEffect].} +proc c_ungetc(c: cint, f: File): cint {. + importc: "ungetc", header: "", tags: [].} +proc c_putc(c: cint, stream: File): cint {. + importc: "putc", header: "", tags: [WriteIOEffect].} +proc c_fflush(f: File): cint {. + importc: "fflush", header: "".} +proc c_fclose(f: File): cint {. + importc: "fclose", header: "".} +proc c_clearerr(f: File) {. + importc: "clearerr", header: "".} +proc c_feof(f: File): cint {. + importc: "feof", header: "".} + +when not declared(c_fwrite): + proc c_fwrite(buf: pointer, size, n: csize, f: File): cint {. + importc: "fwrite", header: "".} + +# C routine that is used here: +proc c_fread(buf: pointer, size, n: csize, f: File): csize {. + importc: "fread", header: "", tags: [ReadIOEffect].} +when defined(windows): + when not defined(amd64): + proc c_fseek(f: File, offset: int64, whence: cint): cint {. + importc: "fseek", header: "", tags: [].} + proc c_ftell(f: File): int64 {. + importc: "ftell", header: "", tags: [].} + else: + proc c_fseek(f: File, offset: int64, whence: cint): cint {. + importc: "_fseeki64", header: "", tags: [].} + proc c_ftell(f: File): int64 {. + importc: "_ftelli64", header: "", tags: [].} +else: + proc c_fseek(f: File, offset: int64, whence: cint): cint {. + importc: "fseeko", header: "", tags: [].} + proc c_ftell(f: File): int64 {. + importc: "ftello", header: "", tags: [].} +proc c_ferror(f: File): cint {. + importc: "ferror", header: "", tags: [].} +proc c_setvbuf(f: File, buf: pointer, mode: cint, size: csize): cint {. + importc: "setvbuf", header: "", tags: [].} + +proc c_fprintf(f: File, frmt: cstring): cint {. + importc: "fprintf", header: "", varargs, discardable.} + +template sysFatal(exc, msg) = + raise newException(exc, msg) + +proc raiseEIO(msg: string) {.noinline, noreturn.} = + sysFatal(IOError, msg) + +proc raiseEOF() {.noinline, noreturn.} = + sysFatal(EOFError, "EOF reached") + +proc strerror(errnum: cint): cstring {.importc, header: "".} + +when not defined(NimScript): + var + errno {.importc, header: "".}: cint ## error variable + +proc checkErr(f: File) = + when not defined(NimScript): + if c_ferror(f) != 0: + let msg = "errno: " & $errno & " `" & $strerror(errno) & "`" + c_clearerr(f) + raiseEIO(msg) + else: + # shouldn't happen + quit(1) + +{.push stackTrace:off, profiler:off.} +proc readBuffer*(f: File, buffer: pointer, len: Natural): int {. + tags: [ReadIOEffect], benign.} = + ## reads `len` bytes into the buffer pointed to by `buffer`. Returns + ## the actual number of bytes that have been read which may be less than + ## `len` (if not as many bytes are remaining), but not greater. + result = c_fread(buffer, 1, len, f) + if result != len: checkErr(f) + +proc readBytes*(f: File, a: var openArray[int8|uint8], start, len: Natural): int {. + tags: [ReadIOEffect], benign.} = + ## reads `len` bytes into the buffer `a` starting at ``a[start]``. Returns + ## the actual number of bytes that have been read which may be less than + ## `len` (if not as many bytes are remaining), but not greater. + result = readBuffer(f, addr(a[start]), len) + +proc readChars*(f: File, a: var openArray[char], start, len: Natural): int {. + tags: [ReadIOEffect], benign.} = + ## reads `len` bytes into the buffer `a` starting at ``a[start]``. Returns + ## the actual number of bytes that have been read which may be less than + ## `len` (if not as many bytes are remaining), but not greater. + ## + ## **Warning:** The buffer `a` must be pre-allocated. This can be done + ## using, for example, ``newString``. + if (start + len) > len(a): + raiseEIO("buffer overflow: (start+len) > length of openarray buffer") + result = readBuffer(f, addr(a[start]), len) + +proc write*(f: File, c: cstring) {.tags: [WriteIOEffect], benign.} = + ## Writes a value to the file `f`. May throw an IO exception. + discard c_fputs(c, f) + checkErr(f) + +proc writeBuffer*(f: File, buffer: pointer, len: Natural): int {. + tags: [WriteIOEffect], benign.} = + ## writes the bytes of buffer pointed to by the parameter `buffer` to the + ## file `f`. Returns the number of actual written bytes, which may be less + ## than `len` in case of an error. + result = c_fwrite(buffer, 1, len, f) + checkErr(f) + +proc writeBytes*(f: File, a: openArray[int8|uint8], start, len: Natural): int {. + tags: [WriteIOEffect], benign.} = + ## writes the bytes of ``a[start..start+len-1]`` to the file `f`. Returns + ## the number of actual written bytes, which may be less than `len` in case + ## of an error. + var x = cast[ptr UncheckedArray[int8]](a) + result = writeBuffer(f, addr(x[int(start)]), len) + +proc writeChars*(f: File, a: openArray[char], start, len: Natural): int {. + tags: [WriteIOEffect], benign.} = + ## writes the bytes of ``a[start..start+len-1]`` to the file `f`. Returns + ## the number of actual written bytes, which may be less than `len` in case + ## of an error. + var x = cast[ptr UncheckedArray[int8]](a) + result = writeBuffer(f, addr(x[int(start)]), len) + +proc write*(f: File, s: string) {.tags: [WriteIOEffect], benign.} = + if writeBuffer(f, cstring(s), s.len) != s.len: + raiseEIO("cannot write string to file") +{.pop.} + +when NoFakeVars: + when defined(windows): + const + IOFBF = cint(0) + IONBF = cint(4) + else: + # On all systems I could find, including Linux, Mac OS X, and the BSDs + const + IOFBF = cint(0) + IONBF = cint(2) +else: + var + IOFBF {.importc: "_IOFBF", nodecl.}: cint + IONBF {.importc: "_IONBF", nodecl.}: cint + +const + BufSize = 4000 + +proc close*(f: File) {.tags: [], gcsafe.} = + ## Closes the file. + if not f.isNil: + discard c_fclose(f) + +proc readChar*(f: File): char {.tags: [ReadIOEffect].} = + ## Reads a single character from the stream `f`. Should not be used in + ## performance sensitive code. + let x = c_fgetc(f) + if x < 0: + checkErr(f) + raiseEOF() + result = char(x) + +proc flushFile*(f: File) {.tags: [WriteIOEffect].} = + ## Flushes `f`'s buffer. + discard c_fflush(f) + +proc getFileHandle*(f: File): FileHandle = + ## returns the OS file handle of the file ``f``. This is only useful for + ## platform specific programming. + c_fileno(f) + +proc readLine*(f: File, line: var TaintedString): bool {.tags: [ReadIOEffect], + benign.} = + ## reads a line of text from the file `f` into `line`. May throw an IO + ## exception. + ## A line of text may be delimited by ``LF`` or ``CRLF``. The newline + ## character(s) are not part of the returned string. Returns ``false`` + ## if the end of the file has been reached, ``true`` otherwise. If + ## ``false`` is returned `line` contains no new data. + proc c_memchr(s: pointer, c: cint, n: csize): pointer {. + importc: "memchr", header: "".} + + var pos = 0 + + # Use the currently reserved space for a first try + var sp = max(line.string.len, 80) + line.string.setLen(sp) + + while true: + # memset to \L so that we can tell how far fgets wrote, even on EOF, where + # fgets doesn't append an \L + for i in 0.. 0 and line.string[last-1] == '\c': + line.string.setLen(last-1) + return last > 1 or fgetsSuccess + # We have to distinguish between two possible cases: + # \0\l\0 => line ending in a null character. + # \0\l\l => last line without newline, null was put there by fgets. + elif last > 0 and line.string[last-1] == '\0': + if last < pos + sp - 1 and line.string[last+1] != '\0': + dec last + line.string.setLen(last) + return last > 0 or fgetsSuccess + else: + # fgets will have inserted a null byte at the end of the string. + dec sp + # No \l found: Increase buffer and read more + inc pos, sp + sp = 128 # read in 128 bytes at a time + line.string.setLen(pos+sp) + +proc readLine*(f: File): TaintedString {.tags: [ReadIOEffect], benign.} = + ## reads a line of text from the file `f`. May throw an IO exception. + ## A line of text may be delimited by ``LF`` or ``CRLF``. The newline + ## character(s) are not part of the returned string. + result = TaintedString(newStringOfCap(80)) + if not readLine(f, result): raiseEOF() + +proc write*(f: File, i: int) {.tags: [WriteIOEffect], benign.} = + when sizeof(int) == 8: + if c_fprintf(f, "%lld", i) < 0: checkErr(f) + else: + if c_fprintf(f, "%ld", i) < 0: checkErr(f) + +proc write*(f: File, i: BiggestInt) {.tags: [WriteIOEffect], benign.} = + when sizeof(BiggestInt) == 8: + if c_fprintf(f, "%lld", i) < 0: checkErr(f) + else: + if c_fprintf(f, "%ld", i) < 0: checkErr(f) + +proc write*(f: File, b: bool) {.tags: [WriteIOEffect], benign.} = + if b: write(f, "true") + else: write(f, "false") +proc write*(f: File, r: float32) {.tags: [WriteIOEffect], benign.} = + if c_fprintf(f, "%.16g", r) < 0: checkErr(f) +proc write*(f: File, r: BiggestFloat) {.tags: [WriteIOEffect], benign.} = + if c_fprintf(f, "%.16g", r) < 0: checkErr(f) + +proc write*(f: File, c: char) {.tags: [WriteIOEffect], benign.} = + discard c_putc(cint(c), f) + +proc write*(f: File, a: varargs[string, `$`]) {.tags: [WriteIOEffect], benign.} = + for x in items(a): write(f, x) + +proc readAllBuffer(file: File): string = + # This proc is for File we want to read but don't know how many + # bytes we need to read before the buffer is empty. + result = "" + var buffer = newString(BufSize) + while true: + var bytesRead = readBuffer(file, addr(buffer[0]), BufSize) + if bytesRead == BufSize: + result.add(buffer) + else: + buffer.setLen(bytesRead) + result.add(buffer) + break + +proc rawFileSize(file: File): int64 = + # this does not raise an error opposed to `getFileSize` + var oldPos = c_ftell(file) + discard c_fseek(file, 0, 2) # seek the end of the file + result = c_ftell(file) + discard c_fseek(file, oldPos, 0) + +proc endOfFile*(f: File): bool {.tags: [], benign.} = + ## Returns true iff `f` is at the end. + var c = c_fgetc(f) + discard c_ungetc(c, f) + return c < 0'i32 + #result = c_feof(f) != 0 + +proc readAllFile(file: File, len: int64): string = + # We acquire the filesize beforehand and hope it doesn't change. + # Speeds things up. + result = newString(len) + let bytes = readBuffer(file, addr(result[0]), len) + if endOfFile(file): + if bytes < len: + result.setLen(bytes) + else: + # We read all the bytes but did not reach the EOF + # Try to read it as a buffer + result.add(readAllBuffer(file)) + +proc readAllFile(file: File): string = + var len = rawFileSize(file) + result = readAllFile(file, len) + +proc readAll*(file: File): TaintedString {.tags: [ReadIOEffect], benign.} = + ## Reads all data from the stream `file`. + ## + ## Raises an IO exception in case of an error. It is an error if the + ## current file position is not at the beginning of the file. + + # Separate handling needed because we need to buffer when we + # don't know the overall length of the File. + when declared(stdin): + let len = if file != stdin: rawFileSize(file) else: -1 + else: + let len = rawFileSize(file) + if len > 0: + result = readAllFile(file, len).TaintedString + else: + result = readAllBuffer(file).TaintedString + +proc writeLn[Ty](f: File, x: varargs[Ty, `$`]) = + for i in items(x): + write(f, i) + write(f, "\n") + +proc writeLine*[Ty](f: File, x: varargs[Ty, `$`]) {.inline, + tags: [WriteIOEffect], benign.} = + ## writes the values `x` to `f` and then writes "\\n". + ## May throw an IO exception. + for i in items(x): + write(f, i) + write(f, "\n") + +# interface to the C procs: + +when defined(windows) and not defined(useWinAnsi): + when defined(cpp): + proc wfopen(filename, mode: WideCString): pointer {. + importcpp: "_wfopen((const wchar_t*)#, (const wchar_t*)#)", nodecl.} + proc wfreopen(filename, mode: WideCString, stream: File): File {. + importcpp: "_wfreopen((const wchar_t*)#, (const wchar_t*)#, #)", nodecl.} + else: + proc wfopen(filename, mode: WideCString): pointer {. + importc: "_wfopen", nodecl.} + proc wfreopen(filename, mode: WideCString, stream: File): File {. + importc: "_wfreopen", nodecl.} + + proc fopen(filename, mode: cstring): pointer = + var f = newWideCString(filename) + var m = newWideCString(mode) + result = wfopen(f, m) + + proc freopen(filename, mode: cstring, stream: File): File = + var f = newWideCString(filename) + var m = newWideCString(mode) + result = wfreopen(f, m, stream) + +else: + proc fopen(filename, mode: cstring): pointer {.importc: "fopen", noDecl.} + proc freopen(filename, mode: cstring, stream: File): File {. + importc: "freopen", nodecl.} + +const + FormatOpen: array[FileMode, string] = ["rb", "wb", "w+b", "r+b", "ab"] + #"rt", "wt", "w+t", "r+t", "at" + # we always use binary here as for Nim the OS line ending + # should not be translated. + +when defined(posix) and not defined(nimscript): + when defined(linux) and defined(amd64): + type + Mode {.importc: "mode_t", header: "".} = cint + + # fillers ensure correct size & offsets + Stat {.importc: "struct stat", + header: "", final, pure.} = object ## struct stat + filler_1: array[24, char] + st_mode: Mode ## Mode of file + filler_2: array[144 - 24 - 4, char] + + proc S_ISDIR(m: Mode): bool = + ## Test for a directory. + (m and 0o170000) == 0o40000 + + else: + type + Mode {.importc: "mode_t", header: "".} = cint + + Stat {.importc: "struct stat", + header: "", final, pure.} = object ## struct stat + st_mode: Mode ## Mode of file + + proc S_ISDIR(m: Mode): bool {.importc, header: "".} + ## Test for a directory. + + proc c_fstat(a1: cint, a2: var Stat): cint {. + importc: "fstat", header: "".} + + +proc open*(f: var File, filename: string, + mode: FileMode = fmRead, + bufSize: int = -1): bool {.tags: [], raises: [], benign.} = + ## Opens a file named `filename` with given `mode`. + ## + ## Default mode is readonly. Returns true iff the file could be opened. + ## This throws no exception if the file could not be opened. + var p: pointer = fopen(filename, FormatOpen[mode]) + if p != nil: + when defined(posix) and not defined(nimscript): + # How `fopen` handles opening a directory is not specified in ISO C and + # POSIX. We do not want to handle directories as regular files that can + # be opened. + var f2 = cast[File](p) + var res: Stat + if c_fstat(getFileHandle(f2), res) >= 0'i32 and S_ISDIR(res.st_mode): + close(f2) + return false + result = true + f = cast[File](p) + if bufSize > 0 and bufSize <= high(cint).int: + discard c_setvbuf(f, nil, IOFBF, bufSize.cint) + elif bufSize == 0: + discard c_setvbuf(f, nil, IONBF, 0) + +proc reopen*(f: File, filename: string, mode: FileMode = fmRead): bool {. + tags: [], benign.} = + ## reopens the file `f` with given `filename` and `mode`. This + ## is often used to redirect the `stdin`, `stdout` or `stderr` + ## file variables. + ## + ## Default mode is readonly. Returns true iff the file could be reopened. + var p: pointer = freopen(filename, FormatOpen[mode], f) + result = p != nil + +proc open*(f: var File, filehandle: FileHandle, + mode: FileMode = fmRead): bool {.tags: [], raises: [], benign.} = + ## Creates a ``File`` from a `filehandle` with given `mode`. + ## + ## Default mode is readonly. Returns true iff the file could be opened. + + f = c_fdopen(filehandle, FormatOpen[mode]) + result = f != nil + +proc open*(filename: string, + mode: FileMode = fmRead, bufSize: int = -1): File = + ## Opens a file named `filename` with given `mode`. + ## + ## Default mode is readonly. Raises an ``IOError`` if the file + ## could not be opened. + if not open(result, filename, mode, bufSize): + sysFatal(IOError, "cannot open: " & filename) + +proc setFilePos*(f: File, pos: int64, relativeTo: FileSeekPos = fspSet) {.benign.} = + ## sets the position of the file pointer that is used for read/write + ## operations. The file's first byte has the index zero. + if c_fseek(f, pos, cint(relativeTo)) != 0: + raiseEIO("cannot set file position") + +proc getFilePos*(f: File): int64 {.benign.} = + ## retrieves the current position of the file pointer that is used to + ## read from the file `f`. The file's first byte has the index zero. + result = c_ftell(f) + if result < 0: raiseEIO("cannot retrieve file position") + +proc getFileSize*(f: File): int64 {.tags: [ReadIOEffect], benign.} = + ## retrieves the file size (in bytes) of `f`. + var oldPos = getFilePos(f) + discard c_fseek(f, 0, 2) # seek the end of the file + result = getFilePos(f) + setFilePos(f, oldPos) + +proc setStdIoUnbuffered*() {.tags: [], benign.} = + ## Configures `stdin`, `stdout` and `stderr` to be unbuffered. + when declared(stdout): + discard c_setvbuf(stdout, nil, IONBF, 0) + when declared(stderr): + discard c_setvbuf(stderr, nil, IONBF, 0) + when declared(stdin): + discard c_setvbuf(stdin, nil, IONBF, 0) + +when declared(stdout): + when defined(windows) and compileOption("threads"): + const insideRLocksModule = false + include "system/syslocks" + + var echoLock: SysLock + initSysLock echoLock + + proc echoBinSafe(args: openArray[string]) {.compilerProc.} = + # flockfile deadlocks some versions of Android 5.x.x + when not defined(windows) and not defined(android) and not defined(nintendoswitch): + proc flockfile(f: File) {.importc, noDecl.} + proc funlockfile(f: File) {.importc, noDecl.} + flockfile(stdout) + when defined(windows) and compileOption("threads"): + acquireSys echoLock + for s in args: + discard c_fwrite(s.cstring, s.len, 1, stdout) + const linefeed = "\n" # can be 1 or more chars + discard c_fwrite(linefeed.cstring, linefeed.len, 1, stdout) + discard c_fflush(stdout) + when not defined(windows) and not defined(android) and not defined(nintendoswitch): + funlockfile(stdout) + when defined(windows) and compileOption("threads"): + releaseSys echoLock + + +when defined(windows) and not defined(nimscript): + # work-around C's sucking abstraction: + # BUGFIX: stdin and stdout should be binary files! + proc c_setmode(handle, mode: cint) {. + importc: when defined(bcc): "setmode" else: "_setmode", + header: "".} + var + O_BINARY {.importc: "_O_BINARY", header:"".}: cint + + # we use binary mode on Windows: + c_setmode(c_fileno(stdin), O_BINARY) + c_setmode(c_fileno(stdout), O_BINARY) + c_setmode(c_fileno(stderr), O_BINARY) + + +proc readFile*(filename: string): TaintedString {.tags: [ReadIOEffect], benign.} = + ## Opens a file named `filename` for reading, calls `readAll + ## <#readAll>`_ and closes the file afterwards. Returns the string. + ## Raises an IO exception in case of an error. If # you need to call + ## this inside a compile time macro you can use `staticRead + ## <#staticRead>`_. + var f: File + if open(f, filename): + try: + result = readAll(f).TaintedString + finally: + close(f) + else: + sysFatal(IOError, "cannot open: " & filename) + +proc writeFile*(filename, content: string) {.tags: [WriteIOEffect], benign.} = + ## Opens a file named `filename` for writing. Then writes the + ## `content` completely to the file and closes the file afterwards. + ## Raises an IO exception in case of an error. + var f: File + if open(f, filename, fmWrite): + try: + f.write(content) + finally: + close(f) + else: + sysFatal(IOError, "cannot open: " & filename) + +iterator lines*(filename: string): TaintedString {.tags: [ReadIOEffect].} = + ## Iterates over any line in the file named `filename`. + ## + ## If the file does not exist `IOError` is raised. The trailing newline + ## character(s) are removed from the iterated lines. Example: + ## + ## .. code-block:: nim + ## import strutils + ## + ## proc transformLetters(filename: string) = + ## var buffer = "" + ## for line in filename.lines: + ## buffer.add(line.replace("a", "0") & '\x0A') + ## writeFile(filename, buffer) + var f = open(filename, bufSize=8000) + defer: close(f) + var res = TaintedString(newStringOfCap(80)) + while f.readLine(res): yield res + +iterator lines*(f: File): TaintedString {.tags: [ReadIOEffect].} = + ## Iterate over any line in the file `f`. + ## + ## The trailing newline character(s) are removed from the iterated lines. + ## Example: + ## + ## .. code-block:: nim + ## proc countZeros(filename: File): tuple[lines, zeros: int] = + ## for line in filename.lines: + ## for letter in line: + ## if letter == '0': + ## result.zeros += 1 + ## result.lines += 1 + var res = TaintedString(newStringOfCap(80)) + while f.readLine(res): yield res diff --git a/lib/system/mmdisp.nim b/lib/system/mmdisp.nim index 9cc7ab323153..9284f07d215e 100644 --- a/lib/system/mmdisp.nim +++ b/lib/system/mmdisp.nim @@ -62,7 +62,7 @@ const proc raiseOutOfMem() {.noinline.} = if outOfMemHook != nil: outOfMemHook() - echo("out of memory") + cstderr.rawWrite("out of memory") quit(1) when defined(boehmgc): diff --git a/lib/system/sysio.nim b/lib/system/sysio.nim index 5b0278d7454b..f49d681bf6c5 100644 --- a/lib/system/sysio.nim +++ b/lib/system/sysio.nim @@ -15,439 +15,5 @@ {.push debugger:off .} # the user does not want to trace a part # of the standard library! -when defined(windows): - proc c_fdopen(filehandle: cint, mode: cstring): File {. - importc: "_fdopen", header: "".} -else: - proc c_fdopen(filehandle: cint, mode: cstring): File {. - importc: "fdopen", header: "".} -proc c_fputs(c: cstring, f: File): cint {. - importc: "fputs", header: "", tags: [WriteIOEffect].} -proc c_fgets(c: cstring, n: cint, f: File): cstring {. - importc: "fgets", header: "", tags: [ReadIOEffect].} -proc c_fgetc(stream: File): cint {. - importc: "fgetc", header: "", tags: [ReadIOEffect].} -proc c_ungetc(c: cint, f: File): cint {. - importc: "ungetc", header: "", tags: [].} -proc c_putc(c: cint, stream: File): cint {. - importc: "putc", header: "", tags: [WriteIOEffect].} -proc c_fflush(f: File): cint {. - importc: "fflush", header: "".} -proc c_fclose(f: File): cint {. - importc: "fclose", header: "".} -proc c_clearerr(f: File) {. - importc: "clearerr", header: "".} -proc c_feof(f: File): cint {. - importc: "feof", header: "".} - -when not declared(c_fwrite): - proc c_fwrite(buf: pointer, size, n: csize, f: File): cint {. - importc: "fwrite", header: "".} - -# C routine that is used here: -proc c_fread(buf: pointer, size, n: csize, f: File): csize {. - importc: "fread", header: "", tags: [ReadIOEffect].} -when defined(windows): - when not defined(amd64): - proc c_fseek(f: File, offset: int64, whence: cint): cint {. - importc: "fseek", header: "", tags: [].} - proc c_ftell(f: File): int64 {. - importc: "ftell", header: "", tags: [].} - else: - proc c_fseek(f: File, offset: int64, whence: cint): cint {. - importc: "_fseeki64", header: "", tags: [].} - proc c_ftell(f: File): int64 {. - importc: "_ftelli64", header: "", tags: [].} -else: - proc c_fseek(f: File, offset: int64, whence: cint): cint {. - importc: "fseeko", header: "", tags: [].} - proc c_ftell(f: File): int64 {. - importc: "ftello", header: "", tags: [].} -proc c_ferror(f: File): cint {. - importc: "ferror", header: "", tags: [].} -proc c_setvbuf(f: File, buf: pointer, mode: cint, size: csize): cint {. - importc: "setvbuf", header: "", tags: [].} - -proc raiseEIO(msg: string) {.noinline, noreturn.} = - sysFatal(IOError, msg) - -proc raiseEOF() {.noinline, noreturn.} = - sysFatal(EOFError, "EOF reached") - -proc strerror(errnum: cint): cstring {.importc, header: "".} - -when not defined(NimScript): - var - errno {.importc, header: "".}: cint ## error variable - -proc checkErr(f: File) = - when not defined(NimScript): - if c_ferror(f) != 0: - let msg = "errno: " & $errno & " `" & $strerror(errno) & "`" - c_clearerr(f) - raiseEIO(msg) - else: - # shouldn't happen - quit(1) - -{.push stackTrace:off, profiler:off.} -proc readBuffer(f: File, buffer: pointer, len: Natural): int = - result = c_fread(buffer, 1, len, f) - if result != len: checkErr(f) - -proc readBytes(f: File, a: var openArray[int8|uint8], start, len: Natural): int = - result = readBuffer(f, addr(a[start]), len) - -proc readChars(f: File, a: var openArray[char], start, len: Natural): int = - if (start + len) > len(a): - raiseEIO("buffer overflow: (start+len) > length of openarray buffer") - result = readBuffer(f, addr(a[start]), len) - -proc write(f: File, c: cstring) = - discard c_fputs(c, f) - checkErr(f) - -proc writeBuffer(f: File, buffer: pointer, len: Natural): int = - result = c_fwrite(buffer, 1, len, f) - checkErr(f) - -proc writeBytes(f: File, a: openArray[int8|uint8], start, len: Natural): int = - var x = cast[ptr UncheckedArray[int8]](a) - result = writeBuffer(f, addr(x[int(start)]), len) -proc writeChars(f: File, a: openArray[char], start, len: Natural): int = - var x = cast[ptr UncheckedArray[int8]](a) - result = writeBuffer(f, addr(x[int(start)]), len) - -proc write(f: File, s: string) = - if writeBuffer(f, cstring(s), s.len) != s.len: - raiseEIO("cannot write string to file") -{.pop.} - -when NoFakeVars: - when defined(windows): - const - IOFBF = cint(0) - IONBF = cint(4) - else: - # On all systems I could find, including Linux, Mac OS X, and the BSDs - const - IOFBF = cint(0) - IONBF = cint(2) -else: - var - IOFBF {.importc: "_IOFBF", nodecl.}: cint - IONBF {.importc: "_IONBF", nodecl.}: cint - -const - BufSize = 4000 - -proc close*(f: File) = - if not f.isNil: - discard c_fclose(f) - -proc readChar(f: File): char = - let x = c_fgetc(f) - if x < 0: - checkErr(f) - raiseEOF() - result = char(x) - -proc flushFile*(f: File) = discard c_fflush(f) -proc getFileHandle*(f: File): FileHandle = c_fileno(f) - -proc readLine(f: File, line: var TaintedString): bool = - var pos = 0 - - # Use the currently reserved space for a first try - var sp = max(line.string.len, 80) - line.string.setLen(sp) - - while true: - # memset to \L so that we can tell how far fgets wrote, even on EOF, where - # fgets doesn't append an \L - nimSetMem(addr line.string[pos], '\L'.ord, sp) - var fgetsSuccess = c_fgets(addr line.string[pos], sp.cint, f) != nil - if not fgetsSuccess: checkErr(f) - let m = c_memchr(addr line.string[pos], '\L'.ord, sp) - if m != nil: - # \l found: Could be our own or the one by fgets, in any case, we're done - var last = cast[ByteAddress](m) - cast[ByteAddress](addr line.string[0]) - if last > 0 and line.string[last-1] == '\c': - line.string.setLen(last-1) - return last > 1 or fgetsSuccess - # We have to distinguish between two possible cases: - # \0\l\0 => line ending in a null character. - # \0\l\l => last line without newline, null was put there by fgets. - elif last > 0 and line.string[last-1] == '\0': - if last < pos + sp - 1 and line.string[last+1] != '\0': - dec last - line.string.setLen(last) - return last > 0 or fgetsSuccess - else: - # fgets will have inserted a null byte at the end of the string. - dec sp - # No \l found: Increase buffer and read more - inc pos, sp - sp = 128 # read in 128 bytes at a time - line.string.setLen(pos+sp) - -proc readLine(f: File): TaintedString = - result = TaintedString(newStringOfCap(80)) - if not readLine(f, result): raiseEOF() - -proc write(f: File, i: int) = - when sizeof(int) == 8: - if c_fprintf(f, "%lld", i) < 0: checkErr(f) - else: - if c_fprintf(f, "%ld", i) < 0: checkErr(f) - -proc write(f: File, i: BiggestInt) = - when sizeof(BiggestInt) == 8: - if c_fprintf(f, "%lld", i) < 0: checkErr(f) - else: - if c_fprintf(f, "%ld", i) < 0: checkErr(f) - -proc write(f: File, b: bool) = - if b: write(f, "true") - else: write(f, "false") -proc write(f: File, r: float32) = - if c_fprintf(f, "%.16g", r) < 0: checkErr(f) -proc write(f: File, r: BiggestFloat) = - if c_fprintf(f, "%.16g", r) < 0: checkErr(f) - -proc write(f: File, c: char) = discard c_putc(cint(c), f) -proc write(f: File, a: varargs[string, `$`]) = - for x in items(a): write(f, x) - -proc readAllBuffer(file: File): string = - # This proc is for File we want to read but don't know how many - # bytes we need to read before the buffer is empty. - result = "" - var buffer = newString(BufSize) - while true: - var bytesRead = readBuffer(file, addr(buffer[0]), BufSize) - if bytesRead == BufSize: - result.add(buffer) - else: - buffer.setLen(bytesRead) - result.add(buffer) - break - -proc rawFileSize(file: File): int64 = - # this does not raise an error opposed to `getFileSize` - var oldPos = c_ftell(file) - discard c_fseek(file, 0, 2) # seek the end of the file - result = c_ftell(file) - discard c_fseek(file, oldPos, 0) - -proc endOfFile(f: File): bool = - var c = c_fgetc(f) - discard c_ungetc(c, f) - return c < 0'i32 - #result = c_feof(f) != 0 - -proc readAllFile(file: File, len: int64): string = - # We acquire the filesize beforehand and hope it doesn't change. - # Speeds things up. - result = newString(len) - let bytes = readBuffer(file, addr(result[0]), len) - if endOfFile(file): - if bytes < len: - result.setLen(bytes) - else: - # We read all the bytes but did not reach the EOF - # Try to read it as a buffer - result.add(readAllBuffer(file)) - -proc readAllFile(file: File): string = - var len = rawFileSize(file) - result = readAllFile(file, len) - -proc readAll(file: File): TaintedString = - # Separate handling needed because we need to buffer when we - # don't know the overall length of the File. - when declared(stdin): - let len = if file != stdin: rawFileSize(file) else: -1 - else: - let len = rawFileSize(file) - if len > 0: - result = readAllFile(file, len).TaintedString - else: - result = readAllBuffer(file).TaintedString - -proc writeLn[Ty](f: File, x: varargs[Ty, `$`]) = - for i in items(x): - write(f, i) - write(f, "\n") - -proc writeLine[Ty](f: File, x: varargs[Ty, `$`]) = - for i in items(x): - write(f, i) - write(f, "\n") - -when declared(stdout): - proc rawEcho(x: string) {.inline, compilerproc.} = write(stdout, x) - proc rawEchoNL() {.inline, compilerproc.} = write(stdout, "\n") - -# interface to the C procs: - -include "system/widestrs" - -when defined(windows) and not defined(useWinAnsi): - when defined(cpp): - proc wfopen(filename, mode: WideCString): pointer {. - importcpp: "_wfopen((const wchar_t*)#, (const wchar_t*)#)", nodecl.} - proc wfreopen(filename, mode: WideCString, stream: File): File {. - importcpp: "_wfreopen((const wchar_t*)#, (const wchar_t*)#, #)", nodecl.} - else: - proc wfopen(filename, mode: WideCString): pointer {. - importc: "_wfopen", nodecl.} - proc wfreopen(filename, mode: WideCString, stream: File): File {. - importc: "_wfreopen", nodecl.} - - proc fopen(filename, mode: cstring): pointer = - var f = newWideCString(filename) - var m = newWideCString(mode) - result = wfopen(f, m) - - proc freopen(filename, mode: cstring, stream: File): File = - var f = newWideCString(filename) - var m = newWideCString(mode) - result = wfreopen(f, m, stream) - -else: - proc fopen(filename, mode: cstring): pointer {.importc: "fopen", noDecl.} - proc freopen(filename, mode: cstring, stream: File): File {. - importc: "freopen", nodecl.} - -const - FormatOpen: array[FileMode, string] = ["rb", "wb", "w+b", "r+b", "ab"] - #"rt", "wt", "w+t", "r+t", "at" - # we always use binary here as for Nim the OS line ending - # should not be translated. - -when defined(posix) and not defined(nimscript): - when defined(linux) and defined(amd64): - type - Mode {.importc: "mode_t", header: "".} = cint - - # fillers ensure correct size & offsets - Stat {.importc: "struct stat", - header: "", final, pure.} = object ## struct stat - filler_1: array[24, char] - st_mode: Mode ## Mode of file - filler_2: array[144 - 24 - 4, char] - - proc S_ISDIR(m: Mode): bool = - ## Test for a directory. - (m and 0o170000) == 0o40000 - - else: - type - Mode {.importc: "mode_t", header: "".} = cint - - Stat {.importc: "struct stat", - header: "", final, pure.} = object ## struct stat - st_mode: Mode ## Mode of file - - proc S_ISDIR(m: Mode): bool {.importc, header: "".} - ## Test for a directory. - - proc c_fstat(a1: cint, a2: var Stat): cint {. - importc: "fstat", header: "".} - -proc open(f: var File, filename: string, - mode: FileMode = fmRead, - bufSize: int = -1): bool = - var p: pointer = fopen(filename, FormatOpen[mode]) - if p != nil: - when defined(posix) and not defined(nimscript): - # How `fopen` handles opening a directory is not specified in ISO C and - # POSIX. We do not want to handle directories as regular files that can - # be opened. - var f2 = cast[File](p) - var res: Stat - if c_fstat(getFileHandle(f2), res) >= 0'i32 and S_ISDIR(res.st_mode): - close(f2) - return false - result = true - f = cast[File](p) - if bufSize > 0 and bufSize <= high(cint).int: - discard c_setvbuf(f, nil, IOFBF, bufSize.cint) - elif bufSize == 0: - discard c_setvbuf(f, nil, IONBF, 0) - -proc reopen(f: File, filename: string, mode: FileMode = fmRead): bool = - var p: pointer = freopen(filename, FormatOpen[mode], f) - result = p != nil - -proc open(f: var File, filehandle: FileHandle, mode: FileMode): bool = - f = c_fdopen(filehandle, FormatOpen[mode]) - result = f != nil - -proc setFilePos(f: File, pos: int64, relativeTo: FileSeekPos = fspSet) = - if c_fseek(f, pos, cint(relativeTo)) != 0: - raiseEIO("cannot set file position") - -proc getFilePos(f: File): int64 = - result = c_ftell(f) - if result < 0: raiseEIO("cannot retrieve file position") - -proc getFileSize(f: File): int64 = - var oldPos = getFilePos(f) - discard c_fseek(f, 0, 2) # seek the end of the file - result = getFilePos(f) - setFilePos(f, oldPos) - -proc readFile(filename: string): TaintedString = - var f: File - if open(f, filename): - try: - result = readAll(f).TaintedString - finally: - close(f) - else: - sysFatal(IOError, "cannot open: ", filename) - -proc writeFile(filename, content: string) = - var f: File - if open(f, filename, fmWrite): - try: - f.write(content) - finally: - close(f) - else: - sysFatal(IOError, "cannot open: ", filename) - -proc setStdIoUnbuffered() = - when declared(stdout): - discard c_setvbuf(stdout, nil, IONBF, 0) - when declared(stderr): - discard c_setvbuf(stderr, nil, IONBF, 0) - when declared(stdin): - discard c_setvbuf(stdin, nil, IONBF, 0) - -when declared(stdout): - when defined(windows) and compileOption("threads"): - var echoLock: SysLock - initSysLock echoLock - - proc echoBinSafe(args: openArray[string]) {.compilerProc.} = - # flockfile deadlocks some versions of Android 5.x.x - when not defined(windows) and not defined(android) and not defined(nintendoswitch): - proc flockfile(f: File) {.importc, noDecl.} - proc funlockfile(f: File) {.importc, noDecl.} - flockfile(stdout) - when defined(windows) and compileOption("threads"): - acquireSys echoLock - for s in args: - discard c_fwrite(s.cstring, s.len, 1, stdout) - const linefeed = "\n" # can be 1 or more chars - discard c_fwrite(linefeed.cstring, linefeed.len, 1, stdout) - discard c_fflush(stdout) - when not defined(windows) and not defined(android) and not defined(nintendoswitch): - funlockfile(stdout) - when defined(windows) and compileOption("threads"): - releaseSys echoLock {.pop.} diff --git a/lib/system/threads.nim b/lib/system/threads.nim index f89de4376da1..bbe170376da7 100644 --- a/lib/system/threads.nim +++ b/lib/system/threads.nim @@ -325,11 +325,8 @@ when not defined(useNimRtl): when emulatedThreadVars: if nimThreadVarsSize() > sizeof(ThreadLocalStorage): - echo "too large thread local storage size requested ", - "(", nimThreadVarsSize(), "/", sizeof(ThreadLocalStorage), "). ", - "Use -d:\"nimTlsSize=", nimThreadVarsSize(), - "\" to preallocate sufficient storage." - + c_fprintf(cstderr, """too large thread local storage size requested, +use -d:\"nimTlsSize=X\" to setup even more or stop using unittest.nim""") quit 1 when hasSharedHeap and not defined(boehmgc) and not defined(gogc) and not defined(nogc): diff --git a/lib/system/widestrs.nim b/lib/system/widestrs.nim index 85e5e1462b82..a52e58ac36fe 100644 --- a/lib/system/widestrs.nim +++ b/lib/system/widestrs.nim @@ -10,8 +10,8 @@ # Nim support for C/C++'s `wide strings`:idx:. This is part of the system # module! Do not import it directly! -when not declared(ThisIsSystem): - {.error: "You must not import this module explicitly".} +#when not declared(ThisIsSystem): +# {.error: "You must not import this module explicitly".} type Utf16Char* = distinct int16 diff --git a/tests/effects/teffects1.nim b/tests/effects/teffects1.nim index 767845cb4432..8f827110cab5 100644 --- a/tests/effects/teffects1.nim +++ b/tests/effects/teffects1.nim @@ -1,10 +1,10 @@ discard """ errormsg: "can raise an unlisted exception: ref IOError" - file: "system.nim" + file: "io.nim" """ type - TObj = object {.pure, inheritable.} + TObj {.pure, inheritable.} = object TObjB = object of TObj a, b, c: string