mirror of
https://github.com/nim-lang/Nim.git
synced 2026-05-28 15:55:14 +00:00
Merge remote-tracking branch 'origin/devel' into pr_when_typ
This commit is contained in:
2
.github/workflows/ci_publish.yml
vendored
2
.github/workflows/ci_publish.yml
vendored
@@ -60,7 +60,7 @@ jobs:
|
||||
run: nim c -r -d:release ci/action.nim
|
||||
|
||||
- name: 'Comment'
|
||||
uses: actions/github-script@v8
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
|
||||
@@ -35,6 +35,10 @@ errors.
|
||||
|
||||
- Adds a new warning `--warning:ImplicitRangeConversion` that detects downsizing implicit conversions to range types (e.g., `int -> range[0..255]` or `range[1..256] -> range[0..255]`) that could cause runtime panics. Safe conversions like `range[0..255] -> range[0..65535]` and explicit casts do not trigger warnings. `int` to `Natural` and `Positive` conversions do not trigger warnings, which can be enabled with `--warning:systemRangeConversion`.
|
||||
|
||||
- Procedure compatibility also checks the backend representation of the
|
||||
parameter and result types, not just their source-level shape. Use
|
||||
`--legacy:procParamTypeBackendAliases` to restore the older behavior.
|
||||
|
||||
## Standard library additions and changes
|
||||
|
||||
[//]: # "Additions:"
|
||||
@@ -66,12 +70,17 @@ errors.
|
||||
Modes include `Nim` (default, fully compatible) and two new experimental modes:
|
||||
`Lax` and `Gnu` for different option parsing behaviors.
|
||||
|
||||
- `std/nre2` is added to replace deprecated NRE.
|
||||
|
||||
[//]: # "Changes:"
|
||||
|
||||
- `std/math` The `^` symbol now supports floating-point as exponent in addition to the Natural type.
|
||||
- `min`, `max`, and `sequtils`' `minIndex`, `maxIndex` and `minmax` for `openArray`s now accept a comparison function.
|
||||
- `system.substr` implementation now uses `copymem` (wrapped C `memcpy`) for copying data, if available at compilation.
|
||||
- `system.newStringUninit` is now considered free of side-effects allowing it to be used with `--experimental:strictFuncs`.
|
||||
- `std/re` and `std/nre` are deprecated as PCRE library is obsolete.
|
||||
Use https://github.com/nitely/nim-regex or `std/nre2`.
|
||||
See: https://github.com/nim-lang/Nim/issues/23668.
|
||||
|
||||
## Language changes
|
||||
|
||||
|
||||
@@ -230,11 +230,11 @@ proc genOpenArraySlice(p: BProc; q: PNode; formalType, destType: PType; prepareF
|
||||
of tyString, tySequence:
|
||||
let atyp = skipTypes(a.t, abstractInst)
|
||||
if formalType.skipTypes(abstractInst).kind in {tyVar} and atyp.kind == tyString and
|
||||
optSeqDestructors in p.config.globalOptions and not p.config.isDefined("nimsso"):
|
||||
optSeqDestructors in p.config.globalOptions and not p.config.usesSso():
|
||||
let bra = byRefLoc(p, a)
|
||||
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimPrepareStrMutationV2"),
|
||||
bra)
|
||||
if p.config.isDefined("nimsso") and
|
||||
if p.config.usesSso() and
|
||||
skipTypes(a.t, abstractVar + abstractInst).kind == tyString:
|
||||
let strPtr = if atyp.kind in {tyVar} and not compileToCpp(p.module): ra
|
||||
else: addrLoc(p.config, a)
|
||||
@@ -296,11 +296,11 @@ proc openArrayLoc(p: BProc, formalType: PType, n: PNode; result: var Builder) =
|
||||
of tyString, tySequence:
|
||||
let ntyp = skipTypes(n.typ, abstractInst)
|
||||
if formalType.skipTypes(abstractInst).kind in {tyVar} and ntyp.kind == tyString and
|
||||
optSeqDestructors in p.config.globalOptions and not p.config.isDefined("nimsso"):
|
||||
optSeqDestructors in p.config.globalOptions and not p.config.usesSso():
|
||||
let bra = byRefLoc(p, a)
|
||||
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimPrepareStrMutationV2"),
|
||||
bra)
|
||||
if p.config.isDefined("nimsso") and
|
||||
if p.config.usesSso() and
|
||||
skipTypes(n.typ, abstractVar + abstractInst).kind == tyString:
|
||||
if ntyp.kind in {tyVar} and not compileToCpp(p.module):
|
||||
let ra = a.rdLoc
|
||||
@@ -335,7 +335,7 @@ proc openArrayLoc(p: BProc, formalType: PType, n: PNode; result: var Builder) =
|
||||
let ra = a.rdLoc
|
||||
var t = TLoc(snippet: cDeref(ra))
|
||||
let lt = lenExpr(p, t)
|
||||
if p.config.isDefined("nimsso"):
|
||||
if p.config.usesSso():
|
||||
result.add(cCall(cgsymValue(p.module, "nimStrData"), ra))
|
||||
result.addArgumentSeparator()
|
||||
result.add(cCall(cgsymValue(p.module, "nimStrLen"), t.snippet))
|
||||
@@ -370,7 +370,7 @@ proc expressionsNeedsTmp(p: BProc, a: TLoc): TLoc =
|
||||
proc genArgStringToCString(p: BProc, n: PNode; result: var Builder; needsTmp: bool) {.inline.} =
|
||||
var a = initLocExpr(p, n[0])
|
||||
let tmp = withTmpIfNeeded(p, a, needsTmp)
|
||||
let ra = if p.config.isDefined("nimsso"): byRefLoc(p, tmp) else: tmp.rdLoc
|
||||
let ra = if p.config.usesSso(): byRefLoc(p, tmp) else: tmp.rdLoc
|
||||
result.addCall(cgsymValue(p.module, "nimToCStringConv"), ra)
|
||||
|
||||
proc genArg(p: BProc, n: PNode, param: PSym; call: PNode; result: var Builder; needsTmp = false) =
|
||||
|
||||
@@ -322,7 +322,7 @@ proc genOpenArrayConv(p: BProc; d: TLoc; a: TLoc; flags: TAssignmentFlags) =
|
||||
bra)
|
||||
let rd = d.rdLoc
|
||||
let la = lenExpr(p, a)
|
||||
if p.config.isDefined("nimsso"):
|
||||
if p.config.usesSso():
|
||||
let bra = byRefLoc(p, a)
|
||||
p.s(cpsStmts).addFieldAssignment(rd, "Field0",
|
||||
cCall(cgsymValue(p.module, "nimStrData"), bra))
|
||||
@@ -963,7 +963,7 @@ proc genDeref(p: BProc, e: PNode, d: var TLoc) =
|
||||
|
||||
proc cowBracket(p: BProc; n: PNode) =
|
||||
if n.kind == nkBracketExpr and optSeqDestructors in p.config.globalOptions and
|
||||
not p.config.isDefined("nimsso"):
|
||||
not p.config.usesSso():
|
||||
let strCandidate = n[0]
|
||||
if strCandidate.typ.skipTypes(abstractInst).kind == tyString:
|
||||
var a: TLoc = initLocExpr(p, strCandidate)
|
||||
@@ -989,7 +989,7 @@ proc genAddr(p: BProc, e: PNode, d: var TLoc) =
|
||||
# bug #19497
|
||||
d.lode = e
|
||||
else:
|
||||
let ssoStrSub = p.config.isDefined("nimsso") and e[0].kind == nkBracketExpr and
|
||||
let ssoStrSub = p.config.usesSso() and e[0].kind == nkBracketExpr and
|
||||
e[0][0].typ.skipTypes(abstractVar).kind == tyString
|
||||
var a: TLoc = initLocExpr(p, e[0], if ssoStrSub: {lfEnforceDeref, lfPrepareForMutation} else: {})
|
||||
if e[0].kind in {nkHiddenStdConv, nkHiddenSubConv, nkConv} and not ignoreConv(e[0]):
|
||||
@@ -1318,7 +1318,7 @@ proc genSeqElem(p: BProc, n, x, y: PNode, d: var TLoc) =
|
||||
if skipTypes(a.t, abstractVar).kind in {tyRef, tyPtr}:
|
||||
a.snippet = cDeref(a.snippet)
|
||||
|
||||
if p.config.isDefined("nimsso") and ty.kind == tyString:
|
||||
if p.config.usesSso() and ty.kind == tyString:
|
||||
let bra = byRefLoc(p, a)
|
||||
if lfPrepareForMutation in d.flags:
|
||||
# Use nimStrAtMutV3 to get a mutable reference (char*) to the element.
|
||||
@@ -1889,10 +1889,17 @@ proc genObjConstr(p: BProc, e: PNode, d: var TLoc) =
|
||||
var t = e.typ.skipTypes(abstractInstOwned)
|
||||
let isRef = t.kind == tyRef
|
||||
|
||||
# check if we need to construct the object in a temporary
|
||||
# check if we need to construct the object in a temporary.
|
||||
# A temp is needed when:
|
||||
# - the constructor produces a ref (isRef)
|
||||
# - the destination is not a writable location (d.k == locNone)
|
||||
# - the constructed type differs from the destination type (subtype
|
||||
# assignments need the genAssignment path for ObjectAssignmentDefect)
|
||||
# - the constructor's field values may alias the destination (isPartOf)
|
||||
var useTemp =
|
||||
isRef or
|
||||
(d.k notin {locTemp,locLocalVar,locGlobalVar,locParam,locField}) or
|
||||
d.k == locNone or
|
||||
(d.t != nil and not sameBackendType(t, d.t.skipTypes(abstractInstOwned))) or
|
||||
(isPartOf(d.lode, e) != arNo)
|
||||
|
||||
var tmp: TLoc = default(TLoc)
|
||||
@@ -2143,7 +2150,7 @@ proc genRepr(p: BProc, e: PNode, d: var TLoc) =
|
||||
putIntoDest(p, b, e, ra & cArgumentSeparator & ra & "Len_0", a.storage)
|
||||
of tyString, tySequence:
|
||||
let la = lenExpr(p, a)
|
||||
if p.config.isDefined("nimsso") and
|
||||
if p.config.usesSso() and
|
||||
skipTypes(a.t, abstractVarRange).kind == tyString:
|
||||
let bra = byRefLoc(p, a)
|
||||
putIntoDest(p, b, e,
|
||||
@@ -2736,7 +2743,7 @@ proc genConv(p: BProc, e: PNode, d: var TLoc) =
|
||||
|
||||
proc convStrToCStr(p: BProc, n: PNode, d: var TLoc) =
|
||||
var a: TLoc = initLocExpr(p, n[0])
|
||||
let arg = if p.config.isDefined("nimsso"): byRefLoc(p, a) else: rdLoc(a)
|
||||
let arg = if p.config.usesSso(): byRefLoc(p, a) else: rdLoc(a)
|
||||
putIntoDest(p, d, n,
|
||||
cgCall(p, "nimToCStringConv", arg),
|
||||
a.storage)
|
||||
@@ -2815,7 +2822,7 @@ proc genMove(p: BProc; n: PNode; d: var TLoc) =
|
||||
var src: TLoc = initLocExpr(p, n[2])
|
||||
let destVal = rdLoc(a)
|
||||
let srcVal = rdLoc(src)
|
||||
if p.config.isDefined("nimsso") and
|
||||
if p.config.usesSso() and
|
||||
n[1].typ.skipTypes(abstractVar).kind == tyString:
|
||||
# SmallString: destroy dst then struct-copy src; no .p field aliasing needed
|
||||
genStmts(p, n[3])
|
||||
@@ -2864,7 +2871,7 @@ proc genDestroy(p: BProc; n: PNode) =
|
||||
case t.kind
|
||||
of tyString:
|
||||
var a: TLoc = initLocExpr(p, arg)
|
||||
if p.config.isDefined("nimsso"):
|
||||
if p.config.usesSso():
|
||||
# SmallString: delegate to nimDestroyStrV1 (rc-based, handles static strings)
|
||||
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimDestroyStrV1"), rdLoc(a))
|
||||
else:
|
||||
@@ -4236,7 +4243,7 @@ proc genBracedInit(p: BProc, n: PNode; isConst: bool; optionalType: PType; resul
|
||||
genConstObjConstr(p, n, isConst, result)
|
||||
of tyString, tyCstring:
|
||||
if optSeqDestructors in p.config.globalOptions and n.kind != nkNilLit and ty == tyString:
|
||||
if p.config.isDefined("nimsso"):
|
||||
if p.config.usesSso():
|
||||
genStringLiteralV3Const(p.module, n, isConst, result)
|
||||
else:
|
||||
genStringLiteralV2Const(p.module, n, isConst, result)
|
||||
|
||||
@@ -22,7 +22,7 @@ template detectVersion(field, corename) =
|
||||
result = 1
|
||||
|
||||
proc detectStrVersion(m: BModule): int =
|
||||
if m.g.config.isDefined("nimsso") and
|
||||
if m.g.config.usesSso() and
|
||||
m.g.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc, gcHooks}:
|
||||
result = 3
|
||||
else:
|
||||
|
||||
@@ -230,7 +230,7 @@ proc blockLeaveActions(p: BProc, howManyTrys, howManyExcepts: int, isReturnStmt
|
||||
# Called by return and break stmts.
|
||||
# Deals with issues faced when jumping out of try/except/finally stmts.
|
||||
|
||||
var stack = newSeq[tuple[fin: PNode, inExcept: bool, label: Natural]](0)
|
||||
var stack = newSeq[tuple[fin: PNode, inExcept: bool, isHidden: bool, label: Natural]](0)
|
||||
|
||||
inc p.withinBlockLeaveActions
|
||||
for i in 1..howManyTrys:
|
||||
@@ -836,12 +836,26 @@ proc raiseExitCleanup(p: BProc, destroy: string) =
|
||||
p.s(cpsStmts).addGoto("LA" & $p.nestedTryStmts[^1].label & "_")
|
||||
|
||||
proc finallyActions(p: BProc) =
|
||||
if p.config.exc != excGoto and p.nestedTryStmts.len > 0 and p.nestedTryStmts[^1].inExcept:
|
||||
# if the current try stmt have a finally block,
|
||||
# we must execute it before reraising
|
||||
let finallyBlock = p.nestedTryStmts[^1].fin
|
||||
if finallyBlock != nil:
|
||||
genSimpleBlock(p, finallyBlock[0])
|
||||
if p.config.exc != excGoto:
|
||||
# Walk past compiler-injected `nkHiddenTryStmt` wrappers (e.g. ARC's
|
||||
# destructor try/finally that wraps `except T as e:` bodies) to reach
|
||||
# the user's actual try. We must NOT walk past a real user try whose
|
||||
# body we are currently in, because a raise from there will be caught
|
||||
# by that try's own except branches rather than escaping outward.
|
||||
#
|
||||
# If after skipping wrappers the next entry is a user try in its
|
||||
# except branch (inExcept=true), inline its finally body before the
|
||||
# raise propagates — without this, the C++ sibling-catch rule would
|
||||
# cause the user's catch(...)/finally pair to be bypassed and the
|
||||
# finally would be silently dropped.
|
||||
for i in countdown(p.nestedTryStmts.high, 0):
|
||||
if p.nestedTryStmts[i].isHidden:
|
||||
continue
|
||||
if p.nestedTryStmts[i].inExcept:
|
||||
let finallyBlock = p.nestedTryStmts[i].fin
|
||||
if finallyBlock != nil:
|
||||
genSimpleBlock(p, finallyBlock[0])
|
||||
return
|
||||
|
||||
proc raiseInstr(p: BProc; result: var Builder) =
|
||||
if p.config.exc == excGoto:
|
||||
@@ -1185,7 +1199,7 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) =
|
||||
lineCg(p, cpsLocals, "std::exception_ptr T$1_;$n", [etmp])
|
||||
|
||||
let fin = if t[^1].kind == nkFinally: t[^1] else: nil
|
||||
p.nestedTryStmts.add((fin, false, 0.Natural))
|
||||
p.nestedTryStmts.add((fin, false, t.kind == nkHiddenTryStmt, 0.Natural))
|
||||
|
||||
if t.kind == nkHiddenTryStmt:
|
||||
lineCg(p, cpsStmts, "try {$n", [])
|
||||
@@ -1371,7 +1385,7 @@ proc genTryCppOld(p: BProc, t: PNode, d: var TLoc) =
|
||||
genLineDir(p, t)
|
||||
cgsym(p.module, "popCurrentExceptionEx")
|
||||
let fin = if t[^1].kind == nkFinally: t[^1] else: nil
|
||||
p.nestedTryStmts.add((fin, false, 0.Natural))
|
||||
p.nestedTryStmts.add((fin, false, t.kind == nkHiddenTryStmt, 0.Natural))
|
||||
startBlockWith(p):
|
||||
p.s(cpsStmts).add("try {\n")
|
||||
expr(p, t[0], d)
|
||||
@@ -1450,7 +1464,7 @@ proc genTryGoto(p: BProc; t: PNode; d: var TLoc) =
|
||||
let lab = p.labels
|
||||
let hasExcept = t[1].kind == nkExceptBranch
|
||||
if hasExcept: inc p.withinTryWithExcept
|
||||
p.nestedTryStmts.add((fin, false, Natural lab))
|
||||
p.nestedTryStmts.add((fin, false, t.kind == nkHiddenTryStmt, Natural lab))
|
||||
|
||||
p.flags.incl nimErrorFlagAccessed
|
||||
|
||||
@@ -1656,7 +1670,7 @@ proc genTrySetjmp(p: BProc, t: PNode, d: var TLoc) =
|
||||
initElifBranch(p.s(cpsStmts), nonQuirkyIf, removeSinglePar(
|
||||
cOp(Equal, dotField(safePoint, "status"), cIntValue(0))))
|
||||
let fin = if t[^1].kind == nkFinally: t[^1] else: nil
|
||||
p.nestedTryStmts.add((fin, quirkyExceptions, 0.Natural))
|
||||
p.nestedTryStmts.add((fin, quirkyExceptions, t.kind == nkHiddenTryStmt, 0.Natural))
|
||||
expr(p, t[0], d)
|
||||
var quirkyIf = default(IfBuilder)
|
||||
var quirkyScope = default(ScopeBuilder)
|
||||
@@ -1940,7 +1954,7 @@ proc genAsgn(p: BProc, e: PNode, fastAsgn: bool) =
|
||||
elif optFieldCheck in p.options and isDiscriminantField(e[0]):
|
||||
genLineDir(p, e)
|
||||
asgnFieldDiscriminant(p, e)
|
||||
elif p.config.isDefined("nimsso") and e[0].kind == nkBracketExpr and
|
||||
elif p.config.usesSso() and e[0].kind == nkBracketExpr and
|
||||
e[0][0].typ.skipTypes(abstractVar).kind == tyString:
|
||||
# nimsso: s[i] = c → nimStrPutV3(&s, i, c) (handles COW internally)
|
||||
genLineDir(p, e)
|
||||
|
||||
@@ -389,7 +389,7 @@ proc lenField(p: BProc, val: Rope): Rope {.inline.} =
|
||||
|
||||
proc lenExpr(p: BProc; a: TLoc): Rope =
|
||||
if optSeqDestructors in p.config.globalOptions:
|
||||
if p.config.isDefined("nimsso") and a.lode != nil and a.t != nil and
|
||||
if p.config.usesSso() and a.lode != nil and a.t != nil and
|
||||
a.t.skipTypes(abstractInst).kind == tyString:
|
||||
result = cCall(cgsymValue(p.module, "nimStrLen"), rdLoc(a))
|
||||
else:
|
||||
@@ -534,7 +534,7 @@ proc resetLoc(p: BProc, loc: var TLoc) =
|
||||
|
||||
let atyp = skipTypes(loc.t, abstractInst)
|
||||
let rl = rdLoc(loc)
|
||||
if typ.kind == tyString and p.config.isDefined("nimsso"):
|
||||
if typ.kind == tyString and p.config.usesSso():
|
||||
# SmallString zero state: bytes=0 (slen=0 in low byte, all inline chars zeroed)
|
||||
if atyp.kind in {tyVar, tyLent}:
|
||||
p.s(cpsStmts).addAssignment(derefField(rl, "bytes"), cIntValue(0))
|
||||
@@ -592,7 +592,7 @@ proc constructLoc(p: BProc, loc: var TLoc, isTemp = false) =
|
||||
let typ = loc.t
|
||||
if optSeqDestructors in p.config.globalOptions and skipTypes(typ, abstractInst + {tyStatic}).kind in {tyString, tySequence}:
|
||||
let rl = rdLoc(loc)
|
||||
if skipTypes(typ, abstractInst + {tyStatic}).kind == tyString and p.config.isDefined("nimsso"):
|
||||
if skipTypes(typ, abstractInst + {tyStatic}).kind == tyString and p.config.usesSso():
|
||||
# SmallString zero state: bytes=0 (slen=0 in low byte, all inline chars zeroed)
|
||||
p.s(cpsStmts).addFieldAssignment(rl, "bytes", cIntValue(0))
|
||||
p.s(cpsStmts).addFieldAssignment(rl, "more", NimNil)
|
||||
|
||||
@@ -75,10 +75,13 @@ type
|
||||
flags*: set[TCProcFlag]
|
||||
lastLineInfo*: TLineInfo # to avoid generating excessive 'nimln' statements
|
||||
currLineInfo*: TLineInfo # AST codegen will make this superfluous
|
||||
nestedTryStmts*: seq[tuple[fin: PNode, inExcept: bool, label: Natural]]
|
||||
nestedTryStmts*: seq[tuple[fin: PNode, inExcept: bool, isHidden: bool, label: Natural]]
|
||||
# in how many nested try statements we are
|
||||
# (the vars must be volatile then)
|
||||
# bool is true when are in the except part of a try block
|
||||
# `inExcept` is true when we are in the except part of a try block.
|
||||
# `isHidden` is true for compiler-injected `nkHiddenTryStmt` wrappers
|
||||
# (e.g. ARC's destructor try/finally around `except T as e:` bodies);
|
||||
# finallyActions walks past such wrappers to reach the user's try.
|
||||
finallySafePoints*: seq[Rope] # For correctly cleaning up exceptions when
|
||||
# using return in finally statements
|
||||
labels*: Natural # for generating unique labels in the C proc
|
||||
|
||||
@@ -250,6 +250,7 @@ const
|
||||
errGuiConsoleOrLibExpectedButXFound = "'gui', 'console', 'lib' or 'staticlib' expected, but '$1' found"
|
||||
errInvalidExceptionSystem = "'goto', 'setjmp', 'cpp' or 'quirky' expected, but '$1' found"
|
||||
errInvalidFeatureButXFound = Feature.toSeq.map(proc(val:Feature): string = "'$1'" % $val).join(", ") & " expected, but '$1' found"
|
||||
errDefaultOrSsoExpectedButXFound = "'default' or 'sso' expected, but '$1' found"
|
||||
|
||||
template warningOptionNoop(switch: string) =
|
||||
warningDeprecated(conf, info, "'$#' is deprecated, now a noop" % switch)
|
||||
@@ -306,6 +307,13 @@ proc testCompileOptionArg*(conf: ConfigRef; switch, arg: string, info: TLineInfo
|
||||
else:
|
||||
result = false
|
||||
localError(conf, info, errInvalidExceptionSystem % arg)
|
||||
of "strings":
|
||||
case arg.normalize
|
||||
of "default": result = conf.selectedStrings == stringDefault
|
||||
of "sso": result = conf.selectedStrings == stringSso
|
||||
else:
|
||||
result = false
|
||||
localError(conf, info, errDefaultOrSsoExpectedButXFound % arg)
|
||||
of "experimental":
|
||||
try:
|
||||
result = conf.features.contains parseEnum[Feature](arg)
|
||||
@@ -750,6 +758,17 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||
processMemoryManagementOption(switch, arg, pass, info, conf)
|
||||
of "mm":
|
||||
processMemoryManagementOption(switch, arg, pass, info, conf)
|
||||
of "strings":
|
||||
expectArg(conf, switch, arg, pass, info)
|
||||
if pass in {passCmd2, passPP}:
|
||||
case arg.normalize
|
||||
of "default":
|
||||
conf.selectedStrings = stringDefault
|
||||
of "sso":
|
||||
conf.selectedStrings = stringSso
|
||||
defineSymbol(conf.symbols, "nimsso")
|
||||
else:
|
||||
localError(conf, info, errDefaultOrSsoExpectedButXFound % arg)
|
||||
of "warnings", "w":
|
||||
if processOnOffSwitchOrList(conf, {optWarns}, arg, pass, info): listWarnings(conf)
|
||||
of "warning": processSpecificNote(arg, wWarning, pass, info, switch, conf)
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
## Generate a .build.nif file for nifmake from a Nim project.
|
||||
## This enables incremental and parallel compilation using the `m` switch.
|
||||
|
||||
import std / [os, tables, sets, times, osproc, strutils]
|
||||
import std / [os, tables, sets, times, osproc]
|
||||
import options, msgs, lineinfos, pathutils
|
||||
|
||||
import "../dist/nimony/src/lib" / [nifstreams, nifcursors, bitabs, nifreader, nifbuilder]
|
||||
import "../dist/nimony/src/lib" / [nifstreams, bitabs, nifreader, nifbuilder]
|
||||
import "../dist/nimony/src/gear2" / modnames
|
||||
|
||||
type
|
||||
@@ -79,22 +79,19 @@ proc runNifler(c: DepContext; nimFile: string): bool =
|
||||
let exitCode = execShellCmd(cmd)
|
||||
result = exitCode == 0
|
||||
|
||||
proc resolveFile(c: DepContext; origin, toResolve: string): string =
|
||||
## Resolve an import path relative to origin file
|
||||
# Handle std/ prefix
|
||||
var path = toResolve
|
||||
if path.startsWith("std/"):
|
||||
path = path.substr(4)
|
||||
proc resolveImport(c: DepContext; origin, toResolve: string): string =
|
||||
## Resolve an import path using the compiler's normal module lookup rules.
|
||||
result = findModule(c.config, toResolve, origin).string
|
||||
|
||||
# Try relative to origin first
|
||||
proc resolveInclude(c: DepContext; origin, toResolve: string): string =
|
||||
## Resolve an include path relative to the including file or the search paths.
|
||||
let originDir = parentDir(origin)
|
||||
result = originDir / path.addFileExt("nim")
|
||||
result = originDir / toResolve.addFileExt("nim")
|
||||
if fileExists(result):
|
||||
return result
|
||||
|
||||
# Try search paths
|
||||
for searchPath in c.config.searchPaths:
|
||||
result = searchPath.string / path.addFileExt("nim")
|
||||
result = searchPath.string / toResolve.addFileExt("nim")
|
||||
if fileExists(result):
|
||||
return result
|
||||
|
||||
@@ -103,7 +100,7 @@ proc resolveFile(c: DepContext; origin, toResolve: string): string =
|
||||
proc traverseDeps(c: var DepContext; pair: FilePair; current: Node)
|
||||
|
||||
proc processInclude(c: var DepContext; includePath: string; current: Node) =
|
||||
let resolved = resolveFile(c, current.files[current.files.len - 1].nimFile, includePath)
|
||||
let resolved = resolveInclude(c, current.files[current.files.len - 1].nimFile, includePath)
|
||||
if resolved.len == 0 or not fileExists(resolved):
|
||||
return
|
||||
|
||||
@@ -118,7 +115,7 @@ proc processInclude(c: var DepContext; includePath: string; current: Node) =
|
||||
discard c.includeStack.pop()
|
||||
|
||||
proc processImport(c: var DepContext; importPath: string; current: Node) =
|
||||
let resolved = resolveFile(c, current.files[0].nimFile, importPath)
|
||||
let resolved = resolveImport(c, current.files[0].nimFile, importPath)
|
||||
if resolved.len == 0 or not fileExists(resolved):
|
||||
return
|
||||
|
||||
@@ -140,6 +137,171 @@ proc processImport(c: var DepContext; importPath: string; current: Node) =
|
||||
if existingIdx notin current.deps:
|
||||
current.deps.add existingIdx
|
||||
|
||||
proc skipSubtree(s: var Stream; first: PackedToken) =
|
||||
## Consume tokens until the ParLe at `first` is balanced. Caller has
|
||||
## already obtained `first`.
|
||||
if first.kind != ParLe: return
|
||||
var depth = 1
|
||||
while depth > 0:
|
||||
let t = next(s)
|
||||
if t.kind == ParLe: inc depth
|
||||
elif t.kind == ParRi: dec depth
|
||||
elif t.kind == EofToken: return
|
||||
|
||||
proc evalCondExpr(c: DepContext; s: var Stream): bool =
|
||||
## Read exactly one condition expression from `s` and return its truth
|
||||
## value. Consumes tokens whether the expression is recognised or not so
|
||||
## the caller stays in sync. Recognises `defined(IDENT)`, the boolean
|
||||
## operators `not`/`and`/`or`, and the literals `true`/`false`. Anything
|
||||
## else (e.g. a call to an arbitrary proc) is treated as `true` — the
|
||||
## conservative direction, since a false negative here drops a real
|
||||
## dependency from the build graph.
|
||||
let t = next(s)
|
||||
case t.kind
|
||||
of Ident:
|
||||
case pool.strings[t.litId]
|
||||
of "true": result = true
|
||||
of "false": result = false
|
||||
else: result = true
|
||||
of ParLe:
|
||||
let tag = pool.tags[t.tagId]
|
||||
case tag
|
||||
of "call", "cmd", "callstrlit", "infix", "prefix":
|
||||
# First child is the head (function/operator name).
|
||||
let head = next(s)
|
||||
var name = ""
|
||||
if head.kind == Ident: name = pool.strings[head.litId]
|
||||
case name
|
||||
of "defined":
|
||||
let arg = next(s)
|
||||
var sym = ""
|
||||
if arg.kind == Ident: sym = pool.strings[arg.litId]
|
||||
result = sym.len > 0 and isDefined(c.config, sym)
|
||||
of "not":
|
||||
result = not evalCondExpr(c, s)
|
||||
of "and":
|
||||
result = evalCondExpr(c, s)
|
||||
if result: result = evalCondExpr(c, s)
|
||||
else: skipSubtree(s, next(s))
|
||||
of "or":
|
||||
result = evalCondExpr(c, s)
|
||||
if not result: result = evalCondExpr(c, s)
|
||||
else: skipSubtree(s, next(s))
|
||||
else:
|
||||
result = true
|
||||
# Drain whatever remains until the matching ParRi.
|
||||
var depth = 1
|
||||
while depth > 0:
|
||||
let n = next(s)
|
||||
if n.kind == ParLe: inc depth
|
||||
elif n.kind == ParRi: dec depth
|
||||
elif n.kind == EofToken: return
|
||||
of "not":
|
||||
result = not evalCondExpr(c, s)
|
||||
var depth = 1
|
||||
while depth > 0:
|
||||
let n = next(s)
|
||||
if n.kind == ParLe: inc depth
|
||||
elif n.kind == ParRi: dec depth
|
||||
elif n.kind == EofToken: return
|
||||
of "and":
|
||||
result = evalCondExpr(c, s)
|
||||
if result: result = evalCondExpr(c, s)
|
||||
else: skipSubtree(s, next(s))
|
||||
# consume closing ParRi
|
||||
var depth = 1
|
||||
while depth > 0:
|
||||
let n = next(s)
|
||||
if n.kind == ParLe: inc depth
|
||||
elif n.kind == ParRi: dec depth
|
||||
elif n.kind == EofToken: return
|
||||
of "or":
|
||||
result = evalCondExpr(c, s)
|
||||
if not result: result = evalCondExpr(c, s)
|
||||
else: skipSubtree(s, next(s))
|
||||
var depth = 1
|
||||
while depth > 0:
|
||||
let n = next(s)
|
||||
if n.kind == ParLe: inc depth
|
||||
elif n.kind == ParRi: dec depth
|
||||
elif n.kind == EofToken: return
|
||||
else:
|
||||
skipSubtree(s, t)
|
||||
result = true
|
||||
else:
|
||||
result = true
|
||||
|
||||
proc whenMarkerHolds(c: DepContext; s: var Stream): bool =
|
||||
## Caller has just consumed the `(when` ParLe. Read children until the
|
||||
## matching `)`, AND-ing each evaluated condition.
|
||||
result = true
|
||||
while true:
|
||||
# peek by reading; if it's ParRi, we're done
|
||||
let t = next(s)
|
||||
if t.kind == ParRi: return
|
||||
if t.kind == EofToken: return
|
||||
if t.kind == ParLe:
|
||||
# Re-feed by manually evaluating the subtree starting at `t`.
|
||||
# evalCondExpr expects to read its own opener, so handle it directly.
|
||||
let tag = pool.tags[t.tagId]
|
||||
case tag
|
||||
of "call", "cmd", "callstrlit", "infix", "prefix":
|
||||
let head = next(s)
|
||||
var name = ""
|
||||
if head.kind == Ident: name = pool.strings[head.litId]
|
||||
var ok = true
|
||||
case name
|
||||
of "defined":
|
||||
let arg = next(s)
|
||||
var sym = ""
|
||||
if arg.kind == Ident: sym = pool.strings[arg.litId]
|
||||
ok = sym.len > 0 and isDefined(c.config, sym)
|
||||
of "not":
|
||||
ok = not evalCondExpr(c, s)
|
||||
of "and":
|
||||
ok = evalCondExpr(c, s)
|
||||
if ok: ok = evalCondExpr(c, s)
|
||||
of "or":
|
||||
ok = evalCondExpr(c, s)
|
||||
if not ok: ok = evalCondExpr(c, s)
|
||||
else:
|
||||
ok = true
|
||||
# finish the subtree
|
||||
var depth = 1
|
||||
while depth > 0:
|
||||
let n = next(s)
|
||||
if n.kind == ParLe: inc depth
|
||||
elif n.kind == ParRi: dec depth
|
||||
elif n.kind == EofToken: return
|
||||
if not ok: result = false
|
||||
of "not", "and", "or":
|
||||
# Re-emit a synthetic dispatch: rewrap by descending.
|
||||
var ok = true
|
||||
case tag
|
||||
of "not":
|
||||
ok = not evalCondExpr(c, s)
|
||||
of "and":
|
||||
ok = evalCondExpr(c, s)
|
||||
if ok: ok = evalCondExpr(c, s)
|
||||
of "or":
|
||||
ok = evalCondExpr(c, s)
|
||||
if not ok: ok = evalCondExpr(c, s)
|
||||
else: discard
|
||||
var depth = 1
|
||||
while depth > 0:
|
||||
let n = next(s)
|
||||
if n.kind == ParLe: inc depth
|
||||
elif n.kind == ParRi: dec depth
|
||||
elif n.kind == EofToken: return
|
||||
if not ok: result = false
|
||||
else:
|
||||
# Unknown — treat as true and skip.
|
||||
skipSubtree(s, t)
|
||||
elif t.kind == Ident:
|
||||
let v = pool.strings[t.litId]
|
||||
if v == "false": result = false
|
||||
# else (true / unknown ident): keep result
|
||||
|
||||
proc readDepsFile(c: var DepContext; pair: FilePair; current: Node) =
|
||||
## Read a .deps.nif file and process imports/includes
|
||||
let depsPath = c.depsFile(pair)
|
||||
@@ -161,12 +323,27 @@ proc readDepsFile(c: var DepContext; pair: FilePair; current: Node) =
|
||||
if t.kind == ParLe:
|
||||
let tag = pool.tags[t.tagId]
|
||||
case tag
|
||||
of "import", "fromimport":
|
||||
# Read import path
|
||||
of "import", "fromimport", "include":
|
||||
# Read first child. May be a `(when COND...)` marker — parse and
|
||||
# evaluate; if the condition is statically false, skip the import
|
||||
# entirely. Otherwise advance past the marker and parse the path.
|
||||
t = next(s)
|
||||
# Check for "when" marker (conditional import)
|
||||
if t.kind == Ident and pool.strings[t.litId] == "when":
|
||||
t = next(s) # skip it, still process the import
|
||||
var live = true
|
||||
if t.kind == ParLe and pool.tags[t.tagId] == "when":
|
||||
# whenMarkerHolds consumes everything up to and including the
|
||||
# closing `)` of the `(when ...)` subtree.
|
||||
live = whenMarkerHolds(c, s)
|
||||
t = next(s)
|
||||
if not live:
|
||||
# Drain the rest of this import/include node.
|
||||
var depth = 1
|
||||
while depth > 0:
|
||||
let n = next(s)
|
||||
if n.kind == ParLe: inc depth
|
||||
elif n.kind == ParRi: dec depth
|
||||
elif n.kind == EofToken: break
|
||||
t = next(s)
|
||||
continue
|
||||
# Handle path expression (could be ident, string, or infix like std/foo)
|
||||
var importPath = ""
|
||||
if t.kind == Ident:
|
||||
@@ -184,26 +361,11 @@ proc readDepsFile(c: var DepContext; pair: FilePair; current: Node) =
|
||||
if t.kind == Ident: # second part (foo)
|
||||
importPath = importPath & "/" & pool.strings[t.litId]
|
||||
if importPath.len > 0:
|
||||
processImport(c, importPath, current)
|
||||
# Skip to end of import node
|
||||
var depth = 1
|
||||
while depth > 0:
|
||||
t = next(s)
|
||||
if t.kind == ParLe: inc depth
|
||||
elif t.kind == ParRi: dec depth
|
||||
of "include":
|
||||
# Read include path
|
||||
t = next(s)
|
||||
if t.kind == Ident and pool.strings[t.litId] == "when":
|
||||
t = next(s) # skip conditional marker
|
||||
var includePath = ""
|
||||
if t.kind == Ident:
|
||||
includePath = pool.strings[t.litId]
|
||||
elif t.kind == StringLit:
|
||||
includePath = pool.strings[t.litId]
|
||||
if includePath.len > 0:
|
||||
processInclude(c, includePath, current)
|
||||
# Skip to end
|
||||
if tag == "include":
|
||||
processInclude(c, importPath, current)
|
||||
else:
|
||||
processImport(c, importPath, current)
|
||||
# Skip to end of node
|
||||
var depth = 1
|
||||
while depth > 0:
|
||||
t = next(s)
|
||||
@@ -326,10 +488,19 @@ proc generateBuildFile(c: DepContext): string =
|
||||
let exeFile = changeFileExt(c.nodes[0].files[0].nimFile, ExeExt)
|
||||
b.addTree "do"
|
||||
b.addIdent "nim_nifc"
|
||||
# Input: .nim file (expanded as argument) and .nif file (dependency)
|
||||
# Input: .nim file (expanded as argument)
|
||||
b.addTree "input"
|
||||
b.addStrLit mainNif
|
||||
b.endTree()
|
||||
# Also depend on the semmed .nif files of the main module and all its
|
||||
# dependencies. nifmake's topological sort orders nodes by depth; without
|
||||
# these inputs the nim_nifc node sits at depth 1 (no recognized inputs)
|
||||
# alongside the nifler nodes and runs *before* the nim_m steps that
|
||||
# produce the .nif files it needs to read.
|
||||
for node in c.nodes:
|
||||
b.addTree "input"
|
||||
b.addStrLit c.semmedFile(node.files[0])
|
||||
b.endTree()
|
||||
b.addTree "output"
|
||||
b.addStrLit exeFile
|
||||
b.endTree()
|
||||
|
||||
@@ -1544,7 +1544,7 @@ proc genSymAddr(p: PProc, n: PNode, typ: PType, r: var TCompRes) =
|
||||
r.res = s.loc.snippet
|
||||
r.address = ""
|
||||
r.typ = etyNone
|
||||
of skVar, skLet, skResult:
|
||||
of skVar, skLet, skResult, skTemp, skForVar:
|
||||
r.kind = resExpr
|
||||
let jsType = mapType(p):
|
||||
if typ.isNil:
|
||||
|
||||
@@ -732,7 +732,7 @@ proc fillStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
|
||||
of attachedAsgn, attachedDeepCopy, attachedDup:
|
||||
body.add callCodegenProc(c.g, "nimAsgnStrV2", c.info, genAddr(c, x), y)
|
||||
of attachedSink:
|
||||
if c.g.config.isDefined("nimsso"):
|
||||
if c.g.config.usesSso():
|
||||
# SmallString: destroy old dst, then bit-copy src (no rc increment — this is a move).
|
||||
# No .p aliasing check needed; rc-based destroy handles COW sharing correctly.
|
||||
doAssert t.destructor != nil
|
||||
|
||||
@@ -12,7 +12,8 @@ define:nimPreviewNonVarDestructor
|
||||
define:nimPreviewCheckedClose
|
||||
define:nimPreviewAsmSemSymbol
|
||||
define:nimPreviewCStringComparisons
|
||||
define:nimPreviewDuplicateModuleError
|
||||
#define:nimPreviewDuplicateModuleError
|
||||
# Incompatible with Nimony's compat2.nim for now
|
||||
|
||||
threads:off
|
||||
|
||||
|
||||
@@ -121,6 +121,11 @@ proc handleCmdLine(cache: IdentCache; conf: ConfigRef) =
|
||||
conf.cmd in {cmdGendepend, cmdNifC, cmdIc, cmdM}:
|
||||
initOrcDefines(conf)
|
||||
|
||||
if conf.selectedStrings == stringSso and
|
||||
conf.selectedGC notin {gcArc, gcOrc, gcYrc, gcAtomicArc}:
|
||||
rawMessage(conf, errGenerated,
|
||||
"--strings:sso requires --mm:arc, --mm:orc, --mm:yrc, or --mm:atomicArc")
|
||||
|
||||
mainCommand(graph)
|
||||
if conf.hasHint(hintGCStats): echo(GC_getStatistics())
|
||||
#echo(GC_getStatistics())
|
||||
|
||||
@@ -259,6 +259,9 @@ type
|
||||
## Old transformation for closures in JS backend
|
||||
noPanicOnExcept
|
||||
## don't panic on bare except
|
||||
procParamTypeBackendAliases
|
||||
## Keep the old proc type compatibility rules that ignore backend
|
||||
## c type aliases.
|
||||
|
||||
SymbolFilesOption* = enum
|
||||
disabledSf, writeOnlySf, readOnlySf, v2Sf, stressTest
|
||||
@@ -267,6 +270,10 @@ type
|
||||
ccNone, ccGcc, ccNintendoSwitch, ccLLVM_Gcc, ccCLang, ccBcc, ccVcc,
|
||||
ccTcc, ccEnv, ccIcl, ccIcc, ccClangCl, ccHipcc, ccNvcc
|
||||
|
||||
StringsMode* = enum
|
||||
stringDefault = "default"
|
||||
stringSso = "sso"
|
||||
|
||||
ExceptionSystem* = enum
|
||||
excNone, # no exception system selected yet
|
||||
excSetjmp, # setjmp based exception handling
|
||||
@@ -366,6 +373,7 @@ type
|
||||
implicitCmd*: bool # whether some flag triggered an implicit `command`
|
||||
selectedGC*: TGCMode # the selected GC (+)
|
||||
exc*: ExceptionSystem
|
||||
selectedStrings*: StringsMode
|
||||
hintProcessingDots*: bool # true for dots, false for filenames
|
||||
verbosity*: int # how verbose the compiler is
|
||||
numberOfProcessors*: int # number of processors
|
||||
@@ -698,6 +706,7 @@ template quitOrRaise*(conf: ConfigRef, msg = "") =
|
||||
|
||||
proc importantComments*(conf: ConfigRef): bool {.inline.} = conf.cmd in cmdDocLike + {cmdIdeTools}
|
||||
proc usesWriteBarrier*(conf: ConfigRef): bool {.inline.} = conf.selectedGC >= gcRefc
|
||||
proc usesSso*(conf: ConfigRef): bool {.inline.} = conf.selectedStrings == stringSso
|
||||
|
||||
template compilationCachePresent*(conf: ConfigRef): untyped =
|
||||
false
|
||||
|
||||
@@ -180,9 +180,9 @@ type
|
||||
sideEffects*: Table[int, seq[(TLineInfo, PSym)]] # symbol.id index
|
||||
inUncheckedAssignSection*: int
|
||||
importModuleLookup*: Table[int, seq[int]] # (module.ident.id, [module.id])
|
||||
forwardTypeUpdates*: seq[(PType, PNode)]
|
||||
# types that need to be updated in a type section
|
||||
# due to containing forward types, and their corresponding nodes
|
||||
forwardTypeUpdates*: seq[(PSym, PType, PNode)]
|
||||
# top-level owner, type, and type node for delayed retries inside a
|
||||
# type section due to containing forward types
|
||||
forwardFieldUpdates*: seq[(PType, PNode, PType)]
|
||||
# object/tuple field definitions whose default values mention forward
|
||||
# types and need delayed const checking
|
||||
|
||||
@@ -652,6 +652,9 @@ proc overloadedCallOpr(c: PContext, n: PNode): PNode =
|
||||
result = semExpr(c, result, flags = {efNoUndeclared})
|
||||
|
||||
proc changeType(c: PContext; n: PNode, newType: PType, check: bool) =
|
||||
template isViewTarget(t: PType): bool =
|
||||
t.skipTypes({tyGenericInst, tyAlias, tySink}).kind in {tyVar, tyLent}
|
||||
|
||||
case n.kind
|
||||
of nkCurly:
|
||||
for i in 0..<n.len:
|
||||
@@ -680,12 +683,15 @@ proc changeType(c: PContext; n: PNode, newType: PType, check: bool) =
|
||||
if f == nil:
|
||||
globalError(c.config, m.info, "unknown identifier: " & m.sym.name.s)
|
||||
return
|
||||
changeType(c, n[i][1], f.typ, check)
|
||||
if not isViewTarget(f.typ):
|
||||
changeType(c, n[i][1], f.typ, check)
|
||||
else:
|
||||
changeType(c, n[i][1], tup[i], check)
|
||||
if not isViewTarget(tup[i]):
|
||||
changeType(c, n[i][1], tup[i], check)
|
||||
else:
|
||||
for i in 0..<n.len:
|
||||
changeType(c, n[i], tup[i], check)
|
||||
if not isViewTarget(tup[i]):
|
||||
changeType(c, n[i], tup[i], check)
|
||||
when false:
|
||||
var m = n[i]
|
||||
var a = newNodeIT(nkExprColonExpr, m.info, newType[i])
|
||||
@@ -708,6 +714,7 @@ proc changeType(c: PContext; n: PNode, newType: PType, check: bool) =
|
||||
localError(c.config, n.info, "cannot convert '" & n.sym.name.s &
|
||||
"' to '" & typeNameAndDesc(newType) & "'")
|
||||
else: discard
|
||||
|
||||
n.typ = newType
|
||||
|
||||
proc arrayConstrType(c: PContext, n: PNode): PType =
|
||||
|
||||
@@ -248,10 +248,13 @@ proc evalTypeTrait(c: PContext; traitCall: PNode, operand: PType, context: PSym)
|
||||
assert operand.kind == tyTuple, $operand.kind
|
||||
result = newIntNodeT(toInt128(operand.len), traitCall, c.idgen, c.graph)
|
||||
of "distinctBase":
|
||||
var arg = operand.skipTypes({tyGenericInst})
|
||||
var arg = operand.skipTypes(skippedTypes)
|
||||
let rec = semConstExpr(c, traitCall[2]).intVal != 0
|
||||
while arg.kind == tyDistinct:
|
||||
arg = arg.base.skipTypes(skippedTypes + {tyGenericInst})
|
||||
while true:
|
||||
let distinctArg = arg.skipTypes(skippedTypes + {tyGenericInst})
|
||||
if distinctArg.kind != tyDistinct:
|
||||
break
|
||||
arg = distinctArg.base.skipTypes(skippedTypes)
|
||||
if not rec: break
|
||||
result = getTypeDescNode(c, arg, operand.owner, traitCall.info)
|
||||
of "rangeBase":
|
||||
|
||||
@@ -486,6 +486,11 @@ proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType
|
||||
# we have to watch out, there are also 'owned proc' types that can be used
|
||||
# multiple times as long as they don't have closures.
|
||||
result.typ.incl tfHasOwned
|
||||
if t.kind == tyForward and efDetermineType in flags:
|
||||
# a forward object type does not error during determine-type analysis;
|
||||
# it now stays unresolved long enough for the existing delayed field-default pass to resolve it after the type section finishes.
|
||||
result.typ = t
|
||||
return result
|
||||
if t.kind != tyObject:
|
||||
return localErrorNode(c, result, if t.kind != tyGenericBody:
|
||||
"object constructor needs an object type".dup(addTypeNodeDeclaredLoc(c.config, t))
|
||||
|
||||
@@ -1808,15 +1808,32 @@ proc checkForMetaFields(c: PContext; n: PNode; hasError: var bool) =
|
||||
internalAssert c.config, false
|
||||
|
||||
proc typeSectionFinalPass(c: PContext, n: PNode) =
|
||||
for (typ, typeNode) in c.forwardTypeUpdates:
|
||||
# types that need to be updated due to containing forward types
|
||||
# and their corresponding type nodes
|
||||
# for example generic invocations of forward types end up here
|
||||
var reified = semTypeNode(c, typeNode, nil)
|
||||
assert reified != nil
|
||||
assignType(typ, reified)
|
||||
typ.itemId = reified.itemId # same id
|
||||
c.forwardTypeUpdates = @[]
|
||||
# each top level type needs to be processed, each epoch should reify at least one
|
||||
var remainingOwners = initIntSet()
|
||||
for (owner, _, _) in c.forwardTypeUpdates:
|
||||
remainingOwners.incl owner.id
|
||||
|
||||
while c.forwardTypeUpdates.len > 0:
|
||||
let pending = move c.forwardTypeUpdates
|
||||
var madeProgress = false
|
||||
|
||||
for (owner, typ, typeNode) in pending:
|
||||
# types that need to be updated due to containing forward types
|
||||
# and their corresponding type nodes
|
||||
# for example generic invocations of forward types end up here
|
||||
var reified = semTypeNode(c, typeNode, nil)
|
||||
assert reified != nil
|
||||
assignType(typ, reified)
|
||||
typ.itemId = reified.itemId # same id
|
||||
if containsForwardType(typ):
|
||||
c.forwardTypeUpdates.add (owner, typ, typeNode)
|
||||
elif not remainingOwners.missingOrExcl(owner.id):
|
||||
madeProgress = true
|
||||
|
||||
if not madeProgress:
|
||||
# can't error here unfortunately
|
||||
break
|
||||
|
||||
for (owner, field, expectedType) in c.forwardFieldUpdates:
|
||||
semDelayedFieldDefault(c, owner, expectedType, field)
|
||||
c.forwardFieldUpdates = @[]
|
||||
|
||||
@@ -223,7 +223,7 @@ proc semSet(c: PContext, n: PNode, prev: PType): PType =
|
||||
if base.kind in {tyGenericInst, tyAlias, tySink}: base = skipModifier(base)
|
||||
if base.kind notin {tyGenericParam, tyGenericInvocation}:
|
||||
if base.kind == tyForward:
|
||||
c.forwardTypeUpdates.add (base, n[1])
|
||||
c.forwardTypeUpdates.add (getCurrOwner(c), result, n)
|
||||
elif not isOrdinalType(base, allowEnumWithHoles = true):
|
||||
localError(c.config, n.info, errOrdinalTypeExpected % typeToString(base, preferDesc))
|
||||
elif lengthOrd(c.config, base) > MaxSetElements:
|
||||
@@ -370,6 +370,7 @@ proc semFieldDefault(c: PContext; owner, expectedType: PType; field: PNode): PTy
|
||||
propagateToOwner(owner, result)
|
||||
|
||||
proc semDelayedFieldDefault(c: PContext; owner, expectedType: PType; field: PNode) =
|
||||
resetSemFlag(field[^1])
|
||||
fitDefaultNode(c, field[^1], expectedType)
|
||||
propagateToOwner(owner, field[^1].typ.skipIntLit(c.idgen))
|
||||
|
||||
@@ -1114,7 +1115,7 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType; flags: TTypeFlags): PType
|
||||
if needsForwardUpdate:
|
||||
# if the inherited object is a forward type,
|
||||
# the entire object needs to be checked again
|
||||
c.forwardTypeUpdates.add (result, n) # we retry in the final pass
|
||||
c.forwardTypeUpdates.add (getCurrOwner(c), result, n) # we retry in the final pass
|
||||
rawAddSon(result, realBase)
|
||||
if realBase == nil and tfInheritable in flags:
|
||||
result.incl tfInheritable
|
||||
@@ -1762,7 +1763,7 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
|
||||
for i in 1..<n.len:
|
||||
var elem = semGenericParamInInvocation(c, n[i])
|
||||
addToResult(elem, true)
|
||||
c.forwardTypeUpdates.add (result, n)
|
||||
c.forwardTypeUpdates.add (getCurrOwner(c), result, n)
|
||||
return
|
||||
elif t.kind != tyGenericBody:
|
||||
# we likely got code of the form TypeA[TypeB] where TypeA is
|
||||
@@ -1838,7 +1839,7 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
|
||||
else:
|
||||
assignType(result, newTypeS(tyForward, c))
|
||||
result.sym = s
|
||||
c.forwardTypeUpdates.add (result, n) #fixes 1500
|
||||
c.forwardTypeUpdates.add (getCurrOwner(c), result, n) #fixes 1500
|
||||
return
|
||||
else:
|
||||
result = instGenericContainer(c, n.info, result,
|
||||
|
||||
@@ -784,6 +784,17 @@ proc procParamTypeRel(c: var TCandidate; f, a: PType): TTypeRelation =
|
||||
# if f is metatype.
|
||||
result = typeRel(c, f, a)
|
||||
|
||||
if result == isEqual and
|
||||
procParamTypeBackendAliases notin c.c.config.legacyFeatures:
|
||||
# Ensure types that are semantically equal also match at the backend level.
|
||||
# E.g. reject assigning proc(csize_t) to proc(uint) since these map to
|
||||
# different C types (size_t vs unsigned long long).
|
||||
let fCheck = concreteType(c, f)
|
||||
let aCheck = concreteType(c, a)
|
||||
if fCheck != nil and aCheck != nil and
|
||||
not sameBackendTypePickyAliases(fCheck, aCheck):
|
||||
result = isNone
|
||||
|
||||
if result <= isSubrange or inconsistentVarTypes(f, a):
|
||||
result = isNone
|
||||
|
||||
|
||||
@@ -897,7 +897,7 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool =
|
||||
c.flags = oldFlags
|
||||
|
||||
if x == y: return true
|
||||
let aliasSkipSet = maybeSkipRange({tyAlias})
|
||||
let aliasSkipSet = maybeSkipRange({tyAlias, tyInferred})
|
||||
var a = skipTypes(x, aliasSkipSet)
|
||||
while a.kind == tyUserTypeClass and tfResolved in a.flags:
|
||||
a = skipTypes(a.last, aliasSkipSet)
|
||||
|
||||
@@ -33,7 +33,7 @@ The text representation is particularly valuable for debugging and introspection
|
||||
Each ``.nim`` module produces its own ``.nif`` file during compilation.
|
||||
The NIF format contains:
|
||||
|
||||
- **Header** - Version information (e.g., `(.nif26)`)
|
||||
- **Header** - Version information (e.g., `(.nif27)`)
|
||||
- **Dependencies** - List of source files and dependencies
|
||||
- **Interface** - Exported symbols and their indices
|
||||
- **Body** - The intermediate representation of the module's code in Lisp-like syntax
|
||||
|
||||
@@ -1024,6 +1024,9 @@ These are the major type classes:
|
||||
* procedural type
|
||||
* generic type
|
||||
|
||||
The compiler's internal type zoo is richer than this summary suggests:
|
||||
some types that are structurally equal still differ in backend representation.
|
||||
|
||||
|
||||
Ordinal types
|
||||
-------------
|
||||
@@ -2174,6 +2177,10 @@ Procedural type
|
||||
A procedural type is internally a pointer to a procedure. `nil` is
|
||||
an allowed value for a variable of a procedural type.
|
||||
|
||||
Procedure compatibility also checks the backend representation of the
|
||||
parameter and result types, not just their source-level shape. Use
|
||||
`--legacy:procParamTypeBackendAliases` to restore the older behavior.
|
||||
|
||||
Examples:
|
||||
|
||||
```nim
|
||||
|
||||
3
koch.nim
3
koch.nim
@@ -16,10 +16,11 @@ const
|
||||
ChecksumsStableCommit = "0b8e46379c5bc1bf73d8b3011908389c60fb9b98" # 2.0.1
|
||||
SatStableCommit = "e63eaea8baf00bed8bcd5a29ffd8823abb265b39"
|
||||
|
||||
NimonyStableCommit = "bbfb21529845567c55b67d176354daef0e7d6c29" # unversioned \
|
||||
NimonyStableCommit = "750aa47f2139fe5ad69f04b44428b752011fe873" # unversioned \
|
||||
# Note that Nimony uses Nim as a git submodule but we don't want to install
|
||||
# Nimony's dependency to Nim as we are Nim. So a `git clone` without --recursive
|
||||
# is **required** here.
|
||||
# Commit from 2026-05-05
|
||||
|
||||
# examples of possible values for fusion: #head, #ea82b54, 1.2.3
|
||||
FusionStableHash = "#562467452b32cb7a97410ea177f083e6d8405734"
|
||||
|
||||
@@ -1559,6 +1559,8 @@ macro expandMacros*(body: typed): untyped =
|
||||
echo body.toStrLit
|
||||
result = body
|
||||
|
||||
proc getTypeInstSkipAlias(n: NimNode): NimNode {.magic: "NGetType", noSideEffect.}
|
||||
|
||||
proc extractTypeImpl(n: NimNode): NimNode =
|
||||
## attempts to extract the type definition of the given symbol
|
||||
case n.kind
|
||||
@@ -1573,11 +1575,17 @@ proc extractTypeImpl(n: NimNode): NimNode =
|
||||
result = n[0].getImpl()
|
||||
of nnkTypeDef:
|
||||
result = n[2]
|
||||
if result.kind notin {nnkSym, nnkObjectTy, nnkRefTy, nnkPtrTy, nnkBracketExpr}:
|
||||
# Handle typeof() and similar unresolvable type expressions
|
||||
let typSym = if n[0].kind == nnkPragmaExpr: n[0][0] else: n[0]
|
||||
if typSym.kind == nnkSym:
|
||||
let resolved = typSym.getTypeInstSkipAlias()
|
||||
if resolved.kind == nnkSym:
|
||||
return resolved.getImpl.extractTypeImpl()
|
||||
error("Invalid node to retrieve type implementation of: " & $result.kind)
|
||||
else: error("Invalid node to retrieve type implementation of: " & $n.kind)
|
||||
|
||||
|
||||
proc getTypeInstSkipAlias(n: NimNode): NimNode {.magic: "NGetType", noSideEffect.}
|
||||
|
||||
proc customPragmaNode(n: NimNode): NimNode =
|
||||
result = nil
|
||||
expectKind(n, {nnkSym, nnkDotExpr, nnkBracketExpr, nnkTypeOfExpr, nnkType, nnkCheckedFieldExpr})
|
||||
@@ -1618,6 +1626,15 @@ proc customPragmaNode(n: NimNode): NimNode =
|
||||
var typDef = getImpl(typInst)
|
||||
while typDef != nil:
|
||||
typDef.expectKind(nnkTypeDef)
|
||||
# Resolve typeof() and similar unresolvable type expressions
|
||||
if typDef[2].kind notin {nnkSym, nnkObjectTy, nnkRefTy, nnkPtrTy, nnkBracketExpr}:
|
||||
let typSym = if typDef[0].kind == nnkPragmaExpr: typDef[0][0] else: typDef[0]
|
||||
if typSym.kind == nnkSym:
|
||||
let resolved = typSym.getTypeInstSkipAlias()
|
||||
if resolved.kind == nnkSym:
|
||||
typDef = getImpl(resolved)
|
||||
continue
|
||||
break
|
||||
let typ = typDef[2].extractTypeImpl()
|
||||
if typ.kind notin {nnkRefTy, nnkPtrTy, nnkObjectTy}: break
|
||||
let isRef = typ.kind in {nnkRefTy, nnkPtrTy}
|
||||
|
||||
@@ -9,6 +9,11 @@
|
||||
when defined(js):
|
||||
{.error: "This library needs to be compiled with a c-like backend, and depends on PCRE; See jsre for JS backend.".}
|
||||
|
||||
## .. warning:: NRE is deprecated.
|
||||
## Use [Regex](https://github.com/nitely/nim-regex) or
|
||||
## `NRE2 <nre2.html>`_ that wraps Regex so that you can easily replace NRE.
|
||||
## PCRE library is now at end of life.
|
||||
##
|
||||
## What is NRE?
|
||||
## ============
|
||||
##
|
||||
@@ -84,7 +89,7 @@ type
|
||||
Regex* = ref RegexDesc
|
||||
## Represents the pattern that things are matched against, constructed with
|
||||
## `re(string)`. Examples: `re"foo"`, `re(r"(*ANYCRLF)(?x)foo #
|
||||
## comment".`
|
||||
## comment")`
|
||||
##
|
||||
## `pattern: string`
|
||||
## : the string that was used to create the pattern. For details on how
|
||||
@@ -154,7 +159,7 @@ type
|
||||
## will need to pass these as separate flags to PCRE.
|
||||
|
||||
RegexMatch* = object
|
||||
## Usually seen as Option[RegexMatch], it represents the result of an
|
||||
## Usually seen as `Option[RegexMatch]`, it represents the result of an
|
||||
## execution. On failure, it is none, on success, it is some.
|
||||
##
|
||||
## `pattern: Regex`
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
when defined(js):
|
||||
{.error: "This library needs to be compiled with a c-like backend, and depends on PCRE; See jsre for JS backend.".}
|
||||
|
||||
## .. warning:: This module is deprecated.
|
||||
## Use [Regex](https://github.com/nitely/nim-regex).
|
||||
## PCRE library is now at end of life.
|
||||
##
|
||||
## Regular expression support for Nim.
|
||||
##
|
||||
## This module is implemented by providing a wrapper around the
|
||||
|
||||
@@ -153,7 +153,7 @@ proc parseProtocol(protocol: string): tuple[orig: string, major, minor: int] =
|
||||
protocol)
|
||||
result.orig = protocol
|
||||
i.inc protocol.parseSaturatedNatural(result.major, i)
|
||||
i.inc # Skip .
|
||||
if i < protocol.len: inc i # Skip .
|
||||
i.inc protocol.parseSaturatedNatural(result.minor, i)
|
||||
|
||||
proc sendStatus(client: AsyncSocket, status: string): Future[void] =
|
||||
|
||||
@@ -128,7 +128,7 @@ proc getContentLength*(): string =
|
||||
|
||||
proc getContentType*(): string =
|
||||
## Returns contents of the `CONTENT_TYPE` environment variable.
|
||||
return getEnv("CONTENT_Type")
|
||||
return getEnv("CONTENT_TYPE")
|
||||
|
||||
proc getDocumentRoot*(): string =
|
||||
## Returns contents of the `DOCUMENT_ROOT` environment variable.
|
||||
|
||||
@@ -36,6 +36,8 @@ elif defined(linux):
|
||||
# Android
|
||||
"/data/data/com.termux/files/usr/etc/tls/cert.pem",
|
||||
"/system/etc/security/cacerts",
|
||||
# Nix
|
||||
"/etc/ssl/certs/ca-bundle.crt"
|
||||
]
|
||||
elif defined(bsd):
|
||||
const certificatePaths = [
|
||||
|
||||
@@ -259,7 +259,7 @@ proc readDataStr*(s: Stream, buffer: var string, slice: Slice[int]): int =
|
||||
result = s.readDataStrImpl(s, buffer, slice)
|
||||
else:
|
||||
# fallback
|
||||
result = s.readData(beginStore(buffer, slice.b + 1 - slice.a, slice.a), slice.b + 1 - slice.a)
|
||||
result = s.readData(beginStore(buffer, buffer.len, slice.a), slice.b + 1 - slice.a)
|
||||
endStore(buffer)
|
||||
|
||||
template jsOrVmBlock(caseJsOrVm, caseElse: untyped): untyped =
|
||||
@@ -1226,7 +1226,7 @@ else: # after 1.3 or JS not defined
|
||||
jsOrVmBlock:
|
||||
buffer[slice.a..<slice.a+result] = s.data[s.pos..<s.pos+result]
|
||||
do:
|
||||
copyMem(beginStore(buffer, result, slice.a), readRawData(s.data, s.pos), result)
|
||||
copyMem(beginStore(buffer, buffer.len, slice.a), readRawData(s.data, s.pos), result)
|
||||
endStore(buffer)
|
||||
inc(s.pos, result)
|
||||
else:
|
||||
@@ -1267,16 +1267,16 @@ else: # after 1.3 or JS not defined
|
||||
var s = StringStream(s)
|
||||
if bufLen <= 0:
|
||||
return
|
||||
if s.pos + bufLen > s.data.len:
|
||||
setLen(s.data, s.pos + bufLen)
|
||||
when defined(js):
|
||||
if s.pos + bufLen > s.data.len:
|
||||
setLen(s.data, s.pos + bufLen)
|
||||
try:
|
||||
s.data[s.pos..<s.pos+bufLen] = cast[ptr string](buffer)[][0..<bufLen]
|
||||
except:
|
||||
raise newException(Defect, "could not write to string stream, " &
|
||||
"did you use a non-string buffer pointer?", getCurrentException())
|
||||
elif not defined(nimscript):
|
||||
copyMem(beginStore(s.data, bufLen, s.pos), buffer, bufLen)
|
||||
copyMem(beginStore(s.data, s.pos + bufLen, s.pos), buffer, bufLen)
|
||||
endStore(s.data)
|
||||
inc(s.pos, bufLen)
|
||||
|
||||
@@ -1346,7 +1346,7 @@ proc fsReadData(s: Stream, buffer: pointer, bufLen: int): int =
|
||||
|
||||
proc fsReadDataStr(s: Stream, buffer: var string, slice: Slice[int]): int =
|
||||
let len = slice.b + 1 - slice.a
|
||||
result = readBuffer(FileStream(s).f, beginStore(buffer, len, slice.a), len)
|
||||
result = readBuffer(FileStream(s).f, beginStore(buffer, buffer.len, slice.a), len)
|
||||
endStore(buffer)
|
||||
|
||||
proc fsPeekData(s: Stream, buffer: pointer, bufLen: int): int =
|
||||
|
||||
@@ -18,12 +18,12 @@ proc addCstringN(result: var string, buf: cstring; buflen: int) =
|
||||
# no nimvm support needed, so it doesn't need to be fast here either
|
||||
let oldLen = result.len
|
||||
let newLen = oldLen + buflen
|
||||
result.setLen newLen
|
||||
{.cast(noSideEffect).}:
|
||||
when declared(completeStore):
|
||||
c_memcpy(beginStore(result, buflen, oldLen), buf, buflen.csize_t)
|
||||
when declared(beginStore):
|
||||
c_memcpy(beginStore(result, newLen, oldLen), buf, buflen.csize_t)
|
||||
endStore(result)
|
||||
else:
|
||||
result.setLen newLen
|
||||
discard c_memcpy(result[oldLen].addr, buf, buflen.csize_t)
|
||||
|
||||
import std/private/[dragonbox, schubfach]
|
||||
|
||||
344
lib/std/nre2.nim
Normal file
344
lib/std/nre2.nim
Normal file
@@ -0,0 +1,344 @@
|
||||
#
|
||||
# Nim's Runtime Library
|
||||
# (c) Copyright 2026 Nim Contributors
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## What is NRE2?
|
||||
## =============
|
||||
##
|
||||
## A regular expression library for Nim to replace deprecated NRE.
|
||||
## It is implemented with `Regex<https://github.com/nitely/nim-regex>`_ ,
|
||||
## that is pure Nim regex engine and guarantees linear time matching.
|
||||
## It supports compiling regex and matching at compile-time and
|
||||
## works with JS backend.
|
||||
##
|
||||
## NRE2 is mostly compatible with NRE and the syntax of regular expression is similar to PCRE.
|
||||
## But it lacks a few features and how to set options in a pattern is different.
|
||||
##
|
||||
## The syntax of regular expression is explained in https://nitely.github.io/nim-regex/regex.html
|
||||
runnableExamples:
|
||||
import std/sugar
|
||||
let vowels = re"[aeoui]"
|
||||
let bounds = collect:
|
||||
for match in "moiga".findIter(vowels): match.matchBounds
|
||||
assert bounds == @[1 .. 1, 2 .. 2, 4 .. 4]
|
||||
from std/sequtils import toSeq
|
||||
let s = sequtils.toSeq("moiga".findIter(vowels))
|
||||
# fully qualified to avoid confusion with nre.toSeq
|
||||
assert s.len == 3
|
||||
|
||||
let firstVowel = "foo".find(vowels)
|
||||
let hasVowel = firstVowel.isSome()
|
||||
assert hasVowel
|
||||
let matchBounds = firstVowel.get().captureBounds[-1]
|
||||
assert matchBounds.a == 1
|
||||
|
||||
# as with module `re`, unless specified otherwise, `start` parameter in each
|
||||
# proc indicates where the scan starts, but outputs are relative to the start
|
||||
# of the input string, not to `start`:
|
||||
assert find("uxabc", re"(?<=x|y)ab", start = 1).get.captures[-1] == "ab"
|
||||
assert find("uxabc", re"ab", start = 3).isNone
|
||||
|
||||
import std/[options, tables]
|
||||
import regex, regex/nfatype
|
||||
|
||||
export options
|
||||
export regex.RegexFlags, regex.RegexError
|
||||
|
||||
type
|
||||
Regex* = regex.Regex2
|
||||
## Represents the pattern that things are matched against, constructed with
|
||||
## `re(string)`. Examples: `re"foo"`, `re(r"(?x)foo #comment")`
|
||||
##
|
||||
## `captureCount: int`
|
||||
## : the number of captures that the pattern has.
|
||||
##
|
||||
## `captureNameId: Table[string, int]`
|
||||
## : a table from the capture names to their numeric id.
|
||||
##
|
||||
## The syntax of regular expression of Regex is explained in https://nitely.github.io/nim-regex/regex.html
|
||||
|
||||
RegexMatch* = object
|
||||
## Usually seen as `Option[RegexMatch]`, it represents the result of an
|
||||
## execution. On failure, it is none, on success, it is some.
|
||||
##
|
||||
## `str: string`
|
||||
## : the string that was matched against
|
||||
##
|
||||
## `captures[]: string`
|
||||
## : the string value of whatever was captured at that id. If the value
|
||||
## is invalid, then behavior is undefined. If the id is `-1`, then
|
||||
## the whole match is returned. If the given capture was not matched,
|
||||
## `nil` is returned. See examples for `match`.
|
||||
##
|
||||
## `captureBounds[]: HSlice[int, int]`
|
||||
## : gets the bounds of the given capture according to the same rules as
|
||||
## the above. If the capture is not filled, then `None` is returned.
|
||||
## The bounds are both inclusive. See examples for `match`.
|
||||
##
|
||||
## `match: string`
|
||||
## : the full text of the match.
|
||||
##
|
||||
## `matchBounds: HSlice[int, int]`
|
||||
## : the bounds of the match, as in `captureBounds[]`
|
||||
##
|
||||
## `(captureBounds|captures).toTable`
|
||||
## : returns a table with each named capture as a key.
|
||||
##
|
||||
## `(captureBounds|captures).toSeq`
|
||||
## : returns all the captures by their number.
|
||||
##
|
||||
## `$: string`
|
||||
## : same as `match`
|
||||
str*: string ## The string that was matched against.
|
||||
matchImpl: regex.RegexMatch2
|
||||
|
||||
Captures* {.borrow: `.`.} = distinct RegexMatch
|
||||
CaptureBounds* {.borrow: `.`.} = distinct RegexMatch
|
||||
|
||||
func captureCount*(pattern: Regex): int {.inline.} =
|
||||
pattern.toRegex().groupsCount
|
||||
|
||||
func captureNameId*(pattern: Regex): Table[string, int] =
|
||||
result = initTable[string, int](pattern.toRegex().namedGroups.len)
|
||||
for k, v in pattern.toRegex().namedGroups:
|
||||
result[k] = v
|
||||
|
||||
func captureBounds*(match: RegexMatch): CaptureBounds {.inline.} =
|
||||
CaptureBounds(match)
|
||||
|
||||
func captures*(match: RegexMatch): Captures {.inline.} =
|
||||
Captures(match)
|
||||
|
||||
func contains*(match: Captures or CaptureBounds, i: int): bool {.inline.} =
|
||||
i >= -1 and i < match.matchImpl.groupsCount and match.matchImpl.group(i) != reNonCapture
|
||||
|
||||
func len*(match: Captures or CaptureBounds): int {.inline.} =
|
||||
## Return the number of capturing groups
|
||||
match.matchImpl.groupsCount
|
||||
|
||||
func `[]`*(match: CaptureBounds; i: int): HSlice[int, int] {.inline.} =
|
||||
if i == -1: match.matchImpl.boundaries else: match.matchImpl.group(i)
|
||||
|
||||
func `[]`*(match: CaptureBounds; name: string): HSlice[int, int] {.inline.} =
|
||||
result = match.matchImpl.group(name)
|
||||
if result == reNonCapture:
|
||||
raise newException(KeyError, "Group '" & name & "' was not captured")
|
||||
|
||||
func `[]`*(match: Captures; i: int): string {.inline.} =
|
||||
match.str[CaptureBounds(match)[i]]
|
||||
|
||||
func `[]`*(match: Captures, name: string): string {.inline.} =
|
||||
match.str[CaptureBounds(match)[name]]
|
||||
|
||||
func match*(match: RegexMatch): string {.inline.} =
|
||||
match.str[match.matchImpl.boundaries]
|
||||
|
||||
func matchBounds*(match: RegexMatch): HSlice[int, int] {.inline.} =
|
||||
match.matchImpl.boundaries
|
||||
|
||||
func contains*(match: CaptureBounds or Captures, name: string): bool {.inline.} =
|
||||
name in match.matchImpl.namedGroups and
|
||||
match.matchImpl.group(name) != reNonCapture
|
||||
|
||||
func toTable*(match: Captures): Table[string, string] =
|
||||
result = initTable[string, string]()
|
||||
for k, i in match.matchImpl.namedGroups:
|
||||
let r = match.matchImpl.group(i)
|
||||
if r != reNonCapture:
|
||||
result[k] = match.str[r]
|
||||
|
||||
func toTable*(match: CaptureBounds): Table[string, HSlice[int, int]] =
|
||||
result = initTable[string, HSlice[int, int]]()
|
||||
for k, i in match.matchImpl.namedGroups:
|
||||
let r = match.matchImpl.group(i)
|
||||
if r != reNonCapture:
|
||||
result[k] = match.matchImpl.group(i)
|
||||
|
||||
iterator items*(match: CaptureBounds; default = none(HSlice[int, int])): Option[HSlice[int, int]] =
|
||||
for i in 0 ..< match.len:
|
||||
yield if i in match: some(match[i]) else: default
|
||||
|
||||
iterator items*(match: Captures; default = none(string)): Option[string] =
|
||||
for i in 0 ..< match.len:
|
||||
yield if i in match: some(match[i]) else: default
|
||||
|
||||
func toSeq*(match: CaptureBounds;
|
||||
default = none(HSlice[int, int])): seq[Option[HSlice[int, int]]] =
|
||||
result = @[]
|
||||
for it in match.items(default): result.add it
|
||||
|
||||
func toSeq*(match: Captures;
|
||||
default: Option[string] = none(string)): seq[Option[string]] =
|
||||
result = @[]
|
||||
for it in match.items(default): result.add it
|
||||
|
||||
func `$`*(match: RegexMatch): string =
|
||||
match.match
|
||||
|
||||
func re*(pattern: static string; flags: static RegexFlags = {}): static[Regex2] =
|
||||
## Parse and compile a regular expression at compile-time
|
||||
result = regex.re2(pattern, flags)
|
||||
|
||||
func re*(pattern: string; flags: RegexFlags = {}): Regex =
|
||||
## Parse and compile a regular expression at run-time
|
||||
result = regex.re2(pattern, flags)
|
||||
|
||||
func match*(str: string, pattern: Regex, start = 0, endpos = int.high): Option[RegexMatch] =
|
||||
## Like `find(...)<#find,string,Regex,int>`_, but anchored to the start of the
|
||||
## string.
|
||||
runnableExamples:
|
||||
assert "foo".match(re"f").isSome
|
||||
assert "foo".match(re"o").isNone
|
||||
|
||||
assert "abc".match(re"(\w)").get.captures[0] == "a"
|
||||
assert "abc".match(re"(?P<letter>\w)").get.captures["letter"] == "a"
|
||||
assert "abc".match(re"(\w)\w").get.captures[-1] == "ab"
|
||||
|
||||
assert "abc".match(re"(\w)").get.captureBounds[0] == 0 .. 0
|
||||
assert 0 in "abc".match(re"(\w)").get.captureBounds
|
||||
assert "abc".match(re"").get.captureBounds[-1] == 0 .. -1
|
||||
assert "abc".match(re"abc").get.captureBounds[-1] == 0 .. 2
|
||||
var mat = default(RegexMatch)
|
||||
let r = regex.startsWith(str.toOpenArray(0, min(str.high, endpos)), pattern, mat.matchImpl, start)
|
||||
if r:
|
||||
mat.str = str
|
||||
some(mat)
|
||||
else:
|
||||
none(RegexMatch)
|
||||
|
||||
iterator findIter*(str: string; pattern: Regex; start = 0, endpos = int.high): RegexMatch =
|
||||
## Works the same as `find(...)<#find,string,Regex,int>`_, but finds every
|
||||
## non-overlapping match:
|
||||
runnableExamples:
|
||||
import std/sugar
|
||||
assert collect(for a in "2222".findIter(re"22"): a.match) == @["22", "22"]
|
||||
# not @["22", "22", "22"]
|
||||
## Arguments are the same as `find(...)<#find,string,Regex,int>`_
|
||||
##
|
||||
## Variants:
|
||||
##
|
||||
## - `proc findAll(...)` returns a `seq[string]`
|
||||
var mat = RegexMatch(str: str)
|
||||
# TODO:
|
||||
# needs following PR to remove `substr` call.
|
||||
# https://github.com/nitely/nim-regex/pull/162
|
||||
for m in regex.findAll(str.substr(start, endpos), pattern):
|
||||
mat.matchImpl = m
|
||||
yield mat
|
||||
|
||||
proc find*(str: string; pattern: Regex; start = 0; endpos = int.high): Option[RegexMatch] =
|
||||
## Finds the given pattern in the string between the end and start
|
||||
## positions.
|
||||
##
|
||||
## `start`
|
||||
## : The start point at which to start matching. `|abc` is `0`;
|
||||
## `a|bc` is `1`
|
||||
##
|
||||
## `endpos`
|
||||
## : The maximum index for a match; `int.high` means the end of the
|
||||
## string, otherwise it’s an inclusive upper bound.
|
||||
var mat = default(RegexMatch)
|
||||
let r = regex.find(str.substr(start, endpos), pattern, mat.matchImpl)
|
||||
|
||||
# remove following code after regex.find get `start`/`last` parameter
|
||||
for v in mat.matchImpl.captures.mitems:
|
||||
v.a += start
|
||||
v.b += start
|
||||
mat.matchImpl.boundaries.a += start
|
||||
mat.matchImpl.boundaries.b += start
|
||||
|
||||
if r:
|
||||
mat.str = str
|
||||
some(mat)
|
||||
else:
|
||||
none(RegexMatch)
|
||||
|
||||
proc findAll*(str: string; pattern: Regex; start = 0; endpos = int.high): seq[string] =
|
||||
result = @[]
|
||||
for match in str.findIter(pattern, start, endpos):
|
||||
result.add(match.match)
|
||||
|
||||
proc contains*(str: string; pattern: Regex; start = 0; endpos = int.high): bool =
|
||||
## Determine if the string contains the given pattern between the end and
|
||||
## start positions:
|
||||
## This function is equivalent to `isSome(str.find(pattern, start, endpos))`.
|
||||
runnableExamples:
|
||||
assert "abc".contains(re"bc")
|
||||
assert not "abc".contains(re"cd")
|
||||
assert not "abc".contains(re"a", start = 1)
|
||||
|
||||
isSome(str.find(pattern, start, endpos))
|
||||
|
||||
proc split*(str: string; pattern: Regex; maxSplit = -1; start = 0): seq[string] =
|
||||
## Splits the string with the given regex. This works according to the
|
||||
## rules that Perl and Javascript use.
|
||||
##
|
||||
## `start` behaves the same as in `find(...)<#find,string,Regex,int>`_.
|
||||
##
|
||||
runnableExamples:
|
||||
# - If the match is zero-width, then the string is still split:
|
||||
assert "123".split(re"") == @["1", "2", "3"]
|
||||
|
||||
# - If the pattern has a capture in it, it is added after the string
|
||||
# split:
|
||||
assert "12".split(re"(\d)") == @["", "1", "", "2", ""]
|
||||
|
||||
# - If `maxsplit != -1`, then the string will only be split
|
||||
# `maxsplit - 1` times. This means that there will be `maxsplit`
|
||||
# strings in the output seq.
|
||||
assert "1.2.3".split(re"\.", maxsplit = 2) == @["1", "2.3"]
|
||||
|
||||
result = splitIncl(str, pattern, maxSplit, start)
|
||||
|
||||
proc replace*(str: string; pattern: Regex;
|
||||
subproc: proc (match: RegexMatch): string): string =
|
||||
## Replaces each match of Regex in the string with `subproc`, which should
|
||||
## never be or return `nil`.
|
||||
##
|
||||
## If `subproc` is a `proc (RegexMatch): string`, then it is executed with
|
||||
## each match and the return value is the replacement value.
|
||||
##
|
||||
## If `subproc` is a `proc (string): string`, then it is executed with the
|
||||
## full text of the match and the return value is the replacement value.
|
||||
##
|
||||
## If `subproc` is a string, the syntax is as follows:
|
||||
##
|
||||
## - `$$` - literal `$`
|
||||
## - `$123` - capture number `123`
|
||||
## - `$1$#` - first and second captures
|
||||
## - `$#` - first capture
|
||||
##
|
||||
## Following syntax is not supported in NRE2
|
||||
##
|
||||
## - `$foo` - named capture `foo`
|
||||
## - `${foo}` - same as above
|
||||
## - `$0` - full match
|
||||
##
|
||||
## If a given capture is missing, `ValueError` is thrown.
|
||||
proc by(m: RegexMatch2, s: string): string =
|
||||
let mat = RegexMatch(str: s, matchImpl: m)
|
||||
result = subproc(mat)
|
||||
|
||||
result = regex.replace(str, pattern, by)
|
||||
|
||||
proc replace*(str: string; pattern: Regex;
|
||||
subproc: proc (match: string): string): string =
|
||||
proc by(m: RegexMatch2; s: string): string =
|
||||
result = subproc(s)
|
||||
|
||||
result = regex.replace(str, pattern, by)
|
||||
|
||||
proc replace*(str: string; pattern: Regex; sub: string): string =
|
||||
result = regex.replace(str, pattern, sub)
|
||||
|
||||
func escapeRe*(str: string): string =
|
||||
## Escapes the string so it doesn't match any special characters.
|
||||
runnableExamples:
|
||||
assert escapeRe("fly+wind") == "fly\\+wind"
|
||||
assert escapeRe("nim*") == "nim\\*"
|
||||
|
||||
result = regex.escapeRe(str)
|
||||
14
lib/std/nre2.nims
Normal file
14
lib/std/nre2.nims
Normal file
@@ -0,0 +1,14 @@
|
||||
import std/os
|
||||
|
||||
if getCommand() == "doc":
|
||||
# std/nre2 requires nim-regex and it requires nim-unicodedb.
|
||||
# when build documentation on CI, git clone them as nimble is not available
|
||||
|
||||
const PkgDir = "build/deps"
|
||||
const Pkgs = ["nim-regex", "nim-unicodedb"]
|
||||
|
||||
for n in Pkgs:
|
||||
if not dirExists(PkgDir / n):
|
||||
exec("git clone -q https://github.com/nitely/" & n & " " & (PkgDir / n))
|
||||
|
||||
switch("path", "$nim" / PkgDir / n / "src")
|
||||
@@ -294,7 +294,11 @@ proc containsOrIncl*[A](s: var PackedSet[A], key: A): bool =
|
||||
for i in 0..<s.elems:
|
||||
if s.a[i] == ord(key):
|
||||
return true
|
||||
incl(s, key)
|
||||
if s.elems < s.a.len:
|
||||
s.a[s.elems] = ord(key)
|
||||
inc(s.elems)
|
||||
else:
|
||||
incl(s, key)
|
||||
result = false
|
||||
else:
|
||||
var t = packedSetGet(s, ord(key) shr TrunkShift)
|
||||
|
||||
@@ -84,7 +84,7 @@ func setSlice*(s: var string, slice: Slice[int]) =
|
||||
when not declared(moveMem):
|
||||
impl()
|
||||
else:
|
||||
let p = beginStore(s, last - first + 1)
|
||||
let p = beginStore(s, s.len)
|
||||
moveMem(p, addr p[first], last - first + 1)
|
||||
endStore(s)
|
||||
s.setLen(last - first + 1)
|
||||
|
||||
@@ -485,7 +485,7 @@ proc readLine*(f: File, line: var string): bool {.tags: [ReadIOEffect],
|
||||
while true:
|
||||
# fixes #9634; this pattern may need to be abstracted as a template if reused;
|
||||
# likely other io procs need this for correctness.
|
||||
fgetsSuccess = c_fgets(cast[cstring](beginStore(line, sp, pos)), sp.cint, f) != nil
|
||||
fgetsSuccess = c_fgets(cast[cstring](beginStore(line, pos + sp, pos)), sp.cint, f) != nil
|
||||
endStore(line)
|
||||
if fgetsSuccess: break
|
||||
when not defined(nimscript):
|
||||
|
||||
@@ -1703,7 +1703,8 @@ when not (notJSnotNims and defined(nimSeqsV2)):
|
||||
# Needed so modules imported by system (e.g. syncio) can reference these without guards.
|
||||
when notJSnotNims:
|
||||
# mm:refc: string = ptr NimStringDesc with data: UncheckedArray[char]
|
||||
proc beginStore*(s: var string; ensuredLen: int; start = 0): ptr UncheckedArray[char] {.inline, noSideEffect, raises: [], tags: [].} =
|
||||
proc beginStore*(s: var string; newLen: int; start = 0): ptr UncheckedArray[char] {.inline, noSideEffect, raises: [], tags: [].} =
|
||||
{.cast(noSideEffect).}: s.setLen(newLen)
|
||||
let ns = cast[NimString](s)
|
||||
if ns == nil: nil
|
||||
else: cast[ptr UncheckedArray[char]](addr ns.data[start])
|
||||
@@ -1714,7 +1715,7 @@ when not (notJSnotNims and defined(nimSeqsV2)):
|
||||
else: cast[ptr UncheckedArray[char]](addr ns.data[start])
|
||||
else:
|
||||
# JS/nimscript: callers are guarded by whenNotVmJsNims/when not defined(js)
|
||||
proc beginStore*(s: var string; ensuredLen: int; start = 0): ptr UncheckedArray[char] {.inline, noSideEffect, raises: [], tags: [].} = nil
|
||||
proc beginStore*(s: var string; newLen: int; start = 0): ptr UncheckedArray[char] {.inline, noSideEffect, raises: [], tags: [].} = nil
|
||||
proc endStore*(s: var string) {.inline, noSideEffect, raises: [], tags: [].} = discard
|
||||
template readRawData*(s: string; start = 0): ptr UncheckedArray[char] = nil
|
||||
|
||||
|
||||
@@ -236,13 +236,17 @@ func capacity*(self: string): int {.inline.} =
|
||||
let str = cast[ptr NimStringV2](unsafeAddr self)
|
||||
result = if str.p != nil: str.p.cap and not strlitFlag else: 0
|
||||
|
||||
proc beginStore*(s: var string; ensuredLen: int; start = 0): ptr UncheckedArray[char] {.inline, noSideEffect, raises: [], tags: [].} =
|
||||
## Returns a writable pointer for bulk write of `ensuredLen` bytes starting at `start`.
|
||||
proc beginStore*(s: var string; newLen: int; start = 0): ptr UncheckedArray[char] {.inline, noSideEffect, raises: [], tags: [].} =
|
||||
## Sets s.len to `newLen` (new bytes are uninitialized), ensures unique
|
||||
## ownership, and returns a pointer to s[start] for bulk writing.
|
||||
## Call `endStore(s)` afterwards for portability.
|
||||
{.cast(noSideEffect).}: prepareMutation(s)
|
||||
let str = cast[ptr NimStringV2](unsafeAddr s)
|
||||
if str.p == nil: nil
|
||||
else: cast[ptr UncheckedArray[char]](addr str.p.data[start])
|
||||
## To keep the current length, pass `s.len`.
|
||||
{.cast(noSideEffect).}:
|
||||
let p = cast[ptr NimStringV2](addr s)
|
||||
setLengthStrV2Uninit(p[], newLen)
|
||||
prepareMutation(s)
|
||||
if p.p == nil: nil
|
||||
else: cast[ptr UncheckedArray[char]](addr p.p.data[start])
|
||||
|
||||
proc endStore*(s: var string) {.inline, noSideEffect, raises: [], tags: [].} =
|
||||
## No-op for non-SSO strings; call after bulk writes via `beginStore`.
|
||||
|
||||
@@ -504,18 +504,57 @@ proc setLengthStr(s: var SmallString; newLen: int; zeroing: bool) =
|
||||
let slen = ssLen(s)
|
||||
let curLen = if slen > PayloadSize: s.more.fullLen else: slen
|
||||
if newLen == curLen: return
|
||||
if newLen <= 0:
|
||||
# Pattern 's.setLen 0' is common for avoiding allocations; do NOT free the buffer.
|
||||
if newLen < curLen:
|
||||
# Shrinking:
|
||||
if slen > PayloadSize:
|
||||
if slen == HeapSlen and s.more.rc == 1:
|
||||
s.more.fullLen = 0
|
||||
s.more.data[0] = '\0'
|
||||
# Unique heap block: keep the buffer allocated to avoid alloc/dealloc
|
||||
# ping-pong when callers shrink then grow (e.g. setLen(0) + add loops).
|
||||
s.more.fullLen = newLen
|
||||
s.more.data[newLen] = '\0'
|
||||
else:
|
||||
# shared or static block: detach and go back to empty inline
|
||||
nimDestroyStrV1(s)
|
||||
s.bytes = 0 # slen=0, all inline chars zeroed
|
||||
# shared or static block: detach and go back to inline
|
||||
if newLen <= 0:
|
||||
nimDestroyStrV1(s)
|
||||
s.bytes = 0
|
||||
else:
|
||||
let old = s.more
|
||||
let inl = inlinePtr(s)
|
||||
copyMem(inl, addr old.data[0], newLen)
|
||||
inl[newLen] = '\0'
|
||||
if slen == HeapSlen and atomicSubFetch(old.rc, 1) == 0:
|
||||
dealloc(old)
|
||||
if newLen < AlwaysAvail:
|
||||
when system.cpuEndian == littleEndian:
|
||||
let keepBits = (newLen + 1) * 8
|
||||
let charMask = ((uint(1) shl keepBits) - 1'u) and not 0xFF'u
|
||||
s.bytes = (s.bytes and charMask) or uint(newLen)
|
||||
else:
|
||||
let discardBits = (AlwaysAvail - newLen) * 8
|
||||
let slenBit = 8 * (sizeof(uint) - 1)
|
||||
let charMask = not ((uint(1) shl discardBits) - 1'u) and not (0xFF'u shl slenBit)
|
||||
s.bytes = (s.bytes and charMask) or (uint(newLen) shl slenBit)
|
||||
else:
|
||||
setSSLen(s, newLen)
|
||||
else:
|
||||
s.bytes = 0 # slen=0, all inline chars zeroed (SWAR safe)
|
||||
# inline/medium shrink
|
||||
if newLen <= 0:
|
||||
s.bytes = 0
|
||||
else:
|
||||
let inl = inlinePtr(s)
|
||||
inl[newLen] = '\0'
|
||||
if newLen < AlwaysAvail:
|
||||
when system.cpuEndian == littleEndian:
|
||||
let keepBits = (newLen + 1) * 8
|
||||
let charMask = ((uint(1) shl keepBits) - 1'u) and not 0xFF'u
|
||||
s.bytes = (s.bytes and charMask) or uint(newLen)
|
||||
else:
|
||||
let discardBits = (AlwaysAvail - newLen) * 8
|
||||
let slenBit = 8 * (sizeof(uint) - 1)
|
||||
let charMask = not ((uint(1) shl discardBits) - 1'u) and not (0xFF'u shl slenBit)
|
||||
s.bytes = (s.bytes and charMask) or (uint(newLen) shl slenBit)
|
||||
else:
|
||||
setSSLen(s, newLen)
|
||||
return
|
||||
if slen <= PayloadSize:
|
||||
if newLen <= PayloadSize:
|
||||
@@ -564,34 +603,11 @@ proc setLengthStr(s: var SmallString; newLen: int; zeroing: bool) =
|
||||
s.more = p
|
||||
setSSLen(s, HeapSlen)
|
||||
else:
|
||||
# currently long
|
||||
if newLen <= PayloadSize:
|
||||
# shrink back to inline/medium
|
||||
let old = s.more
|
||||
let inl = inlinePtr(s)
|
||||
copyMem(inl, addr old.data[0], newLen)
|
||||
inl[newLen] = '\0'
|
||||
if slen == HeapSlen and atomicSubFetch(old.rc, 1) == 0:
|
||||
dealloc(old)
|
||||
# Zero padding bytes in `bytes` for SWAR invariant
|
||||
if newLen < AlwaysAvail:
|
||||
when system.cpuEndian == littleEndian:
|
||||
let keepBits = (newLen + 1) * 8
|
||||
let charMask = ((uint(1) shl keepBits) - 1'u) and not 0xFF'u
|
||||
s.bytes = (s.bytes and charMask) or uint(newLen)
|
||||
else:
|
||||
let discardBits = (AlwaysAvail - newLen) * 8
|
||||
let slenBit = 8 * (sizeof(uint) - 1)
|
||||
let charMask = not ((uint(1) shl discardBits) - 1'u) and not (0xFF'u shl slenBit)
|
||||
s.bytes = (s.bytes and charMask) or (uint(newLen) shl slenBit)
|
||||
else:
|
||||
setSSLen(s, newLen)
|
||||
else:
|
||||
# long -> long
|
||||
ensureUniqueLong(s, curLen, newLen) # sets fullLen = newLen
|
||||
if newLen > curLen:
|
||||
zeroMem(addr s.more.data[curLen], newLen - curLen)
|
||||
s.more.data[newLen] = '\0'
|
||||
# currently long: grow within the heap buffer (shrinking already returned above)
|
||||
ensureUniqueLong(s, curLen, newLen) # sets fullLen = newLen
|
||||
if zeroing and newLen > curLen:
|
||||
zeroMem(addr s.more.data[curLen], newLen - curLen)
|
||||
s.more.data[newLen] = '\0'
|
||||
|
||||
proc setLengthStrV2(s: var SmallString; newLen: int) {.compilerRtl.} =
|
||||
## Sets the length of `s` to `newLen`, zeroing new bytes on growth.
|
||||
@@ -705,18 +721,37 @@ proc completeStore(s: var SmallString) {.compilerproc, inline.} =
|
||||
proc completeStore*(s: var string) {.inline.} =
|
||||
completeStore(cast[ptr SmallString](addr s)[])
|
||||
|
||||
proc beginStore*(s: var string; ensuredLen: int; start = 0): ptr UncheckedArray[char] {.inline, noSideEffect, raises: [], tags: [].} =
|
||||
## Prepares `s` for a bulk write of `ensuredLen` bytes starting at `start`.
|
||||
## The caller must ensure `s.len >= start + ensuredLen` (e.g. via `newString` or `setLen`).
|
||||
proc beginStore*(s: var string; newLen: int; start = 0): ptr UncheckedArray[char] {.inline, noSideEffect, raises: [], tags: [].} =
|
||||
## Sets s.len to `newLen` (new bytes are uninitialized), ensures unique
|
||||
## ownership, and returns a pointer to s[start] for bulk writing.
|
||||
## Call `endStore(s)` afterwards to sync the inline cache.
|
||||
## To keep the current length, pass `s.len`.
|
||||
{.cast(noSideEffect).}:
|
||||
let ss = cast[ptr SmallString](addr s)
|
||||
let slen = ssLen(ss[])
|
||||
if slen > PayloadSize:
|
||||
ensureUniqueLong(ss[], ss[].more.fullLen, ss[].more.fullLen)
|
||||
let curLen = if slen > PayloadSize: ss[].more.fullLen else: slen
|
||||
if newLen <= PayloadSize and slen <= PayloadSize:
|
||||
# Stay inline/medium.
|
||||
if newLen != curLen:
|
||||
setSSLen(ss[], newLen)
|
||||
result = cast[ptr UncheckedArray[char]](cast[uint](inlinePtr(ss[])) + uint(start))
|
||||
elif slen <= PayloadSize:
|
||||
# Inline/medium → long.
|
||||
let newCap = resize(newLen)
|
||||
let p = cast[ptr LongString](alloc(LongStringDataOffset + newCap + 1))
|
||||
p.rc = 1
|
||||
p.fullLen = newLen
|
||||
p.capImpl = newCap
|
||||
copyMem(addr p.data[0], inlinePtr(ss[]), curLen)
|
||||
p.data[newLen] = '\0'
|
||||
ss[].more = p
|
||||
setSSLen(ss[], HeapSlen)
|
||||
result = cast[ptr UncheckedArray[char]](addr ss[].more.data[start])
|
||||
else:
|
||||
result = cast[ptr UncheckedArray[char]](cast[uint](inlinePtr(ss[])) + uint(start))
|
||||
# Already long: resize within heap (no transition back to inline).
|
||||
ensureUniqueLong(ss[], curLen, newLen)
|
||||
ss[].more.data[newLen] = '\0'
|
||||
result = cast[ptr UncheckedArray[char]](addr ss[].more.data[start])
|
||||
|
||||
proc endStore*(s: var string) {.inline, noSideEffect, raises: [], tags: [].} =
|
||||
## Syncs the inline cache after bulk writes via `beginStore`. No-op for short/medium strings.
|
||||
|
||||
@@ -264,7 +264,7 @@ proc setLengthStrUninit(s: var string, newlen: Natural) {.nodestroy.} =
|
||||
str.data[n] = '\0'
|
||||
str.len = n
|
||||
s = cast[string](str)
|
||||
elif n < s.len:
|
||||
elif n != s.len:
|
||||
str.data[n] = '\0'
|
||||
str.len = n
|
||||
else: return
|
||||
@@ -299,12 +299,22 @@ proc incrSeqV3(s: PGenericSeq, typ: PNimType): PGenericSeq {.compilerproc.} =
|
||||
# since we steal the content from 's', it's crucial to set s's len to 0.
|
||||
s.len = 0
|
||||
|
||||
proc newSeqUninitRaw(typ: PNimType; len: int): pointer {.inline.} =
|
||||
## Creates a sequence payload with capacity and length `len` without
|
||||
## forcing zero-initialization for `ntfNoRefs` element types.
|
||||
result = nimNewSeqOfCap(typ, len)
|
||||
cast[PGenericSeq](result).len = len
|
||||
|
||||
proc extendCapacityRaw(src: PGenericSeq; typ: PNimType;
|
||||
elemSize, elemAlign, newLen: int): PGenericSeq {.inline.} =
|
||||
elemSize, elemAlign, newLen: int;
|
||||
doInit: static bool): PGenericSeq {.inline.} =
|
||||
## Reallocs `src` to fit `newLen` elements without any checks.
|
||||
## Capacity always increases to at least next `resize` step.
|
||||
let newCap = max(resize(src.space), newLen)
|
||||
result = cast[PGenericSeq](newSeq(typ, newCap))
|
||||
when doInit:
|
||||
result = cast[PGenericSeq](newSeq(typ, newCap))
|
||||
else:
|
||||
result = cast[PGenericSeq](newSeqUninitRaw(typ, newCap))
|
||||
copyMem(dataPointer(result, elemAlign), dataPointer(src, elemAlign), src.len * elemSize)
|
||||
# since we steal the content from 's', it's crucial to set s's len to 0.
|
||||
src.len = 0
|
||||
@@ -335,15 +345,19 @@ proc truncateRaw(src: PGenericSeq; baseFlags: set[TNimTypeFlag]; isTrivial: bool
|
||||
((result.len-%newLen) *% elemSize))
|
||||
|
||||
template setLengthSeqImpl(s: PGenericSeq, typ: PNimType, newLen: int; isTrivial: bool;
|
||||
doInit: static bool) =
|
||||
doInit: static bool) =
|
||||
if s == nil:
|
||||
if newLen == 0: return s
|
||||
else: return cast[PGenericSeq](newSeq(typ, newLen)) # newSeq zeroes!
|
||||
else:
|
||||
when doInit:
|
||||
return cast[PGenericSeq](newSeq(typ, newLen)) # newSeq zeroes!
|
||||
else:
|
||||
return cast[PGenericSeq](newSeqUninitRaw(typ, newLen))
|
||||
else:
|
||||
let elemSize = typ.base.size
|
||||
let elemAlign = typ.base.align
|
||||
result = if newLen > s.space:
|
||||
s.extendCapacityRaw(typ, elemSize, elemAlign, newLen)
|
||||
s.extendCapacityRaw(typ, elemSize, elemAlign, newLen, doInit)
|
||||
elif newLen < s.len:
|
||||
s.truncateRaw(typ.base.flags, isTrivial, elemSize, elemAlign, newLen)
|
||||
else:
|
||||
|
||||
@@ -39,7 +39,7 @@ architecture combinations:
|
||||
|--------------------------------|----------------------------------------|
|
||||
| Windows (Windows XP or greater) | x86 and x86_64 |
|
||||
| Linux (most distributions) | x86, x86_64, ppc64, and armv6l |
|
||||
| Mac OS X (10.04 or greater) | x86, x86_64, ppc64, and Apple Silicon (ARM64) |
|
||||
| Mac OS X (10.4 or greater) | x86, x86_64, ppc64, and Apple Silicon (ARM64) |
|
||||
|
||||
More platforms are supported, however, they are not tested regularly and they
|
||||
may not be as stable as the above-listed platforms.
|
||||
|
||||
@@ -176,6 +176,42 @@ block t6462:
|
||||
var s = SeqGen[int](fil: FilterMixin[int](test: nil, trans: nil))
|
||||
doAssert s.test() == nil
|
||||
|
||||
block concept_with_cint:
|
||||
# Generic proc matching through concepts with cint should still work
|
||||
type
|
||||
FilterMixin[T] = ref object
|
||||
test: (T) -> bool
|
||||
trans: (T) -> T
|
||||
|
||||
SeqGen[T] = ref object
|
||||
fil: FilterMixin[T]
|
||||
|
||||
WithFilter[T] = concept a
|
||||
a.fil is FilterMixin[T]
|
||||
|
||||
proc test[T](a: WithFilter[T]): (T) -> bool =
|
||||
a.fil.test
|
||||
|
||||
var s = SeqGen[cint](fil: FilterMixin[cint](test: nil, trans: nil))
|
||||
doAssert s.test() == nil
|
||||
|
||||
block concept_with_int:
|
||||
type
|
||||
FilterMixin[T] = ref object
|
||||
test: (T) -> bool
|
||||
trans: (T) -> T
|
||||
|
||||
SeqGen[T] = ref object
|
||||
fil: FilterMixin[T]
|
||||
|
||||
WithFilter[T] = concept a
|
||||
a.fil is FilterMixin[T]
|
||||
|
||||
proc test[T](a: WithFilter[T]): (T) -> bool =
|
||||
a.fil.test
|
||||
|
||||
var s = SeqGen[int](fil: FilterMixin[int](test: nil, trans: nil))
|
||||
doAssert s.test() == nil
|
||||
|
||||
|
||||
block t6770:
|
||||
|
||||
61
tests/exception/tcpp_handler_raise_finally.nim
Normal file
61
tests/exception/tcpp_handler_raise_finally.nim
Normal file
@@ -0,0 +1,61 @@
|
||||
discard """
|
||||
targets: "cpp"
|
||||
matrix: "--mm:arc; --mm:orc; --mm:refc"
|
||||
output: '''
|
||||
inner: orig
|
||||
finally
|
||||
outer: re:orig
|
||||
inner-typeless: orig
|
||||
finally-typeless
|
||||
outer-typeless: re-tl:orig
|
||||
no-catch-finally
|
||||
caught-propagated: prop
|
||||
'''
|
||||
"""
|
||||
|
||||
# When an `except` handler raises a new exception, the enclosing `finally`
|
||||
# block must still run before the new exception propagates to the outer
|
||||
# try.
|
||||
#
|
||||
# The C++ backend previously emitted the finally's `catch (...)` as a
|
||||
# sibling of the user-written catches. C++ does not allow sibling catches
|
||||
# to catch each other's throws, so a handler-raised exception bypassed the
|
||||
# finally entirely. The fix wraps the inner try/catch sequence in an
|
||||
# outer try, so any escaping exception (whether from the body or from a
|
||||
# handler) is captured before the finally runs.
|
||||
|
||||
block typed_except:
|
||||
try:
|
||||
try:
|
||||
raise newException(CatchableError, "orig")
|
||||
except CatchableError as e:
|
||||
echo "inner: ", e.msg
|
||||
raise newException(CatchableError, "re:" & e.msg)
|
||||
finally:
|
||||
echo "finally"
|
||||
except CatchableError as outer:
|
||||
echo "outer: ", outer.msg
|
||||
|
||||
block typeless_except:
|
||||
try:
|
||||
try:
|
||||
raise newException(CatchableError, "orig")
|
||||
except:
|
||||
let e = getCurrentException()
|
||||
echo "inner-typeless: ", e.msg
|
||||
raise newException(CatchableError, "re-tl:" & e.msg)
|
||||
finally:
|
||||
echo "finally-typeless"
|
||||
except CatchableError as outer:
|
||||
echo "outer-typeless: ", outer.msg
|
||||
|
||||
# try/finally without an except: the body's exception must still propagate
|
||||
# after the finally runs.
|
||||
block no_catch_finally:
|
||||
try:
|
||||
try:
|
||||
raise newException(CatchableError, "prop")
|
||||
finally:
|
||||
echo "no-catch-finally"
|
||||
except CatchableError as e:
|
||||
echo "caught-propagated: ", e.msg
|
||||
@@ -1,5 +1,5 @@
|
||||
discard """
|
||||
matrix: "--mm:refc"
|
||||
matrix: "--mm:refc; --mm:orc"
|
||||
targets: "cpp"
|
||||
output: '''
|
||||
caught as std::exception
|
||||
|
||||
@@ -10,6 +10,7 @@ discard """
|
||||
@[1, 2]
|
||||
'''
|
||||
"""
|
||||
import std/strbasics
|
||||
|
||||
# Object variant / case object
|
||||
type
|
||||
|
||||
@@ -4,7 +4,12 @@ js 3.14
|
||||
7
|
||||
1
|
||||
-21550
|
||||
-21550'''
|
||||
-21550
|
||||
none(TT)
|
||||
()
|
||||
destroyed
|
||||
destroyed
|
||||
'''
|
||||
"""
|
||||
|
||||
# This file tests the JavaScript generator
|
||||
@@ -56,3 +61,15 @@ proc foo09() =
|
||||
const y = 86400
|
||||
echo (x - (y - 1)) div y # Still gives `-21551`
|
||||
foo09()
|
||||
|
||||
import std/options
|
||||
|
||||
type TT = object
|
||||
|
||||
proc `=destroy`(x: TT) = echo "destroyed"
|
||||
|
||||
func test1: Option[TT] = discard
|
||||
func test2: TT = discard
|
||||
|
||||
echo test1() # Crash in JS backend, not crash in C backend
|
||||
echo test2() # Not crash
|
||||
|
||||
12
tests/lent/tlent_tuple_address.nim
Normal file
12
tests/lent/tlent_tuple_address.nim
Normal file
@@ -0,0 +1,12 @@
|
||||
discard """
|
||||
errormsg: "expression has no address"
|
||||
"""
|
||||
|
||||
iterator foo(x: int): (lent int, lent int) =
|
||||
yield (x, x + 1)
|
||||
|
||||
|
||||
var x = 12
|
||||
for i in foo(x):
|
||||
echo i[0]
|
||||
echo i[1]
|
||||
@@ -434,3 +434,32 @@ block: # bug #24378
|
||||
type Win222[T] = typeof("foobar")
|
||||
doAssert not supportsCopyMem((int, Win222[int]))
|
||||
doAssert not supportsCopyMem(tuple[a: int, b: Win222[int]])
|
||||
|
||||
block: # bug #25789
|
||||
type
|
||||
L[T; N: static int] = distinct seq[T]
|
||||
EPF = distinct L[int, 100]
|
||||
|
||||
var e: EPF = EPF(L[int, 100](@[1, 2, 3]))
|
||||
|
||||
template classifyGeneric[T](x: T): bool =
|
||||
when typeof(x) is L:
|
||||
true
|
||||
else:
|
||||
false
|
||||
|
||||
template classifyConcrete[T](x: T): bool =
|
||||
when typeof(x) is L[int, 100]:
|
||||
true
|
||||
else:
|
||||
false
|
||||
|
||||
let viaConv = L[int, 100](e)
|
||||
doAssert $type(viaConv) == "L[system.int, 100]"
|
||||
doAssert classifyGeneric(viaConv)
|
||||
doAssert classifyConcrete(viaConv)
|
||||
|
||||
let viaDB = distinctBase(e, recursive = false)
|
||||
doAssert $type(viaDB) == "L[system.int, 100]"
|
||||
doAssert classifyGeneric(viaDB)
|
||||
doAssert classifyConcrete(viaDB)
|
||||
|
||||
@@ -68,3 +68,20 @@ block:
|
||||
let sized = Sized()
|
||||
doAssert sized.files.x == sizeof(Sized)
|
||||
|
||||
block:
|
||||
type
|
||||
Generic[T] = object
|
||||
t: T
|
||||
|
||||
WindowObj = object
|
||||
svgCache: Generic[SVGSVGElement]
|
||||
|
||||
SVGSVGElement = Generic[SVGSVGElementObj]
|
||||
|
||||
SVGSVGElementObj = object
|
||||
|
||||
proc foo() =
|
||||
let p: pointer = nil
|
||||
discard cast[ptr WindowObj](p)
|
||||
|
||||
foo()
|
||||
|
||||
@@ -833,4 +833,37 @@ proc overloaded[T: object](x: T) =
|
||||
var v: typeof(val)
|
||||
overloaded(v)
|
||||
|
||||
overloaded(Thing())
|
||||
overloaded(Thing())
|
||||
|
||||
block:
|
||||
type
|
||||
Foo = object
|
||||
x = Bar()
|
||||
|
||||
Bar = object
|
||||
x: int
|
||||
|
||||
var f = Foo()
|
||||
doassert f.x.x == 0
|
||||
|
||||
block:
|
||||
type
|
||||
Foo = object
|
||||
x = Bar(x: 55)
|
||||
|
||||
Bar = object
|
||||
x: int
|
||||
|
||||
var f = Foo()
|
||||
doassert f.x.x == 55
|
||||
|
||||
block:
|
||||
type
|
||||
Bar = object
|
||||
x: int
|
||||
|
||||
Foo = object
|
||||
x = Bar()
|
||||
|
||||
var f = Foo()
|
||||
doassert f.x.x == 0
|
||||
|
||||
@@ -549,3 +549,20 @@ block:
|
||||
|
||||
type X {.p.} = object
|
||||
doAssert foo(X())
|
||||
|
||||
block: # typeof() type alias preserves field pragmas
|
||||
template myFieldPragma {.pragma.}
|
||||
type Orig = object
|
||||
x {.myFieldPragma.}: int
|
||||
|
||||
var orig: Orig
|
||||
|
||||
# Direct typeof alias
|
||||
type TAlias = typeof(orig)
|
||||
var a: TAlias
|
||||
doAssert a.x.hasCustomPragma(myFieldPragma)
|
||||
|
||||
# Indirect alias of typeof alias
|
||||
type TAlias2 = TAlias
|
||||
var b: TAlias2
|
||||
doAssert b.x.hasCustomPragma(myFieldPragma)
|
||||
|
||||
44
tests/proc/tbackendtypealias.nim
Normal file
44
tests/proc/tbackendtypealias.nim
Normal file
@@ -0,0 +1,44 @@
|
||||
# bug #25617
|
||||
# Ensure that proc types with backend type alias mismatches
|
||||
# (e.g. uint vs csize_t) are rejected at the Nim level rather
|
||||
# than producing invalid C code.
|
||||
|
||||
discard """
|
||||
cmd: "nim check --hints:off --warnings:off --errorMax:0 $file"
|
||||
action: "reject"
|
||||
nimout: '''
|
||||
tbackendtypealias.nim(21, 7) Error: type mismatch: got <proc (len: csize_t){.closure.}> but expected 'proc (len: uint){.closure.}'
|
||||
tbackendtypealias.nim(28, 7) Error: type mismatch: got <proc (len: uint){.closure.}> but expected 'proc (len: csize_t){.closure.}'
|
||||
'''
|
||||
"""
|
||||
|
||||
block direct_assignment:
|
||||
# Direct proc variable assignment with backend type alias mismatch
|
||||
var
|
||||
a: proc (len: uint)
|
||||
b: proc (len: csize_t)
|
||||
c = a
|
||||
c = b
|
||||
|
||||
block direct_assignment_reverse:
|
||||
var
|
||||
a: proc (len: csize_t)
|
||||
b: proc (len: uint)
|
||||
c = a
|
||||
c = b
|
||||
|
||||
block same_backend_type:
|
||||
# Same backend type should still work
|
||||
var
|
||||
a: proc (len: uint)
|
||||
b: proc (len: uint)
|
||||
c = a
|
||||
c = b
|
||||
|
||||
block cint_same_type:
|
||||
# cint to cint should work
|
||||
var
|
||||
a: proc (len: cint)
|
||||
b: proc (len: cint)
|
||||
c = a
|
||||
c = b
|
||||
196
tests/stdlib/tnre2.nim
Normal file
196
tests/stdlib/tnre2.nim
Normal file
@@ -0,0 +1,196 @@
|
||||
import std/[assertions, options, sequtils, strutils, tables]
|
||||
import std/nre2
|
||||
|
||||
block:
|
||||
let pattern = "[0-9"
|
||||
doAssertRaises(RegexError): discard re(pattern)
|
||||
|
||||
block: # captures
|
||||
block: # capture bounds are correct
|
||||
let ex1 = re("([0-9])")
|
||||
doAssert "1 23".find(ex1).get.matchBounds == 0 .. 0
|
||||
doAssert "1 23".find(ex1).get.captureBounds[0] == 0 .. 0
|
||||
doAssert "1 23".find(ex1, 1).get.matchBounds == 2 .. 2
|
||||
doAssert "1 23".find(ex1, 3).get.matchBounds == 3 .. 3
|
||||
|
||||
let ex2 = re("()()()()()()()()()()([0-9])")
|
||||
doAssert "824".find(ex2).get.captureBounds[0] == 0 .. -1
|
||||
doAssert "824".find(ex2).get.captureBounds[10] == 0 .. 0
|
||||
|
||||
let ex3 = re("([0-9]+)")
|
||||
doAssert "824".find(ex3).get.captureBounds[0] == 0 .. 2
|
||||
|
||||
block: # named captures
|
||||
let ex1 = "foobar".find(re("(?P<foo>foo)(?P<bar>bar)"))
|
||||
doAssert ex1.get.captures["foo"] == "foo"
|
||||
doAssert ex1.get.captures["bar"] == "bar"
|
||||
|
||||
let ex2 = "foo".find(re("(?P<foo>foo)(?P<bar>bar)?"))
|
||||
doAssert "foo" in ex2.get.captureBounds
|
||||
doAssert ex2.get.captures["foo"] == "foo"
|
||||
doAssert not ("bar" in ex2.get.captures)
|
||||
doAssertRaises(KeyError):
|
||||
discard ex2.get.captures["bar"]
|
||||
|
||||
block: # named capture bounds
|
||||
let ex1 = "foo".find(re("(?P<foo>foo)(?P<bar>bar)?"))
|
||||
doAssert "foo" in ex1.get.captureBounds
|
||||
doAssert ex1.get.captureBounds["foo"] == 0..2
|
||||
doAssert not ("bar" in ex1.get.captures)
|
||||
doAssertRaises(KeyError):
|
||||
discard ex1.get.captureBounds["bar"]
|
||||
|
||||
block: # capture count
|
||||
let ex1 = re("(?P<foo>foo)(?P<bar>bar)?")
|
||||
doAssert ex1.captureCount == 2
|
||||
doAssert ex1.captureNameId == {"foo" : 0, "bar" : 1}.toTable()
|
||||
|
||||
block: # named capture table
|
||||
let ex1 = "foo".find(re("(?P<foo>foo)(?P<bar>bar)?"))
|
||||
doAssert ex1.get.captures.toTable == {"foo" : "foo"}.toTable()
|
||||
doAssert ex1.get.captureBounds.toTable == {"foo" : 0..2}.toTable()
|
||||
|
||||
let ex2 = "foobar".find(re("(?P<foo>foo)(?P<bar>bar)?"))
|
||||
doAssert ex2.get.captures.toTable == {"foo" : "foo", "bar" : "bar"}.toTable()
|
||||
|
||||
block: # capture sequence
|
||||
let ex1 = "foo".find(re("(?P<foo>foo)(?P<bar>bar)?"))
|
||||
doAssert ex1.get.captures.toSeq == @[some("foo"), none(string)]
|
||||
doAssert ex1.get.captureBounds.toSeq == @[some(0..2), none(Slice[int])]
|
||||
doAssert ex1.get.captures.toSeq(some("")) == @[some("foo"), some("")]
|
||||
|
||||
let ex2 = "foobar".find(re("(?P<foo>foo)(?P<bar>bar)?"))
|
||||
doAssert ex2.get.captures.toSeq == @[some("foo"), some("bar")]
|
||||
|
||||
block: # match
|
||||
block: # upper bound must be inclusive
|
||||
doAssert "abc".match(re"abc", endpos = -1) == none(RegexMatch)
|
||||
doAssert "abc".match(re"abc", endpos = 1) == none(RegexMatch)
|
||||
doAssert "abc".match(re"abc", endpos = 2) != none(RegexMatch)
|
||||
|
||||
block: # match examples
|
||||
doAssert "abc".match(re"(\w)").get.captures[0] == "a"
|
||||
doAssert "abc".match(re"(?P<letter>\w)").get.captures["letter"] == "a"
|
||||
doAssert "abc".match(re"(\w)\w").get.captures[-1] == "ab"
|
||||
doAssert "abc".match(re"(\w)").get.captureBounds[0] == 0 .. 0
|
||||
doAssert "abc".match(re"").get.captureBounds[-1] == 0 .. -1
|
||||
doAssert "abc".match(re"abc").get.captureBounds[-1] == 0 .. 2
|
||||
|
||||
let cap1 = "abc".match(re"(\w)(\w)+").get.captures
|
||||
doAssert cap1.len == 2
|
||||
doAssert 0 in cap1
|
||||
doAssert 1 in cap1
|
||||
doAssert cap1[0] == "a" and cap1[1] == "c"
|
||||
doAssert 0 in "abc".match(re"(\w)+").get.captureBounds
|
||||
|
||||
block: # match test cases
|
||||
doAssert "123".match(re"").get.matchBounds == 0 .. -1
|
||||
let mat1 = "123".match(re"123").get
|
||||
doAssert mat1.matchBounds == 0 .. 2
|
||||
doAssert mat1.match == "123"
|
||||
|
||||
block: # find
|
||||
block: # find text
|
||||
doAssert "3213a".find(re"[a-z]").get.match == "a"
|
||||
doAssert sequtils.toSeq(findIter("1 2 3 4 5 6 7 8 ", re" ")).mapIt(
|
||||
it.match
|
||||
) == @[" ", " ", " ", " ", " ", " ", " ", " "]
|
||||
|
||||
block: # find bounds
|
||||
doAssert sequtils.toSeq(findIter("1 2 3 4 5 ", re" ")).mapIt(
|
||||
it.matchBounds
|
||||
) == @[1..1, 3..3, 5..5, 7..7, 9..9]
|
||||
|
||||
block: # overlapping find
|
||||
doAssert "222".findAll(re"22") == @["22"]
|
||||
doAssert "2222".findAll(re"22") == @["22", "22"]
|
||||
|
||||
block: # len 0 find
|
||||
doAssert "".findAll(re"\ ") == newSeq[string]()
|
||||
doAssert "".findAll(re"") == @[""]
|
||||
doAssert "abc".findAll(re"") == @["", "", "", ""]
|
||||
doAssert "word word".findAll(re"\b") == @["", "", "", ""]
|
||||
doAssert "word\r\lword".findAll(re"(?m)$") == @["", ""]
|
||||
doAssert "слово слово".findAll(re"\b") == @["", "", "", ""]
|
||||
|
||||
block: # contains
|
||||
doAssert "abc".contains(re"bc")
|
||||
doAssert not "abc".contains(re"cd")
|
||||
doAssert not "abc".contains(re"a", start = 1)
|
||||
|
||||
block: # string splitting
|
||||
block: # splitting strings
|
||||
doAssert "1 2 3 4 5 6 ".split(re" ") == @["1", "2", "3", "4", "5", "6", ""]
|
||||
doAssert "1 2 ".split(re(" ")) == @["1", "", "2", "", ""]
|
||||
doAssert "1 2".split(re(" ")) == @["1", "2"]
|
||||
doAssert "foo".split(re("foo")) == @["", ""]
|
||||
doAssert "".split(re"foo") == @[""]
|
||||
doAssert "9".split(re"\son\s") == @["9"]
|
||||
|
||||
block: # captured patterns
|
||||
doAssert "12".split(re"(\d)") == @["", "1", "", "2", ""]
|
||||
|
||||
block: # maxsplit
|
||||
doAssert "123".split(re"", maxsplit = 2) == @["1", "23"]
|
||||
doAssert "123".split(re"", maxsplit = 1) == @["123"]
|
||||
doAssert "123".split(re"", maxsplit = -1) == @["1", "2", "3"]
|
||||
doAssert "1 2 3".split(re" ", maxsplit = 1) == @["1 2 3"]
|
||||
doAssert "1 2 3".split(re" ", maxsplit = 2) == @["1", "2 3"]
|
||||
doAssert "1 2 3".split(re"( )", maxsplit = 2) == @["1", " ", "2 3"]
|
||||
|
||||
block: # split with 0-length match
|
||||
doAssert "12345".split(re("")) == @["1", "2", "3", "4", "5"]
|
||||
doAssert "".split(re"") == newSeq[string]()
|
||||
doAssert "word word".split(re"\b") == @["word", " ", "word"]
|
||||
#doAssert "word\r\lword".split(re"(?m)$") == @["word", "\r\lword"]
|
||||
doAssert "слово слово".split(re"(\b)") == @["слово", "", " ", "", "слово", ""]
|
||||
|
||||
block: # perl split tests
|
||||
doAssert "forty-two" .split(re"") .join(",") == "f,o,r,t,y,-,t,w,o"
|
||||
doAssert "forty-two" .split(re"", 3) .join(",") == "f,o,rty-two"
|
||||
doAssert "split this string" .split(re" ") .join(",") == "split,this,string"
|
||||
doAssert "split this string" .split(re" ", 2) .join(",") == "split,this string"
|
||||
doAssert "try$this$string" .split(re"\$") .join(",") == "try,this,string"
|
||||
doAssert "try$this$string" .split(re"\$", 2) .join(",") == "try,this$string"
|
||||
doAssert "comma, separated, values" .split(re", ") .join("|") == "comma|separated|values"
|
||||
doAssert "comma, separated, values" .split(re", ", 2) .join("|") == "comma|separated, values"
|
||||
doAssert "Perl6::Camelia::Test" .split(re"::") .join(",") == "Perl6,Camelia,Test"
|
||||
doAssert "Perl6::Camelia::Test" .split(re"::", 2) .join(",") == "Perl6,Camelia::Test"
|
||||
doAssert "split,me,please" .split(re",") .join("|") == "split|me|please"
|
||||
doAssert "split,me,please" .split(re",", 2) .join("|") == "split|me,please"
|
||||
doAssert "Hello World Goodbye Mars".split(re"\s+") .join(",") == "Hello,World,Goodbye,Mars"
|
||||
doAssert "Hello World Goodbye Mars".split(re"\s+", 3).join(",") == "Hello,World,Goodbye Mars"
|
||||
doAssert "Hello test" .split(re"(\s+)") .join(",") == "Hello, ,test"
|
||||
doAssert "this will be split" .split(re" ") .join(",") == "this,will,be,split"
|
||||
doAssert "this will be split" .split(re" ", 3) .join(",") == "this,will,be split"
|
||||
doAssert "a.b" .split(re"\.") .join(",") == "a,b"
|
||||
doAssert "" .split(re"") .len == 0
|
||||
doAssert ":" .split(re"") .len == 1
|
||||
|
||||
block: # start position
|
||||
doAssert "abc".split(re"", start = 1) == @["b", "c"]
|
||||
doAssert "abc".split(re"", start = 2) == @["c"]
|
||||
doAssert "abc".split(re"", start = 3) == newSeq[string]()
|
||||
doAssert "abc".split(re"^b", start = 1) == @["bc"]
|
||||
|
||||
block: # replace
|
||||
block: # replace with 0-length strings
|
||||
doAssert "".replace(re"1", proc (v: RegexMatch): string = "1") == ""
|
||||
doAssert " ".replace(re"", proc (v: RegexMatch): string = "1") == "1 1"
|
||||
doAssert "".replace(re"", proc (v: RegexMatch): string = "1") == "1"
|
||||
|
||||
block: # regular replace
|
||||
doAssert "123".replace(re"\d", "foo") == "foofoofoo"
|
||||
doAssert "123".replace(re"(\d)", "$1$1") == "112233"
|
||||
doAssert "123".replace(re"(\d)(\d)", "$1$2") == "123"
|
||||
doAssert "123".replace(re"(\d)(\d)", "$#$#") == "123"
|
||||
doAssert "abcdefghijklm".replace(re"(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)(m)", "$12") == "l"
|
||||
|
||||
block: # replacing missing captures should throw instead of segfaulting
|
||||
doAssertRaises(ValueError): discard "ab".replace(re"(a)", "$1$2")
|
||||
|
||||
block: # escape strings
|
||||
block: # escape strings
|
||||
doAssert "123".escapeRe() == "123"
|
||||
doAssert "[]".escapeRe() == r"\[\]"
|
||||
doAssert "()".escapeRe() == r"\(\)"
|
||||
3
tests/stdlib/tnre2.nims
Normal file
3
tests/stdlib/tnre2.nims
Normal file
@@ -0,0 +1,3 @@
|
||||
# std/nre2 requires nim-regex and it requires nim-unicodedb
|
||||
exec("nimble --nimbleDir:build/deps install unicodedb@#head")
|
||||
exec("nimble --nimbleDir:build/deps install regex@#head")
|
||||
@@ -1,5 +1,5 @@
|
||||
discard """
|
||||
matrix: "--backend:c --mm:refc; --backend:c --mm:orc; --backend:c --mm:orc -d:nimsso; --backend:cpp --mm:refc; --backend:cpp --mm:orc; --backend:js --mm:refc; --backend:js --mm:orc"
|
||||
matrix: "--backend:c --mm:refc; --backend:c --mm:orc; --backend:c --mm:orc --strings:sso; --backend:cpp --mm:refc; --backend:cpp --mm:orc; --backend:js --mm:refc; --backend:js --mm:orc"
|
||||
"""
|
||||
|
||||
from std/sequtils import toSeq, map
|
||||
@@ -176,6 +176,13 @@ proc main() =
|
||||
result.add char(ord('a') + i mod 26)
|
||||
|
||||
proc checkSetLenUninit(oldLen, newLen: int; cmpAfter = -1) =
|
||||
## Verifies `setLenUninit`:
|
||||
## - preserves the existing prefix
|
||||
## - updates the string length
|
||||
## - keeps internal null termination valid for both shrink and growth
|
||||
##
|
||||
## `cmpAfter` is used for layouts where trailing zeroed padding affects
|
||||
## string comparison semantics after the resize.
|
||||
var s = makeStr(oldLen)
|
||||
let prefixLen = min(oldLen, newLen)
|
||||
let prefix = makeStr(prefixLen)
|
||||
@@ -203,9 +210,18 @@ proc main() =
|
||||
|
||||
block setLenUninit:
|
||||
# Shared baseline for both SSO and V2: noop, shrink, grow.
|
||||
checkSetLenUninit(numbers.len, numbers.len)
|
||||
checkSetLenUninit(numbers.len, 5)
|
||||
checkSetLenUninit(numbers.len, 11)
|
||||
checkSetLenUninit(10, 10)
|
||||
checkSetLenUninit(10, 5)
|
||||
checkSetLenUninit(10, 11)
|
||||
|
||||
block growingWithinBiggerCapacity:
|
||||
# Strings can reserve spare capacity even for short strings.
|
||||
# Growing within that capacity must still update len and the trailing zero.
|
||||
var s = newStringOfCap(10)
|
||||
s.add("abc")
|
||||
s.setLenUninit(6)
|
||||
s.checkStrInternals(6)
|
||||
doAssert s[0..2] == "abc"
|
||||
|
||||
when hasNativeSso:
|
||||
const
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
discard """
|
||||
matrix: "-d:nimsso"
|
||||
matrix: "--strings:sso --mm:orc"
|
||||
targets: "c cpp"
|
||||
"""
|
||||
|
||||
|
||||
10
tests/types/tforwardcycletimeout.nim
Normal file
10
tests/types/tforwardcycletimeout.nim
Normal file
@@ -0,0 +1,10 @@
|
||||
discard """
|
||||
timeout: "1.0"
|
||||
"""
|
||||
|
||||
type
|
||||
Generic[T] = object
|
||||
t: T
|
||||
|
||||
A = Generic[B]
|
||||
B = Generic[A]
|
||||
7
tests/types/tillegalset.nim
Normal file
7
tests/types/tillegalset.nim
Normal file
@@ -0,0 +1,7 @@
|
||||
discard """
|
||||
errormsg: "set is too large; use `std/sets` for ordinal types with more than 2^16 elements"
|
||||
"""
|
||||
|
||||
type
|
||||
Foo = set[Bar]
|
||||
Bar = int32
|
||||
8
tests/types/tillegalset2.nim
Normal file
8
tests/types/tillegalset2.nim
Normal file
@@ -0,0 +1,8 @@
|
||||
discard """
|
||||
errormsg: "set is too large; use `std/sets` for ordinal types with more than 2^16 elements"
|
||||
"""
|
||||
|
||||
type
|
||||
Foo = int32
|
||||
Bar = set[Baz]
|
||||
Baz = Foo
|
||||
Reference in New Issue
Block a user