envPairs works in vm, nims (#18615)

* envPairs works in vm, nims

* fixup
This commit is contained in:
Timothee Cour 2021-09-29 00:32:39 -07:00 • committed by GitHub
commit f061971a9b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 213 additions and 210 deletions

View file

@ -13,7 +13,7 @@ from std/math import sqrt, ln, log10, log2, exp, round, arccos, arcsin,
arctan, arctan2, cos, cosh, hypot, sinh, sin, tan, tanh, pow, trunc, arctan, arctan2, cos, cosh, hypot, sinh, sin, tan, tanh, pow, trunc,
floor, ceil, `mod`, cbrt, arcsinh, arccosh, arctanh, erf, erfc, gamma, floor, ceil, `mod`, cbrt, arcsinh, arccosh, arctanh, erf, erfc, gamma,
lgamma lgamma
from std/sequtils import toSeq
when declared(math.copySign): when declared(math.copySign):
# pending bug #18762, avoid renaming math # pending bug #18762, avoid renaming math
from std/math as math2 import copySign from std/math as math2 import copySign
@ -22,8 +22,8 @@ when declared(math.signbit):
# ditto # ditto
from std/math as math3 import signbit from std/math as math3 import signbit
from std/os import getEnv, existsEnv, delEnv, putEnv, dirExists, fileExists, walkDir, from std/os import getEnv, existsEnv, delEnv, putEnv, envPairs,
getAppFilename, raiseOSError, osLastError dirExists, fileExists, walkDir, getAppFilename, raiseOSError, osLastError
from std/md5 import getMD5 from std/md5 import getMD5
from std/times import cpuTime from std/times import cpuTime
@ -156,6 +156,12 @@ proc stackTrace2(c: PCtx, msg: string, n: PNode) =
stackTrace(c, PStackFrame(prc: c.prc.sym, comesFrom: 0, next: nil), c.exceptionInstr, msg, n.info) stackTrace(c, PStackFrame(prc: c.prc.sym, comesFrom: 0, next: nil), c.exceptionInstr, msg, n.info)
proc registerAdditionalOps*(c: PCtx) = proc registerAdditionalOps*(c: PCtx) =
template wrapIterator(fqname: string, iter: untyped) =
registerCallback c, fqname, proc(a: VmArgs) =
setResult(a, toLit(toSeq(iter)))
proc gorgeExWrapper(a: VmArgs) = proc gorgeExWrapper(a: VmArgs) =
let ret = opGorge(getString(a, 0), getString(a, 1), getString(a, 2), let ret = opGorge(getString(a, 0), getString(a, 1), getString(a, 2),
a.currentLineInfo, c.config) a.currentLineInfo, c.config)
@ -341,3 +347,5 @@ proc registerAdditionalOps*(c: PCtx) =
let p = a.getVar(0) let p = a.getVar(0)
let x = a.getFloat(1) let x = a.getFloat(1)
addFloatSprintf(p.strVal, x) addFloatSprintf(p.strVal, x)
wrapIterator("stdlib.os.envPairsImplSeq"): envPairs()

View file

@ -3,189 +3,200 @@
when not declared(os) and not declared(ospaths): when not declared(os) and not declared(ospaths):
{.error: "This is an include file for os.nim!".} {.error: "This is an include file for os.nim!".}
when defined(nodejs): when not defined(nimscript):
proc getEnv*(key: string, default = ""): string {.tags: [ReadEnvEffect].} = when defined(nodejs):
var ret = default.cstring proc getEnv*(key: string, default = ""): string {.tags: [ReadEnvEffect].} =
let key2 = key.cstring var ret = default.cstring
{.emit: "const value = process.env[`key2`];".} let key2 = key.cstring
{.emit: "if (value !== undefined) { `ret` = value };".} {.emit: "const value = process.env[`key2`];".}
result = $ret {.emit: "if (value !== undefined) { `ret` = value };".}
result = $ret
proc existsEnv*(key: string): bool {.tags: [ReadEnvEffect].} = proc existsEnv*(key: string): bool {.tags: [ReadEnvEffect].} =
var key2 = key.cstring var key2 = key.cstring
var ret: bool var ret: bool
{.emit: "`ret` = `key2` in process.env;".} {.emit: "`ret` = `key2` in process.env;".}
result = ret result = ret
proc putEnv*(key, val: string) {.tags: [WriteEnvEffect].} = proc putEnv*(key, val: string) {.tags: [WriteEnvEffect].} =
var key2 = key.cstring var key2 = key.cstring
var val2 = val.cstring var val2 = val.cstring
{.emit: "process.env[`key2`] = `val2`;".} {.emit: "process.env[`key2`] = `val2`;".}
proc delEnv*(key: string) {.tags: [WriteEnvEffect].} = proc delEnv*(key: string) {.tags: [WriteEnvEffect].} =
var key2 = key.cstring var key2 = key.cstring
{.emit: "delete process.env[`key2`];".} {.emit: "delete process.env[`key2`];".}
iterator envPairs*(): tuple[key, value: string] {.tags: [ReadEnvEffect].} = iterator envPairsImpl(): tuple[key, value: string] {.tags: [ReadEnvEffect].} =
var num: int var num: int
var keys: RootObj var keys: RootObj
{.emit: "`keys` = Object.keys(process.env); `num` = `keys`.length;".} {.emit: "`keys` = Object.keys(process.env); `num` = `keys`.length;".}
for i in 0..<num: for i in 0..<num:
var key, value: cstring var key, value: cstring
{.emit: "`key` = `keys`[`i`]; `value` = process.env[`key`];".} {.emit: "`key` = `keys`[`i`]; `value` = process.env[`key`];".}
yield ($key, $value) yield ($key, $value)
# commented because it must keep working with js+VM # commented because it must keep working with js+VM
# elif defined(js): # elif defined(js):
# {.error: "requires -d:nodejs".} # {.error: "requires -d:nodejs".}
else:
proc c_getenv(env: cstring): cstring {.
importc: "getenv", header: "<stdlib.h>".}
when defined(windows):
proc c_putenv_s(envname: cstring, envval: cstring): cint {.importc: "_putenv_s", header: "<stdlib.h>".}
from std/private/win_setenv import setEnvImpl
else: else:
proc c_setenv(envname: cstring, envval: cstring, overwrite: cint): cint {.importc: "setenv", header: "<stdlib.h>".}
proc c_unsetenv(env: cstring): cint {.importc: "unsetenv", header: "<stdlib.h>".}
proc getEnv*(key: string, default = ""): string {.tags: [ReadEnvEffect].} = proc c_getenv(env: cstring): cstring {.
## Returns the value of the `environment variable`:idx: named `key`. importc: "getenv", header: "<stdlib.h>".}
##
## If the variable does not exist, `""` is returned. To distinguish
## whether a variable exists or it's value is just `""`, call
## `existsEnv(key) proc <#existsEnv,string>`_.
##
## See also:
## * `existsEnv proc <#existsEnv,string>`_
## * `putEnv proc <#putEnv,string,string>`_
## * `delEnv proc <#delEnv,string>`_
## * `envPairs iterator <#envPairs.i>`_
runnableExamples:
assert getEnv("unknownEnv") == ""
assert getEnv("unknownEnv", "doesn't exist") == "doesn't exist"
let env = c_getenv(key)
if env == nil: return default
result = $env
proc existsEnv*(key: string): bool {.tags: [ReadEnvEffect].} =
## Checks whether the environment variable named `key` exists.
## Returns true if it exists, false otherwise.
##
## See also:
## * `getEnv proc <#getEnv,string,string>`_
## * `putEnv proc <#putEnv,string,string>`_
## * `delEnv proc <#delEnv,string>`_
## * `envPairs iterator <#envPairs.i>`_
runnableExamples:
assert not existsEnv("unknownEnv")
return c_getenv(key) != nil
proc putEnv*(key, val: string) {.tags: [WriteEnvEffect].} =
## Sets the value of the `environment variable`:idx: named `key` to `val`.
## If an error occurs, `OSError` is raised.
##
## See also:
## * `getEnv proc <#getEnv,string,string>`_
## * `existsEnv proc <#existsEnv,string>`_
## * `delEnv proc <#delEnv,string>`_
## * `envPairs iterator <#envPairs.i>`_
when defined(windows): when defined(windows):
if key.len == 0 or '=' in key: proc c_putenv_s(envname: cstring, envval: cstring): cint {.importc: "_putenv_s", header: "<stdlib.h>".}
raise newException(OSError, "invalid key, got: " & $(key, val)) from std/private/win_setenv import setEnvImpl
if setEnvImpl(key, val, 1'i32) != 0'i32:
raiseOSError(osLastError(), $(key, val))
else: else:
if c_setenv(key, val, 1'i32) != 0'i32: proc c_setenv(envname: cstring, envval: cstring, overwrite: cint): cint {.importc: "setenv", header: "<stdlib.h>".}
raiseOSError(osLastError(), $(key, val)) proc c_unsetenv(env: cstring): cint {.importc: "unsetenv", header: "<stdlib.h>".}
proc delEnv*(key: string) {.tags: [WriteEnvEffect].} = proc getEnv*(key: string, default = ""): string {.tags: [ReadEnvEffect].} =
## Deletes the `environment variable`:idx: named `key`. ## Returns the value of the `environment variable`:idx: named `key`.
## If an error occurs, `OSError` is raised. ##
## ## If the variable does not exist, `""` is returned. To distinguish
## See also:ven ## whether a variable exists or it's value is just `""`, call
## * `getEnv proc <#getEnv,string,string>`_ ## `existsEnv(key) proc <#existsEnv,string>`_.
## * `existsEnv proc <#existsEnv,string>`_ ##
## * `putEnv proc <#putEnv,string,string>`_ ## See also:
## * `envPairs iterator <#envPairs.i>`_ ## * `existsEnv proc <#existsEnv,string>`_
template bail = raiseOSError(osLastError(), key) ## * `putEnv proc <#putEnv,string,string>`_
when defined(windows): ## * `delEnv proc <#delEnv,string>`_
#[ ## * `envPairs iterator <#envPairs.i>`_
# https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/putenv-s-wputenv-s?view=msvc-160 runnableExamples:
> You can remove a variable from the environment by specifying an empty string (that is, "") for value_string assert getEnv("unknownEnv") == ""
note that nil is not legal assert getEnv("unknownEnv", "doesn't exist") == "doesn't exist"
]#
if key.len == 0 or '=' in key:
raise newException(OSError, "invalid key, got: " & key)
if c_putenv_s(key, "") != 0'i32: bail
else:
if c_unsetenv(key) != 0'i32: bail
when defined(windows): let env = c_getenv(key)
when useWinUnicode: if env == nil: return default
when defined(cpp): result = $env
proc strEnd(cstr: WideCString, c = 0'i32): WideCString {.importcpp: "(NI16*)wcschr((const wchar_t *)#, #)",
header: "<string.h>".} proc existsEnv*(key: string): bool {.tags: [ReadEnvEffect].} =
## Checks whether the environment variable named `key` exists.
## Returns true if it exists, false otherwise.
##
## See also:
## * `getEnv proc <#getEnv,string,string>`_
## * `putEnv proc <#putEnv,string,string>`_
## * `delEnv proc <#delEnv,string>`_
## * `envPairs iterator <#envPairs.i>`_
runnableExamples:
assert not existsEnv("unknownEnv")
return c_getenv(key) != nil
proc putEnv*(key, val: string) {.tags: [WriteEnvEffect].} =
## Sets the value of the `environment variable`:idx: named `key` to `val`.
## If an error occurs, `OSError` is raised.
##
## See also:
## * `getEnv proc <#getEnv,string,string>`_
## * `existsEnv proc <#existsEnv,string>`_
## * `delEnv proc <#delEnv,string>`_
## * `envPairs iterator <#envPairs.i>`_
when defined(windows):
if key.len == 0 or '=' in key:
raise newException(OSError, "invalid key, got: " & $(key, val))
if setEnvImpl(key, val, 1'i32) != 0'i32:
raiseOSError(osLastError(), $(key, val))
else: else:
proc strEnd(cstr: WideCString, c = 0'i32): WideCString {.importc: "wcschr", if c_setenv(key, val, 1'i32) != 0'i32:
header: "<string.h>".} raiseOSError(osLastError(), $(key, val))
else:
proc strEnd(cstr: cstring, c = 0'i32): cstring {.importc: "strchr", proc delEnv*(key: string) {.tags: [WriteEnvEffect].} =
header: "<string.h>".} ## Deletes the `environment variable`:idx: named `key`.
elif defined(macosx) and not defined(ios) and not defined(emscripten): ## If an error occurs, `OSError` is raised.
# From the manual: ##
# Shared libraries and bundles don't have direct access to environ, ## See also:ven
# which is only available to the loader ld(1) when a complete program ## * `getEnv proc <#getEnv,string,string>`_
# is being linked. ## * `existsEnv proc <#existsEnv,string>`_
# The environment routines can still be used, but if direct access to ## * `putEnv proc <#putEnv,string,string>`_
# environ is needed, the _NSGetEnviron() routine, defined in ## * `envPairs iterator <#envPairs.i>`_
# <crt_externs.h>, can be used to retrieve the address of environ template bail = raiseOSError(osLastError(), key)
# at runtime. when defined(windows):
proc NSGetEnviron(): ptr cstringArray {.importc: "_NSGetEnviron", #[
header: "<crt_externs.h>".} # https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/putenv-s-wputenv-s?view=msvc-160
elif defined(haiku): > You can remove a variable from the environment by specifying an empty string (that is, "") for value_string
var gEnv {.importc: "environ", header: "<stdlib.h>".}: cstringArray note that nil is not legal
else: ]#
var gEnv {.importc: "environ".}: cstringArray if key.len == 0 or '=' in key:
raise newException(OSError, "invalid key, got: " & key)
if c_putenv_s(key, "") != 0'i32: bail
else:
if c_unsetenv(key) != 0'i32: bail
iterator envPairs*(): tuple[key, value: string] {.tags: [ReadEnvEffect].} =
## Iterate over all `environments variables`:idx:.
##
## In the first component of the tuple is the name of the current variable stored,
## in the second its value.
##
## See also:
## * `getEnv proc <#getEnv,string,string>`_
## * `existsEnv proc <#existsEnv,string>`_
## * `putEnv proc <#putEnv,string,string>`_
## * `delEnv proc <#delEnv,string>`_
when defined(windows): when defined(windows):
block: when useWinUnicode:
template impl(get_fun, typ, size, zero, free_fun) = when defined(cpp):
let env = get_fun() proc strEnd(cstr: WideCString, c = 0'i32): WideCString {.importcpp: "(NI16*)wcschr((const wchar_t *)#, #)",
var e = env header: "<string.h>".}
if e == nil: break
while true:
let eend = strEnd(e)
let kv = $e
let p = find(kv, '=')
yield (substr(kv, 0, p-1), substr(kv, p+1))
e = cast[typ](cast[ByteAddress](eend)+size)
if typeof(zero)(eend[1]) == zero: break
discard free_fun(env)
when useWinUnicode:
impl(getEnvironmentStringsW, WideCString, 2, 0, freeEnvironmentStringsW)
else: else:
impl(getEnvironmentStringsA, cstring, 1, '\0', freeEnvironmentStringsA) proc strEnd(cstr: WideCString, c = 0'i32): WideCString {.importc: "wcschr",
header: "<string.h>".}
else:
proc strEnd(cstr: cstring, c = 0'i32): cstring {.importc: "strchr",
header: "<string.h>".}
elif defined(macosx) and not defined(ios) and not defined(emscripten):
# From the manual:
# Shared libraries and bundles don't have direct access to environ,
# which is only available to the loader ld(1) when a complete program
# is being linked.
# The environment routines can still be used, but if direct access to
# environ is needed, the _NSGetEnviron() routine, defined in
# <crt_externs.h>, can be used to retrieve the address of environ
# at runtime.
proc NSGetEnviron(): ptr cstringArray {.importc: "_NSGetEnviron",
header: "<crt_externs.h>".}
elif defined(haiku):
var gEnv {.importc: "environ", header: "<stdlib.h>".}: cstringArray
else: else:
var i = 0 var gEnv {.importc: "environ".}: cstringArray
when defined(macosx) and not defined(ios) and not defined(emscripten):
var gEnv = NSGetEnviron()[] iterator envPairsImpl(): tuple[key, value: string] {.tags: [ReadEnvEffect].} =
while gEnv[i] != nil: when defined(windows):
let kv = $gEnv[i] block:
inc(i) template impl(get_fun, typ, size, zero, free_fun) =
let p = find(kv, '=') let env = get_fun()
yield (substr(kv, 0, p-1), substr(kv, p+1)) var e = env
if e == nil: break
while true:
let eend = strEnd(e)
let kv = $e
let p = find(kv, '=')
yield (substr(kv, 0, p-1), substr(kv, p+1))
e = cast[typ](cast[ByteAddress](eend)+size)
if typeof(zero)(eend[1]) == zero: break
discard free_fun(env)
when useWinUnicode:
impl(getEnvironmentStringsW, WideCString, 2, 0, freeEnvironmentStringsW)
else:
impl(getEnvironmentStringsA, cstring, 1, '\0', freeEnvironmentStringsA)
else:
var i = 0
when defined(macosx) and not defined(ios) and not defined(emscripten):
var gEnv = NSGetEnviron()[]
while gEnv[i] != nil:
let kv = $gEnv[i]
inc(i)
let p = find(kv, '=')
yield (substr(kv, 0, p-1), substr(kv, p+1))
proc envPairsImplSeq(): seq[tuple[key, value: string]] = discard # vmops
iterator envPairs*(): tuple[key, value: string] {.tags: [ReadEnvEffect].} =
## Iterate over all `environments variables`:idx:.
##
## In the first component of the tuple is the name of the current variable stored,
## in the second its value.
##
## Works in native backends, nodejs and vm, like the following APIs:
## * `getEnv proc <#getEnv,string,string>`_
## * `existsEnv proc <#existsEnv,string>`_
## * `putEnv proc <#putEnv,string,string>`_
## * `delEnv proc <#delEnv,string>`_
when nimvm:
for ai in envPairsImplSeq(): yield ai
else:
when defined(nimscript): discard
else:
for ai in envPairsImpl(): yield ai

View file

@ -871,8 +871,7 @@ proc unixToNativePath*(path: string, drive=""): string {.
inc(i) inc(i)
include "includes/oserr" include "includes/oserr"
when not defined(nimscript): include "includes/osenv"
include "includes/osenv"
proc getHomeDir*(): string {.rtl, extern: "nos$1", proc getHomeDir*(): string {.rtl, extern: "nos$1",
tags: [ReadEnvEffect, ReadIOEffect].} = tags: [ReadEnvEffect, ReadIOEffect].} =

View file

@ -1,5 +1,8 @@
import std/private/miscdollars import std/private/miscdollars
from std/os import getEnv when defined(nimscript):
import std/os # xxx investigate why needed
else:
from std/os import getEnv
import std/[macros, genasts] import std/[macros, genasts]
template flakyAssert*(cond: untyped, msg = "", notifySuccess = true) = template flakyAssert*(cond: untyped, msg = "", notifySuccess = true) =
@ -26,7 +29,7 @@ template flakyAssert*(cond: untyped, msg = "", notifySuccess = true) =
msg2.add $expr & " " & msg msg2.add $expr & " " & msg
echo msg2 echo msg2
when not defined(js): when not defined(js) and not defined(nimscript):
import std/strutils import std/strutils
proc greedyOrderedSubsetLines*(lhs, rhs: string, allowPrefixMatch = false): bool = proc greedyOrderedSubsetLines*(lhs, rhs: string, allowPrefixMatch = false): bool =

View file

@ -1,3 +1,5 @@
# xxx consider merging this in tests/stdlib/tos.nim for increased coverage (with selecting disabling)
static: doAssert defined(nodejs) static: doAssert defined(nodejs)
import os import os
@ -19,29 +21,3 @@ block:
if not isWindows: if not isWindows:
doAssert cwd.isAbsolute doAssert cwd.isAbsolute
doAssert relativePath(getCurrentDir() / "foo", "bar") == "../foo" doAssert relativePath(getCurrentDir() / "foo", "bar") == "../foo"
import std/sequtils
template main =
putEnv("foo", "bar")
doAssert getEnv("foo") == "bar"
doAssert existsEnv("foo")
putEnv("foo", "")
doAssert existsEnv("foo")
putEnv("foo", "bar2")
doAssert getEnv("foo") == "bar2"
when nimvm:
discard
else:
# need support in vmops: envPairs, delEnv
let s = toSeq(envPairs())
doAssert ("foo", "bar2") in s
doAssert ("foo", "bar") notin s
delEnv("foo")
doAssert not existsEnv("foo")
static: main()
main()

View file

@ -13,16 +13,18 @@ template main =
for val in ["val", ""]: # ensures empty val works too for val in ["val", ""]: # ensures empty val works too
const key = "NIM_TESTS_TOSENV_KEY" const key = "NIM_TESTS_TOSENV_KEY"
doAssert not existsEnv(key) doAssert not existsEnv(key)
putEnv(key, val)
putEnv(key, "tempval")
doAssert existsEnv(key)
doAssert getEnv(key) == "tempval"
putEnv(key, val) # change a key that already exists
doAssert existsEnv(key) doAssert existsEnv(key)
doAssert getEnv(key) == val doAssert getEnv(key) == val
when nimvm: discard
else: doAssert (key, val) in toSeq(envPairs())
doAssert (key, val) in toSeq(envPairs())
delEnv(key) delEnv(key)
when nimvm: discard doAssert (key, val) notin toSeq(envPairs())
else:
doAssert (key, val) notin toSeq(envPairs())
doAssert not existsEnv(key) doAssert not existsEnv(key)
delEnv(key) # deleting an already deleted env var delEnv(key) # deleting an already deleted env var
doAssert not existsEnv(key) doAssert not existsEnv(key)
@ -43,7 +45,7 @@ template main =
static: main() static: main()
main() main()
when not defined(js): when not defined(js) and not defined(nimscript):
block: # bug #18533 block: # bug #18533
proc c_getenv(env: cstring): cstring {.importc: "getenv", header: "<stdlib.h>".} proc c_getenv(env: cstring): cstring {.importc: "getenv", header: "<stdlib.h>".}
var thr: Thread[void] var thr: Thread[void]

View file

@ -71,7 +71,11 @@ import std/[
decls, compilesettings, with, wrapnils decls, compilesettings, with, wrapnils
] ]
# non-std imports
import stdtest/testutils
# tests (increase coverage via code reuse)
import stdlib/trandom import stdlib/trandom
import stdlib/tosenv
echo "Nimscript imports are successful." echo "Nimscript imports are successful."