1 //: Addresses help us spend less time copying data around. 2 3 //: So far we've been operating on primitives like numbers and characters, and 4 //: we've started combining these primitives together into larger logical 5 //: units (containers or arrays) that may contain many different primitives at 6 //: once. Containers and arrays can grow quite large in complex programs, and 7 //: we'd like some way to efficiently share them between recipes without 8 //: constantly having to make copies. Right now 'next-ingredient' and 'return' 9 //: copy data across recipe boundaries. To avoid copying large quantities of 10 //: data around, we'll use *addresses*. An address is a bookmark to some 11 //: arbitrary quantity of data (the *payload*). It's a primitive, so it's as 12 //: efficient to copy as a number. To read or modify the payload 'pointed to' 13 //: by an address, we'll perform a *lookup*. 14 //: 15 //: The notion of 'lookup' isn't an instruction like 'add' or 'subtract'. 16 //: Instead it's an operation that can be performed when reading any of the 17 //: ingredients of an instruction, and when writing to any of the products. To 18 //: write to the payload of an ingredient rather than its value, simply add 19 //: the /lookup property to it. Modern computers provide efficient support for 20 //: addresses and lookups, making this a realistic feature. 21 //: 22 //: To recap: an address is a bookmark to some potentially large payload, and 23 //: you can replace any ingredient or product with a lookup to an address of 24 //: the appropriate type. But how do we get addresses to begin with? That 25 //: requires a little more explanation. Once we introduce the notion of 26 //: bookmarks to data, we have to think about the life cycle of a piece of 27 //: data and its bookmarks (because remember, bookmarks can be copied around 28 //: just like anything else). Otherwise several bad outcomes can result (and 29 //: indeed *have* resulted in past languages like C): 30 //: 31 //: a) You can run out of memory if you don't have a way to reclaim 32 //: data. 33 //: b) If you allow data to be reclaimed, you have to be careful not to 34 //: leave any stale addresses pointing at it. Otherwise your program might 35 //: try to lookup such an address and find something unexpected. Such 36 //: problems can be very hard to track down, and they can also be exploited 37 //: to break into your computer over the network, etc. 38 //: 39 //: To avoid these problems, we introduce the notion of a *reference count* or 40 //: refcount. The life cycle of a bit of data accessed through addresses looks 41 //: like this. 42 //: 43 //: We create space in computer memory for it using the 'new' instruction. 44 //: The 'new' instruction takes a type as an ingredient, allocates 45 //: sufficient space to hold that type, and returns an address (bookmark) 46 //: to the allocated space. 47 //: 48 //: x:address:num <- new number:type 49 //: 50 //: +------------+ 51 //: x -------> | number | 52 //: +------------+ 53 //: 54 //: That isn't entirely accurate. Under the hood, 'new' allocates an extra 55 //: number -- the refcount: 56 //: 57 //: +------------+------------+ 58 //: x -------> | refcount | number | 59 //: +------------+------------+ 60 //: 61 pre { line-height: 125%; } td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } .highlight .hll { background-color: #ffffcc } .highlight .c { color: #888888 } /* Comment */ .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ .highlight .k { color: #008800; font-weight: bold } /* Keyword */ .highlight .ch { color: #888888 } /* Comment.Hashbang */ .highlight .cm { color: #888888 } /* Comment.Multiline */ .highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */ .highlight .cpf { color: #888888 } /* Comment.PreprocFile */ .highlight .c1 { color: #888888 } /* Comment.Single */ .highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */ .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ .highlight .gr { color: #aa0000 } /* Generic.Error */ .highlight .gh { color: #333333 } /* Generic.Heading */ .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ .highlight .go { color: #888888 } /* Generic.Output */ .highlight .gp { color: #555555 } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #666666 } /* Generic.Subheading */ .highlight .gt { color: #aa0000 } /* Generic.Traceback */ .highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */ .highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */ .highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */ .highlight .kp { color: #008800 } /* Keyword.Pseudo */ .highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */ .highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ .highlight .na { color: #336699 } /* Name.Attribute */ .highlight .nb { color: #003388 } /* Name.Builtin */ .highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */ .highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ .highlight .nd { color: #555555 } /* Name.Decorator */ .highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ .highlight .nl { color: #336699; font-style: italic } /* Name.Label */ .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */# # # Nim's Runtime Library # (c) Copyright 2021 Nim contributors # # See the file "copying.txt", included in this # distribution, for details about the copyright. # ## This module creates temporary files and directories. ## ## Experimental API, subject to change. #[ See also: * `GetTempFileName` (on windows), refs https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettempfilenamea * `mkstemp` (posix), refs https://man7.org/linux/man-pages/man3/mkstemp.3.html ]# import os, random when defined(nimPreviewSlimSystem): import std/syncio const maxRetry = 10000 letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" nimTempPathLength {.intdefine.} = 8 when defined(windows): import winlean when defined(nimPreviewSlimSystem): import std/widestrs var O_RDWR {.importc: "_O_RDWR", header: "<fcntl.h>".}: cint proc c_fdopen( filehandle: cint, mode: cstring ): File {.importc: "_fdopen",header: "<stdio.h>".} proc open_osfhandle(osh: Handle, mode: cint): cint {. importc: "_open_osfhandle", header: "<io.h>".} proc close_osfandle(fd: cint): cint {. importc: "_close", header: "<io.h>".} else: import posix proc c_fdopen( filehandle: cint, mode: cstring ): File {.importc: "fdopen",header: "<stdio.h>".} proc safeOpen(filename: string): File = ## Open files exclusively; returns `nil` if the file already exists. # xxx this should be clarified; it doesn't in particular prevent other processes # from opening the file, at least currently. when defined(windows): let dwShareMode = FILE_SHARE_DELETE or FILE_SHARE_READ or FILE_SHARE_WRITE let dwCreation = CREATE_NEW let dwFlags = FILE_FLAG_BACKUP_SEMANTICS or FILE_ATTRIBUTE_NORMAL let handle = createFileW(newWideCString(filename), GENERIC_READ or GENERIC_WRITE, dwShareMode, nil, dwCreation, dwFlags, Handle(0)) if handle == INVALID_HANDLE_VALUE: if getLastError() == ERROR_FILE_EXISTS: return nil else: raiseOSError(osLastError(), filename) let fileHandle = open_osfhandle(handle, O_RDWR) if fileHandle == -1: discard closeHandle(handle) raiseOSError(osLastError(), filename) result = c_fdopen(fileHandle, "w+") if result == nil: discard close_osfandle(fileHandle) raiseOSError(osLastError(), filename) else: # xxx we need a `proc toMode(a: FilePermission): Mode`, possibly by # exposing fusion/filepermissions.fromFilePermissions to stdlib; then we need # to expose a `perm` param so users can customize this (e.g. the temp file may # need execute permissions), and figure out how to make the API cross platform. let mode = Mode(S_IRUSR or S_IWUSR) let flags = posix.O_RDWR or posix.O_CREAT or posix.O_EXCL let fileHandle = posix.open(filename, flags, mode) if fileHandle == -1: if errno == EEXIST: # xxx `getLastError()` should be defined on posix too and resolve to `errno`? return nil else: raiseOSError(osLastError(), filename) result = c_fdopen(fileHandle, "w+") if result == nil: discard posix.close(fileHandle) # TODO handles failure when closing file raiseOSError(osLastError(), filename) type NimTempPathState = object state: Rand isInit: bool var nimTempPathState {.threadvar.}: NimTempPathState template randomPathName(length: Natural): string = var res = newString(length) if not nimTempPathState.isInit: nimTempPathState.isInit = true nimTempPathState.state = initRand() for i in 0 ..< length: res[i] = nimTempPathState.state.sample(letters) res proc getTempDirImpl(dir: string): string {.inline.} = result = dir if result.len == 0: result = getTempDir() proc genTempPath*(prefix, suffix: string, dir = ""): string = ## Generates a path name in `dir`. ## ## The path begins with `prefix` and ends with `suffix`. ## ## .. note:: `dir` must exist (empty `dir` will resolve to `getTempDir <os.html#getTempDir>`_). let dir = getTempDirImpl(dir) result = dir / (prefix & randomPathName(nimTempPathLength) & suffix) proc createTempFile*(prefix, suffix: string, dir = ""): tuple[cfile: File, path: string] = ## Creates a new temporary file in the directory `dir`. ## ## This generates a path name using `genTempPath(prefix, suffix, dir)` and ## returns a file handle to an open file and the path of that file, possibly after ## retrying to ensure it doesn't already exist. ## ## If failing to create a temporary file, `OSError` will be raised. ## ## .. note:: It is the caller's responsibility to close `result.cfile` and ## remove `result.file` when no longer needed. ## .. note:: `dir` must exist (empty `dir` will resolve to `getTempDir <os.html#getTempDir>`_). runnableExamples: import std/os doAssertRaises(OSError): discard createTempFile("", "", "nonexistent") let (cfile, path) = createTempFile("tmpprefix_", "_end.tmp") # path looks like: getTempDir() / "tmpprefix_FDCIRZA0_end.tmp" cfile.write "foo" cfile.setFilePos 0 assert readAll(cfile) == "foo" close cfile assert readFile(path) == "foo" removeFile(path) # xxx why does above work without `cfile.flushFile` ? let dir = getTempDirImpl(dir) for i in 0 ..< maxRetry: result.path = genTempPath(prefix, suffix, dir) result.cfile = safeOpen(result.path) if result.cfile != nil: return raise newException(OSError, "Failed to create a temporary file under directory " & dir) proc createTempDir*(prefix, suffix: string, dir = ""): string = ## Creates a new temporary directory in the directory `dir`. ## ## This generates a dir name using `genTempPath(prefix, suffix, dir)`, creates ## the directory and returns it, possibly after retrying to ensure it doesn't ## already exist. ## ## If failing to create a temporary directory, `OSError` will be raised. ## ## .. note:: It is the caller's responsibility to remove the directory when no longer needed. ## .. note:: `dir` must exist (empty `dir` will resolve to `getTempDir <os.html#getTempDir>`_). runnableExamples: import std/os doAssertRaises(OSError): discard createTempDir("", "", "nonexistent") let dir = createTempDir("tmpprefix_", "_end") # dir looks like: getTempDir() / "tmpprefix_YEl9VuVj_end" assert dirExists(dir) removeDir(dir) let dir = getTempDirImpl(dir) for i in 0 ..< maxRetry: result = genTempPath(prefix, suffix, dir) if not existsOrCreateDir(result): return raise newException(OSError, "Failed to create a temporary directory under directory " & dir)