Merge branch 'devel' into araq-misc
This commit is contained in:
commit
e4c759fbff
28 changed files with 286 additions and 71 deletions
|
|
@ -1680,6 +1680,14 @@ proc skipStmtList*(n: PNode): PNode =
|
||||||
else:
|
else:
|
||||||
result = n
|
result = n
|
||||||
|
|
||||||
|
proc toVar*(typ: PType): PType =
|
||||||
|
## If ``typ`` is not a tyVar then it is converted into a `var <typ>` and
|
||||||
|
## returned. Otherwise ``typ`` is simply returned as-is.
|
||||||
|
result = typ
|
||||||
|
if typ.kind != tyVar:
|
||||||
|
result = newType(tyVar, typ.owner)
|
||||||
|
rawAddSon(result, typ)
|
||||||
|
|
||||||
proc toRef*(typ: PType): PType =
|
proc toRef*(typ: PType): PType =
|
||||||
## If ``typ`` is a tyObject then it is converted into a `ref <typ>` and
|
## If ``typ`` is a tyObject then it is converted into a `ref <typ>` and
|
||||||
## returned. Otherwise ``typ`` is simply returned as-is.
|
## returned. Otherwise ``typ`` is simply returned as-is.
|
||||||
|
|
|
||||||
|
|
@ -412,6 +412,8 @@ proc debugTree(conf: ConfigRef; n: PNode, indent: int, maxRecDepth: int;
|
||||||
else:
|
else:
|
||||||
addf(result, ",$N$1\"ident\": null", [istr])
|
addf(result, ",$N$1\"ident\": null", [istr])
|
||||||
else:
|
else:
|
||||||
|
if renderType and n.typ != nil:
|
||||||
|
addf(result, ",$N$1\"typ\": $2", [istr, debugType(conf, n.typ, 2)])
|
||||||
if sonsLen(n) > 0:
|
if sonsLen(n) > 0:
|
||||||
addf(result, ",$N$1\"sons\": [", [istr])
|
addf(result, ",$N$1\"sons\": [", [istr])
|
||||||
for i in countup(0, sonsLen(n) - 1):
|
for i in countup(0, sonsLen(n) - 1):
|
||||||
|
|
|
||||||
|
|
@ -217,7 +217,7 @@ proc isAssignable*(owner: PSym, n: PNode; isUnsafeAddr=false): TAssignableResult
|
||||||
if n.typ != nil and n.typ.kind == tyVar:
|
if n.typ != nil and n.typ.kind == tyVar:
|
||||||
result = arLValue
|
result = arLValue
|
||||||
of nkSym:
|
of nkSym:
|
||||||
let kinds = if isUnsafeAddr: {skVar, skResult, skTemp, skParam, skLet}
|
let kinds = if isUnsafeAddr: {skVar, skResult, skTemp, skParam, skLet, skForVar}
|
||||||
else: {skVar, skResult, skTemp}
|
else: {skVar, skResult, skTemp}
|
||||||
if n.sym.kind in kinds:
|
if n.sym.kind in kinds:
|
||||||
if owner != nil and owner == n.sym.owner and
|
if owner != nil and owner == n.sym.owner and
|
||||||
|
|
|
||||||
|
|
@ -369,6 +369,8 @@ proc transformAddrDeref(c: PTransf, n: PNode, a, b: TNodeKind): PTransNode =
|
||||||
result = PTransNode(n.sons[0])
|
result = PTransNode(n.sons[0])
|
||||||
if n.typ.skipTypes(abstractVar).kind != tyOpenArray:
|
if n.typ.skipTypes(abstractVar).kind != tyOpenArray:
|
||||||
PNode(result).typ = n.typ
|
PNode(result).typ = n.typ
|
||||||
|
elif n.typ.skipTypes(abstractInst).kind in {tyVar}:
|
||||||
|
PNode(result).typ = toVar(PNode(result).typ)
|
||||||
of nkHiddenStdConv, nkHiddenSubConv, nkConv:
|
of nkHiddenStdConv, nkHiddenSubConv, nkConv:
|
||||||
var m = n.sons[0].sons[1]
|
var m = n.sons[0].sons[1]
|
||||||
if m.kind == a or m.kind == b:
|
if m.kind == a or m.kind == b:
|
||||||
|
|
@ -377,6 +379,8 @@ proc transformAddrDeref(c: PTransf, n: PNode, a, b: TNodeKind): PTransNode =
|
||||||
result = PTransNode(n.sons[0])
|
result = PTransNode(n.sons[0])
|
||||||
if n.typ.skipTypes(abstractVar).kind != tyOpenArray:
|
if n.typ.skipTypes(abstractVar).kind != tyOpenArray:
|
||||||
PNode(result).typ = n.typ
|
PNode(result).typ = n.typ
|
||||||
|
elif n.typ.skipTypes(abstractInst).kind in {tyVar}:
|
||||||
|
PNode(result).typ = toVar(PNode(result).typ)
|
||||||
else:
|
else:
|
||||||
if n.sons[0].kind == a or n.sons[0].kind == b:
|
if n.sons[0].kind == a or n.sons[0].kind == b:
|
||||||
# addr ( deref ( x )) --> x
|
# addr ( deref ( x )) --> x
|
||||||
|
|
|
||||||
|
|
@ -82,19 +82,21 @@ path="$lib/pure"
|
||||||
clang.cpp.options.linker = "-ldl"
|
clang.cpp.options.linker = "-ldl"
|
||||||
tcc.options.linker = "-ldl"
|
tcc.options.linker = "-ldl"
|
||||||
@end
|
@end
|
||||||
@if bsd or haiku:
|
@if bsd:
|
||||||
# BSD got posix_spawn only recently, so we deactivate it for osproc:
|
# BSD got posix_spawn only recently, so we deactivate it for osproc:
|
||||||
define:useFork
|
define:useFork
|
||||||
# at least NetBSD has problems with thread local storage:
|
# at least NetBSD has problems with thread local storage:
|
||||||
tlsEmulation:on
|
tlsEmulation:on
|
||||||
@end
|
@end
|
||||||
@if haiku:
|
@if haiku:
|
||||||
# -fopenmp
|
# Haiku currently have problems with TLS
|
||||||
gcc.options.linker = "-lroot -lnetwork"
|
# https://dev.haiku-os.org/ticket/14342
|
||||||
gcc.cpp.options.linker = "-lroot -lnetwork"
|
tlsEmulation:on
|
||||||
clang.options.linker = "-lroot -lnetwork"
|
gcc.options.linker = "-Wl,--as-needed -lnetwork"
|
||||||
clang.cpp.options.linker = "-lroot -lnetwork"
|
gcc.cpp.options.linker = "-Wl,--as-needed -lnetwork"
|
||||||
tcc.options.linker = "-lroot -lnetwork"
|
clang.options.linker = "-Wl,--as-needed -lnetwork"
|
||||||
|
clang.cpp.options.linker = "-Wl,--as-needed -lnetwork"
|
||||||
|
tcc.options.linker = "-Wl,--as-needed -lnetwork"
|
||||||
@end
|
@end
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
|
||||||
40
doc/nimc.rst
40
doc/nimc.rst
|
|
@ -68,6 +68,46 @@ User Some user defined warning.
|
||||||
========================== ============================================
|
========================== ============================================
|
||||||
|
|
||||||
|
|
||||||
|
List of hints
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Each hint can be activated individually with ``--hint[NAME]:on|off`` or in a
|
||||||
|
``push`` pragma.
|
||||||
|
|
||||||
|
========================== ============================================
|
||||||
|
Name Description
|
||||||
|
========================== ============================================
|
||||||
|
CC Shows when the C compiler is called.
|
||||||
|
CodeBegin
|
||||||
|
CodeEnd
|
||||||
|
CondTrue
|
||||||
|
Conf A config file was loaded.
|
||||||
|
ConvToBaseNotNeeded
|
||||||
|
ConvFromXtoItselfNotNeeded
|
||||||
|
Dependency
|
||||||
|
Exec Program is executed.
|
||||||
|
ExprAlwaysX
|
||||||
|
ExtendedContext
|
||||||
|
GCStats Dumps statistics about the Garbage Collector.
|
||||||
|
GlobalVar Shows global variables declarations.
|
||||||
|
LineTooLong Line exceeds the maximum length.
|
||||||
|
Link Linking phase.
|
||||||
|
Name
|
||||||
|
Path Search paths modifications.
|
||||||
|
Pattern
|
||||||
|
Performance
|
||||||
|
Processing Artifact being compiled.
|
||||||
|
QuitCalled
|
||||||
|
Source The source line that triggered a diagnostic
|
||||||
|
message.
|
||||||
|
StackTrace
|
||||||
|
Success, SuccessX Successful compilation of a library or a binary.
|
||||||
|
User
|
||||||
|
UserRaw
|
||||||
|
XDeclaredButNotUsed Unused symbols in the code.
|
||||||
|
========================== ============================================
|
||||||
|
|
||||||
|
|
||||||
Verbosity levels
|
Verbosity levels
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,8 @@ include "system/inclrtl"
|
||||||
|
|
||||||
when hostOS == "solaris":
|
when hostOS == "solaris":
|
||||||
{.passl: "-lsocket -lnsl".}
|
{.passl: "-lsocket -lnsl".}
|
||||||
|
elif hostOS == "haiku":
|
||||||
|
{.passl: "-lnetwork".}
|
||||||
|
|
||||||
import os, parseutils
|
import os, parseutils
|
||||||
from times import epochTime
|
from times import epochTime
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
{.deadCodeElim: on.} # dce option deprecated
|
{.deadCodeElim: on.} # dce option deprecated
|
||||||
|
|
||||||
const
|
const
|
||||||
hasSpawnH = not defined(haiku) # should exist for every Posix system nowadays
|
hasSpawnH = true # should exist for every Posix system nowadays
|
||||||
hasAioH = defined(linux)
|
hasAioH = defined(linux)
|
||||||
|
|
||||||
when defined(linux) and not defined(android):
|
when defined(linux) and not defined(android):
|
||||||
|
|
@ -43,6 +43,9 @@ type
|
||||||
|
|
||||||
Dirent* {.importc: "struct dirent",
|
Dirent* {.importc: "struct dirent",
|
||||||
header: "<dirent.h>", final, pure.} = object ## dirent_t struct
|
header: "<dirent.h>", final, pure.} = object ## dirent_t struct
|
||||||
|
when defined(haiku):
|
||||||
|
d_dev*: Dev ## Device (not POSIX)
|
||||||
|
d_pdev*: Dev ## Parent device (only for queries) (not POSIX)
|
||||||
d_ino*: Ino ## File serial number.
|
d_ino*: Ino ## File serial number.
|
||||||
when defined(dragonfly):
|
when defined(dragonfly):
|
||||||
# DragonflyBSD doesn't have `d_reclen` field.
|
# DragonflyBSD doesn't have `d_reclen` field.
|
||||||
|
|
@ -54,6 +57,9 @@ type
|
||||||
## (not POSIX)
|
## (not POSIX)
|
||||||
when defined(linux) or defined(openbsd):
|
when defined(linux) or defined(openbsd):
|
||||||
d_off*: Off ## Not an offset. Value that ``telldir()`` would return.
|
d_off*: Off ## Not an offset. Value that ``telldir()`` would return.
|
||||||
|
elif defined(haiku):
|
||||||
|
d_pino*: Ino ## Parent inode (only for queries) (not POSIX)
|
||||||
|
d_reclen*: cushort ## Length of this record. (not POSIX)
|
||||||
|
|
||||||
d_name*: array[0..255, char] ## Name of entry.
|
d_name*: array[0..255, char] ## Name of entry.
|
||||||
|
|
||||||
|
|
@ -599,6 +605,10 @@ else:
|
||||||
MSG_NOSIGNAL* {.importc, header: "<sys/socket.h>".}: cint
|
MSG_NOSIGNAL* {.importc, header: "<sys/socket.h>".}: cint
|
||||||
## No SIGPIPE generated when an attempt to send is made on a stream-oriented socket that is no longer connected.
|
## No SIGPIPE generated when an attempt to send is made on a stream-oriented socket that is no longer connected.
|
||||||
|
|
||||||
|
when defined(haiku):
|
||||||
|
const
|
||||||
|
SIGKILLTHR* = 21 ## BeOS specific: Kill just the thread, not team
|
||||||
|
|
||||||
when hasSpawnH:
|
when hasSpawnH:
|
||||||
when defined(linux):
|
when defined(linux):
|
||||||
# better be safe than sorry; Linux has this flag, macosx doesn't, don't
|
# better be safe than sorry; Linux has this flag, macosx doesn't, don't
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,28 @@ import macros
|
||||||
when not defined(nimhygiene):
|
when not defined(nimhygiene):
|
||||||
{.pragma: dirty.}
|
{.pragma: dirty.}
|
||||||
|
|
||||||
|
|
||||||
|
macro evalOnceAs(expAlias, exp: untyped, letAssigneable: static[bool]): untyped =
|
||||||
|
## Injects ``expAlias`` in caller scope, to avoid bugs involving multiple
|
||||||
|
## substitution in macro arguments such as
|
||||||
|
## https://github.com/nim-lang/Nim/issues/7187
|
||||||
|
## ``evalOnceAs(myAlias, myExp)`` will behave as ``let myAlias = myExp``
|
||||||
|
## except when ``letAssigneable`` is false (eg to handle openArray) where
|
||||||
|
## it just forwards ``exp`` unchanged
|
||||||
|
expectKind(expAlias, nnkIdent)
|
||||||
|
var val = exp
|
||||||
|
|
||||||
|
result = newStmtList()
|
||||||
|
# If `exp` is not a symbol we evaluate it once here and then use the temporary
|
||||||
|
# symbol as alias
|
||||||
|
if exp.kind != nnkSym and letAssigneable:
|
||||||
|
val = genSym()
|
||||||
|
result.add(newLetStmt(val, exp))
|
||||||
|
|
||||||
|
result.add(
|
||||||
|
newProc(name = genSym(nskTemplate, $expAlias), params = [getType(untyped)],
|
||||||
|
body = val, procType = nnkTemplateDef))
|
||||||
|
|
||||||
proc concat*[T](seqs: varargs[seq[T]]): seq[T] =
|
proc concat*[T](seqs: varargs[seq[T]]): seq[T] =
|
||||||
## Takes several sequences' items and returns them inside a new sequence.
|
## Takes several sequences' items and returns them inside a new sequence.
|
||||||
##
|
##
|
||||||
|
|
@ -635,30 +657,7 @@ template mapIt*(s, typ, op: untyped): untyped =
|
||||||
result.add(op)
|
result.add(op)
|
||||||
result
|
result
|
||||||
|
|
||||||
# This is needed in order not to break the bootstrap, the fallback
|
template mapIt*(s: typed, op: untyped): untyped =
|
||||||
# implementation is a "dumb" let that won't work in some cases (eg. when `exp`
|
|
||||||
# is an openArray)
|
|
||||||
when declared(macros.symKind):
|
|
||||||
macro evalOnce(v, exp: untyped): untyped =
|
|
||||||
expectKind(v, nnkIdent)
|
|
||||||
var val = exp
|
|
||||||
|
|
||||||
result = newStmtList()
|
|
||||||
|
|
||||||
# Not a parameter we can pass as-is, evaluate and store in a temporary
|
|
||||||
# variable
|
|
||||||
if exp.kind != nnkSym or exp.symKind != nskParam:
|
|
||||||
val = genSym()
|
|
||||||
result.add(newLetStmt(val, exp))
|
|
||||||
|
|
||||||
result.add(
|
|
||||||
newProc(name = genSym(nskTemplate, $v), params = [getType(untyped)],
|
|
||||||
body = val, procType = nnkTemplateDef))
|
|
||||||
else:
|
|
||||||
macro evalOnce(v, exp: untyped): untyped =
|
|
||||||
result = newLetStmt(v, exp)
|
|
||||||
|
|
||||||
template mapIt*(s, op: untyped): untyped =
|
|
||||||
## Convenience template around the ``map`` proc to reduce typing.
|
## Convenience template around the ``map`` proc to reduce typing.
|
||||||
##
|
##
|
||||||
## The template injects the ``it`` variable which you can use directly in an
|
## The template injects the ``it`` variable which you can use directly in an
|
||||||
|
|
@ -675,16 +674,21 @@ template mapIt*(s, op: untyped): untyped =
|
||||||
block:
|
block:
|
||||||
var it{.inject.}: type(items(s));
|
var it{.inject.}: type(items(s));
|
||||||
op))
|
op))
|
||||||
var result: seq[outType]
|
|
||||||
when compiles(s.len):
|
when compiles(s.len):
|
||||||
evalOnce(t, s)
|
block: # using a block avoids https://github.com/nim-lang/Nim/issues/8580
|
||||||
|
|
||||||
|
# BUG: `evalOnceAs(s2, s, false)` would lead to C compile errors
|
||||||
|
# (`error: use of undeclared identifier`) instead of Nim compile errors
|
||||||
|
evalOnceAs(s2, s, compiles((let _ = s)))
|
||||||
|
|
||||||
var i = 0
|
var i = 0
|
||||||
result = newSeq[outType](t.len)
|
var result = newSeq[outType](s2.len)
|
||||||
for it {.inject.} in t:
|
for it {.inject.} in s2:
|
||||||
result[i] = op
|
result[i] = op
|
||||||
i += 1
|
i += 1
|
||||||
|
result
|
||||||
else:
|
else:
|
||||||
result = @[]
|
var result: seq[outType] = @[]
|
||||||
for it {.inject.} in s:
|
for it {.inject.} in s:
|
||||||
result.add(op)
|
result.add(op)
|
||||||
result
|
result
|
||||||
|
|
@ -774,6 +778,14 @@ macro mapLiterals*(constructor, op: untyped;
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
import strutils
|
import strutils
|
||||||
|
|
||||||
|
# helper for testing double substitution side effects which are handled
|
||||||
|
# by `evalOnceAs`
|
||||||
|
var counter = 0
|
||||||
|
proc identity[T](a:T):auto=
|
||||||
|
counter.inc
|
||||||
|
a
|
||||||
|
|
||||||
block: # concat test
|
block: # concat test
|
||||||
let
|
let
|
||||||
s1 = @[1, 2, 3]
|
s1 = @[1, 2, 3]
|
||||||
|
|
@ -1045,10 +1057,12 @@ when isMainModule:
|
||||||
assert multiplication == 495, "Multiplication is (5*(9*(11)))"
|
assert multiplication == 495, "Multiplication is (5*(9*(11)))"
|
||||||
assert concatenation == "nimiscool"
|
assert concatenation == "nimiscool"
|
||||||
|
|
||||||
block: # mapIt tests
|
block: # mapIt + applyIt test
|
||||||
|
counter = 0
|
||||||
var
|
var
|
||||||
nums = @[1, 2, 3, 4]
|
nums = @[1, 2, 3, 4]
|
||||||
strings = nums.mapIt($(4 * it))
|
strings = nums.identity.mapIt($(4 * it))
|
||||||
|
doAssert counter == 1
|
||||||
nums.applyIt(it * 3)
|
nums.applyIt(it * 3)
|
||||||
assert nums[0] + nums[3] == 15
|
assert nums[0] + nums[3] == 15
|
||||||
assert strings[2] == "12"
|
assert strings[2] == "12"
|
||||||
|
|
@ -1067,9 +1081,43 @@ when isMainModule:
|
||||||
doAssert mapLiterals(([1], ("abc"), 2), `$`, nested=true) == (["1"], "abc", "2")
|
doAssert mapLiterals(([1], ("abc"), 2), `$`, nested=true) == (["1"], "abc", "2")
|
||||||
|
|
||||||
block: # mapIt with openArray
|
block: # mapIt with openArray
|
||||||
when declared(macros.symKind):
|
counter = 0
|
||||||
proc foo(x: openArray[int]): seq[int] = x.mapIt(it + 1)
|
proc foo(x: openArray[int]): seq[int] = x.mapIt(it * 10)
|
||||||
doAssert foo([1,2,3]) == @[2,3,4]
|
doAssert foo([identity(1),identity(2)]) == @[10, 20]
|
||||||
|
doAssert counter == 2
|
||||||
|
|
||||||
|
block: # mapIt with direct openArray
|
||||||
|
proc foo1(x: openArray[int]): seq[int] = x.mapIt(it * 10)
|
||||||
|
counter = 0
|
||||||
|
doAssert foo1(openArray[int]([identity(1),identity(2)])) == @[10,20]
|
||||||
|
doAssert counter == 2
|
||||||
|
|
||||||
|
template foo2(x: openArray[int]): seq[int] = x.mapIt(it * 10)
|
||||||
|
counter = 0
|
||||||
|
doAssert foo2(openArray[int]([identity(1),identity(2)])) == @[10,20]
|
||||||
|
# TODO: this fails; not sure how to fix this case
|
||||||
|
# doAssert counter == 2
|
||||||
|
|
||||||
|
block: # mapIt empty test, see https://github.com/nim-lang/Nim/pull/8584#pullrequestreview-144723468
|
||||||
|
# NOTE: `[].mapIt(it)` is illegal, just as `let a = @[]` is (lacks type
|
||||||
|
# of elements)
|
||||||
|
doAssert: not compiles(mapIt(@[], it))
|
||||||
|
doAssert: not compiles(mapIt([], it))
|
||||||
|
doAssert newSeq[int](0).mapIt(it) == @[]
|
||||||
|
|
||||||
|
block: # mapIt redifinition check, see https://github.com/nim-lang/Nim/issues/8580
|
||||||
|
let t = [1,2].mapIt(it)
|
||||||
|
doAssert t == @[1,2]
|
||||||
|
|
||||||
|
block:
|
||||||
|
var counter = 0
|
||||||
|
proc getInput():auto =
|
||||||
|
counter.inc
|
||||||
|
[1, 2]
|
||||||
|
doAssert getInput().mapIt(it*2).mapIt(it*10) == @[20, 40]
|
||||||
|
# make sure argument evaluated only once, analog to
|
||||||
|
# https://github.com/nim-lang/Nim/issues/7187 test case
|
||||||
|
doAssert counter == 1
|
||||||
|
|
||||||
block: # mapIt with invalid RHS for `let` (#8566)
|
block: # mapIt with invalid RHS for `let` (#8566)
|
||||||
type X = enum
|
type X = enum
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,14 @@ when defined(genode):
|
||||||
proc affinitySpaceTotal(env: GenodeEnvPtr): cuint {.
|
proc affinitySpaceTotal(env: GenodeEnvPtr): cuint {.
|
||||||
importcpp: "@->cpu().affinity_space().total()".}
|
importcpp: "@->cpu().affinity_space().total()".}
|
||||||
|
|
||||||
|
when defined(haiku):
|
||||||
|
{.emit: "#include <OS.h>".}
|
||||||
|
type
|
||||||
|
SystemInfo {.importc: "system_info", bycopy.} = object
|
||||||
|
cpuCount {.importc: "cpu_count".}: uint32
|
||||||
|
|
||||||
|
proc getSystemInfo(info: ptr SystemInfo): int32 {.importc: "get_system_info".}
|
||||||
|
|
||||||
proc countProcessors*(): int {.rtl, extern: "ncpi$1".} =
|
proc countProcessors*(): int {.rtl, extern: "ncpi$1".} =
|
||||||
## returns the numer of the processors/cores the machine has.
|
## returns the numer of the processors/cores the machine has.
|
||||||
## Returns 0 if it cannot be detected.
|
## Returns 0 if it cannot be detected.
|
||||||
|
|
@ -86,6 +94,10 @@ proc countProcessors*(): int {.rtl, extern: "ncpi$1".} =
|
||||||
result = sysconf(SC_NPROC_ONLN)
|
result = sysconf(SC_NPROC_ONLN)
|
||||||
elif defined(genode):
|
elif defined(genode):
|
||||||
result = runtimeEnv.affinitySpaceTotal().int
|
result = runtimeEnv.affinitySpaceTotal().int
|
||||||
|
elif defined(haiku):
|
||||||
|
var sysinfo: SystemInfo
|
||||||
|
if getSystemInfo(addr sysinfo) == 0:
|
||||||
|
result = sysinfo.cpuCount.int
|
||||||
else:
|
else:
|
||||||
result = sysconf(SC_NPROCESSORS_ONLN)
|
result = sysconf(SC_NPROCESSORS_ONLN)
|
||||||
if result <= 0: result = 0
|
if result <= 0: result = 0
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,10 @@ when defined(windows):
|
||||||
{.warning: "ucontext coroutine backend is not available on windows, defaulting to fibers.".}
|
{.warning: "ucontext coroutine backend is not available on windows, defaulting to fibers.".}
|
||||||
when defined(nimCoroutinesSetjmp):
|
when defined(nimCoroutinesSetjmp):
|
||||||
{.warning: "setjmp coroutine backend is not available on windows, defaulting to fibers.".}
|
{.warning: "setjmp coroutine backend is not available on windows, defaulting to fibers.".}
|
||||||
|
elif defined(haiku):
|
||||||
|
const coroBackend = CORO_BACKEND_SETJMP
|
||||||
|
when defined(nimCoroutinesUcontext):
|
||||||
|
{.warning: "ucontext coroutine backend is not available on haiku, defaulting to setjmp".}
|
||||||
elif defined(nimCoroutinesSetjmp) or defined(nimCoroutinesSetjmpBundled):
|
elif defined(nimCoroutinesSetjmp) or defined(nimCoroutinesSetjmpBundled):
|
||||||
const coroBackend = CORO_BACKEND_SETJMP
|
const coroBackend = CORO_BACKEND_SETJMP
|
||||||
else:
|
else:
|
||||||
|
|
@ -55,21 +59,21 @@ when coroBackend == CORO_BACKEND_FIBERS:
|
||||||
|
|
||||||
elif coroBackend == CORO_BACKEND_UCONTEXT:
|
elif coroBackend == CORO_BACKEND_UCONTEXT:
|
||||||
type
|
type
|
||||||
stack_t {.importc, header: "<sys/ucontext.h>".} = object
|
stack_t {.importc, header: "<ucontext.h>".} = object
|
||||||
ss_sp: pointer
|
ss_sp: pointer
|
||||||
ss_flags: int
|
ss_flags: int
|
||||||
ss_size: int
|
ss_size: int
|
||||||
|
|
||||||
ucontext_t {.importc, header: "<sys/ucontext.h>".} = object
|
ucontext_t {.importc, header: "<ucontext.h>".} = object
|
||||||
uc_link: ptr ucontext_t
|
uc_link: ptr ucontext_t
|
||||||
uc_stack: stack_t
|
uc_stack: stack_t
|
||||||
|
|
||||||
Context = ucontext_t
|
Context = ucontext_t
|
||||||
|
|
||||||
proc getcontext(context: var ucontext_t): int32 {.importc, header: "<sys/ucontext.h>".}
|
proc getcontext(context: var ucontext_t): int32 {.importc, header: "<ucontext.h>".}
|
||||||
proc setcontext(context: var ucontext_t): int32 {.importc, header: "<sys/ucontext.h>".}
|
proc setcontext(context: var ucontext_t): int32 {.importc, header: "<ucontext.h>".}
|
||||||
proc swapcontext(fromCtx, toCtx: var ucontext_t): int32 {.importc, header: "<sys/ucontext.h>".}
|
proc swapcontext(fromCtx, toCtx: var ucontext_t): int32 {.importc, header: "<ucontext.h>".}
|
||||||
proc makecontext(context: var ucontext_t, fn: pointer, argc: int32) {.importc, header: "<sys/ucontext.h>", varargs.}
|
proc makecontext(context: var ucontext_t, fn: pointer, argc: int32) {.importc, header: "<ucontext.h>", varargs.}
|
||||||
|
|
||||||
elif coroBackend == CORO_BACKEND_SETJMP:
|
elif coroBackend == CORO_BACKEND_SETJMP:
|
||||||
proc coroExecWithStack*(fn: pointer, stack: pointer) {.noreturn, importc: "narch_$1", fastcall.}
|
proc coroExecWithStack*(fn: pointer, stack: pointer) {.noreturn, importc: "narch_$1", fastcall.}
|
||||||
|
|
@ -190,7 +194,7 @@ proc switchTo(current, to: CoroutinePtr) =
|
||||||
elif to.state == CORO_CREATED:
|
elif to.state == CORO_CREATED:
|
||||||
# Coroutine is started.
|
# Coroutine is started.
|
||||||
coroExecWithStack(runCurrentTask, to.stack.bottom)
|
coroExecWithStack(runCurrentTask, to.stack.bottom)
|
||||||
doAssert false
|
#doAssert false
|
||||||
else:
|
else:
|
||||||
{.error: "Invalid coroutine backend set.".}
|
{.error: "Invalid coroutine backend set.".}
|
||||||
# Execution was just resumed. Restore frame information and set active stack.
|
# Execution was just resumed. Restore frame information and set active stack.
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,8 @@ type
|
||||||
OpenBSD
|
OpenBSD
|
||||||
DragonFlyBSD
|
DragonFlyBSD
|
||||||
|
|
||||||
|
Haiku
|
||||||
|
|
||||||
|
|
||||||
const
|
const
|
||||||
LacksDevPackages* = {Distribution.Gentoo, Distribution.Slackware,
|
LacksDevPackages* = {Distribution.Gentoo, Distribution.Slackware,
|
||||||
|
|
@ -166,6 +168,8 @@ proc detectOsImpl(d: Distribution): bool =
|
||||||
of Distribution.Solaris:
|
of Distribution.Solaris:
|
||||||
let uname = toLowerAscii(uname())
|
let uname = toLowerAscii(uname())
|
||||||
result = ("sun" in uname) or ("solaris" in uname)
|
result = ("sun" in uname) or ("solaris" in uname)
|
||||||
|
of Distribution.Haiku:
|
||||||
|
result = defined(haiku)
|
||||||
else:
|
else:
|
||||||
let dd = toLowerAscii($d)
|
let dd = toLowerAscii($d)
|
||||||
result = dd in toLowerAscii(uname()) or dd in toLowerAscii(release())
|
result = dd in toLowerAscii(uname()) or dd in toLowerAscii(release())
|
||||||
|
|
@ -224,6 +228,8 @@ proc foreignDepInstallCmd*(foreignPackageName: string): (string, bool) =
|
||||||
result = ("pacman -S " & p, true)
|
result = ("pacman -S " & p, true)
|
||||||
else:
|
else:
|
||||||
result = ("<your package manager here> install " & p, true)
|
result = ("<your package manager here> install " & p, true)
|
||||||
|
elif defined(haiku):
|
||||||
|
result = ("pkgman install " & p, true)
|
||||||
else:
|
else:
|
||||||
result = ("brew install " & p, false)
|
result = ("brew install " & p, false)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -255,7 +255,7 @@ when defined(windows):
|
||||||
|
|
||||||
else:
|
else:
|
||||||
when defined(haiku):
|
when defined(haiku):
|
||||||
const iconvDll = "(libc.so.6|libiconv.so|libtextencoding.so)"
|
const iconvDll = "libiconv.so"
|
||||||
elif defined(macosx):
|
elif defined(macosx):
|
||||||
const iconvDll = "libiconv.dylib"
|
const iconvDll = "libiconv.dylib"
|
||||||
else:
|
else:
|
||||||
|
|
@ -272,6 +272,8 @@ else:
|
||||||
const EILSEQ = 86.cint
|
const EILSEQ = 86.cint
|
||||||
elif defined(solaris):
|
elif defined(solaris):
|
||||||
const EILSEQ = 88.cint
|
const EILSEQ = 88.cint
|
||||||
|
elif defined(haiku):
|
||||||
|
const EILSEQ = -2147454938.cint
|
||||||
|
|
||||||
var errno {.importc, header: "<errno.h>".}: cint
|
var errno {.importc, header: "<errno.h>".}: cint
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
{.deadCodeElim: on.} # dce option deprecated
|
{.deadCodeElim: on.} # dce option deprecated
|
||||||
|
|
||||||
when defined(Posix) and not defined(haiku):
|
when defined(Posix):
|
||||||
{.passl: "-lm".}
|
{.passl: "-lm".}
|
||||||
|
|
||||||
var
|
var
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ proc fac*(n: int): int =
|
||||||
|
|
||||||
{.push checks:off, line_dir:off, stack_trace:off.}
|
{.push checks:off, line_dir:off, stack_trace:off.}
|
||||||
|
|
||||||
when defined(Posix) and not defined(haiku):
|
when defined(Posix):
|
||||||
{.passl: "-lm".}
|
{.passl: "-lm".}
|
||||||
|
|
||||||
const
|
const
|
||||||
|
|
@ -529,8 +529,8 @@ proc sgn*[T: SomeNumber](x: T): int {.inline.} =
|
||||||
{.pop.}
|
{.pop.}
|
||||||
|
|
||||||
proc `^`*[T](x: T, y: Natural): T =
|
proc `^`*[T](x: T, y: Natural): T =
|
||||||
## Computes ``x`` to the power ``y`. ``x`` must be non-negative, use
|
## Computes ``x`` to the power ``y``. ``x`` must be non-negative, use
|
||||||
## `pow <#pow,float,float>` for negative exponents.
|
## `pow <#pow,float,float>`_ for negative exponents.
|
||||||
when compiles(y >= T(0)):
|
when compiles(y >= T(0)):
|
||||||
assert y >= T(0)
|
assert y >= T(0)
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -248,9 +248,10 @@ proc getAddrInfo*(address: string, port: Port, domain: Domain = AF_INET,
|
||||||
hints.ai_socktype = toInt(sockType)
|
hints.ai_socktype = toInt(sockType)
|
||||||
hints.ai_protocol = toInt(protocol)
|
hints.ai_protocol = toInt(protocol)
|
||||||
# OpenBSD doesn't support AI_V4MAPPED and doesn't define the macro AI_V4MAPPED.
|
# OpenBSD doesn't support AI_V4MAPPED and doesn't define the macro AI_V4MAPPED.
|
||||||
# FreeBSD doesn't support AI_V4MAPPED but defines the macro.
|
# FreeBSD, Haiku don't support AI_V4MAPPED but defines the macro.
|
||||||
# https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=198092
|
# https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=198092
|
||||||
when not defined(freebsd) and not defined(openbsd) and not defined(netbsd) and not defined(android):
|
# https://dev.haiku-os.org/ticket/14323
|
||||||
|
when not defined(freebsd) and not defined(openbsd) and not defined(netbsd) and not defined(android) and not defined(haiku):
|
||||||
if domain == AF_INET6:
|
if domain == AF_INET6:
|
||||||
hints.ai_flags = AI_V4MAPPED
|
hints.ai_flags = AI_V4MAPPED
|
||||||
var gaiResult = getaddrinfo(address, $port, addr(hints), result)
|
var gaiResult = getaddrinfo(address, $port, addr(hints), result)
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,7 @@ proc get*[T](self: Option[T]): T =
|
||||||
## Returns contents of the Option. If it is none, then an exception is
|
## Returns contents of the Option. If it is none, then an exception is
|
||||||
## thrown.
|
## thrown.
|
||||||
if self.isNone:
|
if self.isNone:
|
||||||
raise UnpackError(msg : "Can't obtain a value from a `none`")
|
raise UnpackError(msg: "Can't obtain a value from a `none`")
|
||||||
self.val
|
self.val
|
||||||
|
|
||||||
proc get*[T](self: Option[T], otherwise: T): T =
|
proc get*[T](self: Option[T], otherwise: T): T =
|
||||||
|
|
@ -139,6 +139,13 @@ proc get*[T](self: Option[T], otherwise: T): T =
|
||||||
else:
|
else:
|
||||||
otherwise
|
otherwise
|
||||||
|
|
||||||
|
proc get*[T](self: var Option[T]): var T =
|
||||||
|
## Returns contents of the Option. If it is none, then an exception is
|
||||||
|
## thrown.
|
||||||
|
if self.isNone:
|
||||||
|
raise UnpackError(msg: "Can't obtain a value from a `none`")
|
||||||
|
return self.val
|
||||||
|
|
||||||
proc map*[T](self: Option[T], callback: proc (input: T)) =
|
proc map*[T](self: Option[T], callback: proc (input: T)) =
|
||||||
## Applies a callback to the value in this Option
|
## Applies a callback to the value in this Option
|
||||||
if self.isSome:
|
if self.isSome:
|
||||||
|
|
|
||||||
|
|
@ -615,7 +615,10 @@ proc copyFile*(source, dest: string) {.rtl, extern: "nos$1",
|
||||||
|
|
||||||
when not declared(ENOENT) and not defined(Windows):
|
when not declared(ENOENT) and not defined(Windows):
|
||||||
when NoFakeVars:
|
when NoFakeVars:
|
||||||
|
when not defined(haiku):
|
||||||
const ENOENT = cint(2) # 2 on most systems including Solaris
|
const ENOENT = cint(2) # 2 on most systems including Solaris
|
||||||
|
else:
|
||||||
|
const ENOENT = cint(-2147459069)
|
||||||
else:
|
else:
|
||||||
var ENOENT {.importc, header: "<errno.h>".}: cint
|
var ENOENT {.importc, header: "<errno.h>".}: cint
|
||||||
|
|
||||||
|
|
@ -972,6 +975,14 @@ proc rawCreateDir(dir: string): bool =
|
||||||
result = false
|
result = false
|
||||||
else:
|
else:
|
||||||
raiseOSError(osLastError(), dir)
|
raiseOSError(osLastError(), dir)
|
||||||
|
elif defined(haiku):
|
||||||
|
let res = mkdir(dir, 0o777)
|
||||||
|
if res == 0'i32:
|
||||||
|
result = true
|
||||||
|
elif errno == EEXIST or errno == EROFS:
|
||||||
|
result = false
|
||||||
|
else:
|
||||||
|
raiseOSError(osLastError(), dir)
|
||||||
elif defined(posix):
|
elif defined(posix):
|
||||||
let res = mkdir(dir, 0o777)
|
let res = mkdir(dir, 0o777)
|
||||||
if res == 0'i32:
|
if res == 0'i32:
|
||||||
|
|
|
||||||
|
|
@ -892,7 +892,7 @@ else:
|
||||||
weekday {.importc: "tm_wday".},
|
weekday {.importc: "tm_wday".},
|
||||||
yearday {.importc: "tm_yday".},
|
yearday {.importc: "tm_yday".},
|
||||||
isdst {.importc: "tm_isdst".}: cint
|
isdst {.importc: "tm_isdst".}: cint
|
||||||
when defined(linux) and defined(amd64):
|
when defined(linux) and defined(amd64) or defined(haiku):
|
||||||
gmtoff {.importc: "tm_gmtoff".}: clong
|
gmtoff {.importc: "tm_gmtoff".}: clong
|
||||||
zone {.importc: "tm_zone".}: cstring
|
zone {.importc: "tm_zone".}: cstring
|
||||||
type
|
type
|
||||||
|
|
|
||||||
|
|
@ -1327,7 +1327,7 @@ const
|
||||||
hostOS* {.magic: "HostOS".}: string = ""
|
hostOS* {.magic: "HostOS".}: string = ""
|
||||||
## a string that describes the host operating system. Possible values:
|
## a string that describes the host operating system. Possible values:
|
||||||
## "windows", "macosx", "linux", "netbsd", "freebsd", "openbsd", "solaris",
|
## "windows", "macosx", "linux", "netbsd", "freebsd", "openbsd", "solaris",
|
||||||
## "aix", "standalone".
|
## "aix", "haiku", "standalone".
|
||||||
|
|
||||||
hostCPU* {.magic: "HostCPU".}: string = ""
|
hostCPU* {.magic: "HostCPU".}: string = ""
|
||||||
## a string that describes the host CPU. Possible values:
|
## a string that describes the host CPU. Possible values:
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,15 @@ elif defined(macosx) or defined(linux) or defined(freebsd) or
|
||||||
SIGSEGV = cint(11)
|
SIGSEGV = cint(11)
|
||||||
SIGTERM = cint(15)
|
SIGTERM = cint(15)
|
||||||
SIGPIPE = cint(13)
|
SIGPIPE = cint(13)
|
||||||
|
elif defined(haiku):
|
||||||
|
const
|
||||||
|
SIGABRT = cint(6)
|
||||||
|
SIGFPE = cint(8)
|
||||||
|
SIGILL = cint(4)
|
||||||
|
SIGINT = cint(2)
|
||||||
|
SIGSEGV = cint(11)
|
||||||
|
SIGTERM = cint(15)
|
||||||
|
SIGPIPE = cint(7)
|
||||||
else:
|
else:
|
||||||
when NoFakeVars:
|
when NoFakeVars:
|
||||||
{.error: "SIGABRT not ported to your platform".}
|
{.error: "SIGABRT not ported to your platform".}
|
||||||
|
|
@ -74,6 +83,8 @@ else:
|
||||||
|
|
||||||
when defined(macosx):
|
when defined(macosx):
|
||||||
const SIGBUS = cint(10)
|
const SIGBUS = cint(10)
|
||||||
|
elif defined(haiku):
|
||||||
|
const SIGBUS = cint(30)
|
||||||
else:
|
else:
|
||||||
template SIGBUS: untyped = SIGSEGV
|
template SIGBUS: untyped = SIGSEGV
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -207,6 +207,9 @@ elif defined(posix):
|
||||||
# some arches like mips and alpha use different values
|
# some arches like mips and alpha use different values
|
||||||
const MAP_ANONYMOUS = 0x20
|
const MAP_ANONYMOUS = 0x20
|
||||||
const MAP_PRIVATE = 0x02 # Changes are private
|
const MAP_PRIVATE = 0x02 # Changes are private
|
||||||
|
elif defined(haiku):
|
||||||
|
const MAP_ANONYMOUS = 0x08
|
||||||
|
const MAP_PRIVATE = 0x02
|
||||||
else:
|
else:
|
||||||
var
|
var
|
||||||
MAP_ANONYMOUS {.importc: "MAP_ANONYMOUS", header: "<sys/mman.h>".}: cint
|
MAP_ANONYMOUS {.importc: "MAP_ANONYMOUS", header: "<sys/mman.h>".}: cint
|
||||||
|
|
|
||||||
|
|
@ -162,10 +162,12 @@ elif defined(genode):
|
||||||
mainTls
|
mainTls
|
||||||
|
|
||||||
else:
|
else:
|
||||||
when not defined(macosx):
|
when not (defined(macosx) or defined(haiku)):
|
||||||
{.passL: "-pthread".}
|
{.passL: "-pthread".}
|
||||||
|
|
||||||
|
when not defined(haiku):
|
||||||
{.passC: "-pthread".}
|
{.passC: "-pthread".}
|
||||||
|
|
||||||
const
|
const
|
||||||
schedh = "#define _GNU_SOURCE\n#include <sched.h>"
|
schedh = "#define _GNU_SOURCE\n#include <sched.h>"
|
||||||
pthreadh = "#define _GNU_SOURCE\n#include <pthread.h>"
|
pthreadh = "#define _GNU_SOURCE\n#include <pthread.h>"
|
||||||
|
|
@ -714,3 +716,13 @@ elif defined(solaris):
|
||||||
if threadId == 0:
|
if threadId == 0:
|
||||||
threadId = int(thr_self())
|
threadId = int(thr_self())
|
||||||
result = threadId
|
result = threadId
|
||||||
|
|
||||||
|
elif defined(haiku):
|
||||||
|
type thr_id {.importc: "thread_id", header: "<OS.h>".} = distinct int32
|
||||||
|
proc find_thread(name: cstring): thr_id {.importc, header: "<OS.h>".}
|
||||||
|
|
||||||
|
proc getThreadId*(): int =
|
||||||
|
## get the ID of the currently running thread.
|
||||||
|
if threadId == 0:
|
||||||
|
threadId = int(find_thread(nil))
|
||||||
|
result = threadId
|
||||||
|
|
|
||||||
8
tests/ccgbugs/topenarraycast.nim
Normal file
8
tests/ccgbugs/topenarraycast.nim
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
proc foo[T](s: var openArray[T]): T =
|
||||||
|
for x in s: result += x
|
||||||
|
|
||||||
|
proc bar(xyz: var seq[int]) =
|
||||||
|
doAssert 6 == (seq[int](xyz)).foo()
|
||||||
|
|
||||||
|
var t = @[1,2,3]
|
||||||
|
bar(t)
|
||||||
13
tests/compiles/t8630.nim
Normal file
13
tests/compiles/t8630.nim
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
discard """
|
||||||
|
output: '''
|
||||||
|
foo
|
||||||
|
bar
|
||||||
|
'''
|
||||||
|
"""
|
||||||
|
|
||||||
|
proc test(strings: seq[string]) =
|
||||||
|
for s in strings:
|
||||||
|
var p3 = unsafeAddr(s)
|
||||||
|
echo p3[]
|
||||||
|
|
||||||
|
test(@["foo", "bar"])
|
||||||
|
|
@ -28,6 +28,9 @@ if paramCount() == 0:
|
||||||
# windows kill happens using TerminateProcess(h, 0), so we should get a
|
# windows kill happens using TerminateProcess(h, 0), so we should get a
|
||||||
# 0 here
|
# 0 here
|
||||||
echo p.waitForExit() == 0
|
echo p.waitForExit() == 0
|
||||||
|
elif defined(haiku):
|
||||||
|
# on Haiku, the program main thread receive SIGKILLTHR
|
||||||
|
echo p.waitForExit() == 128 + SIGKILLTHR
|
||||||
else:
|
else:
|
||||||
# on posix (non-windows), kill sends SIGKILL
|
# on posix (non-windows), kill sends SIGKILL
|
||||||
echo p.waitForExit() == 128 + SIGKILL
|
echo p.waitForExit() == 128 + SIGKILL
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ else:
|
||||||
var process: Process
|
var process: Process
|
||||||
when defined(android):
|
when defined(android):
|
||||||
process = startProcess("/system/bin/env", "/system/bin", ["true"])
|
process = startProcess("/system/bin/env", "/system/bin", ["true"])
|
||||||
|
elif defined(haiku):
|
||||||
|
process = startProcess("/bin/env", "/bin", ["true"])
|
||||||
else:
|
else:
|
||||||
process = startProcess("/usr/bin/env", "/usr/bin", ["true"])
|
process = startProcess("/usr/bin/env", "/usr/bin", ["true"])
|
||||||
let dir2 = getCurrentDir()
|
let dir2 = getCurrentDir()
|
||||||
|
|
|
||||||
|
|
@ -109,10 +109,14 @@ proc runBasicDLLTest(c, r: var TResults, cat: Category, options: string) =
|
||||||
safeCopyFile("lib" / nimrtlDll, "tests/dll" / nimrtlDll)
|
safeCopyFile("lib" / nimrtlDll, "tests/dll" / nimrtlDll)
|
||||||
else:
|
else:
|
||||||
# posix relies on crappy LD_LIBRARY_PATH (ugh!):
|
# posix relies on crappy LD_LIBRARY_PATH (ugh!):
|
||||||
var libpath = getEnv"LD_LIBRARY_PATH".string
|
const libpathenv = when defined(haiku):
|
||||||
|
"LIBRARY_PATH"
|
||||||
|
else:
|
||||||
|
"LD_LIBRARY_PATH"
|
||||||
|
var libpath = getEnv(libpathenv).string
|
||||||
# Temporarily add the lib directory to LD_LIBRARY_PATH:
|
# Temporarily add the lib directory to LD_LIBRARY_PATH:
|
||||||
putEnv("LD_LIBRARY_PATH", "tests/dll" & (if libpath.len > 0: ":" & libpath else: ""))
|
putEnv(libpathenv, "tests/dll" & (if libpath.len > 0: ":" & libpath else: ""))
|
||||||
defer: putEnv("LD_LIBRARY_PATH", libpath)
|
defer: putEnv(libpathenv, libpath)
|
||||||
var nimrtlDll = DynlibFormat % "nimrtl"
|
var nimrtlDll = DynlibFormat % "nimrtl"
|
||||||
safeCopyFile("lib" / nimrtlDll, "tests/dll" / nimrtlDll)
|
safeCopyFile("lib" / nimrtlDll, "tests/dll" / nimrtlDll)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue