Compare commits

..

4 Commits

117 changed files with 609 additions and 3043 deletions

View File

@@ -49,7 +49,7 @@ jobs:
DEBIAN_FRONTEND='noninteractive' \
sudo apt-get install --no-install-recommends -yq \
libcurl4-openssl-dev libgc-dev libsdl1.2-dev libsfml-dev \
valgrind libc6-dbg libblas-dev liblapack-dev libpcre2-dev xorg-dev
valgrind libc6-dbg libblas-dev liblapack-dev libpcre3 xorg-dev
- name: 'Install dependencies (macOS)'
if: runner.os == 'macOS'
run: brew install boehmgc make sfml gtk+3

View File

@@ -60,7 +60,7 @@ jobs:
run: nim c -r -d:release ci/action.nim
- name: 'Comment'
uses: actions/github-script@v9
uses: actions/github-script@v8
with:
script: |
const fs = require('fs');

View File

@@ -35,10 +35,6 @@ 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:"
@@ -64,26 +60,17 @@ parameter and result types, not just their source-level shape. Use
- `copyDirWithPermissions` to recursively preserve attributes
- `system.setLenUninit` now supports refc, JS and VM backends.
- `system.setLenUninit` for the `string` type. Allows setting length without initializing new memory on growth.
- `std/parseopt` now supports multiple parser modes via a `CliMode` enum.
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` now use PCRE2. They remain deprecated;
use https://github.com/nitely/nim-regex or `std/nre2`.
See: https://github.com/nim-lang/Nim/issues/23668.
- `std/pegs` now correctly lexes UTF-8 bytes inside bare identifier-style
terminals, so case-insensitive matching of non-ASCII terms (e.g. ``\i café``)
works without single-quoting.
## Language changes

View File

@@ -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.usesSso():
optSeqDestructors in p.config.globalOptions and not p.config.isDefined("nimsso"):
let bra = byRefLoc(p, a)
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimPrepareStrMutationV2"),
bra)
if p.config.usesSso() and
if p.config.isDefined("nimsso") 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.usesSso():
optSeqDestructors in p.config.globalOptions and not p.config.isDefined("nimsso"):
let bra = byRefLoc(p, a)
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimPrepareStrMutationV2"),
bra)
if p.config.usesSso() and
if p.config.isDefined("nimsso") 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.usesSso():
if p.config.isDefined("nimsso"):
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.usesSso(): byRefLoc(p, tmp) else: tmp.rdLoc
let ra = if p.config.isDefined("nimsso"): addrLoc(p.config, 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) =

View File

@@ -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.usesSso():
if p.config.isDefined("nimsso"):
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.usesSso():
not p.config.isDefined("nimsso"):
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.usesSso() and e[0].kind == nkBracketExpr and
let ssoStrSub = p.config.isDefined("nimsso") 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.usesSso() and ty.kind == tyString:
if p.config.isDefined("nimsso") 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,17 +1889,10 @@ 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.
# 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)
# check if we need to construct the object in a temporary
var useTemp =
isRef or
d.k == locNone or
(d.t != nil and not sameBackendType(t, d.t.skipTypes(abstractInstOwned))) or
(d.k notin {locTemp,locLocalVar,locGlobalVar,locParam,locField}) or
(isPartOf(d.lode, e) != arNo)
var tmp: TLoc = default(TLoc)
@@ -2150,7 +2143,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.usesSso() and
if p.config.isDefined("nimsso") and
skipTypes(a.t, abstractVarRange).kind == tyString:
let bra = byRefLoc(p, a)
putIntoDest(p, b, e,
@@ -2743,7 +2736,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.usesSso(): byRefLoc(p, a) else: rdLoc(a)
let arg = if p.config.isDefined("nimsso"): addrLoc(p.config, a) else: rdLoc(a)
putIntoDest(p, d, n,
cgCall(p, "nimToCStringConv", arg),
a.storage)
@@ -2816,13 +2809,13 @@ proc genWasMoved(p: BProc; n: PNode) =
# [addrLoc(p.config, a), getTypeDesc(p.module, a.t)])
proc genMove(p: BProc; n: PNode; d: var TLoc) =
var a: TLoc = initLocExpr(p, n[1].skipAddr, {lfEnforceDeref, lfPrepareForMutation})
if n.len == 4:
# generated by liftdestructors:
var a: TLoc = initLocExpr(p, n[1].skipAddr, {lfEnforceDeref, lfPrepareForMutation})
var src: TLoc = initLocExpr(p, n[2])
let destVal = rdLoc(a)
let srcVal = rdLoc(src)
if p.config.usesSso() and
if p.config.isDefined("nimsso") and
n[1].typ.skipTypes(abstractVar).kind == tyString:
# SmallString: destroy dst then struct-copy src; no .p field aliasing needed
genStmts(p, n[3])
@@ -2838,16 +2831,29 @@ proc genMove(p: BProc; n: PNode; d: var TLoc) =
else:
if d.k == locNone: d = getTemp(p, n.typ)
if p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc}:
genAssignment(p, d, a, {})
var op = getAttachedOp(p.module.g.graph, n.typ, attachedWasMoved)
if op == nil or sfOverridden notin op.flags:
var a: TLoc = initLocExpr(p, n[1].skipAddr, {lfEnforceDeref, lfPrepareForMutation})
genAssignment(p, d, a, {})
if op == nil:
resetLoc(p, a)
else:
n[1] = makeAddr(n[1], p.module.idgen)
genCall(p, n, d)
var b = initLocExpr(p, newSymNode(op))
case skipTypes(a.t, abstractVar+{tyStatic}).kind
of tyOpenArray, tyVarargs: # todo fixme generated `wasMoved` hooks for
# openarrays, but it probably shouldn't?
let ra = rdLoc(a)
var s: string
if reifiedOpenArray(a.lode):
if a.t.kind in {tyVar, tyLent}:
s = derefField(ra, "Field0") & cArgumentSeparator & derefField(ra, "Field1")
else:
s = dotField(ra, "Field0") & cArgumentSeparator & dotField(ra, "Field1")
else:
s = ra & cArgumentSeparator & ra & "Len_0"
p.s(cpsStmts).addCallStmt(rdLoc(b), s)
else:
let val = if p.module.compileToCpp: rdLoc(a) else: byRefLoc(p, a)
p.s(cpsStmts).addCallStmt(rdLoc(b), val)
else:
var a: TLoc = initLocExpr(p, n[1].skipAddr, {lfEnforceDeref, lfPrepareForMutation})
genAssignment(p, d, a, {})
resetLoc(p, a)
@@ -2858,7 +2864,7 @@ proc genDestroy(p: BProc; n: PNode) =
case t.kind
of tyString:
var a: TLoc = initLocExpr(p, arg)
if p.config.usesSso():
if p.config.isDefined("nimsso"):
# SmallString: delegate to nimDestroyStrV1 (rc-based, handles static strings)
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimDestroyStrV1"), rdLoc(a))
else:
@@ -4230,7 +4236,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.usesSso():
if p.config.isDefined("nimsso"):
genStringLiteralV3Const(p.module, n, isConst, result)
else:
genStringLiteralV2Const(p.module, n, isConst, result)

View File

@@ -22,7 +22,7 @@ template detectVersion(field, corename) =
result = 1
proc detectStrVersion(m: BModule): int =
if m.g.config.usesSso() and
if m.g.config.isDefined("nimsso") and
m.g.config.selectedGC in {gcArc, gcOrc, gcYrc, gcAtomicArc, gcHooks}:
result = 3
else:

View File

@@ -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, isHidden: bool, label: Natural]](0)
var stack = newSeq[tuple[fin: PNode, inExcept: bool, label: Natural]](0)
inc p.withinBlockLeaveActions
for i in 1..howManyTrys:
@@ -341,9 +341,9 @@ proc genCppParamsForCtor(p: BProc; call: PNode; didGenTemp: var bool): Snippet =
call[i][0]
else:
call[i]
if not param.typ.isCompileTimeOnly and (param.kind != nkBracketExpr or param.typ.kind in
if param.kind != nkBracketExpr or param.typ.kind in
{tyRef, tyPtr, tyUncheckedArray, tyArray, tyOpenArray,
tyVarargs, tySequence, tyString, tyCstring, tyTuple}):
tyVarargs, tySequence, tyString, tyCstring, tyTuple}:
let tempLoc = initLocExprSingleUse(p, param)
didGenTemp = didGenTemp or tempLoc.k == locTemp
genOtherArg(p, call, i, typ, res, argBuilder)
@@ -836,26 +836,12 @@ proc raiseExitCleanup(p: BProc, destroy: string) =
p.s(cpsStmts).addGoto("LA" & $p.nestedTryStmts[^1].label & "_")
proc finallyActions(p: BProc) =
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
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])
proc raiseInstr(p: BProc; result: var Builder) =
if p.config.exc == excGoto:
@@ -1179,7 +1165,7 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) =
throw;
}
} catch(...) {
// C++ exception occurred, not under Nim's control.
// C++ exception occured, not under Nim's control.
}
{
/* finally: */
@@ -1199,7 +1185,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, t.kind == nkHiddenTryStmt, 0.Natural))
p.nestedTryStmts.add((fin, false, 0.Natural))
if t.kind == nkHiddenTryStmt:
lineCg(p, cpsStmts, "try {$n", [])
@@ -1385,7 +1371,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, t.kind == nkHiddenTryStmt, 0.Natural))
p.nestedTryStmts.add((fin, false, 0.Natural))
startBlockWith(p):
p.s(cpsStmts).add("try {\n")
expr(p, t[0], d)
@@ -1464,7 +1450,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, t.kind == nkHiddenTryStmt, Natural lab))
p.nestedTryStmts.add((fin, false, Natural lab))
p.flags.incl nimErrorFlagAccessed
@@ -1670,7 +1656,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, t.kind == nkHiddenTryStmt, 0.Natural))
p.nestedTryStmts.add((fin, quirkyExceptions, 0.Natural))
expr(p, t[0], d)
var quirkyIf = default(IfBuilder)
var quirkyScope = default(ScopeBuilder)
@@ -1954,7 +1940,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.usesSso() and e[0].kind == nkBracketExpr and
elif p.config.isDefined("nimsso") 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)

View File

@@ -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.usesSso() and a.lode != nil and a.t != nil and
if p.config.isDefined("nimsso") 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.usesSso():
if typ.kind == tyString and p.config.isDefined("nimsso"):
# 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.usesSso():
if skipTypes(typ, abstractInst + {tyStatic}).kind == tyString and p.config.isDefined("nimsso"):
# 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)

View File

@@ -75,13 +75,10 @@ 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, isHidden: bool, label: Natural]]
nestedTryStmts*: seq[tuple[fin: PNode, inExcept: bool, label: Natural]]
# in how many nested try statements we are
# (the vars must be volatile then)
# `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.
# bool is true when are in the except part of a try block
finallySafePoints*: seq[Rope] # For correctly cleaning up exceptions when
# using return in finally statements
labels*: Natural # for generating unique labels in the C proc

View File

@@ -250,7 +250,6 @@ 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)
@@ -307,13 +306,6 @@ 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)
@@ -758,17 +750,6 @@ 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)
@@ -930,7 +911,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
if m.len == 0:
localError(conf, info, "Cannot resolve filename: " & arg)
else:
conf.implicitImports.add(if arg.startsWith(stdPrefix): arg else: m)
conf.implicitImports.add m
of "include":
expectArg(conf, switch, arg, pass, info)
if pass in {passCmd2, passPP}:

View File

@@ -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]
import std / [os, tables, sets, times, osproc, strutils]
import options, msgs, lineinfos, pathutils
import "../dist/nimony/src/lib" / [nifstreams, bitabs, nifreader, nifbuilder]
import "../dist/nimony/src/lib" / [nifstreams, nifcursors, bitabs, nifreader, nifbuilder]
import "../dist/nimony/src/gear2" / modnames
type
@@ -79,19 +79,22 @@ proc runNifler(c: DepContext; nimFile: string): bool =
let exitCode = execShellCmd(cmd)
result = exitCode == 0
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
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 resolveInclude(c: DepContext; origin, toResolve: string): string =
## Resolve an include path relative to the including file or the search paths.
# Try relative to origin first
let originDir = parentDir(origin)
result = originDir / toResolve.addFileExt("nim")
result = originDir / path.addFileExt("nim")
if fileExists(result):
return result
# Try search paths
for searchPath in c.config.searchPaths:
result = searchPath.string / toResolve.addFileExt("nim")
result = searchPath.string / path.addFileExt("nim")
if fileExists(result):
return result
@@ -100,7 +103,7 @@ proc resolveInclude(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 = resolveInclude(c, current.files[current.files.len - 1].nimFile, includePath)
let resolved = resolveFile(c, current.files[current.files.len - 1].nimFile, includePath)
if resolved.len == 0 or not fileExists(resolved):
return
@@ -115,7 +118,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 = resolveImport(c, current.files[0].nimFile, importPath)
let resolved = resolveFile(c, current.files[0].nimFile, importPath)
if resolved.len == 0 or not fileExists(resolved):
return
@@ -137,171 +140,6 @@ 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)
@@ -323,27 +161,12 @@ proc readDepsFile(c: var DepContext; pair: FilePair; current: Node) =
if t.kind == ParLe:
let tag = pool.tags[t.tagId]
case tag
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.
of "import", "fromimport":
# Read import path
t = next(s)
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
# 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
# Handle path expression (could be ident, string, or infix like std/foo)
var importPath = ""
if t.kind == Ident:
@@ -361,11 +184,26 @@ 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:
if tag == "include":
processInclude(c, importPath, current)
else:
processImport(c, importPath, current)
# Skip to end of node
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
var depth = 1
while depth > 0:
t = next(s)
@@ -488,19 +326,10 @@ 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)
# Input: .nim file (expanded as argument) and .nif file (dependency)
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()

View File

@@ -13,7 +13,7 @@ import
ast, msgs, options, idents, lookups,
semdata, modulepaths, sigmatch, lineinfos,
modulegraphs, wordrecg
from std/strutils import `%`, startsWith, replace
from std/strutils import `%`, startsWith
from std/sequtils import addUnique
import std/[sets, tables, intsets]
@@ -304,9 +304,9 @@ proc myImportModule(c: PContext, n: var PNode, importStmtResult: PNode): PSym =
var prefix = ""
if realModule.constraint != nil: prefix = realModule.constraint.strVal & "; "
message(c.config, n.info, warnDeprecated, prefix & realModule.name.s & " is deprecated")
let moduleNameNorm = getModuleName(c.config, n).replace("\\", "/")
if belongsToStdlib(c.graph, result) and not startsWith(moduleNameNorm, stdPrefix) and
not startsWith(moduleNameNorm, "system/") and not startsWith(moduleNameNorm, "packages/"):
let moduleName = getModuleName(c.config, n)
if belongsToStdlib(c.graph, result) and not startsWith(moduleName, stdPrefix) and
not startsWith(moduleName, "system/") and not startsWith(moduleName, "packages/"):
message(c.config, n.info, warnStdPrefix, realModule.name.s)
proc suggestMod(n: PNode; s: PSym) =

View File

@@ -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, skTemp, skForVar:
of skVar, skLet, skResult:
r.kind = resExpr
let jsType = mapType(p):
if typ.isNil:

View File

@@ -216,10 +216,6 @@ proc newAsgnStmt(le, ri: PNode, info: TLineInfo): PNode =
result[0] = le
result[1] = ri
proc markInjectDestructors(s: PSym) {.inline.} =
backendEnsureMutable s
s.flagsImpl.incl sfInjectDestructors
proc makeClosure*(g: ModuleGraph; idgen: IdGenerator; prc: PSym; env: PNode; info: TLineInfo): PNode =
result = newNodeIT(nkClosure, info, prc.typ)
result.add(newSymNode(prc))
@@ -232,7 +228,7 @@ proc makeClosure*(g: ModuleGraph; idgen: IdGenerator; prc: PSym; env: PNode; inf
#if isClosureIterator(result.typ):
createTypeBoundOps(g, nil, result.typ, info, idgen)
if tfHasAsgn in result.typ.flags or optSeqDestructors in g.config.globalOptions:
markInjectDestructors(prc)
prc.incl sfInjectDestructors
template liftingHarmful(conf: ConfigRef; owner: PSym): bool =
## lambda lifting can be harmful for JS-like code generators.
@@ -244,7 +240,7 @@ proc createTypeBoundOpsLL(g: ModuleGraph; refType: PType; info: TLineInfo; idgen
createTypeBoundOps(g, nil, refType.elementType, info, idgen)
createTypeBoundOps(g, nil, refType, info, idgen)
if tfHasAsgn in refType.flags or optSeqDestructors in g.config.globalOptions:
markInjectDestructors(owner)
owner.incl sfInjectDestructors
proc genCreateEnv(env: PNode): PNode =
var c = newNodeIT(nkObjConstr, env.info, env.typ)
@@ -640,7 +636,7 @@ proc rawClosureCreation(owner: PSym;
if owner.kind != skMacro:
createTypeBoundOps(d.graph, nil, fieldAccess.typ, env.info, d.idgen)
if tfHasAsgn in fieldAccess.typ.flags or optSeqDestructors in d.graph.config.globalOptions:
markInjectDestructors(owner)
owner.incl sfInjectDestructors
let upField = lookupInRecord(env.typ.skipTypes({tyOwned, tyRef, tyPtr}).n, getIdent(d.graph.cache, upName))
if upField != nil:

View File

@@ -1349,7 +1349,7 @@ proc rawGetTok*(L: var Lexer, tok: var Token) =
lexMessage(L, errGenerated, "invalid token: no whitespace between number and identifier")
of '-':
if L.buf[L.bufpos+1] in {'0'..'9'} and
(L.bufpos == 0 or L.buf[L.bufpos-1] in UnaryMinusWhitelist):
(L.bufpos-1 == 0 or L.buf[L.bufpos-1] in UnaryMinusWhitelist):
# x)-23 # binary minus
# ,-23 # unary minus
# \n-78 # unary minus? Yes.

View File

@@ -592,12 +592,10 @@ proc setLenStrCall(c: var TLiftCtx; x, y: PNode): PNode =
result = genBuiltin(c, mSetLengthStr, "setLen", x) # genAddr(g, x))
result.add lenCall
proc setLenSeqCall(c: var TLiftCtx; t: PType; x, y: PNode; noinit = false): PNode =
proc setLenSeqCall(c: var TLiftCtx; t: PType; x, y: PNode): PNode =
let lenCall = genBuiltin(c, mLengthSeq, "len", y)
lenCall.typ = getSysType(c.g, x.info, tyInt)
let name = if noinit: "setLenUninit" else: "setLen"
let magic = if noinit: mSetLengthSeqUninit else: mSetLengthSeq
var op = getSysMagic(c.g, x.info, name, magic)
var op = getSysMagic(c.g, x.info, "setLen", mSetLengthSeq)
op = instantiateGeneric(c, op, t, t)
result = newTree(nkCall, newSymNode(op, x.info), x, lenCall)
@@ -645,9 +643,8 @@ proc genBulkCopySeq(c: var TLiftCtx; t: PType; body, x, y: PNode) =
proc fillSeqOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
case c.kind
of attachedDup:
let bulkCopy = supportsCopyMem(t.elementType)
body.add setLenSeqCall(c, t, x, y, noinit = bulkCopy)
if bulkCopy:
body.add setLenSeqCall(c, t, x, y)
if supportsCopyMem(t.elementType):
genBulkCopySeq(c, t, body, x, y)
else:
forallElements(c, t, body, x, y)
@@ -661,9 +658,8 @@ proc fillSeqOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
# This is usually more efficient than a destroy/create pair.
# For trivially copyable types, use bulk copyMem instead of element loop.
checkSelfAssignment(c, t, body, x, y)
let bulkCopy = supportsCopyMem(t.elementType)
body.add setLenSeqCall(c, t, x, y, noinit = bulkCopy)
if bulkCopy:
body.add setLenSeqCall(c, t, x, y)
if supportsCopyMem(t.elementType):
genBulkCopySeq(c, t, body, x, y)
else:
forallElements(c, t, body, x, y)
@@ -732,7 +728,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.usesSso():
if c.g.config.isDefined("nimsso"):
# 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

View File

@@ -12,8 +12,7 @@ define:nimPreviewNonVarDestructor
define:nimPreviewCheckedClose
define:nimPreviewAsmSemSymbol
define:nimPreviewCStringComparisons
#define:nimPreviewDuplicateModuleError
# Incompatible with Nimony's compat2.nim for now
define:nimPreviewDuplicateModuleError
threads:off

View File

@@ -121,11 +121,6 @@ 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())

View File

@@ -259,9 +259,6 @@ 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
@@ -270,10 +267,6 @@ 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
@@ -373,7 +366,6 @@ 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
@@ -706,7 +698,6 @@ 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

View File

@@ -582,7 +582,6 @@ proc put(g: var TSrcGen, kind: TokType, s: string; sym: PSym = nil) =
inc(g.lineLen, s.len)
proc putComment(g: var TSrcGen, s: string) =
const SpecialWhitespace = {' ', '\t', '\r', '\n', '\0'}
if s.len == 0: return
var i = 0
let hi = s.len - 1
@@ -612,12 +611,12 @@ proc putComment(g: var TSrcGen, s: string) =
# gets too long:
# compute length of the following word:
var j = i
while j <= hi and s[j] notin SpecialWhitespace: inc(j)
while j <= hi and s[j] > ' ': inc(j)
if not isCode and (g.col + (j - i) > MaxLineLen):
put(g, tkComment, com)
optNL(g, ind)
com = "## "
while i <= hi and s[i] notin SpecialWhitespace:
while i <= hi and s[i] > ' ':
com.add(s[i])
inc(i)
put(g, tkComment, com)

View File

@@ -131,7 +131,7 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode,
var sym = syms[0].s
let name = sym.name
var scope = syms[0].scope
c.openShadowScope
if allowTypeBoundOps:
for a in 1 ..< n.len:
# for every already typed argument, add type bound ops
@@ -218,10 +218,6 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode,
scope = syms[nextSymIndex].scope
inc(nextSymIndex)
if best.state == csMatch and best.calleeSym != nil and best.calleeSym.kind in {skTemplate, skMacro}:
c.closeShadowScope
else:
c.mergeShadowScope
proc effectProblem(f, a: PType; result: var string; c: PContext) =
if f.kind == tyProc and a.kind == tyProc:

View File

@@ -180,12 +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[(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
forwardTypeUpdates*: seq[(PType, PNode)]
# types that need to be updated in a type section
# due to containing forward types, and their corresponding nodes
inTypeofContext*: int
semAsgnOpr*: proc (c: PContext; n: PNode; k: TNodeKind): PNode {.nimcall.}
@@ -637,11 +634,6 @@ proc renderNotLValue*(n: PNode): string =
elif n.kind in {nkHiddenStdConv, nkHiddenSubConv} and n.len == 2:
result = typeToString(n.typ.skipTypes(abstractVar)) & "(" & result & ")"
proc isSsoStringIndex*(conf: ConfigRef; n: PNode): bool =
result = conf.usesSso() and n.kind == nkBracketExpr and n.len >= 1 and
n[0].typ != nil and
n[0].typ.skipTypes(abstractVar + abstractInst - {tyTypeDesc}).kind == tyString
proc isAssignable(c: PContext, n: PNode): TAssignableResult =
result = parampatterns.isAssignable(c.p.owner, n)
@@ -749,7 +741,7 @@ proc replaceHookMagic*(c: PContext, n: PNode, kind: TTypeAttachedOp): PNode =
case kind
of attachedDestructor:
result = n
let t = n[1].typ.skipTypes({tyAlias, tyVar, tySink})
let t = n[1].typ.skipTypes(abstractVar)
let op = getAttachedOp(c.graph, t, attachedDestructor)
if op != nil:
result[0] = newSymNode(op)
@@ -761,13 +753,13 @@ proc replaceHookMagic*(c: PContext, n: PNode, kind: TTypeAttachedOp): PNode =
result[1] = skipAddr(n[1])
of attachedTrace:
result = n
let t = n[1].typ.skipTypes({tyAlias, tyVar, tySink})
let t = n[1].typ.skipTypes(abstractVar)
let op = getAttachedOp(c.graph, t, attachedTrace)
if op != nil:
result[0] = newSymNode(op)
of attachedDup:
result = n
let t = n[1].typ.skipTypes({tyAlias, tyVar, tySink})
let t = n[1].typ.skipTypes(abstractVar)
let op = getAttachedOp(c.graph, t, attachedDup)
if op != nil:
result[0] = newSymNode(op)
@@ -777,26 +769,18 @@ proc replaceHookMagic*(c: PContext, n: PNode, kind: TTypeAttachedOp): PNode =
result.add boolLit
of attachedWasMoved:
result = n
let t = n[1].typ.skipTypes({tyAlias, tyVar, tySink})
let t = n[1].typ.skipTypes(abstractVar)
let op = getAttachedOp(c.graph, t, attachedWasMoved)
if op != nil:
result[0] = newSymNode(op)
analyseIfAddressTakenInCall(c, result, false)
of attachedSink:
result = n
let t = n[1].typ.skipTypes({tyAlias, tyVar, tySink})
let op = getAttachedOp(c.graph, t, kind)
if op != nil:
result[0] = newSymNode(op)
result = c.semAsgnOpr(c, n, nkSinkAsgn)
of attachedAsgn:
result = n
let t = n[1].typ.skipTypes({tyAlias, tyVar, tySink})
let op = getAttachedOp(c.graph, t, kind)
if op != nil:
result[0] = newSymNode(op)
result = c.semAsgnOpr(c, n, nkAsgn)
of attachedDeepCopy:
result = n
let t = n[1].typ.skipTypes({tyAlias, tyVar, tySink})
let t = n[1].typ.skipTypes(abstractVar)
let op = getAttachedOp(c.graph, t, kind)
if op != nil:
result[0] = newSymNode(op)

View File

@@ -652,9 +652,6 @@ 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:
@@ -683,15 +680,12 @@ 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
if not isViewTarget(f.typ):
changeType(c, n[i][1], f.typ, check)
changeType(c, n[i][1], f.typ, check)
else:
if not isViewTarget(tup[i]):
changeType(c, n[i][1], tup[i], check)
changeType(c, n[i][1], tup[i], check)
else:
for i in 0..<n.len:
if not isViewTarget(tup[i]):
changeType(c, n[i], tup[i], check)
changeType(c, n[i], tup[i], check)
when false:
var m = n[i]
var a = newNodeIT(nkExprColonExpr, m.info, newType[i])
@@ -714,7 +708,6 @@ 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 =
@@ -970,15 +963,12 @@ proc evalAtCompileTime(c: PContext, n: PNode): PNode =
# echo "SUCCESS evaluated at compile time: ", call.renderTree
proc semStaticExpr(c: PContext, n: PNode; expectedType: PType = nil): PNode =
let oldErrorCount = c.config.errorCounter
inc c.inStaticContext
openScope(c)
let a = semExprWithType(c, n, expectedType = expectedType)
closeScope(c)
dec c.inStaticContext
if a.findUnresolvedStatic != nil or
c.config.errorCounter != oldErrorCount:
return a
if a.findUnresolvedStatic != nil: return a
result = evalStaticExpr(c.module, c.idgen, c.graph, a, c.p.owner)
if result.isNil:
localError(c.config, n.info, errCannotInterpretNodeX % renderTree(n))

View File

@@ -35,9 +35,7 @@ proc semAddr(c: PContext; n: PNode): PNode =
let x = semExprWithType(c, n)
if x.kind == nkSym:
x.sym.flagsImpl.incl(sfAddrTaken)
let aa = isAssignable(c, x)
if aa notin {arLValue, arLocalLValue, arAddressableConst, arLentValue} and
(aa != arDiscriminant or c.inUncheckedAssignSection <= 0):
if isAssignable(c, x) notin {arLValue, arLocalLValue, arAddressableConst, arLentValue}:
localError(c.config, n.info, errExprHasNoAddress)
result.add x
result.typ = makePtrType(c, x.typ.skipTypes({tySink}))
@@ -248,13 +246,10 @@ 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(skippedTypes)
var arg = operand.skipTypes({tyGenericInst})
let rec = semConstExpr(c, traitCall[2]).intVal != 0
while true:
let distinctArg = arg.skipTypes(skippedTypes + {tyGenericInst})
if distinctArg.kind != tyDistinct:
break
arg = distinctArg.base.skipTypes(skippedTypes)
while arg.kind == tyDistinct:
arg = arg.base.skipTypes(skippedTypes + {tyGenericInst})
if not rec: break
result = getTypeDescNode(c, arg, operand.owner, traitCall.info)
of "rangeBase":
@@ -618,9 +613,9 @@ proc magicsAfterOverloadResolution(c: PContext, n: PNode,
of mAsgn:
case n[0].sym.name.s
of "=", "=copy":
result = replaceHookMagic(c, n, attachedAsgn)
result = semAsgnOpr(c, n, nkAsgn)
of "=sink":
result = replaceHookMagic(c, n, attachedSink)
result = semAsgnOpr(c, n, nkSinkAsgn)
else:
result = semShallowCopy(c, n, flags)
of mIsPartOf: result = semIsPartOf(c, n, flags)

View File

@@ -486,11 +486,6 @@ 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))

View File

@@ -809,10 +809,6 @@ proc trackOperandForIndirectCall(tracked: PEffects, n: PNode, formals: PType; ar
markSideEffect(tracked, a, n.info)
let paramType = if formals != nil and argIndex < formals.signatureLen: formals[argIndex] else: nil
if paramType != nil and paramType.kind in {tyVar}:
let arg = n.skipAddr()
if isSsoStringIndex(tracked.config, arg):
localError(tracked.config, arg.info,
"expression '$1' is immutable, not 'var'" % renderNotLValue(arg))
invalidateFacts(tracked.guards, n)
if n.kind == nkSym and isLocalSym(tracked, n.sym):
makeVolatile(tracked, n.sym)
@@ -1168,7 +1164,7 @@ proc trackCall(tracked: PEffects; n: PNode) =
var (isHook, opKind) = findHookKind(a.sym.name.s)
if isHook:
# rebind type bounds operations after createTypeBoundOps call
let t = n[1].typ.skipTypes({tyAlias, tyVar, tySink})
let t = n[1].typ.skipTypes({tyAlias, tyVar})
if a.sym != getAttachedOp(tracked.graph, t, opKind):
createTypeBoundOps(tracked, t, n.info, explicit = true)
# replace builtin hooks with lifted ones

View File

@@ -1808,35 +1808,15 @@ proc checkForMetaFields(c: PContext; n: PNode; hasError: var bool) =
internalAssert c.config, false
proc typeSectionFinalPass(c: PContext, n: PNode) =
# 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 = @[]
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 = @[]
for i in 0..<n.len:
var a = n[i]
if a.kind == nkCommentStmt: continue
@@ -2936,15 +2916,13 @@ proc semPragmaBlock(c: PContext, n: PNode; expectedType: PType = nil): PNode =
proc semStaticStmt(c: PContext, n: PNode): PNode =
#echo "semStaticStmt"
#writeStackTrace()
let oldErrorCount = c.config.errorCounter
inc c.inStaticContext
openScope(c)
let a = semStmt(c, n[0], {})
closeScope(c)
dec c.inStaticContext
n[0] = a
if c.config.errorCounter == oldErrorCount:
evalStaticStmt(c.module, c.idgen, c.graph, a, c.p.owner)
evalStaticStmt(c.module, c.idgen, c.graph, a, c.p.owner)
when false:
# for incremental replays, keep the AST as required for replays:
result = n

View File

@@ -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 (getCurrOwner(c), result, n)
c.forwardTypeUpdates.add (base, n[1])
elif not isOrdinalType(base, allowEnumWithHoles = true):
localError(c.config, n.info, errOrdinalTypeExpected % typeToString(base, preferDesc))
elif lengthOrd(c.config, base) > MaxSetElements:
@@ -318,62 +318,6 @@ proc fitDefaultNode(c: PContext, n: var PNode, expectedType: PType) =
typeAllowedCheck(c, n.info, n.typ, skConst, {taProcContextIsNotMacro, taIsDefaultField})
dec c.inStaticContext
proc containsForwardTypeAux(t: PType; seen: var IntSet): bool
proc containsForwardTypeAux(n: PNode; seen: var IntSet): bool =
result = false
if n.isNil or n.kind in nkLiterals + {nkNilLit, nkEmpty, nkType}:
return
if containsForwardTypeAux(n.typ, seen) or
(n.kind == nkSym and n.sym.typ != n.typ and containsForwardTypeAux(n.sym.typ, seen)):
return true
for i in 0 ..< n.safeLen:
if containsForwardTypeAux(n[i], seen):
return true
proc containsForwardTypeAux(t: PType; seen: var IntSet): bool =
result = false
if t.isNil:
return
if t.kind == tyForward:
return true
if not containsOrIncl(seen, t.id):
if containsForwardTypeAux(t.n, seen):
return true
for i in 0 ..< t.len:
if containsForwardTypeAux(t[i], seen):
return true
proc containsForwardType(arg: PNode): bool =
var seen = initIntSet()
containsForwardTypeAux(arg, seen)
proc containsForwardType(t: PType): bool =
var seen = initIntSet()
containsForwardTypeAux(t, seen)
proc semFieldDefault(c: PContext; owner, expectedType: PType; field: PNode): PType =
result = expectedType
field[^1] = semExprWithType(c, field[^1], {efDetermineType, efAllowSymChoice}, result)
if result == nil:
result = field[^1].typ
if c.inGenericContext == 0:
if containsForwardType(field[^1]):
c.forwardFieldUpdates.add (owner, field, result)
else:
fitDefaultNode(c, field[^1], result)
result = field[^1].typ.skipIntLit(c.idgen)
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))
proc isRecursiveType*(t: PType): bool =
# handle simple recusive types before typeFinalPass
var cycleDetector = initIntSet()
@@ -606,7 +550,13 @@ proc semTuple(c: PContext, n: PNode, prev: PType): PType =
var hasDefaultField = a[^1].kind != nkEmpty
if hasDefaultField:
typ = if a[^2].kind != nkEmpty: semTypeNode(c, a[^2], nil) else: nil
typ = semFieldDefault(c, result, typ, a)
if c.inGenericContext > 0:
a[^1] = semExprWithType(c, a[^1], {efDetermineType, efAllowSymChoice}, typ)
if typ == nil:
typ = a[^1].typ
else:
fitDefaultNode(c, a[^1], typ)
typ = a[^1].typ.skipIntLit(c.idgen)
elif a[^2].kind != nkEmpty:
typ = semTypeNode(c, a[^2], nil)
if c.graph.config.isDefined("nimPreviewRangeDefault") and typ.skipTypes(abstractInst).kind == tyRange:
@@ -972,7 +922,14 @@ proc semRecordNodeAux(c: PContext, n: PNode, check: var IntSet, pos: var int,
var hasDefaultField = n[^1].kind != nkEmpty
if hasDefaultField:
typ = if n[^2].kind != nkEmpty: semTypeNode(c, n[^2], nil) else: nil
typ = semFieldDefault(c, rectype, typ, n)
if c.inGenericContext > 0:
n[^1] = semExprWithType(c, n[^1], {efDetermineType, efAllowSymChoice}, typ)
if typ == nil:
typ = n[^1].typ
else:
fitDefaultNode(c, n[^1], typ)
typ = n[^1].typ.skipIntLit(c.idgen)
propagateToOwner(rectype, typ)
elif n[^2].kind == nkEmpty:
localError(c.config, n.info, errTypeExpected)
typ = errorType(c)
@@ -1115,7 +1072,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 (getCurrOwner(c), result, n) # we retry in the final pass
c.forwardTypeUpdates.add (result, n) # we retry in the final pass
rawAddSon(result, realBase)
if realBase == nil and tfInheritable in flags:
result.incl tfInheritable
@@ -1763,7 +1720,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 (getCurrOwner(c), result, n)
c.forwardTypeUpdates.add (result, n)
return
elif t.kind != tyGenericBody:
# we likely got code of the form TypeA[TypeB] where TypeA is
@@ -1816,14 +1773,10 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
localError(c.config, n.info, errCannotInstantiateX % s.name.s)
result = newOrPrevType(tyError, prev, c)
elif containsGenericInvocationWithForward(n[0]) or hasForwardTypeParam:
# isConcrete == false means this generic type is not instanciated here because
# it invoked with generic parameters.
# Even if isConcrete == true, don't instanciate it now if there are
# unresolved `tyForward` type params.
# Such `tyForward` type params will be semchecked later and we can
# instanciate this next time.
# Some generic types like std/options.Option[T] need the kind of the
# given type argument before their fields can be resolved.
# isConcrete == false means this generic type is not instanciated here because it invoked with generic parameters.
# Even if isConcrete == true, don't instanciate it now if there are any `tyForward` type params.
# Such `tyForward` type params will be semchecked later and we can instanciate this next time.
# Some generic types like std/options.Option[T] needs a type kinds of the given type argument.
# return `tyForward` instead of `tyGenericInvocation` because:
# ```nim
@@ -1839,7 +1792,7 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
else:
assignType(result, newTypeS(tyForward, c))
result.sym = s
c.forwardTypeUpdates.add (getCurrOwner(c), result, n) #fixes 1500
c.forwardTypeUpdates.add (result, n) #fixes 1500
return
else:
result = instGenericContainer(c, n.info, result,
@@ -2381,7 +2334,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
else:
result = typeExpr.typ.base
if result.isMetaType and
result.kind notin tyTypeClasses:
result.kind != tyUserTypeClass:
# the dot expression may refer to a concept type in
# a different module. allow a normal alias then.
let preprocessed = semGenericStmt(c, n)

View File

@@ -784,17 +784,6 @@ 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
@@ -2845,11 +2834,9 @@ proc findFirstArgBlock(m: var TCandidate, n: PNode): int =
else: break
proc matchesAux(c: PContext, n, nOrig: PNode, m: var TCandidate, marker: var IntSet) =
template noMatch() =
if m.calleeSym != nil and m.calleeSym.kind notin {skTemplate, skMacro}:
c.mergeShadowScope
else:
c.closeShadowScope
c.mergeShadowScope #merge so that we don't have to resem for later overloads
m.state = csNoMatch
m.firstMismatch.arg = a
m.firstMismatch.formal = formal

View File

@@ -10,7 +10,7 @@
## This module implements threadpool's ``spawn``.
import ast, types, idents, magicsys, msgs, options, modulegraphs,
lowerings, liftdestructors, renderer, trees
lowerings, liftdestructors, renderer
from trees import getMagic, getRoot
proc callProc(a: PNode): PNode =
@@ -53,24 +53,6 @@ proc typeNeedsNoDeepCopy(t: PType): bool =
if t.kind in {tyVar, tyLent, tySequence}: t = t.elementType
result = not containsGarbageCollectedRef(t)
proc newSpawnMoveStmt(g: ModuleGraph; idgen: IdGenerator; le, ri: PNode): PNode =
let op = getAttachedOp(g, ri.typ.skipTypes({tyGenericInst, tyAlias, tyVar, tySink}), attachedWasMoved)
if op != nil and sfOverridden in op.flags:
result = newNodeI(nkStmtList, le.info)
result.add newFastAsgnStmt(le, ri)
let wasMovedCall = newNodeI(nkCall, ri.info)
wasMovedCall.add newSymNode(op)
if op.typ != nil and op.typ.signatureLen > 1 and op.typ.firstParamType.kind != tyVar:
wasMovedCall.add ri.skipAddr
else:
wasMovedCall.add makeAddr(ri.skipAddr, idgen)
result.add wasMovedCall
else:
result = newFastMoveStmt(g, le, ri)
proc addLocalVar(g: ModuleGraph; varSection, varInit: PNode; idgen: IdGenerator; owner: PSym; typ: PType;
v: PNode; useShallowCopy=false): PSym =
result = newSym(skTemp, getIdent(g.cache, genPrefix), idgen, owner, varSection.info,
@@ -86,10 +68,10 @@ proc addLocalVar(g: ModuleGraph; varSection, varInit: PNode; idgen: IdGenerator;
if varInit != nil:
if g.config.selectedGC in {gcArc, gcOrc, gcAtomicArc, gcYrc}:
# inject destructors pass will do its own analysis
varInit.add newSpawnMoveStmt(g, idgen, newSymNode(result), v)
varInit.add newFastMoveStmt(g, newSymNode(result), v)
else:
if useShallowCopy and typeNeedsNoDeepCopy(typ) or optTinyRtti in g.config.globalOptions:
varInit.add newSpawnMoveStmt(g, idgen, newSymNode(result), v)
varInit.add newFastMoveStmt(g, newSymNode(result), v)
else:
let deepCopyCall = newNodeI(nkCall, varInit.info, 3)
deepCopyCall[0] = newSymNode(getSysMagic(g, varSection.info, "deepCopy", mDeepCopy))

View File

@@ -120,21 +120,18 @@ proc newAsgnStmt(c: PTransf, kind: TNodeKind, le: PNode, ri: PNode; isFirstWrite
proc resolveBorrowedRoutineSym(c: PTransf; s: PSym; info: TLineInfo): PSym =
# Follow borrow aliases to the underlying implementation symbol.
result = nil
var s = s
while true:
# Skips over all borrowed procs getting the last proc symbol without an implementation
# Skips over all borrowed procs getting the last proc symbol without an implementation.
let body = getBody(c.graph, s)
if body.kind == nkSym and sfBorrow in body.sym.flags and getBody(c.graph, body.sym).kind == nkSym:
s = body.sym
else:
break
let body = getBody(c.graph, s)
if body.kind == nkSym:
result = body.sym
else:
result = nil
internalError(c.graph.config, info, "wrong AST for borrowed symbol")
if body.kind != nkSym:
internalError(c.graph.config, info, "wrong AST for borrowed symbol")
return body.sym
internalError(c.graph.config, info, "wrong AST for borrowed symbol")
proc transformSymAux(c: PTransf, n: PNode): PNode =
let s = n.sym
@@ -336,7 +333,7 @@ proc introduceNewLocalVars(c: PTransf, n: PNode): PNode =
if a.kind == nkSym:
n[1] = transformSymAux(c, a)
return n
of nkLambdaKinds, nkProcDef, nkFuncDef, nkMethodDef, nkConverterDef: # todo optimize nosideeffects?
of nkProcDef, nkFuncDef, nkMethodDef, nkConverterDef: # todo optimize nosideeffects?
result = newTransNode(n)
let x = newSymNode(copySym(n[namePos].sym, c.idgen))
c.transCon.mapping[n[namePos].sym.itemId] = x
@@ -702,11 +699,6 @@ proc putArgInto(arg: PNode, formal: PType): TPutArgInto =
of nkAddr, nkHiddenAddr:
result = putArgInto(arg[0], formal)
if result == paViaIndirection: result = paFastAsgn
of nkHiddenStdConv, nkHiddenSubConv, nkConv:
if compareTypes(arg.typ, arg[1].typ, dcEqIgnoreDistinct, {IgnoreRangeShallow}):
result = putArgInto(arg[1], formal)
else:
result = paFastAsgn
of nkCurly, nkBracket:
for i in 0..<arg.len:
if putArgInto(arg[i], formal) != paDirectMapping:

View File

@@ -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, tyInferred})
let aliasSkipSet = maybeSkipRange({tyAlias})
var a = skipTypes(x, aliasSkipSet)
while a.kind == tyUserTypeClass and tfResolved in a.flags:
a = skipTypes(a.last, aliasSkipSet)

View File

@@ -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., `(.nif27)`)
- **Header** - Version information (e.g., `(.nif26)`)
- **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

View File

@@ -596,12 +596,12 @@ Regular expressions
* [re](re.html)
Procedures and operators for handling regular
expressions. The current implementation uses PCRE2.
expressions. The current implementation uses PCRE.
* [nre](nre.html)
Many help functions for handling regular expressions.
The current implementation uses PCRE2.
The current implementation uses PCRE.
Database support
----------------
@@ -661,9 +661,6 @@ Regular expressions
* [pcre](pcre.html)
Wrapper for the PCRE library.
* [pcre2](pcre2.html)
Wrapper for the PCRE2 library.
Database support
----------------

View File

@@ -34,10 +34,10 @@ To learn how to compile Nim programs and generate documentation see
the [Compiler User Guide](nimc.html) and the [DocGen Tools Guide](docgen.html).
The language constructs are explained using an extended BNF, in which `(a)*`
means 0 or more *a*'s, `a+` means 1 or more *a*'s, and `(a)?` means an
means 0 or more `a`'s, `a+` means 1 or more `a`'s, and `(a)?` means an
optional *a*. Parentheses may be used to group elements.
`&` is the lookahead operator; `&a` means that an *a* is expected but
`&` is the lookahead operator; `&a` means that an `a` is expected but
not consumed. It will be consumed in the following rule.
The `|`, `/` symbols are used to mark alternatives and have the lowest
@@ -1024,9 +1024,6 @@ 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
-------------
@@ -2177,10 +2174,6 @@ 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
@@ -8874,7 +8867,7 @@ Byref pragma
The `byref` pragma can be applied to an object or tuple type or a proc param.
When applied to a type it instructs the compiler to pass the type by reference
(hidden pointer) to procs. When applied to a param it will take precedence, even
if the type was marked as `bycopy`. When an `importc` type has a `byref` pragma or
if the the type was marked as `bycopy`. When an `importc` type has a `byref` pragma or
parameters are marked as `byref` in an `importc` proc, these params translate to pointers.
When an `importcpp` type has a `byref` pragma, these params translate to
C++ references `&`.

View File

@@ -86,7 +86,7 @@ That means you can always use only 1 such an option with logical OR, e.g.
Meaning of `^`:literal: and `$`:literal:
========================================
`nimgrep`:cmd: PCRE2 engine is run in a single-line mode so
`nimgrep`:cmd: PCRE engine is run in a single-line mode so
`^`:literal: matches the beginning of whole input *file* and
`$`:literal: matches the end of *file* (or whole input *string* for
options like `--filename`).
@@ -97,7 +97,7 @@ Add the `(?m)`:literal: modifier to the beginning of your pattern for
Examples
========
All examples below use default PCRE2 Regex patterns:
All examples below use default PCRE Regex patterns:
+ To search recursively in Nim files using style-insensitive identifiers:

View File

@@ -1144,7 +1144,7 @@ there is a difference between the `$` and `repr` outputs:
echo myCharacter, ":", repr(myCharacter)
# --> n:'n'
echo myString, ":", repr(myString)
# --> nim:"nim"
# --> nim:0x10fa8c050"nim"
echo myInteger, ":", repr(myInteger)
# --> 42:42
echo myFloat, ":", repr(myFloat)

View File

@@ -16,11 +16,10 @@ const
ChecksumsStableCommit = "0b8e46379c5bc1bf73d8b3011908389c60fb9b98" # 2.0.1
SatStableCommit = "e63eaea8baf00bed8bcd5a29ffd8823abb265b39"
NimonyStableCommit = "750aa47f2139fe5ad69f04b44428b752011fe873" # unversioned \
NimonyStableCommit = "bbfb21529845567c55b67d176354daef0e7d6c29" # 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"

View File

@@ -1559,8 +1559,6 @@ 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
@@ -1575,17 +1573,11 @@ 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})
@@ -1626,15 +1618,6 @@ 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}

View File

@@ -7,21 +7,16 @@
#
when defined(js):
{.error: "This library needs to be compiled with a c-like backend, and depends on PCRE2; See jsre for JS backend.".}
{.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.
## This compatibility module uses PCRE2.
##
## What is NRE?
## ============
##
## A regular expression library for Nim using PCRE2 to do the hard work.
## A regular expression library for Nim using PCRE to do the hard work.
##
## For documentation on how to write patterns, there exists `the official PCRE2
## For documentation on how to write patterns, there exists `the official PCRE
## pattern documentation
## <https://www.pcre.org/current/doc/html/pcre2pattern.html>`_. You can also
## <https://www.pcre.org/original/doc/html/pcrepattern.html>`_. You can also
## search the internet for a wide variety of third-party documentation and
## tools.
##
@@ -39,8 +34,10 @@ runnableExamples:
## Licencing
## ---------
##
## PCRE2 is distributed under a BSD-style licence.
## PCRE has `some additional terms`_ that you must agree to in order to use
## this module.
##
## .. _`some additional terms`: https://pcre.sourceforge.net/license.txt
runnableExamples:
import std/sugar
let vowels = re"[aeoui]"
@@ -64,7 +61,7 @@ runnableExamples:
assert find("uxabc", re"(?<=x|y)ab", start = 1).get.captures[-1] == "ab"
assert find("uxabc", re"ab", start = 3).isNone
import std/pcre2 as pcre
from std/pcre import nil
import nre/private/util
import std/tables
from std/strutils import `%`
@@ -80,19 +77,20 @@ type
RegexDesc* = object
pattern*: string
pcreObj: ptr pcre.Pcre ## not nil
pcreExtra: ptr pcre.ExtraData ## nil
captureNameToId: Table[string, int]
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
## to write a pattern, please see `the official PCRE2 pattern
## to write a pattern, please see `the official PCRE pattern
## documentation.
## <https://www.pcre.org/current/doc/html/pcre2pattern.html>`_
## <https://www.pcre.org/original/doc/html/pcrepattern.html>`_
##
## `captureCount: int`
## : the number of captures that the pattern has.
@@ -137,26 +135,26 @@ type
## NEL (next line, U+0085), LS (line separator, U+2028), and PS
## (paragraph separator, U+2029). For the 8-bit library, the last two
## are recognized only in UTF-8 mode.
## -- man pcre2pattern
## man pcre
##
## - `(*JAVASCRIPT_COMPAT)` - JavaScript compatibility
## - `(*NO_STUDY)` - turn off studying; study is enabled by default
##
## For more details on the leading option groups, see the `Option
## Setting <https://www.pcre.org/current/doc/html/pcre2syntax.html#SEC16>`_
## Setting <https://man7.org/linux/man-pages/man3/pcresyntax.3.html#OPTION_SETTING>`_
## and the `Newline
## Convention <https://www.pcre.org/current/doc/html/pcre2syntax.html#SEC17>`_
## sections of the `PCRE2 syntax
## manual <https://www.pcre.org/current/doc/html/pcre2syntax.html>`_.
## Convention <https://man7.org/linux/man-pages/man3/pcresyntax.3.html#NEWLINE_CONVENTION>`_
## sections of the `PCRE syntax
## manual <https://man7.org/linux/man-pages/man3/pcresyntax.3.html>`_.
##
## Some of these options are not part of a pattern and are converted by nre
## into PCRE2 flags. These include `NEVER_UTF`, `ANCHORED`,
## Some of these options are not part of PCRE and are converted by nre
## into PCRE flags. These include `NEVER_UTF`, `ANCHORED`,
## `DOLLAR_ENDONLY`, `FIRSTLINE`, `NO_AUTO_CAPTURE`,
## `JAVASCRIPT_COMPAT`, `U`, `NO_STUDY`. In other PCRE2 wrappers, you
## will need to pass these as separate flags to PCRE2.
## `JAVASCRIPT_COMPAT`, `U`, `NO_STUDY`. In other PCRE wrappers, you
## 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`
@@ -193,7 +191,7 @@ type
pattern*: Regex ## The regex doing the matching.
## Not nil.
str*: string ## The string that was matched against.
pcreMatchBounds: seq[HSlice[csize_t, csize_t]] ## First item is the bounds of the match
pcreMatchBounds: seq[HSlice[cint, cint]] ## First item is the bounds of the match
## Other items are the captures
## `a` is inclusive start, `b` is exclusive end
@@ -224,32 +222,38 @@ when defined(gcDestructors):
when defined(nimAllowNonVarDestructor) and defined(nimPreviewNonVarDestructor):
proc `=destroy`(pattern: RegexDesc) =
`=destroy`(pattern.pattern)
pcre.code_free(pattern.pcreObj)
pcre.free_substring(cast[cstring](pattern.pcreObj))
if pattern.pcreExtra != nil:
pcre.free_study(pattern.pcreExtra)
`=destroy`(pattern.captureNameToId)
else:
proc `=destroy`(pattern: var RegexDesc) =
`=destroy`(pattern.pattern)
pcre.code_free(pattern.pcreObj)
pcre.free_substring(cast[cstring](pattern.pcreObj))
if pattern.pcreExtra != nil:
pcre.free_study(pattern.pcreExtra)
`=destroy`(pattern.captureNameToId)
else:
proc destroyRegex(pattern: Regex) =
`=destroy`(pattern.pattern)
pcre.code_free(pattern.pcreObj)
pcre.free_substring(cast[cstring](pattern.pcreObj))
if pattern.pcreExtra != nil:
pcre.free_study(pattern.pcreExtra)
`=destroy`(pattern.captureNameToId)
proc getinfo[T](pattern: Regex, opt: uint32): T =
proc getinfo[T](pattern: Regex, opt: cint): T =
result = default(T)
let retcode = pcre.pattern_info(pattern.pcreObj, opt, addr result)
let retcode = pcre.fullinfo(pattern.pcreObj, pattern.pcreExtra, opt, addr result)
if retcode < 0:
# XXX Error message that doesn't expose implementation details
raise newException(FieldDefect, "Invalid getinfo for $1, errno $2" % [$opt, $retcode])
proc getNameToNumberTable(pattern: Regex): Table[string, int] =
let entryCount = getinfo[uint32](pattern, pcre.INFO_NAMECOUNT).int
let entrySize = getinfo[uint32](pattern, pcre.INFO_NAMEENTRYSIZE).int
let entryCount = getinfo[cint](pattern, pcre.INFO_NAMECOUNT)
let entrySize = getinfo[cint](pattern, pcre.INFO_NAMEENTRYSIZE)
let table = cast[ptr UncheckedArray[uint8]](
getinfo[pointer](pattern, pcre.INFO_NAMETABLE))
getinfo[int](pattern, pcre.INFO_NAMETABLE))
result = initTable[string, int]()
@@ -265,69 +269,61 @@ proc getNameToNumberTable(pattern: Regex): Table[string, int] =
result[name] = num
proc pcreErrorMessage(errorCode: cint): string =
var buffer: array[256, uint8]
let length = pcre.get_error_message(errorCode, addr buffer[0], buffer.len.csize_t)
if length >= 0:
result = newString(length)
if length > 0:
copyMem(addr result[0], addr buffer[0], length)
else:
result = $errorCode
proc jitCompile(pattern: ptr pcre.Pcre) =
var hasJit: cint = 0
if pcre.config(pcre.CONFIG_JIT, addr hasJit) == 0 and hasJit == 1:
discard pcre.jit_compile(pattern, pcre.JIT_COMPLETE.uint32)
proc initRegex(pattern: string, flags: uint32, study = true): Regex =
proc initRegex(pattern: string, flags: int, study = true): Regex =
when defined(gcDestructors):
result = Regex()
else:
new(result, destroyRegex)
result.pattern = pattern
var
errorCode: cint = 0
errOffset: csize_t = 0
var errorMsg: cstring = ""
var errOffset: cint = 0
result.pcreObj = pcre.compile(cast[ptr uint8](cstring(pattern)),
pattern.len.csize_t, flags, addr errorCode,
result.pcreObj = pcre.compile(cstring(pattern),
# better hope int is at least 4 bytes..
cint(flags), addr errorMsg,
addr errOffset, nil)
if result.pcreObj == nil:
# failed to compile
raise SyntaxError(msg: pcreErrorMessage(errorCode), pos: errOffset.int,
pattern: pattern)
raise SyntaxError(msg: $errorMsg, pos: errOffset, pattern: pattern)
if study:
jitCompile(result.pcreObj)
var options: cint = 0
var hasJit: cint = cint(0)
if pcre.config(pcre.CONFIG_JIT, addr hasJit) == 0:
if hasJit == 1'i32:
options = pcre.STUDY_JIT_COMPILE
result.pcreExtra = pcre.study(result.pcreObj, options, addr errorMsg)
if errorMsg != nil:
raise StudyError(msg: $errorMsg)
result.captureNameToId = result.getNameToNumberTable()
proc captureCount*(pattern: Regex): int =
return getinfo[uint32](pattern, pcre.INFO_CAPTURECOUNT).int
return getinfo[cint](pattern, pcre.INFO_CAPTURECOUNT)
proc captureNameId*(pattern: Regex): Table[string, int] =
return pattern.captureNameToId
proc matchesCrLf(pattern: Regex): bool =
let newline = getinfo[uint32](pattern, pcre.INFO_NEWLINE)
case newline
of pcre.NEWLINE_CRLF, pcre.NEWLINE_ANY, pcre.NEWLINE_ANYCRLF:
let flags = uint32(getinfo[culong](pattern, pcre.INFO_OPTIONS))
let newlineFlags = flags and (pcre.NEWLINE_CRLF or
pcre.NEWLINE_ANY or
pcre.NEWLINE_ANYCRLF)
if newlineFlags > 0u32:
return true
of pcre.NEWLINE_CR, pcre.NEWLINE_LF, pcre.NEWLINE_NUL:
return false
else:
discard
# get flags from build config
var confFlags: uint32 = 0
var confFlags: cint = cint(0)
if pcre.config(pcre.CONFIG_NEWLINE, addr confFlags) != 0:
assert(false, "CONFIG_NEWLINE apparently got screwed up")
case confFlags
of pcre.NEWLINE_CR, pcre.NEWLINE_LF, pcre.NEWLINE_NUL: return false
of pcre.NEWLINE_CRLF, pcre.NEWLINE_ANY, pcre.NEWLINE_ANYCRLF: return true
of 13: return false
of 10: return false
of (13 shl 8) or 10: return true
of -2: return true
of -1: return true
else: return false
@@ -337,9 +333,7 @@ func captures*(pattern: RegexMatch): Captures = return Captures(pattern)
func contains*(pattern: CaptureBounds, i: int): bool =
let pattern = RegexMatch(pattern)
let index = i + 1
index >= 0 and index < pattern.pcreMatchBounds.len and
pattern.pcreMatchBounds[index].a != pcre.UNSET
pattern.pcreMatchBounds[i + 1].a != -1
func contains*(pattern: Captures, i: int): bool =
i in CaptureBounds(pattern)
@@ -350,7 +344,7 @@ func `[]`*(pattern: CaptureBounds, i: int): HSlice[int, int] =
raise newException(IndexDefect, "Group '" & $i & "' was not captured")
let bounds = pattern.pcreMatchBounds[i + 1]
int(bounds.a) .. (int(bounds.b) - 1)
int(bounds.a)..int(bounds.b-1)
func `[]`*(pattern: Captures, i: int): string =
let pattern = RegexMatch(pattern)
@@ -438,7 +432,8 @@ proc `$`*(pattern: RegexMatch): string =
proc `==`*(a, b: Regex): bool =
if not a.isNil and not b.isNil:
return a.pattern == b.pattern and
a.pcreObj == b.pcreObj
a.pcreObj == b.pcreObj and
a.pcreExtra == b.pcreExtra
else:
return system.`==`(a, b)
@@ -453,7 +448,7 @@ const PcreOptions = {
"FIRSTLINE": pcre.FIRSTLINE,
"NO_AUTO_CAPTURE": pcre.NO_AUTO_CAPTURE,
"JAVASCRIPT_COMPAT": pcre.JAVASCRIPT_COMPAT,
"U": pcre.UTF or pcre.UCP
"U": pcre.UTF8 or pcre.UCP
}.toTable
# Options that are supported inside regular expressions themselves
@@ -503,63 +498,46 @@ proc extractOptions(pattern: string): tuple[pattern: string, flags: int, study:
proc re*(pattern: string): Regex =
let (pattern, flags, study) = extractOptions(pattern)
initRegex(pattern, cast[uint32](flags), study)
initRegex(pattern, flags, study)
func isInvalidUnicodeError(errorCode: cint): bool =
(errorCode <= pcre.ERROR_UTF8_ERR1 and errorCode >= pcre.ERROR_UTF8_ERR21) or
errorCode == pcre.ERROR_BADUTFOFFSET or
errorCode == pcre.ERROR_DFA_UINVALID_UTF
proc newMatchData(pattern: Regex): ptr pcre.MatchData =
result = pcre.match_data_create_from_pattern(pattern.pcreObj, nil)
if result == nil:
raise RegexInternalError(msg: "could not allocate PCRE2 match data")
proc matchImpl(str: string, pattern: Regex, start, endpos: int, options: uint32): Option[RegexMatch] =
proc matchImpl(str: string, pattern: Regex, start, endpos: int, flags: int): Option[RegexMatch] =
var myResult = RegexMatch(pattern: pattern, str: str)
myResult.pcreMatchBounds = newSeq[HSlice[csize_t, csize_t]](pattern.captureCount() + 1)
# See PCRE man pages.
# 2x capture count to make room for start-end pairs
# 1x capture count as slack space for PCRE
let vecsize = (pattern.captureCount() + 1) * 3
# div 2 because each element is 2 cints long
# plus 1 because we need the ceiling, not the floor
myResult.pcreMatchBounds = newSeq[HSlice[cint, cint]]((vecsize + 1) div 2)
myResult.pcreMatchBounds.setLen(vecsize div 3)
let strlen = if endpos == int.high: str.len else: endpos+1
doAssert(strlen <= str.len) # don't want buffer overflows
if start < 0 or start > strlen:
return none(RegexMatch)
let matchData = newMatchData(pattern)
defer: pcre.match_data_free(matchData)
let execRet = pcre.match(pattern.pcreObj,
cast[ptr uint8](cstring(str)),
strlen.csize_t,
start.csize_t,
options,
matchData,
nil)
let rawMatches = cast[ptr UncheckedArray[csize_t]](pcre.get_ovector_pointer(matchData))
let ovectorCount = min(myResult.pcreMatchBounds.len,
pcre.get_ovector_count(matchData).int)
for i in 0 ..< ovectorCount:
myResult.pcreMatchBounds[i] = rawMatches[i * 2] .. rawMatches[i * 2 + 1]
let execRet = pcre.exec(pattern.pcreObj,
pattern.pcreExtra,
cstring(str),
cint(strlen),
cint(start),
cint(flags),
cast[ptr cint](addr myResult.pcreMatchBounds[0]),
cint(vecsize))
if execRet >= 0:
return some(myResult)
if isInvalidUnicodeError(execRet):
let errorPos = if myResult.pcreMatchBounds.len > 0 and
myResult.pcreMatchBounds[0].a != pcre.UNSET:
myResult.pcreMatchBounds[0].a.int
case execRet:
of pcre.ERROR_NOMATCH:
return none(RegexMatch)
of pcre.ERROR_NULL:
raise newException(AccessViolationDefect, "Expected non-null parameters")
of pcre.ERROR_BADOPTION:
raise RegexInternalError(msg: "Unknown pattern flag. Either a bug or " &
"outdated PCRE.")
of pcre.ERROR_BADUTF8, pcre.ERROR_SHORTUTF8, pcre.ERROR_BADUTF8_OFFSET:
raise InvalidUnicodeError(msg: "Invalid unicode byte sequence",
pos: myResult.pcreMatchBounds[0].a)
else:
start
raise InvalidUnicodeError(msg: "Invalid unicode byte sequence", pos: errorPos)
case execRet
of pcre.ERROR_NOMATCH:
return none(RegexMatch)
of pcre.ERROR_NULL:
raise newException(AccessViolationDefect, "Expected non-null parameters")
of pcre.ERROR_BADOPTION:
raise RegexInternalError(msg: "Unknown pattern flag. Either a bug or " &
"outdated PCRE2.")
else:
raise RegexInternalError(msg: "Unknown internal error: " & $execRet)
raise RegexInternalError(msg: "Unknown internal error: " & $execRet)
proc 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
@@ -576,7 +554,7 @@ proc match*(str: string, pattern: Regex, start = 0, endpos = int.high): Option[R
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
return str.matchImpl(pattern, start, endpos, cast[uint32](pcre.ANCHORED))
return str.matchImpl(pattern, start, endpos, pcre.ANCHORED)
iterator findIter*(str: string, pattern: Regex, start = 0, endpos = int.high): RegexMatch =
## Works the same as `find(...)<#find,string,Regex,int>`_, but finds every
@@ -590,21 +568,21 @@ iterator findIter*(str: string, pattern: Regex, start = 0, endpos = int.high): R
## Variants:
##
## - `proc findAll(...)` returns a `seq[string]`
# see pcre2demo for explanation => https://www.pcre.org/current/doc/html/pcre2demo.html
# see pcredemo for explanation => https://www.pcre.org/original/doc/html/pcredemo.html
let matchesCrLf = pattern.matchesCrLf()
let unicode = uint32(getinfo[uint32](pattern, pcre.INFO_ALLOPTIONS) and
pcre.UTF.uint32) > 0u32
let unicode = uint32(getinfo[culong](pattern, pcre.INFO_OPTIONS) and
pcre.UTF8) > 0u32
let strlen = if endpos == int.high: str.len else: endpos+1
var offset = start
var match: Option[RegexMatch] = default(Option[RegexMatch])
var neverMatched = true
while true:
var flags = 0'u32
var flags = 0
if match.isSome and
match.get.matchBounds.a > match.get.matchBounds.b:
# 0-len match
flags = pcre.NOTEMPTY_ATSTART.uint32
flags = pcre.NOTEMPTY_ATSTART
match = str.matchImpl(pattern, offset, endpos, flags)
if match.isNone:
@@ -640,7 +618,7 @@ proc find*(str: string, pattern: Regex, start = 0, endpos = int.high): Option[Re
## `endpos`
## : The maximum index for a match; `int.high` means the end of the
## string, otherwise its an inclusive upper bound.
return str.matchImpl(pattern, start, endpos, 0'u32)
return str.matchImpl(pattern, start, endpos, 0)
proc findAll*(str: string, pattern: Regex, start = 0, endpos = int.high): seq[string] =
result = @[]

View File

@@ -8,25 +8,23 @@
#
when defined(js):
{.error: "This library needs to be compiled with a c-like backend, and depends on PCRE2; See jsre for JS backend.".}
{.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).
## This compatibility module uses PCRE2.
##
## Regular expression support for Nim.
##
## This module is implemented by providing a wrapper around the
## `PCRE2 (Perl-Compatible Regular Expressions) <https://www.pcre.org>`_
## C library. This means that your application will depend on the PCRE2
## `PCRE (Perl-Compatible Regular Expressions) <https://www.pcre.org>`_
## C library. This means that your application will depend on the PCRE
## library's licence when using this module, which should not be a problem
## though.
##
## .. note:: There are also alternative nimble packages such as [tinyre](https://github.com/khchen/tinyre)
## and [regex](https://github.com/nitely/nim-regex).
##
## PCRE2 is distributed under a BSD-style licence.
## PCRE's licence follows:
##
## .. include:: ../../doc/regexprs.txt
##
runnableExamples:
## Unless specified otherwise, `start` parameter in each proc indicates
@@ -38,7 +36,7 @@ runnableExamples:
# can't match start of string since we're starting at 1
import
std/[pcre2, strutils]
std/[pcre, strutils, rtarrays]
when defined(nimPreviewSlimSystem):
import std/syncio
@@ -58,7 +56,8 @@ type
## expression will be used only once)
RegexDesc = object
h: ptr pcre2.Pcre
h: ptr Pcre
e: ptr ExtraData
Regex* = ref RegexDesc ## a compiled regular expression
@@ -68,10 +67,14 @@ type
when defined(gcDestructors):
when defined(nimAllowNonVarDestructor):
proc `=destroy`(x: RegexDesc) =
pcre2.code_free(x.h)
pcre.free_substring(cast[cstring](x.h))
if not isNil(x.e):
pcre.free_study(x.e)
else:
proc `=destroy`(x: var RegexDesc) =
pcre2.code_free(x.h)
pcre.free_substring(cast[cstring](x.h))
if not isNil(x.e):
pcre.free_study(x.e)
proc raiseInvalidRegex(msg: string) {.noinline, noreturn.} =
var e: ref RegexError
@@ -79,43 +82,21 @@ proc raiseInvalidRegex(msg: string) {.noinline, noreturn.} =
e.msg = msg
raise e
proc pcre2ErrorMessage(errorCode: cint): string =
var buffer: array[256, uint8]
let length = pcre2.get_error_message(errorCode, addr buffer[0], buffer.len.csize_t)
if length >= 0:
result = newString(length)
if length > 0:
copyMem(addr result[0], addr buffer[0], length)
else:
result = $errorCode
proc rawCompile(pattern: string, options: uint32): ptr pcre2.Pcre =
proc rawCompile(pattern: string, flags: cint): ptr Pcre =
var
errorCode: cint = 0
offset: csize_t = 0
result = pcre2.compile(cast[ptr uint8](pattern.cstring), pattern.len.csize_t,
options, addr errorCode, addr offset, nil)
msg: cstring = ""
offset: cint = 0
result = pcre.compile(pattern, flags, addr(msg), addr(offset), nil)
if result == nil:
raiseInvalidRegex(pcre2ErrorMessage(errorCode) & "\n" & pattern & "\n" &
spaces(offset.int) & "^\n")
raiseInvalidRegex($msg & "\n" & pattern & "\n" & spaces(offset) & "^\n")
proc finalizeRegEx(x: Regex) =
pcre2.code_free(x.h)
func toPcre2Options(flags: set[RegexFlag]): uint32 =
if reIgnoreCase in flags:
result = result or pcre2.CASELESS.uint32
if reMultiLine in flags:
result = result or pcre2.MULTILINE.uint32
if reDotAll in flags:
result = result or pcre2.DOTALL.uint32
if reExtended in flags:
result = result or pcre2.EXTENDED.uint32
proc jitCompile(pattern: ptr pcre2.Pcre) =
var hasJit: cint = 0
if pcre2.config(pcre2.CONFIG_JIT, addr hasJit) == 0 and hasJit == 1:
discard pcre2.jit_compile(pattern, pcre2.JIT_COMPLETE.uint32)
# XXX This is a hack, but PCRE does not export its "free" function properly.
# Sigh. The hack relies on PCRE's implementation (see `pcre_get.c`).
# Fortunately the implementation is unlikely to change.
pcre.free_substring(cast[cstring](x.h))
if not isNil(x.e):
pcre.free_study(x.e)
proc re*(s: string, flags = {reStudy}): Regex =
## Constructor of regular expressions.
@@ -131,9 +112,16 @@ proc re*(s: string, flags = {reStudy}): Regex =
result = Regex()
else:
new(result, finalizeRegEx)
result.h = rawCompile(s, toPcre2Options(flags))
result.h = rawCompile(s, cast[cint](flags - {reStudy}))
if reStudy in flags:
jitCompile(result.h)
var msg: cstring = ""
var options: cint = 0
var hasJit: cint = 0
if pcre.config(pcre.CONFIG_JIT, addr hasJit) == 0:
if hasJit == 1'i32:
options = pcre.STUDY_JIT_COMPILE
result.e = pcre.study(result.h, options, addr msg)
if not isNil(msg): raiseInvalidRegex($msg)
proc rex*(s: string, flags = {reStudy, reExtended}): Regex =
## Constructor for extended regular expressions.
@@ -150,58 +138,25 @@ proc bufSubstr(b: cstring, sPos, ePos: int): string {.inline.} =
copyMem(addr(result[0]), unsafeAddr(b[sPos]), sz)
result.setLen(sz)
proc newMatchData(slots: int): ptr pcre2.MatchData =
result = pcre2.match_data_create(max(slots, 1).uint32, nil)
if result == nil:
raiseInvalidRegex("could not allocate PCRE2 match data")
template ovector(matchData: ptr pcre2.MatchData): ptr UncheckedArray[csize_t] =
cast[ptr UncheckedArray[csize_t]](pcre2.get_ovector_pointer(matchData))
proc rawMatch(buf: cstring, pattern: Regex, start, bufSize: int,
options: uint32, matchData: ptr pcre2.MatchData): cint =
if start < 0 or bufSize < 0:
return pcre2.ERROR_BADOFFSET
pcre2.match(pattern.h, cast[ptr uint8](buf), bufSize.csize_t,
start.csize_t, options, matchData, nil)
proc copyStringMatches(buf: cstring, rawMatches: ptr UncheckedArray[csize_t],
captureCount: int, matches: var openArray[string]) =
let upper = min(captureCount - 1, matches.len)
if upper > 0:
for i in 1 .. upper:
let matchStart = rawMatches[i * 2]
let matchEnd = rawMatches[i * 2 + 1]
if matchStart != pcre2.UNSET:
matches[i-1] = bufSubstr(buf, int(matchStart), int(matchEnd))
else:
matches[i-1] = ""
proc copyBoundsMatches(rawMatches: ptr UncheckedArray[csize_t],
captureCount: int,
matches: var openArray[tuple[first, last: int]]) =
let upper = min(captureCount - 1, matches.len)
if upper > 0:
for i in 1 .. upper:
let matchStart = rawMatches[i * 2]
let matchEnd = rawMatches[i * 2 + 1]
if matchStart != pcre2.UNSET:
matches[i-1] = (int(matchStart), int(matchEnd) - 1)
else:
matches[i-1] = (-1, 0)
proc matchOrFind(buf: cstring, pattern: Regex, matches: var openArray[string],
start, bufSize: int, options: uint32): int =
let matchData = newMatchData(matches.len + 1)
defer: pcre2.match_data_free(matchData)
let res = rawMatch(buf, pattern, start, bufSize, options, matchData)
let rawMatches = ovector(matchData)
if res < 0: return int(res)
copyStringMatches(buf, rawMatches, int(res), matches)
return int(rawMatches[1]) - int(rawMatches[0])
start, bufSize, flags: cint): cint =
var
rtarray = initRtArray[cint]((matches.len+1)*3)
rawMatches = rtarray.getRawData
res = pcre.exec(pattern.h, pattern.e, buf, bufSize, start, flags,
cast[ptr cint](rawMatches), (matches.len+1).cint*3)
if res < 0'i32: return res
for i in 1..int(res)-1:
var a = rawMatches[i * 2]
var b = rawMatches[i * 2 + 1]
if a >= 0'i32:
matches[i-1] = bufSubstr(buf, int(a), int(b))
else: matches[i-1] = ""
return rawMatches[1] - rawMatches[0]
const MaxReBufSize* = high(int)
## Maximum PCRE2 buffer start/size accepted by this Nim API.
const MaxReBufSize* = high(cint)
## Maximum PCRE (API 1) buffer start/size equal to `high(cint)`, which even
## for 64-bit systems can be either 2`31`:sup:-1 or 2`63`:sup:-1.
proc findBounds*(buf: cstring, pattern: Regex, matches: var openArray[string],
start = 0, bufSize: int): tuple[first, last: int] =
@@ -213,12 +168,17 @@ proc findBounds*(buf: cstring, pattern: Regex, matches: var openArray[string],
##
## Note: The memory for `matches` needs to be allocated before this function is
## called, otherwise it will just remain empty.
let matchData = newMatchData(matches.len + 1)
defer: pcre2.match_data_free(matchData)
let res = rawMatch(buf, pattern, start, bufSize, 0'u32, matchData)
let rawMatches = ovector(matchData)
if res < 0: return (-1, 0)
copyStringMatches(buf, rawMatches, int(res), matches)
var
rtarray = initRtArray[cint]((matches.len+1)*3)
rawMatches = rtarray.getRawData
res = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, start.cint, 0'i32,
cast[ptr cint](rawMatches), (matches.len+1).cint*3)
if res < 0'i32: return (-1, 0)
for i in 1..int(res)-1:
var a = rawMatches[i * 2]
var b = rawMatches[i * 2 + 1]
if a >= 0'i32: matches[i-1] = bufSubstr(buf, int(a), int(b))
else: matches[i-1] = ""
return (rawMatches[0].int, rawMatches[1].int - 1)
proc findBounds*(s: string, pattern: Regex, matches: var openArray[string],
@@ -248,12 +208,17 @@ proc findBounds*(buf: cstring, pattern: Regex,
## `(-1,0)` is returned.
##
## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty.
let matchData = newMatchData(matches.len + 1)
defer: pcre2.match_data_free(matchData)
let res = rawMatch(buf, pattern, start, bufSize, 0'u32, matchData)
let rawMatches = ovector(matchData)
if res < 0: return (-1, 0)
copyBoundsMatches(rawMatches, int(res), matches)
var
rtarray = initRtArray[cint]((matches.len+1)*3)
rawMatches = rtarray.getRawData
res = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, start.cint, 0'i32,
cast[ptr cint](rawMatches), (matches.len+1).cint*3)
if res < 0'i32: return (-1, 0)
for i in 1..int(res)-1:
var a = rawMatches[i * 2]
var b = rawMatches[i * 2 + 1]
if a >= 0'i32: matches[i-1] = (int(a), int(b)-1)
else: matches[i-1] = (-1,0)
return (rawMatches[0].int, rawMatches[1].int - 1)
proc findBounds*(s: string, pattern: Regex,
@@ -275,28 +240,29 @@ proc findBounds*(s: string, pattern: Regex,
min(start, MaxReBufSize), min(s.len, MaxReBufSize))
proc findBoundsImpl(buf: cstring, pattern: Regex,
start = 0, bufSize = 0,
options = 0'u32): tuple[first, last: int] =
let matchData = newMatchData(1)
defer: pcre2.match_data_free(matchData)
let res = rawMatch(buf, pattern, start, bufSize, options, matchData)
let rawMatches = ovector(matchData)
if res < 0:
start = 0, bufSize = 0, flags = 0): tuple[first, last: int] =
var rtarray = initRtArray[cint](3)
let rawMatches = rtarray.getRawData
let res = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, start.cint, flags.int32,
cast[ptr cint](rawMatches), 3)
if res < 0'i32:
result = (-1, 0)
else:
result = (int(rawMatches[0]), int(rawMatches[1]) - 1)
result = (int(rawMatches[0]), int(rawMatches[1]-1))
proc findBounds*(buf: cstring, pattern: Regex,
start = 0, bufSize: int): tuple[first, last: int] =
## returns the `first` and `last` position of `pattern` in `buf`,
## where `buf` has length `bufSize` (not necessarily `'\0'` terminated).
## If it does not match, `(-1,0)` is returned.
let matchData = newMatchData(1)
defer: pcre2.match_data_free(matchData)
let res = rawMatch(buf, pattern, start, bufSize, 0'u32, matchData)
let rawMatches = ovector(matchData)
if res < 0: return (int(res), 0)
return (int(rawMatches[0]), int(rawMatches[1]) - 1)
var
rtarray = initRtArray[cint](3)
rawMatches = rtarray.getRawData
res = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, start.cint, 0'i32,
cast[ptr cint](rawMatches), 3)
if res < 0'i32: return (int(res), 0)
return (int(rawMatches[0]), int(rawMatches[1]-1))
proc findBounds*(s: string, pattern: Regex,
start = 0): tuple[first, last: int] {.inline.} =
@@ -309,16 +275,14 @@ proc findBounds*(s: string, pattern: Regex,
result = findBounds(cstring(s), pattern,
min(start, MaxReBufSize), min(s.len, MaxReBufSize))
proc matchOrFind(buf: cstring, pattern: Regex, start, bufSize: int,
options: uint32): int =
let matchData = newMatchData(1)
defer: pcre2.match_data_free(matchData)
let res = rawMatch(buf, pattern, start, bufSize, options, matchData)
if res >= 0:
let rawMatches = ovector(matchData)
result = int(rawMatches[1]) - int(rawMatches[0])
else:
result = int(res)
proc matchOrFind(buf: cstring, pattern: Regex, start, bufSize: int, flags: cint): cint =
var
rtarray = initRtArray[cint](3)
rawMatches = rtarray.getRawData
result = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, start.cint, flags,
cast[ptr cint](rawMatches), 3)
if result >= 0'i32:
result = rawMatches[1] - rawMatches[0]
proc matchLen*(s: string, pattern: Regex, matches: var openArray[string],
start = 0): int {.inline.} =
@@ -327,7 +291,7 @@ proc matchLen*(s: string, pattern: Regex, matches: var openArray[string],
## of zero can happen.
##
## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty.
result = matchOrFind(cstring(s), pattern, matches, start, s.len, cast[uint32](pcre2.ANCHORED))
result = matchOrFind(cstring(s), pattern, matches, start.cint, s.len.cint, pcre.ANCHORED)
proc matchLen*(buf: cstring, pattern: Regex, matches: var openArray[string],
start = 0, bufSize: int): int {.inline.} =
@@ -336,7 +300,7 @@ proc matchLen*(buf: cstring, pattern: Regex, matches: var openArray[string],
## of zero can happen.
##
## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty.
return matchOrFind(buf, pattern, matches, start, bufSize, cast[uint32](pcre2.ANCHORED))
return matchOrFind(buf, pattern, matches, start.cint, bufSize.cint, pcre.ANCHORED)
proc matchLen*(s: string, pattern: Regex, start = 0): int {.inline.} =
## the same as `match`, but it returns the length of the match,
@@ -347,13 +311,13 @@ proc matchLen*(s: string, pattern: Regex, start = 0): int {.inline.} =
doAssert matchLen("abcdefg", re"cde", 2) == 3
doAssert matchLen("abcdefg", re"abcde") == 5
doAssert matchLen("abcdefg", re"cde") == -1
result = matchOrFind(cstring(s), pattern, start, s.len, cast[uint32](pcre2.ANCHORED))
result = matchOrFind(cstring(s), pattern, start.cint, s.len.cint, pcre.ANCHORED)
proc matchLen*(buf: cstring, pattern: Regex, start = 0, bufSize: int): int {.inline.} =
## the same as `match`, but it returns the length of the match,
## if there is no match, `-1` is returned. Note that a match length
## of zero can happen.
result = matchOrFind(buf, pattern, start, bufSize, cast[uint32](pcre2.ANCHORED))
result = matchOrFind(buf, pattern, start.cint, bufSize, pcre.ANCHORED)
proc match*(s: string, pattern: Regex, start = 0): bool {.inline.} =
## returns `true` if `s[start..]` matches the `pattern`.
@@ -393,13 +357,18 @@ proc find*(buf: cstring, pattern: Regex, matches: var openArray[string],
## `buf` has length `bufSize` (not necessarily `'\0'` terminated).
##
## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty.
let matchData = newMatchData(matches.len + 1)
defer: pcre2.match_data_free(matchData)
let res = rawMatch(buf, pattern, start, bufSize, 0'u32, matchData)
let rawMatches = ovector(matchData)
if res < 0: return int(res)
copyStringMatches(buf, rawMatches, int(res), matches)
return int(rawMatches[0])
var
rtarray = initRtArray[cint]((matches.len+1)*3)
rawMatches = rtarray.getRawData
res = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, start.cint, 0'i32,
cast[ptr cint](rawMatches), (matches.len+1).cint*3)
if res < 0'i32: return res
for i in 1..int(res)-1:
var a = rawMatches[i * 2]
var b = rawMatches[i * 2 + 1]
if a >= 0'i32: matches[i-1] = bufSubstr(buf, int(a), int(b))
else: matches[i-1] = ""
return rawMatches[0]
proc find*(s: string, pattern: Regex, matches: var openArray[string],
start = 0): int {.inline.} =
@@ -414,12 +383,13 @@ proc find*(buf: cstring, pattern: Regex, start = 0, bufSize: int): int =
## returns the starting position of `pattern` in `buf`,
## where `buf` has length `bufSize` (not necessarily `'\0'` terminated).
## If it does not match, `-1` is returned.
let matchData = newMatchData(1)
defer: pcre2.match_data_free(matchData)
let res = rawMatch(buf, pattern, start, bufSize, 0'u32, matchData)
let rawMatches = ovector(matchData)
if res < 0: return int(res)
return int(rawMatches[0])
var
rtarray = initRtArray[cint](3)
rawMatches = rtarray.getRawData
res = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, start.cint, 0'i32,
cast[ptr cint](rawMatches), 3)
if res < 0'i32: return res
return rawMatches[0]
proc find*(s: string, pattern: Regex, start = 0): int {.inline.} =
## returns the starting position of `pattern` in `s`. If it does not
@@ -439,38 +409,40 @@ iterator findAll*(s: string, pattern: Regex, start = 0): string =
##
## Note that since this is an iterator you should not modify the string you
## are iterating over: bad things could happen.
var i = start
let matchData = newMatchData(1)
defer: pcre2.match_data_free(matchData)
var
i = int32(start)
rtarray = initRtArray[cint](3)
rawMatches = rtarray.getRawData
while true:
let res = rawMatch(s.cstring, pattern, i, len(s), 0'u32, matchData)
if res < 0: break
let rawMatches = ovector(matchData)
let matchStart = rawMatches[0]
let matchEnd = rawMatches[1]
if matchStart == matchEnd and matchStart.int == i: break
yield substr(s, int(matchStart), int(matchEnd) - 1)
i = matchEnd.int
let res = pcre.exec(pattern.h, pattern.e, s, len(s).cint, i, 0'i32,
cast[ptr cint](rawMatches), 3)
if res < 0'i32: break
let a = rawMatches[0]
let b = rawMatches[1]
if a == b and a == i: break
yield substr(s, int(a), int(b)-1)
i = b
iterator findAll*(buf: cstring, pattern: Regex, start = 0, bufSize: int): string =
## Yields all matching `substrings` of `s` that match `pattern`.
##
## Note that since this is an iterator you should not modify the string you
## are iterating over: bad things could happen.
var i = start
let matchData = newMatchData(1)
defer: pcre2.match_data_free(matchData)
var
i = int32(start)
rtarray = initRtArray[cint](3)
rawMatches = rtarray.getRawData
while true:
let res = rawMatch(buf, pattern, i, bufSize, 0'u32, matchData)
if res < 0: break
let rawMatches = ovector(matchData)
let matchStart = rawMatches[0]
let matchEnd = rawMatches[1]
if matchStart == matchEnd and matchStart.int == i: break
var str = newString(int(matchEnd - matchStart))
copyMem(str[0].addr, unsafeAddr(buf[int(matchStart)]), int(matchEnd - matchStart))
let res = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, i, 0'i32,
cast[ptr cint](rawMatches), 3)
if res < 0'i32: break
let a = rawMatches[0]
let b = rawMatches[1]
if a == b and a == i: break
var str = newString(b-a)
copyMem(str[0].addr, unsafeAddr(buf[a]), b-a)
yield str
i = matchEnd.int
i = b
proc findAll*(s: string, pattern: Regex, start = 0): seq[string] {.inline.} =
## returns all matching `substrings` of `s` that match `pattern`.
@@ -527,7 +499,7 @@ proc replace*(s: string, sub: Regex, by = ""): string =
doAssert "var1=key; var2=key2".replace(re"(\w+)=(\w+)", "?") == "?; ?"
result = ""
var prev = 0
var flags = 0'u32
var flags = int32(0)
while prev < s.len:
var match = findBoundsImpl(s.cstring, sub, prev, s.len, flags)
flags = 0
@@ -536,7 +508,7 @@ proc replace*(s: string, sub: Regex, by = ""): string =
add(result, by)
if match.first > match.last:
# 0-len match
flags = pcre2.NOTEMPTY_ATSTART.uint32
flags = pcre.NOTEMPTY_ATSTART
prev = match.last + 1
add(result, substr(s, prev))

View File

@@ -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)
if i < protocol.len: inc i # Skip .
i.inc # Skip .
i.inc protocol.parseSaturatedNatural(result.minor, i)
proc sendStatus(client: AsyncSocket, status: string): Future[void] =

View File

@@ -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.

View File

@@ -495,16 +495,13 @@ func `$`*[T](c: CritBitTree[T]): string =
const avgItemLen = 16
result = newStringOfCap(c.count * avgItemLen)
result.add("{")
var first = true
when T is void:
for key in keys(c):
if first: first = false
else: result.add(", ")
if result.len > 1: result.add(", ")
result.addQuoted(key)
else:
for key, val in pairs(c):
if first: first = false
else: result.add(", ")
if result.len > 1: result.add(", ")
result.addQuoted(key)
result.add(": ")
result.addQuoted(val)

View File

@@ -454,10 +454,8 @@ proc `$`*[T](deq: Deque[T]): string =
assert $a == "[10, 20, 30]"
result = "["
var first = true
for x in deq:
if first: first = false
else: result.add(", ")
if result.len > 1: result.add(", ")
result.addQuoted(x)
result.add("]")

View File

@@ -260,9 +260,7 @@ proc `$`*[T](heap: HeapQueue[T]): string =
assert $heap == "[1, 2]"
result = "["
var first = true
for x in heap.data:
if first: first = false
else: result.add(", ")
if result.len > 1: result.add(", ")
result.addQuoted(x)
result.add("]")

View File

@@ -304,10 +304,8 @@ proc `$`*[T](L: SomeLinkedCollection[T]): string =
assert $a == "[1, 2, 3, 4]"
result = "["
var first = true
for x in nodes(L):
if first: first = false
else: result.add(", ")
if result.len > 1: result.add(", ")
result.addQuoted(x.value)
result.add("]")

View File

@@ -588,9 +588,7 @@ proc handleShortOption(p: var OptParser; cmd: string) =
template next(): untyped = p.cmds[p.idx + 1]
let canTakeVal = card(p.shortNoVal) > 0 and
p.key.len > 0 and p.key[0] notin p.shortNoVal
let canTakeVal = card(p.shortNoVal) > 0 and p.key[0] notin p.shortNoVal
if i < cmd.len and cmd[i] in p.separators:
# separator case
if prShortAllowSep in p.rules:

View File

@@ -1668,10 +1668,7 @@ func getSymbol(c: var PegLexer, tok: var Token) =
while pos < c.buf.len:
add(tok.literal, c.buf[pos])
inc(pos)
if pos < c.buf.len:
let ch = c.buf[pos]
# Keep non-ASCII bytes so UTF-8 terminals reach the rune-aware matchers.
if ch notin strutils.IdentChars and ord(ch) < 0x80: break
if pos < c.buf.len and c.buf[pos] notin strutils.IdentChars: break
c.bufpos = pos
tok.kind = tkIdentifier

View File

@@ -36,8 +36,6 @@ 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 = [

View File

@@ -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, buffer.len, slice.a), slice.b + 1 - slice.a)
result = s.readData(beginStore(buffer, slice.b + 1 - slice.a, 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, buffer.len, slice.a), readRawData(s.data, s.pos), result)
copyMem(beginStore(buffer, result, 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, s.pos + bufLen, s.pos), buffer, bufLen)
copyMem(beginStore(s.data, 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, buffer.len, slice.a), len)
result = readBuffer(FileStream(s).f, beginStore(buffer, len, slice.a), len)
endStore(buffer)
proc fsPeekData(s: Stream, buffer: pointer, bufLen: int): int =

View File

@@ -380,10 +380,8 @@ proc `$`*(t: StringTableRef): string {.rtlFunc, extern: "nstDollar".} =
result = "{:}"
else:
result = "{"
var first = true
for key, val in pairs(t):
if first: first = false
else: result.add(", ")
if result.len > 1: result.add(", ")
result.add(key)
result.add(": ")
result.add(val)

View File

@@ -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(beginStore):
c_memcpy(beginStore(result, newLen, oldLen), buf, buflen.csize_t)
when declared(completeStore):
c_memcpy(beginStore(result, buflen, 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]

View File

@@ -1,344 +0,0 @@
#
# 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 its 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)

View File

@@ -1,14 +0,0 @@
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")

View File

@@ -294,11 +294,7 @@ proc containsOrIncl*[A](s: var PackedSet[A], key: A): bool =
for i in 0..<s.elems:
if s.a[i] == ord(key):
return true
if s.elems < s.a.len:
s.a[s.elems] = ord(key)
inc(s.elems)
else:
incl(s, key)
incl(s, key)
result = false
else:
var t = packedSetGet(s, ord(key) shr TrunkShift)

View File

@@ -84,7 +84,7 @@ func setSlice*(s: var string, slice: Slice[int]) =
when not declared(moveMem):
impl()
else:
let p = beginStore(s, s.len)
let p = beginStore(s, last - first + 1)
moveMem(p, addr p[first], last - first + 1)
endStore(s)
s.setLen(last - first + 1)

View File

@@ -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, pos + sp, pos)), sp.cint, f) != nil
fgetsSuccess = c_fgets(cast[cstring](beginStore(line, sp, pos)), sp.cint, f) != nil
endStore(line)
if fgetsSuccess: break
when not defined(nimscript):

View File

@@ -166,7 +166,7 @@ proc wasMoved*[T](obj: var T) {.magic: "WasMoved", noSideEffect.}
## it was "moved" and to signify its destructor should do nothing and
## ideally be optimized away.
proc move*[T](x: var T): T {.magic: "Move", noSideEffect, nodestroy.} =
proc move*[T](x: var T): T {.magic: "Move", noSideEffect.} =
result = x
{.cast(raises: []), cast(tags: []).}:
`=wasMoved`(x)
@@ -1703,8 +1703,7 @@ 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; newLen: int; start = 0): ptr UncheckedArray[char] {.inline, noSideEffect, raises: [], tags: [].} =
{.cast(noSideEffect).}: s.setLen(newLen)
proc beginStore*(s: var string; ensuredLen: int; start = 0): ptr UncheckedArray[char] {.inline, noSideEffect, raises: [], tags: [].} =
let ns = cast[NimString](s)
if ns == nil: nil
else: cast[ptr UncheckedArray[char]](addr ns.data[start])
@@ -1715,7 +1714,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; newLen: int; start = 0): ptr UncheckedArray[char] {.inline, noSideEffect, raises: [], tags: [].} = nil
proc beginStore*(s: var string; ensuredLen: 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
@@ -2419,33 +2418,6 @@ when notJSnotNims and hasAlloc:
when not defined(nimV2):
include "system/repr"
func setLenUninit*(s: var string, newlen: Natural) {.nodestroy.} =
## Sets the length of string `s` to `newlen`.
## New slots will not be initialized.
##
## If the new length is smaller than the new length,
## `s` will be truncated.
let n = max(newLen, 0)
when nimvm:
s.setLen(n)
else:
when notJSnotNims:
when defined(nimSeqsV2):
{.noSideEffect.}:
let str = unsafeAddr s
when defined(nimsso):
setLengthStrV3Uninit(cast[ptr SmallString](str)[], newlen)
else:
setLengthStrV2Uninit(cast[ptr NimStringV2](str)[], newlen)
else:
{.noSideEffect.}:
when hasAlloc:
setLengthStrUninit(s, newlen)
else:
s.setLen(n)
else: s.setLen(n)
when notJSnotNims and hasThreadSupport and hostOS != "standalone":
when not defined(nimPreviewSlimSystem):
include "system/channels_builtin"
@@ -2694,9 +2666,7 @@ when hasAlloc or defined(nimscript):
setLen(x, xl+item.len)
var j = xl-1
while j >= i:
when defined(nimsso):
x[j+item.len] = x[j]
elif defined(gcArc) or defined(gcOrc) or defined(gcYrc) or defined(gcAtomicArc):
when defined(gcArc) or defined(gcOrc) or defined(gcYrc) or defined(gcAtomicArc):
x[j+item.len] = move x[j]
else:
shallowCopy(x[j+item.len], x[j])

View File

@@ -127,12 +127,11 @@ type
# reaches dealloc while the source chunk is active.
# Instead, the receiving chunk gains the capacity and thus reserves space in the foreign chunk.
acc: uint32 # Offset from data, used when there are no free cells available but the chunk is considered free.
foreignCells: int32 # When a free cell is given to a chunk that is not its origin,
foreignCells: int # When a free cell is given to a chunk that is not its origin,
# both the cell and the source chunk are considered foreign.
# Receiving a foreign cell can happen both when deallocating from another thread or when
# the active chunk in `a.freeSmallChunks` is not the current chunk.
# Freeing a chunk while `foreignCells > 0` leaks memory as all references to it become lost.
chunkAlignOff: int32 # Byte offset from `data` where cells begin. Non-zero for alignment > MemAlign.
data {.align: MemAlign.}: UncheckedArray[byte] # start of usable memory
BigChunk = object of BaseChunk # not necessarily > PageSize!
@@ -473,8 +472,8 @@ iterator allObjects(m: var MemRegion): pointer {.inline.} =
var c = cast[PSmallChunk](c)
let size = c.size
var a = cast[int](addr(c.data)) + c.chunkAlignOff.int
let limit = cast[int](addr(c.data)) + c.acc.int
var a = cast[int](addr(c.data))
let limit = a + c.acc.int
while a <% limit:
yield cast[pointer](a)
a = a +% size
@@ -691,7 +690,7 @@ proc getBigChunk(a: var MemRegion, size: int): PBigChunk =
removeChunkFromMatrix2(a, result, fl, sl)
if result.size >= size + PageSize:
splitChunk(a, result, size)
# set 'used' to true:
# set 'used' to to true:
result.prevSize = 1
track("setUsedToFalse", addr result.size, sizeof(int))
sysAssert result.owner == addr a, "getBigChunk: No owner set!"
@@ -708,7 +707,7 @@ proc getHugeChunk(a: var MemRegion; size: int): PBigChunk =
result.next = nil
result.prev = nil
result.size = size
# set 'used' to true:
# set 'used' to to true:
result.prevSize = 1
result.owner = addr a
incl(a, a.chunkStarts, pageIndex(result))
@@ -852,15 +851,6 @@ when defined(heaptrack):
proc heaptrack_malloc(a: pointer, size: int) {.cdecl, importc, dynlib: heaptrackLib.}
proc heaptrack_free(a: pointer) {.cdecl, importc, dynlib: heaptrackLib.}
proc smallChunkAlignOffset(alignment: int): int {.inline.} =
## Compute the initial data offset so that data + result + sizeof(FreeCell)
## is alignment-aligned within a page-aligned small chunk.
if alignment <= MemAlign:
result = 0
else:
result = align(smallChunkOverhead() + sizeof(FreeCell), alignment) -
smallChunkOverhead() - sizeof(FreeCell)
proc bigChunkAlignOffset(alignment: int): int {.inline.} =
## Compute the alignment offset for big chunk data.
if alignment == 0:
@@ -873,13 +863,14 @@ proc rawAlloc(a: var MemRegion, requestedSize: int, alignment: int = 0): pointer
inc(a.allocCounter)
sysAssert(allocInv(a), "rawAlloc: begin")
sysAssert(roundup(65, 8) == 72, "rawAlloc: roundup broken")
var size = roundup(requestedSize, max(MemAlign, alignment))
let alignOff = smallChunkAlignOffset(alignment)
var size = roundup(requestedSize, MemAlign)
sysAssert(size >= sizeof(FreeCell), "rawAlloc: requested size too small")
sysAssert(size >= requestedSize, "insufficient allocated size!")
#c_fprintf(stdout, "alloc; size: %ld; %ld\n", requestedSize, size)
if size + alignOff <= SmallChunkSize-smallChunkOverhead():
# For custom alignments > MemAlign, force big chunk allocation
# Small chunks cannot handle arbitrary alignments due to fixed cell boundaries
if size <= SmallChunkSize-smallChunkOverhead() and alignment == 0:
template fetchSharedCells(tc: PSmallChunk) =
# Consumes cells from (potentially) foreign threads from `a.sharedFreeLists[s]`
when defined(gcDestructors):
@@ -897,19 +888,16 @@ proc rawAlloc(a: var MemRegion, requestedSize: int, alignment: int = 0): pointer
# allocate a small block: for small chunks, we use only its next pointer
let s = size div MemAlign
var c = a.freeSmallChunks[s]
if c != nil and c.chunkAlignOff != alignOff.int32:
c = nil
if c == nil:
# There is no free chunk of the requested size available, we need a new one.
c = getSmallChunk(a)
# init all fields in case memory didn't get zeroed
c.freeList = nil
c.foreignCells = 0
c.chunkAlignOff = alignOff.int32
sysAssert c.size == PageSize, "rawAlloc 3"
c.size = size
c.acc = (alignOff + size).uint32
c.free = SmallChunkSize - smallChunkOverhead() - alignOff.int32 - size.int32
c.acc = size.uint32
c.free = SmallChunkSize - smallChunkOverhead() - size.int32
sysAssert c.owner == addr(a), "rawAlloc: No owner set!"
c.next = nil
c.prev = nil
@@ -920,7 +908,7 @@ proc rawAlloc(a: var MemRegion, requestedSize: int, alignment: int = 0): pointer
# Because removals from `a.freeSmallChunks[s]` only happen in the other alloc branch and during dealloc,
# we must not add it to the list if it cannot be used the next time a pointer of `size` bytes is needed.
listAdd(a.freeSmallChunks[s], c)
result = addr(c.data) +! alignOff
result = addr(c.data)
sysAssert((cast[int](result) and (MemAlign-1)) == 0, "rawAlloc 4")
else:
# There is a free chunk of the requested size available, use it.
@@ -962,7 +950,7 @@ proc rawAlloc(a: var MemRegion, requestedSize: int, alignment: int = 0): pointer
sysAssert(allocInv(a), "rawAlloc: before listRemove test")
listRemove(a.freeSmallChunks[s], c)
sysAssert(allocInv(a), "rawAlloc: end listRemove test")
sysAssert(((cast[int](result) and PageMask) - smallChunkOverhead() - c.chunkAlignOff) %%
sysAssert(((cast[int](result) and PageMask) - smallChunkOverhead()) %%
size == 0, "rawAlloc 21")
sysAssert(allocInv(a), "rawAlloc: end small size")
inc a.occ, size
@@ -1030,7 +1018,7 @@ proc rawDealloc(a: var MemRegion, p: pointer) =
dec a.occ, s
untrackSize(s)
sysAssert a.occ >= 0, "rawDealloc: negative occupied memory (case A)"
sysAssert(((cast[int](p) and PageMask) - smallChunkOverhead() - c.chunkAlignOff) %%
sysAssert(((cast[int](p) and PageMask) - smallChunkOverhead()) %%
s == 0, "rawDealloc 3")
when not defined(gcDestructors):
#echo("setting to nil: ", $cast[int](addr(f.zeroField)))
@@ -1041,8 +1029,7 @@ proc rawDealloc(a: var MemRegion, p: pointer) =
nimSetMem(cast[pointer](cast[int](p) +% sizeof(FreeCell)), -1'i32,
s -% sizeof(FreeCell))
let activeChunk = a.freeSmallChunks[s div MemAlign]
if activeChunk != nil and c != activeChunk and
activeChunk.chunkAlignOff == c.chunkAlignOff:
if activeChunk != nil and c != activeChunk:
# This pointer is not part of the active chunk, lend it out
# and do not adjust the current chunk (same logic as compensateCounters.)
# Put the cell into the active chunk,
@@ -1089,7 +1076,7 @@ proc rawDealloc(a: var MemRegion, p: pointer) =
when defined(gcDestructors):
addToSharedFreeList(c, f, s div MemAlign)
sysAssert(((cast[int](p) and PageMask) - smallChunkOverhead() - c.chunkAlignOff) %%
sysAssert(((cast[int](p) and PageMask) - smallChunkOverhead()) %%
s == 0, "rawDealloc 2")
else:
# set to 0xff to check for usage after free bugs:
@@ -1115,11 +1102,8 @@ when not defined(gcDestructors):
var c = cast[PSmallChunk](c)
var offset = (cast[int](p) and (PageSize-1)) -%
smallChunkOverhead()
if c.acc.int >% offset:
let ao = c.chunkAlignOff.int
result = (offset >= ao) and
((offset -% ao) %% c.size == 0) and
(cast[ptr FreeCell](p).zeroField >% 1)
result = (c.acc.int >% offset) and (offset %% c.size == 0) and
(cast[ptr FreeCell](p).zeroField >% 1)
else:
var c = cast[PBigChunk](c)
# prev stores the aligned data pointer set during rawAlloc
@@ -1138,12 +1122,11 @@ when not defined(gcDestructors):
var c = cast[PSmallChunk](c)
var offset = (cast[int](p) and (PageSize-1)) -%
smallChunkOverhead()
let ao = c.chunkAlignOff.int
if c.acc.int >% offset and offset >= ao:
if c.acc.int >% offset:
sysAssert(cast[int](addr(c.data)) +% offset ==
cast[int](p), "offset is not what you think it is")
var d = cast[ptr FreeCell](cast[int](addr(c.data)) +%
ao +% ((offset -% ao) -% ((offset -% ao) %% c.size)))
offset -% (offset %% c.size))
if d.zeroField >% 1:
result = d
sysAssert isAllocatedPtr(a, result), " result wrong pointer!"
@@ -1274,20 +1257,6 @@ template instantiateForRegion(allocator: untyped) {.dirty.} =
proc alloc0Impl(size: Natural): pointer =
result = alloc0(allocator, size)
when defined(gcOrc) or defined(gcYrc):
proc nimAlignedAlloc0(size: Natural, alignment: int): pointer =
incStat(allocCount)
result = rawAlloc(allocator, size, alignment)
zeroMem(result, size)
proc nimAlignedAlloc(size: Natural, alignment: int): pointer =
incStat(allocCount)
result = rawAlloc(allocator, size, alignment)
proc nimAlignedDealloc(p: pointer) =
incStat(deallocCount)
rawDealloc(allocator, p)
proc deallocImpl(p: pointer) =
dealloc(allocator, p)

View File

@@ -92,27 +92,12 @@ else:
when not defined(nimHasQuirky):
{.pragma: quirky.}
# Forward declarations for native allocator alignment (implemented in alloc.nim).
# rawAlloc's contract: result + sizeof(FreeCell) is alignment-aligned.
# For ORC/YRC, sizeof(FreeCell) == sizeof(RefHeader).
const useNativeAlignedAlloc = (defined(gcOrc) or defined(gcYrc)) and
not defined(useMalloc) and not defined(nimscript) and
not defined(nimdoc) and not defined(useNimRtl)
when useNativeAlignedAlloc:
proc nimAlignedAlloc0(size: Natural, alignment: int): pointer {.gcsafe, raises: [].}
proc nimAlignedAlloc(size: Natural, alignment: int): pointer {.gcsafe, raises: [].}
proc nimAlignedDealloc(p: pointer) {.gcsafe, raises: [].}
proc nimNewObj(size, alignment: int): pointer {.compilerRtl.} =
when defined(nimscript) or defined(nimdoc):
let hdrSize = align(sizeof(RefHeader), alignment)
let s = size +% hdrSize
when defined(nimscript):
discard
elif useNativeAlignedAlloc:
let s = size +% sizeof(RefHeader)
result = nimAlignedAlloc0(s, alignment) +! sizeof(RefHeader)
else:
let hdrSize = align(sizeof(RefHeader), alignment)
let s = size +% hdrSize
result = alignedAlloc0(s, alignment) +! hdrSize
when defined(nimArcDebug) or defined(nimArcIds):
head(result).refId = gRefId
@@ -126,14 +111,12 @@ proc nimNewObj(size, alignment: int): pointer {.compilerRtl.} =
proc nimNewObjUninit(size, alignment: int): pointer {.compilerRtl.} =
# Same as 'newNewObj' but do not initialize the memory to zero.
when defined(nimscript) or defined(nimdoc):
# The codegen proved for us that this is not necessary.
let hdrSize = align(sizeof(RefHeader), alignment)
let s = size + hdrSize
when defined(nimscript):
discard
elif useNativeAlignedAlloc:
let s = size + sizeof(RefHeader)
result = cast[ptr RefHeader](nimAlignedAlloc(s, alignment) +! sizeof(RefHeader))
else:
let hdrSize = align(sizeof(RefHeader), alignment)
let s = size + hdrSize
result = cast[ptr RefHeader](alignedAlloc(s, alignment) +! hdrSize)
head(result).rc = 0
when defined(gcOrc) or defined(gcYrc):
@@ -206,11 +189,8 @@ proc nimRawDispose(p: pointer, alignment: int) {.compilerRtl.} =
if freedCells.data == nil: init(freedCells)
freedCells.incl head(p)
else:
when useNativeAlignedAlloc:
nimAlignedDealloc(p -! sizeof(RefHeader))
else:
let hdrSize = align(sizeof(RefHeader), alignment)
alignedDealloc(p -! hdrSize, alignment)
let hdrSize = align(sizeof(RefHeader), alignment)
alignedDealloc(p -! hdrSize, alignment)
template `=dispose`*[T](x: owned(ref T)) = nimRawDispose(cast[pointer](x), T.alignOf)
#proc dispose*(x: pointer) = nimRawDispose(x)

View File

@@ -143,7 +143,7 @@ when nimCoroutines:
proc find(first: var GcStack, bottom: pointer): ptr GcStack =
## Find stack struct based on bottom pointer. If `bottom` is nil then main
## thread stack is returned.
## thread stack is is returned.
if bottom == nil:
return addr(gch.stack)

View File

@@ -59,35 +59,16 @@ template `[]=`*(s: string; i: int; val: char) = arrPut(s, i, val)
template `^^`(s, i: untyped): untyped =
(when i is BackwardsIndex: s.len - int(i) else: int(i))
template spliceStringImpl(s, a, L, b: typed): untyped =
template spliceImpl(s, a, L, b: typed): untyped =
# make room for additional elements or cut:
var shift = b.len - max(0,L) # ignore negative slice size
var newLen = s.len + shift
if shift > 0:
# enlarge:
setLen(s, newLen)
for i in countdown(newLen-1, a+b.len):
s[i] = s[i-shift]
for i in countdown(newLen-1, a+b.len): movingCopy(s[i], s[i-shift])
else:
for i in countup(a+b.len, newLen-1):
s[i] = s[i-shift]
# cut down:
setLen(s, newLen)
# fill the hole:
for i in 0 ..< b.len: s[a+i] = b[i]
template spliceSeqImpl(s, a, L, b: typed): untyped =
# make room for additional elements or cut:
var shift = b.len - max(0,L) # ignore negative slice size
var newLen = s.len + shift
if shift > 0:
# enlarge:
setLen(s, newLen)
for i in countdown(newLen-1, a+b.len):
movingCopy(s[i], s[i-shift])
else:
for i in countup(a+b.len, newLen-1):
movingCopy(s[i], s[i-shift])
for i in countup(a+b.len, newLen-1): movingCopy(s[i], s[i-shift])
# cut down:
setLen(s, newLen)
# fill the hole:
@@ -121,7 +102,7 @@ proc `[]=`*[T, U: Ordinal](s: var string, x: HSlice[T, U], b: string) {.systemRa
if L == b.len:
for i in 0..<L: s[i+a] = b[i]
else:
spliceStringImpl(s, a, L, b)
spliceImpl(s, a, L, b)
proc `[]`*[Idx, T; U, V: Ordinal](a: array[Idx, T], x: HSlice[U, V]): seq[T] {.systemRaisesDefect.} =
## Slice operation for arrays.
@@ -181,4 +162,4 @@ proc `[]=`*[T; U, V: Ordinal](s: var seq[T], x: HSlice[U, V], b: openArray[T]) {
if L == b.len:
for i in 0 ..< L: s[i+a] = b[i]
else:
spliceSeqImpl(s, a, L, b)
spliceImpl(s, a, L, b)

View File

@@ -262,11 +262,8 @@ proc setLen[T](s: var seq[T], newlen: Natural) {.nodestroy.} =
if xu.p == nil or (xu.p.cap and not strlitFlag) < newlen:
xu.p = cast[typeof(xu.p)](prepareSeqAddUninit(oldLen, xu.p, newlen - oldLen, sizeof(T), alignof(T)))
xu.len = newlen
{.push overflowChecks: off.}
for i in oldLen..<newlen:
xu.p.data[i] = default(T)
{.pop.}
proc newSeq[T](s: var seq[T], len: Natural) =
shrink(s, 0)

View File

@@ -158,26 +158,6 @@ proc setLengthStrV2(s: var NimStringV2, newLen: int) {.compilerRtl.} =
s.p.data[newLen] = '\0'
s.len = newLen
proc setLengthStrV2Uninit(s: var NimStringV2, newLen: int) =
if newLen == 0:
discard "do not free the buffer here, pattern 's.setLen 0' is common for avoiding allocations"
else:
if isLiteral(s):
let oldP = s.p
s.p = allocPayload(newLen)
s.p.cap = newLen
if s.len > 0:
copyMem(unsafeAddr s.p.data[0], unsafeAddr oldP.data[0], min(s.len, newLen))
s.p.data[newLen] = '\0'
elif newLen > s.len:
let oldCap = s.p.cap and not strlitFlag
if newLen > oldCap:
let newCap = max(newLen, resize(oldCap))
s.p = reallocPayload0(s.p, oldCap, newCap)
s.p.cap = newCap
s.p.data[newLen] = '\0'
s.len = newLen
proc nimAsgnStrV2(a: var NimStringV2, b: NimStringV2) {.compilerRtl.} =
if a.p == b.p and a.len == b.len: return
if isLiteral(b):
@@ -236,17 +216,13 @@ 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; 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.
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`.
## Call `endStore(s)` afterwards for portability.
## 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])
{.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])
proc endStore*(s: var string) {.inline, noSideEffect, raises: [], tags: [].} =
## No-op for non-SSO strings; call after bulk writes via `beginStore`.

View File

@@ -224,14 +224,13 @@ proc cmpStringPtrs(a, b: ptr SmallString): int {.inline.} =
minLen - AlwaysAvail)
if result == 0: result = aslen - bslen
return
# At least one is long. Hot prefix mirrors heap data, but only up to fullLen:
# shrinking can leave stale bytes in the inline cache past the logical length.
# At least one is long. Hot prefix: inlinePtr[0..AlwaysAvail-1] mirrors heap data.
let pfxLen = min(min(aslen, bslen), AlwaysAvail)
result = cmpInlineBytes(inlinePtrOf(a), inlinePtrOf(b), pfxLen)
if result != 0: return
let la = if aslen > PayloadSize: a.more.fullLen else: aslen
let lb = if bslen > PayloadSize: b.more.fullLen else: bslen
let minLen = min(la, lb)
let pfxLen = min(minLen, AlwaysAvail)
result = cmpInlineBytes(inlinePtrOf(a), inlinePtrOf(b), pfxLen)
if result != 0: return
if minLen <= AlwaysAvail:
result = la - lb
return
@@ -497,75 +496,28 @@ proc mnewString(len: int): SmallString {.compilerproc.} =
result.more = p
setSSLen(result, HeapSlen)
proc setLengthStr(s: var SmallString; newLen: int; zeroing: bool) =
# Shared implementation for setLengthStrV2 (zeroing) and setLengthStrV3Uninit
# Difference between the two modes:
# - inline/medium -> long growth: alloc0 (zeroing) vs alloc (uninit)
# - long -> long growth: zeroMem the new tail (zeroing) or skip it (uninit)
proc setLengthStrV2(s: var SmallString; newLen: int) {.compilerRtl.} =
## Sets the length of s to newLen, zeroing new bytes on growth.
let slen = ssLen(s)
let curLen = if slen > PayloadSize: s.more.fullLen else: slen
if newLen == curLen: return
if newLen < curLen:
# Shrinking:
if newLen <= 0:
if slen > PayloadSize:
if slen == HeapSlen and s.more.rc == 1:
# 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'
s.more.fullLen = 0
s.more.data[0] = '\0'
else:
# 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)
# shared or static block: detach and go back to empty inline
nimDestroyStrV1(s)
s.bytes = 0 # slen=0, all inline chars zeroed
else:
# 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)
s.bytes = 0 # slen=0, all inline chars zeroed (SWAR safe)
return
if slen <= PayloadSize:
if newLen <= PayloadSize:
let inl = inlinePtr(s)
if newLen > curLen:
# Grow within inline/medium
# Bytes above newLen already zero by the SWAR invariant,
# so setSSLen is sufficient.
if zeroing:
zeroMem(addr inl[curLen], newLen - curLen)
zeroMem(addr inl[curLen], newLen - curLen)
inl[newLen] = '\0'
setSSLen(s, newLen)
else:
@@ -590,33 +542,43 @@ proc setLengthStr(s: var SmallString; newLen: int; zeroing: bool) =
else:
# grow into long
let newCap = resize(newLen)
let p = if zeroing:
# bytes [curLen..newLen] and p.data[newLen] zeroed by alloc0
cast[ptr LongString](alloc0(LongStringDataOffset + newCap + 1))
else:
let p = cast[ptr LongString](alloc(LongStringDataOffset + newCap + 1))
p.data[newLen] = '\0'
p
let p = cast[ptr LongString](alloc0(LongStringDataOffset + newCap + 1))
p.rc = 1
p.fullLen = newLen
p.capImpl = newCap
copyMem(addr p.data[0], inlinePtr(s), curLen)
# bytes [curLen..newLen] zeroed by alloc0; p.data[newLen] = '\0' by alloc0
s.more = p
setSSLen(s, HeapSlen)
else:
# 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.
setLengthStr(s, newLen, zeroing = true)
proc setLengthStrV3Uninit(s: var SmallString; newLen: int) {.compilerRtl.} =
## Sets the length of `s` to `newLen`, NOT zeroing new bytes on growth.
setLengthStr(s, newLen, zeroing = false)
# currently long
if newLen <= PayloadSize:
# shrink back to inline
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:
ensureUniqueLong(s, curLen, newLen)
if newLen > curLen:
zeroMem(addr s.more.data[curLen], newLen - curLen)
s.more.data[newLen] = '\0'
s.more.fullLen = newLen
proc nimAsgnStrV2(a: var SmallString; b: SmallString) {.compilerRtl, inline.} =
if ssLen(b) <= PayloadSize:
@@ -722,37 +684,18 @@ proc completeStore(s: var SmallString) {.compilerproc, inline.} =
proc completeStore*(s: var string) {.inline.} =
completeStore(cast[ptr SmallString](addr s)[])
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.
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`).
## 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[])
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)
if slen > PayloadSize:
ensureUniqueLong(ss[], ss[].more.fullLen, ss[].more.fullLen)
result = cast[ptr UncheckedArray[char]](addr ss[].more.data[start])
else:
# 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])
result = cast[ptr UncheckedArray[char]](cast[uint](inlinePtr(ss[])) + uint(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.

View File

@@ -244,31 +244,6 @@ proc setLengthStr(s: NimString, newLen: int): NimString {.compilerRtl.} =
result.len = n
result.data[n] = '\0'
proc setLengthStrUninit(s: var string, newlen: Natural) {.nodestroy.} =
## Sets the `s` length to `newlen` without zeroing memory on growth.
## Terminating zero for cstring compatibility is set.
var str = cast[NimString](s)
let n = max(newLen, 0)
if str == nil:
if n == 0: return
else:
str = rawNewStringNoInit(n)
str.data[n] = '\0'
str.len = n
s = cast[string](str)
else:
if n > str.space:
let sp = max(resize(str.space), n)
str = rawNewStringNoInit(sp)
copyMem(addr str.data[0], unsafeAddr s[0], s.len)
str.data[n] = '\0'
str.len = n
s = cast[string](str)
elif n != s.len:
str.data[n] = '\0'
str.len = n
else: return
# ----------------- sequences ----------------------------------------------
proc incrSeq(seq: PGenericSeq, elemSize, elemAlign: int): PGenericSeq {.compilerproc.} =
@@ -299,22 +274,12 @@ 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;
doInit: static bool): PGenericSeq {.inline.} =
elemSize, elemAlign, newLen: int): 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)
when doInit:
result = cast[PGenericSeq](newSeq(typ, newCap))
else:
result = cast[PGenericSeq](newSeqUninitRaw(typ, newCap))
result = cast[PGenericSeq](newSeq(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
@@ -345,19 +310,15 @@ 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:
when doInit:
return cast[PGenericSeq](newSeq(typ, newLen)) # newSeq zeroes!
else:
return cast[PGenericSeq](newSeqUninitRaw(typ, newLen))
else: return cast[PGenericSeq](newSeq(typ, newLen)) # newSeq zeroes!
else:
let elemSize = typ.base.size
let elemAlign = typ.base.align
result = if newLen > s.space:
s.extendCapacityRaw(typ, elemSize, elemAlign, newLen, doInit)
s.extendCapacityRaw(typ, elemSize, elemAlign, newLen)
elif newLen < s.len:
s.truncateRaw(typ.base.flags, isTrivial, elemSize, elemAlign, newLen)
else:

View File

@@ -1,260 +0,0 @@
#
# Nim's Runtime Library
# (c) Copyright 2026 Nim Contributors
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Wrapper for the 8-bit PCRE2 API.
when sizeof(int) == 4:
const ANCHORED* = low(int)
else:
const ANCHORED* = int(0x80000000)
const
NO_UTF_CHECK* = int(0x40000000)
ENDANCHORED* = int(0x20000000)
const
ALLOW_EMPTY_CLASS* = 0x00000001
ALT_BSUX* = 0x00000002
AUTO_CALLOUT* = 0x00000004
CASELESS* = 0x00000008
DOLLAR_ENDONLY* = 0x00000010
DOTALL* = 0x00000020
DUPNAMES* = 0x00000040
EXTENDED* = 0x00000080
FIRSTLINE* = 0x00000100
MATCH_UNSET_BACKREF* = 0x00000200
MULTILINE* = 0x00000400
NEVER_UCP* = 0x00000800
NEVER_UTF* = 0x00001000
NO_AUTO_CAPTURE* = 0x00002000
NO_AUTO_POSSESS* = 0x00004000
NO_DOTSTAR_ANCHOR* = 0x00008000
NO_START_OPTIMIZE* = 0x00010000
NO_START_OPTIMISE* = NO_START_OPTIMIZE
UCP* = 0x00020000
UNGREEDY* = 0x00040000
UTF* = 0x00080000
UTF8* = UTF
NEVER_BACKSLASH_C* = 0x00100000
ALT_CIRCUMFLEX* = 0x00200000
ALT_VERBNAMES* = 0x00400000
USE_OFFSET_LIMIT* = 0x00800000
EXTENDED_MORE* = 0x01000000
LITERAL* = 0x02000000
MATCH_INVALID_UTF* = 0x04000000
ALT_EXTENDED_CLASS* = 0x08000000
## PCRE2 no longer exposes PCRE's `JAVASCRIPT_COMPAT` option. `ALT_BSUX`
## preserves the most important JavaScript-style escape handling.
JAVASCRIPT_COMPAT* = ALT_BSUX
const
JIT_COMPLETE* = 0x00000001
JIT_PARTIAL_SOFT* = 0x00000002
JIT_PARTIAL_HARD* = 0x00000004
JIT_INVALID_UTF* = 0x00000100
JIT_TEST_ALLOC* = 0x00000200
const
NOTBOL* = 0x00000001
NOTEOL* = 0x00000002
NOTEMPTY* = 0x00000004
NOTEMPTY_ATSTART* = 0x00000008
PARTIAL_SOFT* = 0x00000010
PARTIAL_HARD* = 0x00000020
DFA_RESTART* = 0x00000040
DFA_SHORTEST* = 0x00000080
NO_JIT* = 0x00002000
COPY_MATCHED_SUBJECT* = 0x00004000
DISABLE_RECURSELOOP_CHECK* = 0x00040000
const
NEWLINE_CR* = 1
NEWLINE_LF* = 2
NEWLINE_CRLF* = 3
NEWLINE_ANY* = 4
NEWLINE_ANYCRLF* = 5
NEWLINE_NUL* = 6
BSR_UNICODE* = 1
BSR_ANYCRLF* = 2
const
ERROR_NOMATCH* = -1
ERROR_PARTIAL* = -2
ERROR_UTF8_ERR1* = -3
ERROR_UTF8_ERR2* = -4
ERROR_UTF8_ERR3* = -5
ERROR_UTF8_ERR4* = -6
ERROR_UTF8_ERR5* = -7
ERROR_UTF8_ERR6* = -8
ERROR_UTF8_ERR7* = -9
ERROR_UTF8_ERR8* = -10
ERROR_UTF8_ERR9* = -11
ERROR_UTF8_ERR10* = -12
ERROR_UTF8_ERR11* = -13
ERROR_UTF8_ERR12* = -14
ERROR_UTF8_ERR13* = -15
ERROR_UTF8_ERR14* = -16
ERROR_UTF8_ERR15* = -17
ERROR_UTF8_ERR16* = -18
ERROR_UTF8_ERR17* = -19
ERROR_UTF8_ERR18* = -20
ERROR_UTF8_ERR19* = -21
ERROR_UTF8_ERR20* = -22
ERROR_UTF8_ERR21* = -23
ERROR_BADDATA* = -29
ERROR_MIXEDTABLES* = -30
ERROR_BADMAGIC* = -31
ERROR_BADMODE* = -32
ERROR_BADOFFSET* = -33
ERROR_BADOPTION* = -34
ERROR_BADREPLACEMENT* = -35
ERROR_BADUTFOFFSET* = -36
ERROR_CALLOUT* = -37
ERROR_INTERNAL* = -44
ERROR_JIT_BADOPTION* = -45
ERROR_JIT_STACKLIMIT* = -46
ERROR_MATCHLIMIT* = -47
ERROR_NOMEMORY* = -48
ERROR_NOSUBSTRING* = -49
ERROR_NULL* = -51
ERROR_RECURSELOOP* = -52
ERROR_DEPTHLIMIT* = -53
ERROR_RECURSIONLIMIT* = ERROR_DEPTHLIMIT
ERROR_UNAVAILABLE* = -54
ERROR_UNSET* = -55
ERROR_BADOFFSETLIMIT* = -56
ERROR_HEAPLIMIT* = -63
ERROR_DFA_UINVALID_UTF* = -66
ERROR_INVALIDOFFSET* = -67
ERROR_JIT_UNSUPPORTED* = -68
const
INFO_ALLOPTIONS* = 0
INFO_ARGOPTIONS* = 1
INFO_BACKREFMAX* = 2
INFO_BSR* = 3
INFO_CAPTURECOUNT* = 4
INFO_FIRSTCODEUNIT* = 5
INFO_FIRSTCODETYPE* = 6
INFO_FIRSTBITMAP* = 7
INFO_HASCRORLF* = 8
INFO_JCHANGED* = 9
INFO_JITSIZE* = 10
INFO_LASTCODEUNIT* = 11
INFO_LASTCODETYPE* = 12
INFO_MATCHEMPTY* = 13
INFO_MATCHLIMIT* = 14
INFO_MAXLOOKBEHIND* = 15
INFO_MINLENGTH* = 16
INFO_NAMECOUNT* = 17
INFO_NAMEENTRYSIZE* = 18
INFO_NAMETABLE* = 19
INFO_NEWLINE* = 20
INFO_DEPTHLIMIT* = 21
INFO_RECURSIONLIMIT* = INFO_DEPTHLIMIT
INFO_SIZE* = 22
INFO_HASBACKSLASHC* = 23
INFO_FRAMESIZE* = 24
INFO_HEAPLIMIT* = 25
INFO_EXTRAOPTIONS* = 26
const
CONFIG_BSR* = 0
CONFIG_JIT* = 1
CONFIG_JITTARGET* = 2
CONFIG_LINKSIZE* = 3
CONFIG_MATCHLIMIT* = 4
CONFIG_NEWLINE* = 5
CONFIG_PARENSLIMIT* = 6
CONFIG_DEPTHLIMIT* = 7
CONFIG_RECURSIONLIMIT* = CONFIG_DEPTHLIMIT
CONFIG_STACKRECURSE* = 8
CONFIG_UNICODE* = 9
CONFIG_UNICODE_VERSION* = 10
CONFIG_VERSION* = 11
CONFIG_HEAPLIMIT* = 12
CONFIG_NEVER_BACKSLASH_C* = 13
CONFIG_COMPILED_WIDTHS* = 14
CONFIG_TABLES_LENGTH* = 15
const
ZERO_TERMINATED* = not 0.csize_t
UNSET* = not 0.csize_t
type
Pcre* = object
CompileContext* = object
GeneralContext* = object
MatchContext* = object
MatchData* = object
JitStack* = object
when not defined(usePcreHeader):
when hostOS == "windows":
const pcre2Dll = "pcre2-8.dll"
elif hostOS == "macosx":
const pcre2Dll = "libpcre2-8(.0|).dylib"
else:
const pcre2Dll = "libpcre2-8.so(.0|)"
{.push dynlib: pcre2Dll.}
else:
{.passC: "-DPCRE2_CODE_UNIT_WIDTH=8".}
{.push header: "<pcre2.h>".}
{.push cdecl, importc: "pcre2_$1_8".}
proc compile*(pattern: ptr uint8,
length: csize_t,
options: uint32,
errorCode: ptr cint,
errorOffset: ptr csize_t,
context: ptr CompileContext): ptr Pcre
proc code_free*(code: ptr Pcre)
proc config*(what: uint32,
where: pointer): cint
proc get_error_message*(errorCode: cint,
buffer: ptr uint8,
bufferLength: csize_t): cint
proc match*(code: ptr Pcre,
subject: ptr uint8,
length: csize_t,
startOffset: csize_t,
options: uint32,
matchData: ptr MatchData,
context: ptr MatchContext): cint
proc match_data_create*(oveccount: uint32,
context: ptr GeneralContext): ptr MatchData
proc match_data_create_from_pattern*(code: ptr Pcre,
context: ptr GeneralContext): ptr MatchData
proc match_data_free*(matchData: ptr MatchData)
proc get_ovector_pointer*(matchData: ptr MatchData): ptr csize_t
proc get_ovector_count*(matchData: ptr MatchData): uint32
proc pattern_info*(code: ptr Pcre,
what: uint32,
where: pointer): cint
proc jit_compile*(code: ptr Pcre,
options: uint32): cint
proc jit_free_unused_memory*()
{.pop.}
{.pop.}

View File

@@ -1042,7 +1042,7 @@ proc iterateOutlineNodes(graph: ModuleGraph, n: PNode, infoPairs: SuggestFileSym
if symData != nil and symData.sym.kind == skEnumField and symData.info.exactEquals(symData.sym.info):
let sym = symData.sym
graph.suggestResult(sym, sym.info, ideOutline, n.endInfo.line, n.endInfo.col)
elif (n.kind in {nkFuncDef, nkProcDef, nkMethodDef, nkIteratorDef, nkTypeDef, nkMacroDef, nkTemplateDef, nkConverterDef, nkEnumFieldDef, nkConstDef}):
elif (n.kind in {nkFuncDef, nkProcDef, nkTypeDef, nkMacroDef, nkTemplateDef, nkConverterDef, nkEnumFieldDef, nkConstDef}):
matched = handleIdentOrSym(graph, n, n.endInfo, infoPairs)
else:
matched = false

View File

@@ -36,9 +36,7 @@ outline skType tv3_outline.FooPrivate FooPrivate $file 7 2 "" 100 8 22
outline skMacro tv3_outline.m macro (arg: untyped): untyped{.noSideEffect, gcsafe, raises: <inferred> [].} $file 10 6 "" 100 10 40
outline skTemplate tv3_outline.t template (arg: untyped): untyped $file 11 9 "" 100 11 43
outline skProc tv3_outline.p proc (){.noSideEffect, gcsafe, raises: <inferred> [].} $file 12 5 "" 100 12 24
outline skIterator tv3_outline.i iterator (): int{.inline, noSideEffect, gcsafe, raises: <inferred> [].} $file 13 9 "" 100 13 27
outline skConverter tv3_outline.c converter (s: string): int{.noSideEffect, gcsafe, raises: <inferred> [].} $file 14 10 "" 100 14 37
outline skMethod tv3_outline.m proc (f: Foo){.noSideEffect, gcsafe, raises: <inferred> [].} $file 15 7 "" 100 15 32
outline skFunc tv3_outline.f proc (){.noSideEffect, gcsafe, raises: <inferred> [].} $file 16 5 "" 100 16 24
outline skConst tv3_outline.con int literal(2) $file 20 6 "" 100 20 13
outline skProc tv3_outline.outer proc (){.noSideEffect, gcsafe, raises: <inferred> [].} $file 22 5 "" 100 23 24

View File

@@ -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.4 or greater) | x86, x86_64, ppc64, and Apple Silicon (ARM64) |
| Mac OS X (10.04 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.

View File

@@ -1,23 +0,0 @@
discard """
matrix: "--mm:refc; --mm:orc; --mm:arc"
targets: "c cpp"
output: "ok"
"""
# Test that heap-allocated objects with .align use small chunks,
# not a big chunk per object (regression test for #25577).
type U = object
d {.align: 32.}: int8
var e: seq[ref U]
for _ in 0 ..< 10000: e.add(new U)
# Without small-chunk alignment, each object gets its own page (~46 MB).
# With the fix, 10000 objects fit in ~1-3 MB depending on the GC.
doAssert getTotalMem() < 8 * 1024 * 1024, "align:32 heap objects use too much memory"
# Verify alignment is actually correct
for i in 0 ..< e.len:
doAssert (cast[int](addr e[i].d) and 31) == 0, "field not 32-byte aligned"
echo "ok"

View File

@@ -1,33 +0,0 @@
discard """
cmd: '''nim c --mm:arc --expandArc:foo $file'''
nimout: '''
--expandArc: foo
var broken_cursor
block :tmp:
var i
var i_1 = 0
let L = len(seq[Large](broken_cursor))
block :tmp_1:
while i_1 < L:
i = seq[Large](broken_cursor)[i_1]
discard i
{.push, overflowChecks: false.}
inc(i_1, 1)
{.pop.}
-- end of expandArc ------------------------
'''
"""
type
Large = array[1024, byte]
List = distinct seq[Large]
proc foo =
var
broken: List
for i in seq[Large](broken):
discard i
foo()

View File

@@ -1,45 +0,0 @@
discard """
output: '''
246
246
'''
"""
# issue #25730
type
Inner[T] = object
x: T
Foo[T] = object
inner: Inner[T]
Bar[T] = object
foo: Foo[T]
proc `=sink`[T](a: var Inner[T], b: Inner[T]) {.nodestroy.} =
a.x = b.x * 2
proc `=copy`[T](a: var Inner[T], b: Inner[T]) {.nodestroy.} =
a.x = b.x * 2
when true:
proc `=sink`[T](a: var Bar[T], b: Bar[T]) {.nodestroy.} =
`=sink`(a.foo, b.foo)
proc `=copy`[T](a: var Bar[T], b: Bar[T]) {.nodestroy.} =
`=copy`(a.foo, b.foo)
proc useSink() =
let a = Bar[int](foo: Foo[int](inner: Inner[int](x: 123)))
var b: Bar[int]
`=sink`(b, a)
echo b.foo.inner.x
useSink()
proc useCopy() =
let a = Bar[int](foo: Foo[int](inner: Inner[int](x: 123)))
var b: Bar[int]
`=copy`(b, a)
echo b.foo.inner.x
useCopy()

View File

@@ -1,13 +0,0 @@
template a(T: type): int =
when T is uint64: 1 else: 2
type
M*[T] = object
data*: seq[T]
b: seq[int]
indices*: array[a(T), int64]
U = distinct uint64
D* = object
c: M[U]
v: array[180000, int64]
g*: M[uint64]

View File

@@ -1,5 +0,0 @@
import ./c
proc p*(): D =
let c = M[uint64](data: @[0], indices: [1])
result = D(g: c)

View File

@@ -1,7 +0,0 @@
/*TYPESECTION*/
struct CppRef {
int* data;
CppRef() : data(new int(42)) {}
~CppRef() { delete data; data = nullptr; }
void reset() { delete data; data = nullptr; }
};

View File

@@ -1,18 +0,0 @@
discard """
matrix: "--mm:refc; --mm:orc"
"""
import ./m25294/[c, t]
block:
let a = new D
a[] = p()
discard a[]
block:
let a = new D
a[] = p()
discard a[]
block:
let a = new D
a[] = p()
discard a[]

View File

@@ -1,23 +0,0 @@
discard """
cmd: "nim cpp $file"
action: "compile"
"""
# Bug Report 1: {.importcpp.} on =wasMoved generates invalid preprocessor directive #.
type CppRef* {.importcpp, bycopy, noInit, header: "m25800.h".} = object
proc `=destroy`(x: var CppRef) {.importcpp: "#.~CppRef()".}
proc `=wasMoved`(x: var CppRef) {.importcpp: "#.reset()".}
proc `=copy`(dest: var CppRef; src: CppRef) {.importcpp: "dest = src".}
proc `=sink`(dest: var CppRef; src: CppRef) {.importcpp: "dest = std::move(src)".}
# This triggers =wasMoved when passing to sink parameter
proc consume(x: sink CppRef) = discard
proc test() =
var x: CppRef
consume(move(x)) # =wasMoved MUST be called here after the move
test()

View File

@@ -1,10 +0,0 @@
discard """
matrix: "-d:nimPreviewSlimSystem --warning:StdPrefix:on --warningAsError:StdPrefix:on --import:std/objectdollar"
output: "(a: 23, b: 45)"
"""
type Foo = object
a, b: int
let x = Foo(a: 23, b: 45)
echo x

View File

@@ -176,42 +176,6 @@ 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:

View File

@@ -12,51 +12,3 @@ proc foo =
doAssert m.id == 999
foo()
block:
type Foo = object
a,b,c: int
var dest: Foo
# proc `=wasMoved`(x: var Foo) =
# debugEcho "wasMoved called"
proc main() =
var x = Foo(a:11, b:12, c:13)
dest = move(x)
main()
block:
type Foo = object
a,b,c: int
var dest: Foo
proc `=wasMoved`(x: var Foo) =
discard "wasMoved called"
proc main() =
var x = Foo(a:11, b:12, c:13)
dest = move(x)
main()
import std/threadpool
block:
type Foo = object
data: string
proc `=wasMoved`(x: var Foo) =
discard
proc work(x: Foo) =
discard
var x = Foo(data: "hello")
spawn work(x)
sync()

View File

@@ -1,15 +0,0 @@
discard """
cmd: "nim check --hints:off $file"
action: "reject"
nimout: '''
t25732.nim(15, 32) Error: undeclared identifier: 'a'
t25732.nim(15, 32) Error: expression 'a' has no type (or is ambiguous)
t25732.nim(15, 33) Error: undeclared field: 'b'
t25732.nim(15, 33) Error: undeclared field: '.'
t25732.nim(15, 33) Error: undeclared field: '.'
'''
"""
static: (for f in [0]: discard a.b == f)

View File

@@ -1,13 +0,0 @@
discard """
cmd: "nim check --strings:sso --mm:orc --hints:off $file"
action: "reject"
nimout: '''
tsso_string_index_var.nim(13, 12) Error: expression 's[0]' is immutable, not 'var'
'''
"""
proc passByVar(c: var char) =
c = 'x'
var s = "abc"
passByVar(s[0])

View File

@@ -1,61 +0,0 @@
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

View File

@@ -1,5 +1,5 @@
discard """
matrix: "--mm:refc; --mm:orc"
matrix: "--mm:refc"
targets: "cpp"
output: '''
caught as std::exception

View File

@@ -7,10 +7,8 @@ discard """
1.0
2.0
55
@[1, 2]
'''
"""
import std/strbasics
# Object variant / case object
type
@@ -81,14 +79,3 @@ let x = compute:
echo x
# Crash: bridge.nim(206, 5) `allowEmpty` unexpected nkEmpty [AssertionDefect]
# Bare closure iterator type alias
type IntIter = iterator(): int {.closure.}
proc run(it: IntIter): seq[int] =
result = @[]
for x in it():
result.add(x)
let gen: IntIter = iterator(): int {.closure.} =
yield 1
yield 2
echo run(gen)

View File

@@ -457,12 +457,3 @@ let runes1 = buggyVersion("en") # <-- CRASHES HERE
doAssert runes1.len == runes2.len
# echo "Got ", runes1.len, " runes"
block: # bug #25724
iterator c(): int =
when nimvm: yield 0
else: yield 1
for w in c():
let n = w
(proc() = discard n)()

View File

@@ -4,12 +4,7 @@ js 3.14
7
1
-21550
-21550
none(TT)
()
destroyed
destroyed
'''
-21550'''
"""
# This file tests the JavaScript generator
@@ -61,15 +56,3 @@ 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

View File

@@ -1,12 +0,0 @@
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]

View File

@@ -1,8 +0,0 @@
type
A* = object
discard
B* = object
discard
C* = A | B

View File

@@ -1,22 +0,0 @@
discard """
action: "compile"
"""
import deps/cisaorb
when true:
# These work fine.
discard default(cisaorb.A)
proc f1(x: cisaorb.A) = discard
discard default(cisaorb.B)
proc f2(x: cisaorb.B) = discard
discard default(A)
proc f3(x: A) = discard
discard default(B)
proc f4(x: B) = discard
proc f5(x: C) = discard
proc f6(x: cisaorb.C | C) = discard
proc doesWork(x: A | B) = discard
# Doesn't compile.
proc f(x: cisaorb.C) = discard

View File

@@ -434,32 +434,3 @@ 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)

View File

@@ -31,9 +31,6 @@ cmdShortOption key: v value: ''
cmdArgument key: ABC value: ''
cmdShortOption key: j value: '4'
cmdArgument key: ok value: ''
parseopt stdin
cmdShortOption key: j value: '4'
cmdShortOption key: value: ''
'''
joinable: false
"""
@@ -157,9 +154,3 @@ arg 6 ai.len:4 :{a7'b}"""
var n = parseopt.initOptParser("-j4 ok", shortnoVal = {'n'}, longnoVal = @["novalue"])
for kind, key, val in parseopt.getopt(n):
echo kind," key: ", key, " value: '", val, "'"
block: # fix #25738
echo "parseopt stdin"
var p = parseopt.initOptParser("-j4 -", shortNoVal = {'n'})
for kind, key, val in parseopt.getopt(p):
echo kind," key: ", key, " value: '", val, "'"

View File

@@ -1,87 +0,0 @@
# issue #25627
import std/tables
type
FsoKind = enum
fsoFile
fsoDir
fsoLink
FakeFso = ref object
kind: FsoKind
dirName: string
files: OrderedTable[string, FakeFso]
DirStruct = object
root = FakeFso(kind: fsoDir, dirName: "/")
let dir = DirStruct()
doAssert dir.root.kind == fsoDir
doAssert dir.root.dirName == "/"
doAssert dir.root.files.len == 0
block:
type
Opt[T] = object
when T is ref:
val: T
x: int
else:
val: T
x: string
DefaultOpt = ref object
files: Opt[DefaultOpt]
OptDirStruct = object
root = DefaultOpt()
let dir = OptDirStruct()
doAssert dir.root.files.x is int
block:
type
Opt[T] = object
when T is ref:
x: int
else:
x: string
Foo[T] = object
x: Opt[T]
Nested = ref object
files: Foo[Nested]
let nested = Nested()
doAssert nested.files.x.x is int
block:
type
Foo[T] = object
x = sizeof(T)
Sized = ref object
files: Foo[Sized]
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()

View File

@@ -833,37 +833,4 @@ proc overloaded[T: object](x: T) =
var v: typeof(val)
overloaded(v)
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
overloaded(Thing())

View File

@@ -27,11 +27,9 @@ t.curr = TokenObject(kind: Token.foo, foo: "foo")
echo "SUCCESS"
proc passToVar(x: var Token) = discard
proc passToPtr(x: ptr Token) = discard
{.cast(uncheckedAssign).}:
passToVar(t.curr.kind)
passToPtr(addr t.curr.kind)
t.curr = TokenObject(kind: t.curr.kind, foo: "abc")

View File

@@ -1,34 +0,0 @@
proc temp(one: int, two: int, three: int) =
discard
template temp(body: untyped): untyped =
body
temp:
proc a(tp: int) =
discard
proc mixedTemp(x: int) =
discard
proc mixedTemp(x: bool) =
discard
template mixedTemp(body: untyped): untyped =
body
# The `bool` proc should win here so `xx` survives
mixedTemp (let xx = 1; true)
discard xx
proc sinkTemp(x: int) =
discard
template sinkTemp(body: untyped): untyped =
discard
# Here the template should win here so `let xy` is sunk into template as AST
sinkTemp (let xy = "template"; xy)
when declared(xy):
{.error: "xy leaked from failed proc candidate".}

View File

@@ -549,20 +549,3 @@ 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)

View File

@@ -1,44 +0,0 @@
# 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

Some files were not shown because too many files have changed in this diff Show More