fixes testament compilation

This commit is contained in:
Araq 2018-05-14 17:45:44 +02:00
commit 5526252fa0
41 changed files with 985 additions and 297 deletions

View file

@ -72,6 +72,8 @@
- ``algorithm.smartBinarySearch`` and ``algorithm.binarySearch`` is - ``algorithm.smartBinarySearch`` and ``algorithm.binarySearch`` is
now joined in ``binarySearch``. ``smartbinarySearch`` is now now joined in ``binarySearch``. ``smartbinarySearch`` is now
deprecated. deprecated.
- The `terminal` module now exports additional procs for generating ANSI color
codes as strings.
### Language additions ### Language additions
@ -95,11 +97,18 @@
- ``nil`` for strings/seqs is finally gone. Instead the default value for - ``nil`` for strings/seqs is finally gone. Instead the default value for
these is ``"" / @[]``. these is ``"" / @[]``.
- Accessing the binary zero terminator in Nim's native strings - Accessing the binary zero terminator in Nim's native strings
is now invalid. Internally a Nim string still has the trailing zero for is now invalid. Internally a Nim string still has the trailing zero for
zero-copy interoperability with ``cstring``. Compile your code with the zero-copy interoperability with ``cstring``. Compile your code with the
new switch ``--laxStrings:on`` if you need a transition period. new switch ``--laxStrings:on`` if you need a transition period.
- The command syntax now supports keyword arguments after the first comma.
- Thread-local variables can now be declared inside procs. This implies all
the effects of the `global` pragma.
- Nim now supports `except` clause in the export statement.
### Tool changes ### Tool changes

View file

@ -1620,6 +1620,19 @@ proc originatingModule*(s: PSym): PSym =
proc isRoutine*(s: PSym): bool {.inline.} = proc isRoutine*(s: PSym): bool {.inline.} =
result = s.kind in skProcKinds result = s.kind in skProcKinds
proc isCompileTimeProc*(s: PSym): bool {.inline.} =
result = s.kind == skMacro or
s.kind == skProc and sfCompileTime in s.flags
proc requiredParams*(s: PSym): int =
# Returns the number of required params (without default values)
# XXX: Perhaps we can store this in the `offset` field of the
# symbol instead?
for i in 1 ..< s.typ.len:
if s.typ.n[i].sym.ast != nil:
return i - 1
return s.typ.len - 1
proc hasPattern*(s: PSym): bool {.inline.} = proc hasPattern*(s: PSym): bool {.inline.} =
result = isRoutine(s) and s.ast.sons[patternPos].kind != nkEmpty result = isRoutine(s) and s.ast.sons[patternPos].kind != nkEmpty
@ -1676,7 +1689,7 @@ proc isException*(t: PType): bool =
var base = t var base = t
while base != nil: while base != nil:
if base.sym.magic == mException: if base.sym != nil and base.sym.magic == mException:
return true return true
base = base.lastSon base = base.lastSon
return false return false

View file

@ -69,22 +69,22 @@ proc debug*(n: PNode) {.deprecated.}
template mdbg*: bool {.dirty.} = template mdbg*: bool {.dirty.} =
when compiles(c.module): when compiles(c.module):
c.module.fileIdx == gProjectMainIdx c.module.fileIdx.int32 == gProjectMainIdx
elif compiles(c.c.module): elif compiles(c.c.module):
c.c.module.fileIdx == gProjectMainIdx c.c.module.fileIdx.int32 == gProjectMainIdx
elif compiles(m.c.module): elif compiles(m.c.module):
m.c.module.fileIdx == gProjectMainIdx m.c.module.fileIdx.int32 == gProjectMainIdx
elif compiles(cl.c.module): elif compiles(cl.c.module):
cl.c.module.fileIdx == gProjectMainIdx cl.c.module.fileIdx.int32 == gProjectMainIdx
elif compiles(p): elif compiles(p):
when compiles(p.lex): when compiles(p.lex):
p.lex.fileIdx == gProjectMainIdx p.lex.fileIdx.int32 == gProjectMainIdx
else: else:
p.module.module.fileIdx == gProjectMainIdx p.module.module.fileIdx.int32 == gProjectMainIdx
elif compiles(m.module.fileIdx): elif compiles(m.module.fileIdx):
m.module.fileIdx == gProjectMainIdx m.module.fileIdx.int32 == gProjectMainIdx
elif compiles(L.fileIdx): elif compiles(L.fileIdx):
L.fileIdx == gProjectMainIdx L.fileIdx.int32 == gProjectMainIdx
else: else:
error() error()

View file

@ -69,7 +69,7 @@ proc evalTemplateArgs(n: PNode, s: PSym; conf: ConfigRef; fromHlo: bool): PNode
# if the template has zero arguments, it can be called without ``()`` # if the template has zero arguments, it can be called without ``()``
# `n` is then a nkSym or something similar # `n` is then a nkSym or something similar
var totalParams = case n.kind var totalParams = case n.kind
of nkCall, nkInfix, nkPrefix, nkPostfix, nkCommand, nkCallStrLit: n.len-1 of nkCallKinds: n.len-1
else: 0 else: 0
var var

View file

@ -70,9 +70,9 @@ proc parseLine(p: var TTmplParser) =
while j <= hi and p.x[j] == ' ': inc(j) while j <= hi and p.x[j] == ' ': inc(j)
if p.x[0] == p.nimDirective and p.x[1] == '?': if p.x.len >= 2 and p.x[0] == p.nimDirective and p.x[1] == '?':
newLine(p) newLine(p)
elif p.x[j] == p.nimDirective: elif j < p.x.len and p.x[j] == p.nimDirective:
newLine(p) newLine(p)
inc(j) inc(j)
while j <= hi and p.x[j] == ' ': inc(j) while j <= hi and p.x[j] == ' ': inc(j)

View file

@ -16,6 +16,13 @@ import
proc evalImport*(c: PContext, n: PNode): PNode proc evalImport*(c: PContext, n: PNode): PNode
proc evalFrom*(c: PContext, n: PNode): PNode proc evalFrom*(c: PContext, n: PNode): PNode
proc readExceptSet*(c: PContext, n: PNode): IntSet =
assert n.kind in {nkImportExceptStmt, nkExportExceptStmt}
result = initIntSet()
for i in 1 ..< n.len:
let ident = lookups.considerQuotedIdent(c.config, n[i])
result.incl(ident.id)
proc importPureEnumField*(c: PContext; s: PSym) = proc importPureEnumField*(c: PContext; s: PSym) =
var check = strTableGet(c.importTable.symbols, s.name) var check = strTableGet(c.importTable.symbols, s.name)
if check == nil: if check == nil:
@ -199,9 +206,5 @@ proc evalImportExcept*(c: PContext, n: PNode): PNode =
if m != nil: if m != nil:
n.sons[0] = newSymNode(m) n.sons[0] = newSymNode(m)
addDecl(c, m, n.info) # add symbol to symbol table of module addDecl(c, m, n.info) # add symbol to symbol table of module
var exceptSet = initIntSet() importAllSymbolsExcept(c, m, readExceptSet(c, n))
for i in countup(1, sonsLen(n) - 1):
let ident = lookups.considerQuotedIdent(c.config, n.sons[i])
exceptSet.incl(ident.id)
importAllSymbolsExcept(c, m, exceptSet)
#importForwarded(c, m.ast, exceptSet) #importForwarded(c, m.ast, exceptSet)

View file

@ -98,6 +98,7 @@ proc compileModule*(graph: ModuleGraph; fileIdx: FileIndex; cache: IdentCache, f
proc importModule*(graph: ModuleGraph; s: PSym, fileIdx: FileIndex; proc importModule*(graph: ModuleGraph; s: PSym, fileIdx: FileIndex;
cache: IdentCache): PSym {.procvar.} = cache: IdentCache): PSym {.procvar.} =
# this is called by the semantic checking phase # this is called by the semantic checking phase
assert graph.config != nil
result = compileModule(graph, fileIdx, cache, {}) result = compileModule(graph, fileIdx, cache, {})
graph.addDep(s, fileIdx) graph.addDep(s, fileIdx)
#if sfSystemModule in result.flags: #if sfSystemModule in result.flags:

View file

@ -8,7 +8,7 @@
# #
import strutils, lexbase, streams import strutils, lexbase, streams
import "../compiler" / [ast, msgs, idents] import ".." / [ast, msgs, idents]
from os import splitFile from os import splitFile
type type

View file

@ -713,10 +713,17 @@ proc namedParams(p: var TParser, callee: PNode,
# progress guaranteed # progress guaranteed
exprColonEqExprListAux(p, endTok, result) exprColonEqExprListAux(p, endTok, result)
proc commandParam(p: var TParser): PNode = proc commandParam(p: var TParser, isFirstParam: var bool): PNode =
result = parseExpr(p) result = parseExpr(p)
if p.tok.tokType == tkDo: if p.tok.tokType == tkDo:
result = postExprBlocks(p, result) result = postExprBlocks(p, result)
elif p.tok.tokType == tkEquals and not isFirstParam:
let lhs = result
result = newNodeP(nkExprEqExpr, p)
getTok(p)
addSon(result, lhs)
addSon(result, parseExpr(p))
isFirstParam = false
proc primarySuffix(p: var TParser, r: PNode, baseIndent: int): PNode = proc primarySuffix(p: var TParser, r: PNode, baseIndent: int): PNode =
#| primarySuffix = '(' (exprColonEqExpr comma?)* ')' doBlocks? #| primarySuffix = '(' (exprColonEqExpr comma?)* ')' doBlocks?
@ -759,10 +766,11 @@ proc primarySuffix(p: var TParser, r: PNode, baseIndent: int): PNode =
let a = result let a = result
result = newNodeP(nkCommand, p) result = newNodeP(nkCommand, p)
addSon(result, a) addSon(result, a)
var isFirstParam = true
when true: when true:
# progress NOT guaranteed # progress NOT guaranteed
p.hasProgress = false p.hasProgress = false
addSon result, commandParam(p) addSon result, commandParam(p, isFirstParam)
if not p.hasProgress: break if not p.hasProgress: break
else: else:
while p.tok.tokType != tkEof: while p.tok.tokType != tkEof:
@ -1314,17 +1322,18 @@ proc parseExprStmt(p: var TParser): PNode =
addSon(result, b) addSon(result, b)
else: else:
# simpleExpr parsed 'p a' from 'p a, b'? # simpleExpr parsed 'p a' from 'p a, b'?
var isFirstParam = false
if p.tok.indent < 0 and p.tok.tokType == tkComma and a.kind == nkCommand: if p.tok.indent < 0 and p.tok.tokType == tkComma and a.kind == nkCommand:
result = a result = a
while true: while true:
getTok(p) getTok(p)
optInd(p, result) optInd(p, result)
addSon(result, commandParam(p)) addSon(result, commandParam(p, isFirstParam))
if p.tok.tokType != tkComma: break if p.tok.tokType != tkComma: break
elif p.tok.indent < 0 and isExprStart(p): elif p.tok.indent < 0 and isExprStart(p):
result = newNode(nkCommand, a.info, @[a]) result = newNode(nkCommand, a.info, @[a])
while true: while true:
addSon(result, commandParam(p)) addSon(result, commandParam(p, isFirstParam))
if p.tok.tokType != tkComma: break if p.tok.tokType != tkComma: break
getTok(p) getTok(p)
optInd(p, result) optInd(p, result)

View file

@ -781,7 +781,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
incl(sym.flags, sfRegister) incl(sym.flags, sfRegister)
of wThreadVar: of wThreadVar:
noVal(c, it) noVal(c, it)
incl(sym.flags, sfThread) incl(sym.flags, {sfThread, sfGlobal})
of wDeadCodeElimUnused: discard # deprecated, dead code elim always on of wDeadCodeElimUnused: discard # deprecated, dead code elim always on
of wNoForward: pragmaNoForward(c, it) of wNoForward: pragmaNoForward(c, it)
of wReorder: pragmaNoForward(c, it, sfReorder) of wReorder: pragmaNoForward(c, it, sfReorder)

View file

@ -89,6 +89,7 @@ type
ambiguousSymbols*: IntSet # ids of all ambiguous symbols (cannot ambiguousSymbols*: IntSet # ids of all ambiguous symbols (cannot
# store this info in the syms themselves!) # store this info in the syms themselves!)
inGenericContext*: int # > 0 if we are in a generic type inGenericContext*: int # > 0 if we are in a generic type
inStaticContext*: int # > 0 if we are inside a static: block
inUnrolledContext*: int # > 0 if we are unrolling a loop inUnrolledContext*: int # > 0 if we are unrolling a loop
compilesContextId*: int # > 0 if we are in a ``compiles`` magic compilesContextId*: int # > 0 if we are in a ``compiles`` magic
compilesContextIdGenerator*: int compilesContextIdGenerator*: int

View file

@ -972,18 +972,20 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode =
else: else:
result = newSymNode(s, n.info) result = newSymNode(s, n.info)
of skMacro: of skMacro:
if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0: if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0 or
(n.kind notin nkCallKinds and s.requiredParams > 0):
markUsed(c.config, n.info, s, c.graph.usageSym) markUsed(c.config, n.info, s, c.graph.usageSym)
styleCheckUse(n.info, s) styleCheckUse(n.info, s)
result = newSymNode(s, n.info) result = symChoice(c, n, s, scClosed)
else: else:
result = semMacroExpr(c, n, n, s, flags) result = semMacroExpr(c, n, n, s, flags)
of skTemplate: of skTemplate:
if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0 or if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0 or
(n.kind notin nkCallKinds and s.requiredParams > 0) or
sfCustomPragma in sym.flags: sfCustomPragma in sym.flags:
markUsed(c.config, n.info, s, c.graph.usageSym) markUsed(c.config, n.info, s, c.graph.usageSym)
styleCheckUse(n.info, s) styleCheckUse(n.info, s)
result = newSymNode(s, n.info) result = symChoice(c, n, s, scClosed)
else: else:
result = semTemplateExpr(c, n, s, flags) result = semTemplateExpr(c, n, s, flags)
of skParam: of skParam:
@ -1785,6 +1787,7 @@ proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
let oldInGenericContext = c.inGenericContext let oldInGenericContext = c.inGenericContext
let oldInUnrolledContext = c.inUnrolledContext let oldInUnrolledContext = c.inUnrolledContext
let oldInGenericInst = c.inGenericInst let oldInGenericInst = c.inGenericInst
let oldInStaticContext = c.inStaticContext
let oldProcCon = c.p let oldProcCon = c.p
c.generics = @[] c.generics = @[]
var err: string var err: string
@ -1799,6 +1802,7 @@ proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
c.inGenericContext = oldInGenericContext c.inGenericContext = oldInGenericContext
c.inUnrolledContext = oldInUnrolledContext c.inUnrolledContext = oldInUnrolledContext
c.inGenericInst = oldInGenericInst c.inGenericInst = oldInGenericInst
c.inStaticContext = oldInStaticContext
c.p = oldProcCon c.p = oldProcCon
msgs.setInfoContextLen(oldContextLen) msgs.setInfoContextLen(oldContextLen)
setLen(c.graph.owners, oldOwnerLen) setLen(c.graph.owners, oldOwnerLen)
@ -2163,9 +2167,25 @@ proc semBlock(c: PContext, n: PNode): PNode =
closeScope(c) closeScope(c)
dec(c.p.nestedBlockCounter) dec(c.p.nestedBlockCounter)
proc semExportExcept(c: PContext, n: PNode): PNode =
let moduleName = semExpr(c, n[0])
if moduleName.kind != nkSym or moduleName.sym.kind != skModule:
localError(c.config, n.info, "The export/except syntax expects a module name")
return n
let exceptSet = readExceptSet(c, n)
let exported = moduleName.sym
strTableAdd(c.module.tab, exported)
var i: TTabIter
var s = initTabIter(i, exported.tab)
while s != nil:
if s.kind in ExportableSymKinds+{skModule} and
s.name.id notin exceptSet:
strTableAdd(c.module.tab, s)
s = nextIter(i, exported.tab)
result = n
proc semExport(c: PContext, n: PNode): PNode = proc semExport(c: PContext, n: PNode): PNode =
var x = newNodeI(n.kind, n.info) var x = newNodeI(n.kind, n.info)
#let L = if n.kind == nkExportExceptStmt: L = 1 else: n.len
for i in 0..<n.len: for i in 0..<n.len:
let a = n.sons[i] let a = n.sons[i]
var o: TOverloadIter var o: TOverloadIter
@ -2451,9 +2471,12 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
of nkIncludeStmt: of nkIncludeStmt:
#if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "include") #if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "include")
result = evalInclude(c, n) result = evalInclude(c, n)
of nkExportStmt, nkExportExceptStmt: of nkExportStmt:
if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "export") if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "export")
result = semExport(c, n) result = semExport(c, n)
of nkExportExceptStmt:
if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "export")
result = semExportExcept(c, n)
of nkPragmaBlock: of nkPragmaBlock:
result = semPragmaBlock(c, n) result = semPragmaBlock(c, n)
of nkStaticStmt: of nkStaticStmt:

View file

@ -199,6 +199,10 @@ proc semBindSym(c: PContext, n: PNode): PNode =
if s != nil: if s != nil:
# we need to mark all symbols: # we need to mark all symbols:
var sc = symChoice(c, id, s, TSymChoiceRule(isMixin.intVal)) var sc = symChoice(c, id, s, TSymChoiceRule(isMixin.intVal))
if not (c.inStaticContext > 0 or getCurrOwner(c).isCompileTimeProc):
# inside regular code, bindSym resolves to the sym-choice
# nodes (see tinspectsymbol)
return sc
result.add(sc) result.add(sc)
else: else:
errorUndeclaredIdentifier(c, n.sons[1].info, sl.strVal) errorUndeclaredIdentifier(c, n.sons[1].info, sl.strVal)

View file

@ -1765,7 +1765,9 @@ proc semPragmaBlock(c: PContext, n: PNode): PNode =
proc semStaticStmt(c: PContext, n: PNode): PNode = proc semStaticStmt(c: PContext, n: PNode): PNode =
#echo "semStaticStmt" #echo "semStaticStmt"
#writeStackTrace() #writeStackTrace()
inc c.inStaticContext
let a = semStmt(c, n.sons[0]) let a = semStmt(c, n.sons[0])
dec c.inStaticContext
n.sons[0] = a n.sons[0] = a
evalStaticStmt(c.module, c.cache, c.graph, a, c.p.owner) evalStaticStmt(c.module, c.cache, c.graph, a, c.p.owner)
result = newNodeI(nkDiscardStmt, n.info, 1) result = newNodeI(nkDiscardStmt, n.info, 1)

View file

@ -1964,7 +1964,8 @@ proc paramTypesMatchAux(m: var TCandidate, f, a: PType,
inc(m.genericMatches) inc(m.genericMatches)
if arg.typ == nil: if arg.typ == nil:
result = arg result = arg
elif skipTypes(arg.typ, abstractVar-{tyTypeDesc}).kind == tyTuple: elif skipTypes(arg.typ, abstractVar-{tyTypeDesc}).kind == tyTuple or
m.inheritancePenalty > 0:
result = implicitConv(nkHiddenSubConv, f, arg, m, c) result = implicitConv(nkHiddenSubConv, f, arg, m, c)
elif arg.typ.isEmptyContainer: elif arg.typ.isEmptyContainer:
result = arg.copyTree result = arg.copyTree
@ -2034,8 +2035,9 @@ proc paramTypesMatch*(m: var TCandidate, f, a: PType,
y.calleeSym = m.calleeSym y.calleeSym = m.calleeSym
z.calleeSym = m.calleeSym z.calleeSym = m.calleeSym
var best = -1 var best = -1
for i in countup(0, sonsLen(arg) - 1): for i in 0 ..< arg.len:
if arg.sons[i].sym.kind in {skProc, skFunc, skMethod, skConverter, skIterator}: if arg.sons[i].sym.kind in {skProc, skFunc, skMethod, skConverter,
skIterator, skMacro, skTemplate}:
copyCandidate(z, m) copyCandidate(z, m)
z.callee = arg.sons[i].typ z.callee = arg.sons[i].typ
if tfUnresolved in z.callee.flags: continue if tfUnresolved in z.callee.flags: continue
@ -2064,6 +2066,7 @@ proc paramTypesMatch*(m: var TCandidate, f, a: PType,
x = z x = z
elif cmp == 0: elif cmp == 0:
y = z # z is as good as x y = z # z is as good as x
if x.state == csEmpty: if x.state == csEmpty:
result = nil result = nil
elif y.state == csMatch and cmpCandidates(x, y) == 0: elif y.state == csMatch and cmpCandidates(x, y) == 0:
@ -2072,7 +2075,7 @@ proc paramTypesMatch*(m: var TCandidate, f, a: PType,
# ambiguous: more than one symbol fits! # ambiguous: more than one symbol fits!
# See tsymchoice_for_expr as an example. 'f.kind == tyExpr' should match # See tsymchoice_for_expr as an example. 'f.kind == tyExpr' should match
# anyway: # anyway:
if f.kind == tyExpr: result = arg if f.kind in {tyExpr, tyStmt}: result = arg
else: result = nil else: result = nil
else: else:
# only one valid interpretation found: # only one valid interpretation found:
@ -2172,7 +2175,8 @@ proc matchesAux(c: PContext, n, nOrig: PNode,
var formal: PSym = if formalLen > 1: m.callee.n.sons[1].sym else: nil var formal: PSym = if formalLen > 1: m.callee.n.sons[1].sym else: nil
while a < n.len: while a < n.len:
if a >= formalLen-1 and formal != nil and formal.typ.isVarargsUntyped: if a >= formalLen-1 and f < formalLen and m.callee.n[f].typ.isVarargsUntyped:
formal = m.callee.n.sons[f].sym
incl(marker, formal.position) incl(marker, formal.position)
if container.isNil: if container.isNil:
container = newNodeIT(nkArgList, n.sons[a].info, arrayConstr(c, n.info)) container = newNodeIT(nkArgList, n.sons[a].info, arrayConstr(c, n.info))

View file

@ -117,6 +117,7 @@ proc applyFilter(p: var TParsers, n: PNode, filename: string,
of filtReplace: of filtReplace:
result = filterReplace(p.config, stdin, filename, n) result = filterReplace(p.config, stdin, filename, n)
if f != filtNone: if f != filtNone:
assert p.config != nil
if hintCodeBegin in p.config.notes: if hintCodeBegin in p.config.notes:
rawMessage(p.config, hintCodeBegin, []) rawMessage(p.config, hintCodeBegin, [])
msgWriteln(p.config, result.s) msgWriteln(p.config, result.s)
@ -124,6 +125,7 @@ proc applyFilter(p: var TParsers, n: PNode, filename: string,
proc evalPipe(p: var TParsers, n: PNode, filename: string, proc evalPipe(p: var TParsers, n: PNode, filename: string,
start: PLLStream): PLLStream = start: PLLStream): PLLStream =
assert p.config != nil
result = start result = start
if n.kind == nkEmpty: return if n.kind == nkEmpty: return
if n.kind == nkInfix and n[0].kind == nkIdent and n[0].ident.s == "|": if n.kind == nkInfix and n[0].kind == nkIdent and n[0].ident.s == "|":
@ -139,10 +141,12 @@ proc evalPipe(p: var TParsers, n: PNode, filename: string,
proc openParsers*(p: var TParsers, fileIdx: FileIndex, inputstream: PLLStream; proc openParsers*(p: var TParsers, fileIdx: FileIndex, inputstream: PLLStream;
cache: IdentCache; config: ConfigRef) = cache: IdentCache; config: ConfigRef) =
assert config != nil
var s: PLLStream var s: PLLStream
p.skin = skinStandard p.skin = skinStandard
let filename = fileIdx.toFullPathConsiderDirty let filename = fileIdx.toFullPathConsiderDirty
var pipe = parsePipe(filename, inputstream, cache, config) var pipe = parsePipe(filename, inputstream, cache, config)
p.config() = config
if pipe != nil: s = evalPipe(p, pipe, filename, inputstream) if pipe != nil: s = evalPipe(p, pipe, filename, inputstream)
else: s = inputstream else: s = inputstream
case p.skin case p.skin

View file

@ -614,7 +614,7 @@ proc firstOrd*(t: PType): BiggestInt =
else: else:
assert(t.n.sons[0].kind == nkSym) assert(t.n.sons[0].kind == nkSym)
result = t.n.sons[0].sym.position result = t.n.sons[0].sym.position
of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias, tyStatic: of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias, tyStatic, tyInferred:
result = firstOrd(lastSon(t)) result = firstOrd(lastSon(t))
of tyOrdinal: of tyOrdinal:
if t.len > 0: result = firstOrd(lastSon(t)) if t.len > 0: result = firstOrd(lastSon(t))
@ -653,7 +653,7 @@ proc lastOrd*(t: PType; fixedUnsigned = false): BiggestInt =
of tyEnum: of tyEnum:
assert(t.n.sons[sonsLen(t.n) - 1].kind == nkSym) assert(t.n.sons[sonsLen(t.n) - 1].kind == nkSym)
result = t.n.sons[sonsLen(t.n) - 1].sym.position result = t.n.sons[sonsLen(t.n) - 1].sym.position
of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias, tyStatic: of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias, tyStatic, tyInferred:
result = lastOrd(lastSon(t)) result = lastOrd(lastSon(t))
of tyProxy: result = 0 of tyProxy: result = 0
of tyOrdinal: of tyOrdinal:

View file

@ -210,8 +210,14 @@ proc putIntoNode(n: var PNode; x: TFullReg) =
of rkInt: n.intVal = x.intVal of rkInt: n.intVal = x.intVal
of rkFloat: n.floatVal = x.floatVal of rkFloat: n.floatVal = x.floatVal
of rkNode: of rkNode:
if nfIsRef in x.node.flags: n = x.node if nfIsRef in x.node.flags:
else: n[] = x.node[] n = x.node
else:
let destIsRef = nfIsRef in n.flags
n[] = x.node[]
# Ref-ness must be kept for the destination
if destIsRef:
n.flags.incl nfIsRef
of rkRegisterAddr: putIntoNode(n, x.regAddr[]) of rkRegisterAddr: putIntoNode(n, x.regAddr[])
of rkNodeAddr: n[] = x.nodeAddr[][] of rkNodeAddr: n[] = x.nodeAddr[][]

View file

@ -118,7 +118,7 @@ path="$lib/pure"
# Configuration for the GNU C/C++ compiler: # Configuration for the GNU C/C++ compiler:
@if windows: @if windows:
#gcc.path = r"$nim\dist\mingw\bin" #gcc.path = r"$nim\dist\mingw\bin"
@if gcc: @if gcc or tcc:
tlsEmulation:on tlsEmulation:on
@end @end
@end @end

View file

@ -6298,6 +6298,9 @@ modules don't need to import a module's dependencies:
var x: MyObject var x: MyObject
echo $x echo $x
When the exported symbol is another module, all of its definitions will
be forwarded. You can use an ``except`` list to exclude some of the symbols.
Note on paths Note on paths
----------- -----------
In module related statements, if any part of the module name / In module related statements, if any part of the module name /
@ -7798,8 +7801,9 @@ Future directions:
Threadvar pragma Threadvar pragma
---------------- ----------------
A global variable can be marked with the ``threadvar`` pragma; it is A variable can be marked with the ``threadvar`` pragma, which makes it a
a `thread-local`:idx: variable then: `thread-local`:idx: variable; Additionally, this implies all the effects
of the ``global`` pragma.
.. code-block:: nim .. code-block:: nim
var checkpoints* {.threadvar.}: seq[string] var checkpoints* {.threadvar.}: seq[string]

View file

@ -385,7 +385,7 @@ type
{.deprecated: [TBindSymRule: BindSymRule].} {.deprecated: [TBindSymRule: BindSymRule].}
proc bindSym*(ident: string, rule: BindSymRule = brClosed): NimNode {. proc bindSym*(ident: static[string], rule: BindSymRule = brClosed): NimNode {.
magic: "NBindSym", noSideEffect.} magic: "NBindSym", noSideEffect.}
## creates a node that binds `ident` to a symbol node. The bound symbol ## creates a node that binds `ident` to a symbol node. The bound symbol
## may be an overloaded symbol. ## may be an overloaded symbol.

View file

@ -126,6 +126,7 @@ proc tryExec*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): bool {. args: varargs[string, `$`]): bool {.
tags: [ReadDbEffect, WriteDbEffect].} = tags: [ReadDbEffect, WriteDbEffect].} =
## tries to execute the query and returns true if successful, false otherwise. ## tries to execute the query and returns true if successful, false otherwise.
assert(not db.isNil, "Database not connected.")
var q = dbFormat(query, args) var q = dbFormat(query, args)
var stmt: sqlite3.Pstmt var stmt: sqlite3.Pstmt
if prepare_v2(db, q, q.len.cint, stmt, nil) == SQLITE_OK: if prepare_v2(db, q, q.len.cint, stmt, nil) == SQLITE_OK:
@ -144,6 +145,7 @@ proc newRow(L: int): Row =
proc setupQuery(db: DbConn, query: SqlQuery, proc setupQuery(db: DbConn, query: SqlQuery,
args: varargs[string]): Pstmt = args: varargs[string]): Pstmt =
assert(not db.isNil, "Database not connected.")
var q = dbFormat(query, args) var q = dbFormat(query, args)
if prepare_v2(db, q, q.len.cint, result, nil) != SQLITE_OK: dbError(db) if prepare_v2(db, q, q.len.cint, result, nil) != SQLITE_OK: dbError(db)
@ -267,6 +269,7 @@ proc tryInsertID*(db: DbConn, query: SqlQuery,
{.tags: [WriteDbEffect], raises: [].} = {.tags: [WriteDbEffect], raises: [].} =
## executes the query (typically "INSERT") and returns the ## executes the query (typically "INSERT") and returns the
## generated ID for the row or -1 in case of an error. ## generated ID for the row or -1 in case of an error.
assert(not db.isNil, "Database not connected.")
var q = dbFormat(query, args) var q = dbFormat(query, args)
var stmt: sqlite3.Pstmt var stmt: sqlite3.Pstmt
result = -1 result = -1

View file

@ -62,6 +62,7 @@ type
frames*: seq[TFrame] frames*: seq[TFrame]
screen*: Screen screen*: Screen
performance*: Performance performance*: Performance
onpopstate*: proc (event: Event)
Frame* = ref FrameObj Frame* = ref FrameObj
FrameObj {.importc.} = object of WindowObj FrameObj {.importc.} = object of WindowObj
@ -175,6 +176,12 @@ type
text*: cstring text*: cstring
value*: cstring value*: cstring
TextAreaElement* = ref object of ElementObj
value*: cstring
selectionStart*, selectionEnd*: int
selectionDirection*: cstring
rows*, cols*: int
FormElement* = ref FormObj FormElement* = ref FormObj
FormObj {.importc.} = object of ElementObj FormObj {.importc.} = object of ElementObj
action*: cstring action*: cstring
@ -446,6 +453,7 @@ type
proc addEventListener*(et: EventTarget, ev: cstring, cb: proc(ev: Event), useCapture: bool = false) proc addEventListener*(et: EventTarget, ev: cstring, cb: proc(ev: Event), useCapture: bool = false)
proc addEventListener*(et: EventTarget, ev: cstring, cb: proc(ev: Event), options: AddEventListenerOptions) proc addEventListener*(et: EventTarget, ev: cstring, cb: proc(ev: Event), options: AddEventListenerOptions)
proc removeEventListener*(et: EventTarget, ev: cstring, cb: proc(ev: Event), useCapture: bool = false) proc removeEventListener*(et: EventTarget, ev: cstring, cb: proc(ev: Event), useCapture: bool = false)
proc dispatchEvent*(et: EventTarget, ev: Event)
# Window "methods" # Window "methods"
proc alert*(w: Window, msg: cstring) proc alert*(w: Window, msg: cstring)
@ -500,7 +508,7 @@ proc removeAttributeNode*(n, attr: Node)
proc removeChild*(n, child: Node) proc removeChild*(n, child: Node)
proc replaceChild*(n, newNode, oldNode: Node) proc replaceChild*(n, newNode, oldNode: Node)
proc replaceData*(n: Node, start, len: int, text: cstring) proc replaceData*(n: Node, start, len: int, text: cstring)
proc scrollIntoView*(n: Node) proc scrollIntoView*(n: Node, alignToTop: bool=true)
proc setAttribute*(n: Node, name, value: cstring) proc setAttribute*(n: Node, name, value: cstring)
proc setAttributeNode*(n: Node, attr: Node) proc setAttributeNode*(n: Node, attr: Node)
@ -596,6 +604,7 @@ proc parseFloat*(s: cstring): BiggestFloat {.importc, nodecl.}
proc parseInt*(s: cstring): int {.importc, nodecl.} proc parseInt*(s: cstring): int {.importc, nodecl.}
proc parseInt*(s: cstring, radix: int):int {.importc, nodecl.} proc parseInt*(s: cstring, radix: int):int {.importc, nodecl.}
proc newEvent*(name: cstring): Event {.importcpp: "new Event(@)", constructor.}
type type
TEventHandlers* {.deprecated.} = EventTargetObj TEventHandlers* {.deprecated.} = EventTargetObj

View file

@ -120,6 +120,13 @@ iterator items*[A](s: HashSet[A]): A =
for h in 0..high(s.data): for h in 0..high(s.data):
if isFilled(s.data[h].hcode): yield s.data[h].key if isFilled(s.data[h].hcode): yield s.data[h].key
proc hash*[A](s: HashSet[A]): Hash =
## hashing of HashSet
assert s.isValid, "The set needs to be initialized."
for h in 0..high(s.data):
result = result xor s.data[h].hcode
result = !$result
const const
growthFactor = 2 growthFactor = 2
@ -690,6 +697,13 @@ iterator items*[A](s: OrderedSet[A]): A =
forAllOrderedPairs: forAllOrderedPairs:
yield s.data[h].key yield s.data[h].key
proc hash*[A](s: OrderedSet[A]): Hash =
## hashing of OrderedSet
assert s.isValid, "The set needs to be initialized."
forAllOrderedPairs:
result = result !& s.data[h].hcode
result = !$result
iterator pairs*[A](s: OrderedSet[A]): tuple[a: int, b: A] = iterator pairs*[A](s: OrderedSet[A]): tuple[a: int, b: A] =
assert s.isValid, "The set needs to be initialized" assert s.isValid, "The set needs to be initialized"
forAllOrderedPairs: forAllOrderedPairs:

View file

@ -331,7 +331,7 @@ proc slave(w: ptr Worker) {.thread.} =
await(w.taskArrived) await(w.taskArrived)
# XXX Somebody needs to look into this (why does this assertion fail # XXX Somebody needs to look into this (why does this assertion fail
# in Visual Studio?) # in Visual Studio?)
when not defined(vcc): assert(not w.ready) when not defined(vcc) and not defined(tcc): assert(not w.ready)
withLock numSlavesLock: withLock numSlavesLock:
inc numSlavesRunning inc numSlavesRunning

View file

@ -960,7 +960,7 @@ when defined(posix) and not defined(nimdoc):
raise newException(ValueError, "socket path too long") raise newException(ValueError, "socket path too long")
copyMem(addr result.sun_path, path.cstring, path.len + 1) copyMem(addr result.sun_path, path.cstring, path.len + 1)
when defined(posix): when defined(posix) or defined(nimdoc):
proc connectUnix*(socket: Socket, path: string) = proc connectUnix*(socket: Socket, path: string) =
## Connects to Unix socket on `path`. ## Connects to Unix socket on `path`.
## This only works on Unix-style systems: Mac OS X, BSD and Linux ## This only works on Unix-style systems: Mac OS X, BSD and Linux

View file

@ -1262,21 +1262,20 @@ proc initSkipTable*(a: var SkipTable, sub: string)
{.noSideEffect, rtl, extern: "nsuInitSkipTable".} = {.noSideEffect, rtl, extern: "nsuInitSkipTable".} =
## Preprocess table `a` for `sub`. ## Preprocess table `a` for `sub`.
let m = len(sub) let m = len(sub)
let m1 = m + 1
var i = 0 var i = 0
while i <= 0xff-7: while i <= 0xff-7:
a[chr(i + 0)] = m1 a[chr(i + 0)] = m
a[chr(i + 1)] = m1 a[chr(i + 1)] = m
a[chr(i + 2)] = m1 a[chr(i + 2)] = m
a[chr(i + 3)] = m1 a[chr(i + 3)] = m
a[chr(i + 4)] = m1 a[chr(i + 4)] = m
a[chr(i + 5)] = m1 a[chr(i + 5)] = m
a[chr(i + 6)] = m1 a[chr(i + 6)] = m
a[chr(i + 7)] = m1 a[chr(i + 7)] = m
i += 8 i += 8
for i in 0..m-1: for i in 0 ..< m - 1:
a[sub[i]] = m-i a[sub[i]] = m - 1 - i
proc find*(a: SkipTable, s, sub: string, start: Natural = 0, last: Natural = 0): int proc find*(a: SkipTable, s, sub: string, start: Natural = 0, last: Natural = 0): int
{.noSideEffect, rtl, extern: "nsuFindStrA".} = {.noSideEffect, rtl, extern: "nsuFindStrA".} =
@ -1284,18 +1283,29 @@ proc find*(a: SkipTable, s, sub: string, start: Natural = 0, last: Natural = 0):
## If `last` is unspecified, it defaults to `s.high`. ## If `last` is unspecified, it defaults to `s.high`.
## ##
## Searching is case-sensitive. If `sub` is not in `s`, -1 is returned. ## Searching is case-sensitive. If `sub` is not in `s`, -1 is returned.
let let
last = if last==0: s.high else: last last = if last==0: s.high else: last
m = len(sub) sLen = last - start + 1
n = last + 1 subLast = sub.len - 1
# search:
var j = start if subLast == -1:
while j <= n - m: # this was an empty needle string,
block match: # we count this as match in the first possible position:
for k in 0..m-1: return start
if sub[k] != s[k+j]: break match
return j # This is an implementation of the Boyer-Moore Horspool algorithms
inc(j, a[s[j+m]]) # https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore%E2%80%93Horspool_algorithm
var skip = start
while last - skip >= subLast:
var i = subLast
while s[skip + i] == sub[i]:
if i == 0:
return skip
dec i
inc skip, a[s[skip + subLast]]
return -1 return -1
when not (defined(js) or defined(nimdoc) or defined(nimscript)): when not (defined(js) or defined(nimdoc) or defined(nimscript)):
@ -1455,23 +1465,41 @@ proc contains*(s: string, chars: set[char]): bool {.noSideEffect.} =
proc replace*(s, sub: string, by = ""): string {.noSideEffect, proc replace*(s, sub: string, by = ""): string {.noSideEffect,
rtl, extern: "nsuReplaceStr".} = rtl, extern: "nsuReplaceStr".} =
## Replaces `sub` in `s` by the string `by`. ## Replaces `sub` in `s` by the string `by`.
var a {.noinit.}: SkipTable
result = "" result = ""
initSkipTable(a, sub) let subLen = sub.len
let last = s.high if subLen == 0:
var i = 0 for c in s:
while true: add result, by
let j = find(a, s, sub, i, last) add result, c
if j < 0: break
add result, substr(s, i, j - 1)
add result, by add result, by
if sub.len == 0: return
if i < s.len: add result, s[i] elif subLen == 1:
i = j + 1 # when the pattern is a single char, we use a faster
else: # char-based search that doesn't need a skip table:
i = j + sub.len var c = sub[0]
# copy the rest: let last = s.high
add result, substr(s, i) var i = 0
while true:
let j = find(s, c, i, last)
if j < 0: break
add result, substr(s, i, j - 1)
add result, by
i = j + subLen
# copy the rest:
add result, substr(s, i)
else:
var a {.noinit.}: SkipTable
initSkipTable(a, sub)
let last = s.high
var i = 0
while true:
let j = find(a, s, sub, i, last)
if j < 0: break
add result, substr(s, i, j - 1)
add result, by
i = j + subLen
# copy the rest:
add result, substr(s, i)
proc replace*(s: string, sub, by: char): string {.noSideEffect, proc replace*(s: string, sub, by: char): string {.noSideEffect,
rtl, extern: "nsuReplaceChar".} = rtl, extern: "nsuReplaceChar".} =
@ -1492,6 +1520,7 @@ proc replaceWord*(s, sub: string, by = ""): string {.noSideEffect,
## Each occurrence of `sub` has to be surrounded by word boundaries ## Each occurrence of `sub` has to be surrounded by word boundaries
## (comparable to ``\\w`` in regular expressions), otherwise it is not ## (comparable to ``\\w`` in regular expressions), otherwise it is not
## replaced. ## replaced.
if sub.len == 0: return s
const wordChars = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\128'..'\255'} const wordChars = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\128'..'\255'}
var a {.noinit.}: SkipTable var a {.noinit.}: SkipTable
result = "" result = ""
@ -2326,236 +2355,243 @@ proc removePrefix*(s: var string, prefix: string) {.
s.delete(0, prefix.len - 1) s.delete(0, prefix.len - 1)
when isMainModule: when isMainModule:
doAssert align("abc", 4) == " abc" proc nonStaticTests =
doAssert align("a", 0) == "a" doAssert formatBiggestFloat(1234.567, ffDecimal, -1) == "1234.567000"
doAssert align("1232", 6) == " 1232" doAssert formatBiggestFloat(1234.567, ffDecimal, 0) == "1235."
doAssert align("1232", 6, '#') == "##1232" doAssert formatBiggestFloat(1234.567, ffDecimal, 1) == "1234.6"
doAssert formatBiggestFloat(0.00000000001, ffDecimal, 11) == "0.00000000001"
doAssert formatBiggestFloat(0.00000000001, ffScientific, 1, ',') in
["1,0e-11", "1,0e-011"]
# bug #6589
doAssert formatFloat(123.456, ffScientific, precision = -1) == "1.234560e+02"
doAssert alignLeft("abc", 4) == "abc " doAssert "$# $3 $# $#" % ["a", "b", "c"] == "a c b c"
doAssert alignLeft("a", 0) == "a" doAssert "${1}12 ${-1}$2" % ["a", "b"] == "a12 bb"
doAssert alignLeft("1232", 6) == "1232 "
doAssert alignLeft("1232", 6, '#') == "1232##"
let block: # formatSize tests
inp = """ this is a long text -- muchlongerthan10chars and here doAssert formatSize((1'i64 shl 31) + (300'i64 shl 20)) == "2.293GiB"
it goes""" doAssert formatSize((2.234*1024*1024).int) == "2.234MiB"
outp = " this is a\nlong text\n--\nmuchlongerthan10chars\nand here\nit goes" doAssert formatSize(4096) == "4KiB"
doAssert wordWrap(inp, 10, false) == outp doAssert formatSize(4096, prefix=bpColloquial, includeSpace=true) == "4 kB"
doAssert formatSize(4096, includeSpace=true) == "4 KiB"
doAssert formatSize(5_378_934, prefix=bpColloquial, decimalSep=',') == "5,13MB"
let block: # formatEng tests
longInp = """ThisIsOneVeryLongStringWhichWeWillSplitIntoEightSeparatePartsNow""" doAssert formatEng(0, 2, trim=false) == "0.00"
longOutp = "ThisIsOn\neVeryLon\ngStringW\nhichWeWi\nllSplitI\nntoEight\nSeparate\nPartsNow" doAssert formatEng(0, 2) == "0"
doAssert wordWrap(longInp, 8, true) == longOutp doAssert formatEng(53, 2, trim=false) == "53.00"
doAssert formatEng(0.053, 2, trim=false) == "53.00e-3"
doAssert formatEng(0.053, 4, trim=false) == "53.0000e-3"
doAssert formatEng(0.053, 4, trim=true) == "53e-3"
doAssert formatEng(0.053, 0) == "53e-3"
doAssert formatEng(52731234) == "52.731234e6"
doAssert formatEng(-52731234) == "-52.731234e6"
doAssert formatEng(52731234, 1) == "52.7e6"
doAssert formatEng(-52731234, 1) == "-52.7e6"
doAssert formatEng(52731234, 1, decimalSep=',') == "52,7e6"
doAssert formatEng(-52731234, 1, decimalSep=',') == "-52,7e6"
doAssert formatBiggestFloat(1234.567, ffDecimal, -1) == "1234.567000" doAssert formatEng(4100, siPrefix=true, unit="V") == "4.1 kV"
doAssert formatBiggestFloat(1234.567, ffDecimal, 0) == "1235." doAssert formatEng(4.1, siPrefix=true, unit="V", useUnitSpace=true) == "4.1 V"
doAssert formatBiggestFloat(1234.567, ffDecimal, 1) == "1234.6" doAssert formatEng(4.1, siPrefix=true) == "4.1" # Note lack of space
doAssert formatBiggestFloat(0.00000000001, ffDecimal, 11) == "0.00000000001" doAssert formatEng(4100, siPrefix=true) == "4.1 k"
doAssert formatBiggestFloat(0.00000000001, ffScientific, 1, ',') in doAssert formatEng(4.1, siPrefix=true, unit="", useUnitSpace=true) == "4.1 " # Includes space
["1,0e-11", "1,0e-011"] doAssert formatEng(4100, siPrefix=true, unit="") == "4.1 k"
# bug #6589 doAssert formatEng(4100) == "4.1e3"
doAssert formatFloat(123.456, ffScientific, precision = -1) == "1.234560e+02" doAssert formatEng(4100, unit="V", useUnitSpace=true) == "4.1e3 V"
doAssert formatEng(4100, unit="", useUnitSpace=true) == "4.1e3 "
# Don't use SI prefix as number is too big
doAssert formatEng(3.1e22, siPrefix=true, unit="a", useUnitSpace=true) == "31e21 a"
# Don't use SI prefix as number is too small
doAssert formatEng(3.1e-25, siPrefix=true, unit="A", useUnitSpace=true) == "310e-27 A"
doAssert "$# $3 $# $#" % ["a", "b", "c"] == "a c b c" proc staticTests =
doAssert "${1}12 ${-1}$2" % ["a", "b"] == "a12 bb" doAssert align("abc", 4) == " abc"
doAssert align("a", 0) == "a"
doAssert align("1232", 6) == " 1232"
doAssert align("1232", 6, '#') == "##1232"
block: # formatSize tests doAssert alignLeft("abc", 4) == "abc "
doAssert formatSize((1'i64 shl 31) + (300'i64 shl 20)) == "2.293GiB" doAssert alignLeft("a", 0) == "a"
doAssert formatSize((2.234*1024*1024).int) == "2.234MiB" doAssert alignLeft("1232", 6) == "1232 "
doAssert formatSize(4096) == "4KiB" doAssert alignLeft("1232", 6, '#') == "1232##"
doAssert formatSize(4096, prefix=bpColloquial, includeSpace=true) == "4 kB"
doAssert formatSize(4096, includeSpace=true) == "4 KiB"
doAssert formatSize(5_378_934, prefix=bpColloquial, decimalSep=',') == "5,13MB"
doAssert "$animal eats $food." % ["animal", "The cat", "food", "fish"] == let
"The cat eats fish." inp = """ this is a long text -- muchlongerthan10chars and here
it goes"""
outp = " this is a\nlong text\n--\nmuchlongerthan10chars\nand here\nit goes"
doAssert wordWrap(inp, 10, false) == outp
doAssert "-ld a-ldz -ld".replaceWord("-ld") == " a-ldz " let
doAssert "-lda-ldz -ld abc".replaceWord("-ld") == "-lda-ldz abc" longInp = """ThisIsOneVeryLongStringWhichWeWillSplitIntoEightSeparatePartsNow"""
longOutp = "ThisIsOn\neVeryLon\ngStringW\nhichWeWi\nllSplitI\nntoEight\nSeparate\nPartsNow"
doAssert wordWrap(longInp, 8, true) == longOutp
doAssert "-lda-ldz -ld abc".replaceWord("") == "lda-ldz ld abc" doAssert "$animal eats $food." % ["animal", "The cat", "food", "fish"] ==
doAssert "oo".replace("", "abc") == "abcoabcoabc" "The cat eats fish."
type MyEnum = enum enA, enB, enC, enuD, enE doAssert "-ld a-ldz -ld".replaceWord("-ld") == " a-ldz "
doAssert parseEnum[MyEnum]("enu_D") == enuD doAssert "-lda-ldz -ld abc".replaceWord("-ld") == "-lda-ldz abc"
doAssert parseEnum("invalid enum value", enC) == enC doAssert "-lda-ldz -ld abc".replaceWord("") == "-lda-ldz -ld abc"
doAssert "oo".replace("", "abc") == "abcoabcoabc"
doAssert center("foo", 13) == " foo " type MyEnum = enum enA, enB, enC, enuD, enE
doAssert center("foo", 0) == "foo" doAssert parseEnum[MyEnum]("enu_D") == enuD
doAssert center("foo", 3, fillChar = 'a') == "foo"
doAssert center("foo", 10, fillChar = '\t') == "\t\t\tfoo\t\t\t\t"
doAssert count("foofoofoo", "foofoo") == 1 doAssert parseEnum("invalid enum value", enC) == enC
doAssert count("foofoofoo", "foofoo", overlapping = true) == 2
doAssert count("foofoofoo", 'f') == 3
doAssert count("foofoofoobar", {'f','b'}) == 4
doAssert strip(" foofoofoo ") == "foofoofoo" doAssert center("foo", 13) == " foo "
doAssert strip("sfoofoofoos", chars = {'s'}) == "foofoofoo" doAssert center("foo", 0) == "foo"
doAssert strip("barfoofoofoobar", chars = {'b', 'a', 'r'}) == "foofoofoo" doAssert center("foo", 3, fillChar = 'a') == "foo"
doAssert strip("stripme but don't strip this stripme", doAssert center("foo", 10, fillChar = '\t') == "\t\t\tfoo\t\t\t\t"
chars = {'s', 't', 'r', 'i', 'p', 'm', 'e'}) ==
" but don't strip this "
doAssert strip("sfoofoofoos", leading = false, chars = {'s'}) == "sfoofoofoo"
doAssert strip("sfoofoofoos", trailing = false, chars = {'s'}) == "foofoofoos"
doAssert " foo\n bar".indent(4, "Q") == "QQQQ foo\nQQQQ bar" doAssert count("foofoofoo", "foofoo") == 1
doAssert count("foofoofoo", "foofoo", overlapping = true) == 2
doAssert count("foofoofoo", 'f') == 3
doAssert count("foofoofoobar", {'f','b'}) == 4
doAssert "abba".multiReplace(("a", "b"), ("b", "a")) == "baab" doAssert strip(" foofoofoo ") == "foofoofoo"
doAssert "Hello World.".multiReplace(("ello", "ELLO"), ("World.", "PEOPLE!")) == "HELLO PEOPLE!" doAssert strip("sfoofoofoos", chars = {'s'}) == "foofoofoo"
doAssert "aaaa".multiReplace(("a", "aa"), ("aa", "bb")) == "aaaaaaaa" doAssert strip("barfoofoofoobar", chars = {'b', 'a', 'r'}) == "foofoofoo"
doAssert strip("stripme but don't strip this stripme",
chars = {'s', 't', 'r', 'i', 'p', 'm', 'e'}) ==
" but don't strip this "
doAssert strip("sfoofoofoos", leading = false, chars = {'s'}) == "sfoofoofoo"
doAssert strip("sfoofoofoos", trailing = false, chars = {'s'}) == "foofoofoos"
doAssert isAlphaAscii('r') doAssert " foo\n bar".indent(4, "Q") == "QQQQ foo\nQQQQ bar"
doAssert isAlphaAscii('A')
doAssert(not isAlphaAscii('$'))
doAssert isAlphaAscii("Rasp") doAssert "abba".multiReplace(("a", "b"), ("b", "a")) == "baab"
doAssert isAlphaAscii("Args") doAssert "Hello World.".multiReplace(("ello", "ELLO"), ("World.", "PEOPLE!")) == "HELLO PEOPLE!"
doAssert(not isAlphaAscii("$Tomato")) doAssert "aaaa".multiReplace(("a", "aa"), ("aa", "bb")) == "aaaaaaaa"
doAssert isAlphaNumeric('3') doAssert isAlphaAscii('r')
doAssert isAlphaNumeric('R') doAssert isAlphaAscii('A')
doAssert(not isAlphaNumeric('!')) doAssert(not isAlphaAscii('$'))
doAssert isAlphaNumeric("34ABc") doAssert isAlphaAscii("Rasp")
doAssert isAlphaNumeric("Rad") doAssert isAlphaAscii("Args")
doAssert isAlphaNumeric("1234") doAssert(not isAlphaAscii("$Tomato"))
doAssert(not isAlphaNumeric("@nose"))
doAssert isDigit('3') doAssert isAlphaNumeric('3')
doAssert(not isDigit('a')) doAssert isAlphaNumeric('R')
doAssert(not isDigit('%')) doAssert(not isAlphaNumeric('!'))
doAssert isDigit("12533") doAssert isAlphaNumeric("34ABc")
doAssert(not isDigit("12.33")) doAssert isAlphaNumeric("Rad")
doAssert(not isDigit("A45b")) doAssert isAlphaNumeric("1234")
doAssert(not isAlphaNumeric("@nose"))
doAssert isSpaceAscii('\t') doAssert isDigit('3')
doAssert isSpaceAscii('\l') doAssert(not isDigit('a'))
doAssert(not isSpaceAscii('A')) doAssert(not isDigit('%'))
doAssert isSpaceAscii("\t\l \v\r\f") doAssert isDigit("12533")
doAssert isSpaceAscii(" ") doAssert(not isDigit("12.33"))
doAssert(not isSpaceAscii("ABc \td")) doAssert(not isDigit("A45b"))
doAssert(isNilOrWhitespace("")) doAssert isSpaceAscii('\t')
doAssert(isNilOrWhitespace(" ")) doAssert isSpaceAscii('\l')
doAssert(isNilOrWhitespace("\t\l \v\r\f")) doAssert(not isSpaceAscii('A'))
doAssert(not isNilOrWhitespace("ABc \td"))
doAssert isLowerAscii('a') doAssert isSpaceAscii("\t\l \v\r\f")
doAssert isLowerAscii('z') doAssert isSpaceAscii(" ")
doAssert(not isLowerAscii('A')) doAssert(not isSpaceAscii("ABc \td"))
doAssert(not isLowerAscii('5'))
doAssert(not isLowerAscii('&'))
doAssert isLowerAscii("abcd") doAssert(isNilOrWhitespace(""))
doAssert(not isLowerAscii("abCD")) doAssert(isNilOrWhitespace(" "))
doAssert(not isLowerAscii("33aa")) doAssert(isNilOrWhitespace("\t\l \v\r\f"))
doAssert(not isNilOrWhitespace("ABc \td"))
doAssert isUpperAscii('A') doAssert isLowerAscii('a')
doAssert(not isUpperAscii('b')) doAssert isLowerAscii('z')
doAssert(not isUpperAscii('5')) doAssert(not isLowerAscii('A'))
doAssert(not isUpperAscii('%')) doAssert(not isLowerAscii('5'))
doAssert(not isLowerAscii('&'))
doAssert isUpperAscii("ABC") doAssert isLowerAscii("abcd")
doAssert(not isUpperAscii("AAcc")) doAssert(not isLowerAscii("abCD"))
doAssert(not isUpperAscii("A#$")) doAssert(not isLowerAscii("33aa"))
doAssert rsplit("foo bar", seps=Whitespace) == @["foo", "bar"] doAssert isUpperAscii('A')
doAssert rsplit(" foo bar", seps=Whitespace, maxsplit=1) == @[" foo", "bar"] doAssert(not isUpperAscii('b'))
doAssert rsplit(" foo bar ", seps=Whitespace, maxsplit=1) == @[" foo bar", ""] doAssert(not isUpperAscii('5'))
doAssert rsplit(":foo:bar", sep=':') == @["", "foo", "bar"] doAssert(not isUpperAscii('%'))
doAssert rsplit(":foo:bar", sep=':', maxsplit=2) == @["", "foo", "bar"]
doAssert rsplit(":foo:bar", sep=':', maxsplit=3) == @["", "foo", "bar"]
doAssert rsplit("foothebar", sep="the") == @["foo", "bar"]
doAssert(unescape(r"\x013", "", "") == "\x013") doAssert isUpperAscii("ABC")
doAssert(not isUpperAscii("AAcc"))
doAssert(not isUpperAscii("A#$"))
doAssert join(["foo", "bar", "baz"]) == "foobarbaz" doAssert rsplit("foo bar", seps=Whitespace) == @["foo", "bar"]
doAssert join(@["foo", "bar", "baz"], ", ") == "foo, bar, baz" doAssert rsplit(" foo bar", seps=Whitespace, maxsplit=1) == @[" foo", "bar"]
doAssert join([1, 2, 3]) == "123" doAssert rsplit(" foo bar ", seps=Whitespace, maxsplit=1) == @[" foo bar", ""]
doAssert join(@[1, 2, 3], ", ") == "1, 2, 3" doAssert rsplit(":foo:bar", sep=':') == @["", "foo", "bar"]
doAssert rsplit(":foo:bar", sep=':', maxsplit=2) == @["", "foo", "bar"]
doAssert rsplit(":foo:bar", sep=':', maxsplit=3) == @["", "foo", "bar"]
doAssert rsplit("foothebar", sep="the") == @["foo", "bar"]
doAssert """~~!!foo doAssert(unescape(r"\x013", "", "") == "\x013")
doAssert join(["foo", "bar", "baz"]) == "foobarbaz"
doAssert join(@["foo", "bar", "baz"], ", ") == "foo, bar, baz"
doAssert join([1, 2, 3]) == "123"
doAssert join(@[1, 2, 3], ", ") == "1, 2, 3"
doAssert """~~!!foo
~~!!bar ~~!!bar
~~!!baz""".unindent(2, "~~!!") == "foo\nbar\nbaz" ~~!!baz""".unindent(2, "~~!!") == "foo\nbar\nbaz"
doAssert """~~!!foo doAssert """~~!!foo
~~!!bar ~~!!bar
~~!!baz""".unindent(2, "~~!!aa") == "~~!!foo\n~~!!bar\n~~!!baz" ~~!!baz""".unindent(2, "~~!!aa") == "~~!!foo\n~~!!bar\n~~!!baz"
doAssert """~~foo doAssert """~~foo
~~ bar ~~ bar
~~ baz""".unindent(4, "~") == "foo\n bar\n baz" ~~ baz""".unindent(4, "~") == "foo\n bar\n baz"
doAssert """foo doAssert """foo
bar bar
baz baz
""".unindent(4) == "foo\nbar\nbaz\n" """.unindent(4) == "foo\nbar\nbaz\n"
doAssert """foo doAssert """foo
bar bar
baz baz
""".unindent(2) == "foo\n bar\n baz\n" """.unindent(2) == "foo\n bar\n baz\n"
doAssert """foo doAssert """foo
bar bar
baz baz
""".unindent(100) == "foo\nbar\nbaz\n" """.unindent(100) == "foo\nbar\nbaz\n"
doAssert """foo doAssert """foo
foo foo
bar bar
""".unindent() == "foo\nfoo\nbar\n" """.unindent() == "foo\nfoo\nbar\n"
let s = " this is an example " let s = " this is an example "
let s2 = ":this;is;an:example;;" let s2 = ":this;is;an:example;;"
doAssert s.split() == @["", "this", "is", "an", "example", "", ""] doAssert s.split() == @["", "this", "is", "an", "example", "", ""]
doAssert s2.split(seps={':', ';'}) == @["", "this", "is", "an", "example", "", ""] doAssert s2.split(seps={':', ';'}) == @["", "this", "is", "an", "example", "", ""]
doAssert s.split(maxsplit=4) == @["", "this", "is", "an", "example "] doAssert s.split(maxsplit=4) == @["", "this", "is", "an", "example "]
doAssert s.split(' ', maxsplit=1) == @["", "this is an example "] doAssert s.split(' ', maxsplit=1) == @["", "this is an example "]
doAssert s.split(" ", maxsplit=4) == @["", "this", "is", "an", "example "] doAssert s.split(" ", maxsplit=4) == @["", "this", "is", "an", "example "]
doAssert s.splitWhitespace() == @["this", "is", "an", "example"] doAssert s.splitWhitespace() == @["this", "is", "an", "example"]
doAssert s.splitWhitespace(maxsplit=1) == @["this", "is an example "] doAssert s.splitWhitespace(maxsplit=1) == @["this", "is an example "]
doAssert s.splitWhitespace(maxsplit=2) == @["this", "is", "an example "] doAssert s.splitWhitespace(maxsplit=2) == @["this", "is", "an example "]
doAssert s.splitWhitespace(maxsplit=3) == @["this", "is", "an", "example "] doAssert s.splitWhitespace(maxsplit=3) == @["this", "is", "an", "example "]
doAssert s.splitWhitespace(maxsplit=4) == @["this", "is", "an", "example"] doAssert s.splitWhitespace(maxsplit=4) == @["this", "is", "an", "example"]
block: # formatEng tests block: # startsWith / endsWith char tests
doAssert formatEng(0, 2, trim=false) == "0.00" var s = "abcdef"
doAssert formatEng(0, 2) == "0" doAssert s.startsWith('a')
doAssert formatEng(53, 2, trim=false) == "53.00" doAssert s.startsWith('b') == false
doAssert formatEng(0.053, 2, trim=false) == "53.00e-3" doAssert s.endsWith('f')
doAssert formatEng(0.053, 4, trim=false) == "53.0000e-3" doAssert s.endsWith('a') == false
doAssert formatEng(0.053, 4, trim=true) == "53e-3" doAssert s.endsWith('\0') == false
doAssert formatEng(0.053, 0) == "53e-3"
doAssert formatEng(52731234) == "52.731234e6"
doAssert formatEng(-52731234) == "-52.731234e6"
doAssert formatEng(52731234, 1) == "52.7e6"
doAssert formatEng(-52731234, 1) == "-52.7e6"
doAssert formatEng(52731234, 1, decimalSep=',') == "52,7e6"
doAssert formatEng(-52731234, 1, decimalSep=',') == "-52,7e6"
doAssert formatEng(4100, siPrefix=true, unit="V") == "4.1 kV" #echo("strutils tests passed")
doAssert formatEng(4.1, siPrefix=true, unit="V", useUnitSpace=true) == "4.1 V"
doAssert formatEng(4.1, siPrefix=true) == "4.1" # Note lack of space
doAssert formatEng(4100, siPrefix=true) == "4.1 k"
doAssert formatEng(4.1, siPrefix=true, unit="", useUnitSpace=true) == "4.1 " # Includes space
doAssert formatEng(4100, siPrefix=true, unit="") == "4.1 k"
doAssert formatEng(4100) == "4.1e3"
doAssert formatEng(4100, unit="V", useUnitSpace=true) == "4.1e3 V"
doAssert formatEng(4100, unit="", useUnitSpace=true) == "4.1e3 "
# Don't use SI prefix as number is too big
doAssert formatEng(3.1e22, siPrefix=true, unit="a", useUnitSpace=true) == "31e21 a"
# Don't use SI prefix as number is too small
doAssert formatEng(3.1e-25, siPrefix=true, unit="A", useUnitSpace=true) == "310e-27 A"
block: # startsWith / endsWith char tests nonStaticTests()
var s = "abcdef" staticTests()
doAssert s.startsWith('a') static: staticTests()
doAssert s.startsWith('b') == false
doAssert s.endsWith('f')
doAssert s.endsWith('a') == false
doAssert s.endsWith('\0') == false
#echo("strutils tests passed")

View file

@ -29,9 +29,7 @@ when not hasThreadSupport:
var var
colorsFGCache = initTable[Color, string]() colorsFGCache = initTable[Color, string]()
colorsBGCache = initTable[Color, string]() colorsBGCache = initTable[Color, string]()
when not defined(windows): styleCache = initTable[int, string]()
var
styleCache = initTable[int, string]()
var var
trueColorIsSupported: bool trueColorIsSupported: bool
@ -41,10 +39,8 @@ var
const const
fgPrefix = "\x1b[38;2;" fgPrefix = "\x1b[38;2;"
bgPrefix = "\x1b[48;2;" bgPrefix = "\x1b[48;2;"
ansiResetCode* = "\e[0m"
when not defined(windows): stylePrefix = "\e["
const
stylePrefix = "\e["
when defined(windows): when defined(windows):
import winlean, os import winlean, os
@ -468,7 +464,7 @@ proc resetAttributes*(f: File) =
else: else:
discard setConsoleTextAttribute(hStdout, oldStdoutAttr) discard setConsoleTextAttribute(hStdout, oldStdoutAttr)
else: else:
f.write("\e[0m") f.write(ansiResetCode)
type type
Style* = enum ## different styles for text output Style* = enum ## different styles for text output
@ -487,15 +483,22 @@ when not defined(windows):
gFG {.threadvar.}: int gFG {.threadvar.}: int
gBG {.threadvar.}: int gBG {.threadvar.}: int
proc getStyleStr(style: int): string = proc ansiStyleCode*(style: int): string =
when hasThreadSupport: when hasThreadSupport:
result = fmt"{stylePrefix}{style}m" result = fmt"{stylePrefix}{style}m"
else:
if styleCache.hasKey(style):
result = styleCache[style]
else: else:
if styleCache.hasKey(style): result = fmt"{stylePrefix}{style}m"
result = styleCache[style] styleCache[style] = result
else:
result = fmt"{stylePrefix}{style}m" template ansiStyleCode*(style: Style): string =
styleCache[style] = result ansiStyleCode(style.int)
# The styleCache can be skipped when `style` is known at compile-time
template ansiStyleCode*(style: static[Style]): string =
(static(stylePrefix & $style.int & "m"))
proc setStyle*(f: File, style: set[Style]) = proc setStyle*(f: File, style: set[Style]) =
## Sets the terminal style. ## Sets the terminal style.
@ -510,7 +513,7 @@ proc setStyle*(f: File, style: set[Style]) =
discard setConsoleTextAttribute(h, old or a) discard setConsoleTextAttribute(h, old or a)
else: else:
for s in items(style): for s in items(style):
f.write(getStyleStr(ord(s))) f.write(ansiStyleCode(s))
proc writeStyled*(txt: string, style: set[Style] = {styleBright}) = proc writeStyled*(txt: string, style: set[Style] = {styleBright}) =
## Writes the text `txt` in a given `style` to stdout. ## Writes the text `txt` in a given `style` to stdout.
@ -524,9 +527,9 @@ proc writeStyled*(txt: string, style: set[Style] = {styleBright}) =
stdout.write(txt) stdout.write(txt)
stdout.resetAttributes() stdout.resetAttributes()
if gFG != 0: if gFG != 0:
stdout.write(getStyleStr(gFG)) stdout.write(ansiStyleCode(gFG))
if gBG != 0: if gBG != 0:
stdout.write(getStyleStr(gBG)) stdout.write(ansiStyleCode(gBG))
type type
ForegroundColor* = enum ## terminal's foreground colors ForegroundColor* = enum ## terminal's foreground colors
@ -572,7 +575,7 @@ proc setForegroundColor*(f: File, fg: ForegroundColor, bright=false) =
else: else:
gFG = ord(fg) gFG = ord(fg)
if bright: inc(gFG, 60) if bright: inc(gFG, 60)
f.write(getStyleStr(gFG)) f.write(ansiStyleCode(gFG))
proc setBackgroundColor*(f: File, bg: BackgroundColor, bright=false) = proc setBackgroundColor*(f: File, bg: BackgroundColor, bright=false) =
## Sets the terminal's background color. ## Sets the terminal's background color.
@ -594,10 +597,18 @@ proc setBackgroundColor*(f: File, bg: BackgroundColor, bright=false) =
else: else:
gBG = ord(bg) gBG = ord(bg)
if bright: inc(gBG, 60) if bright: inc(gBG, 60)
f.write(getStyleStr(gBG)) f.write(ansiStyleCode(gBG))
proc ansiForegroundColorCode*(fg: ForegroundColor, bright=false): string =
var style = ord(fg)
if bright: inc(style, 60)
return ansiStyleCode(style)
proc getFGColorStr(color: Color): string = template ansiForegroundColorCode*(fg: static[ForegroundColor],
bright: static[bool] = false): string =
ansiStyleCode(fg.int + bright.int * 60)
proc ansiForegroundColorCode*(color: Color): string =
when hasThreadSupport: when hasThreadSupport:
let rgb = extractRGB(color) let rgb = extractRGB(color)
result = fmt"{fgPrefix}{rgb.r};{rgb.g};{rgb.b}m" result = fmt"{fgPrefix}{rgb.r};{rgb.g};{rgb.b}m"
@ -609,7 +620,11 @@ proc getFGColorStr(color: Color): string =
result = fmt"{fgPrefix}{rgb.r};{rgb.g};{rgb.b}m" result = fmt"{fgPrefix}{rgb.r};{rgb.g};{rgb.b}m"
colorsFGCache[color] = result colorsFGCache[color] = result
proc getBGColorStr(color: Color): string = template ansiForegroundColorCode*(color: static[Color]): string =
const rgb = extractRGB(color)
(static(fmt"{fgPrefix}{rgb.r};{rgb.g};{rgb.b}m"))
proc ansiBackgroundColorCode*(color: Color): string =
when hasThreadSupport: when hasThreadSupport:
let rgb = extractRGB(color) let rgb = extractRGB(color)
result = fmt"{bgPrefix}{rgb.r};{rgb.g};{rgb.b}m" result = fmt"{bgPrefix}{rgb.r};{rgb.g};{rgb.b}m"
@ -621,15 +636,19 @@ proc getBGColorStr(color: Color): string =
result = fmt"{bgPrefix}{rgb.r};{rgb.g};{rgb.b}m" result = fmt"{bgPrefix}{rgb.r};{rgb.g};{rgb.b}m"
colorsFGCache[color] = result colorsFGCache[color] = result
template ansiBackgroundColorCode*(color: static[Color]): string =
const rgb = extractRGB(color)
(static(fmt"{bgPrefix}{rgb.r};{rgb.g};{rgb.b}m"))
proc setForegroundColor*(f: File, color: Color) = proc setForegroundColor*(f: File, color: Color) =
## Sets the terminal's foreground true color. ## Sets the terminal's foreground true color.
if trueColorIsEnabled: if trueColorIsEnabled:
f.write(getFGColorStr(color)) f.write(ansiForegroundColorCode(color))
proc setBackgroundColor*(f: File, color: Color) = proc setBackgroundColor*(f: File, color: Color) =
## Sets the terminal's background true color. ## Sets the terminal's background true color.
if trueColorIsEnabled: if trueColorIsEnabled:
f.write(getBGColorStr(color)) f.write(ansiBackgroundColorCode(color))
proc setTrueColor(f: File, color: Color) = proc setTrueColor(f: File, color: Color) =
if fgSetColor: if fgSetColor:

View file

@ -1069,17 +1069,15 @@ proc splitData*(textNode: PText, offset: int): PText =
var newNode: PText = textNode.fOwnerDocument.createTextNode(right) var newNode: PText = textNode.fOwnerDocument.createTextNode(right)
return newNode return newNode
# ProcessingInstruction # ProcessingInstruction
proc target*(pi: PProcessingInstruction): string = proc target*(pi: PProcessingInstruction): string =
## Returns the Processing Instructions target ## Returns the Processing Instructions target
return pi.fTarget return pi.fTarget
proc escapeXml*(s: string; result: var string) =
# --Other stuff-- ## Prepares a string for insertion into a XML document
# Writer ## by escaping the XML special characters.
proc addEscaped(s: string): string =
result = "" result = ""
for c in items(s): for c in items(s):
case c case c
@ -1089,11 +1087,20 @@ proc addEscaped(s: string): string =
of '"': result.add("&quot;") of '"': result.add("&quot;")
else: result.add(c) else: result.add(c)
proc escapeXml*(s: string): string =
## Prepares a string for insertion into a XML document
## by escaping the XML special characters.
result = newStringOfCap(s.len + s.len shr 4)
escapeXml(s, result)
# --Other stuff--
# Writer
proc nodeToXml(n: PNode, indent: int = 0): string = proc nodeToXml(n: PNode, indent: int = 0): string =
result = spaces(indent) & "<" & n.nodeName result = spaces(indent) & "<" & n.nodeName
if not isNil(n.attributes): if not isNil(n.attributes):
for i in items(n.attributes): for i in items(n.attributes):
result.add(" " & i.name & "=\"" & addEscaped(i.value) & "\"") result.add(" " & i.name & "=\"" & escapeXml(i.value) & "\"")
if isNil(n.childNodes) or n.childNodes.len() == 0: if isNil(n.childNodes) or n.childNodes.len() == 0:
result.add("/>") # No idea why this doesn't need a \n :O result.add("/>") # No idea why this doesn't need a \n :O
@ -1106,7 +1113,7 @@ proc nodeToXml(n: PNode, indent: int = 0): string =
result.add(nodeToXml(i, indent + 2)) result.add(nodeToXml(i, indent + 2))
of TextNode: of TextNode:
result.add(spaces(indent * 2)) result.add(spaces(indent * 2))
result.add(addEscaped(i.nodeValue)) result.add(escapeXml(i.nodeValue))
of CDataSectionNode: of CDataSectionNode:
result.add(spaces(indent * 2)) result.add(spaces(indent * 2))
result.add("<![CDATA[" & i.nodeValue & "]]>") result.add("<![CDATA[" & i.nodeValue & "]]>")

View file

@ -241,7 +241,7 @@ when defined(vcc):
else: else:
{.error: "invalid CAS instruction".} {.error: "invalid CAS instruction".}
elif defined(tcc) and not defined(windows): elif defined(tcc):
when defined(amd64): when defined(amd64):
{.emit:""" {.emit:"""
static int __tcc_cas(int *ptr, int oldVal, int newVal) static int __tcc_cas(int *ptr, int oldVal, int newVal)
@ -262,7 +262,7 @@ static int __tcc_cas(int *ptr, int oldVal, int newVal)
} }
""".} """.}
else: else:
assert sizeof(int) == 4 #assert sizeof(int) == 4
{.emit:""" {.emit:"""
static int __tcc_cas(int *ptr, int oldVal, int newVal) static int __tcc_cas(int *ptr, int oldVal, int newVal)
{ {

View file

@ -1,4 +1,6 @@
import sets import sets
import hashes
import algorithm
block setEquality: block setEquality:
var var
@ -35,7 +37,7 @@ block setWithSequences:
doAssert( not s.contains(@[4, 5, 6]) ) doAssert( not s.contains(@[4, 5, 6]) )
block setClearWorked: block setClearWorked:
var s = initSet[char]() var s = initSet[char]()
for c in "this is a test": for c in "this is a test":
s.incl(c) s.incl(c)
@ -68,12 +70,54 @@ block orderedSetClearWorked:
for c in "eat at joes": for c in "eat at joes":
s.incl(c) s.incl(c)
r = "" r = ""
for c in items(s): for c in items(s):
add(r, c) add(r, c)
doAssert r == "zeat jos" doAssert r == "zeat jos"
block hashForHashedSet:
let
seq1 = "This is the test."
seq2 = "the test is This."
s1 = seq1.toSet()
s2 = seq2.toSet()
var hashSeq: seq[Hash] = @[]
doAssert s1 == s2
doAssert hash(s1) == hash(s2)
block hashForOrderdSet:
let
str = "This is the test."
rstr = str.reversed
var
s1 = initOrderedSet[char]()
s2 = initOrderedSet[char]()
r = initOrderedSet[char]()
expected: Hash
added: seq[char] = @[]
reversed: Hash
radded: seq[char] = @[]
expected = 0
for c in str:
if (not (c in added)):
expected = expected !& hash(c)
added.add(c)
s1.incl(c)
s2.incl(c)
expected = !$expected
doAssert hash(s1) == expected
doAssert hash(s1) == hash(s2)
doAssert hash(s1) != hash(r)
reversed = 0
for c in rstr:
if (not (c in radded)):
reversed = reversed !& hash(c)
radded.add(c)
r.incl(c)
reversed = !$reversed
doAssert hash(r) == reversed
doAssert hash(s1) != reversed

View file

@ -0,0 +1,14 @@
type
Base[T] = ref object {.inheritable.}
value*: T
Derived[T] = ref object of Base[T]
derivedValue*: T
proc makeDerived*[T](v: T): Derived[T] =
new result
result.value = v
proc setBaseValue*[T](a: Base[T], value: T) =
a.value = value

View file

@ -1,10 +1,11 @@
discard """ discard """
output: "seq[float]\n0" output: "seq[float]\n0"
targets: "c cpp"
""" """
# https://github.com/nim-lang/Nim/issues/5602 # https://github.com/nim-lang/Nim/issues/5602
import typetraits import typetraits, module_with_generics
type type
Foo[T] = object of RootObj Foo[T] = object of RootObj
@ -16,3 +17,8 @@ proc p[T](f: Foo[T]): T =
var s: Bar[float] var s: Bar[float]
echo p(s).len # the bug was: p(s) should return seq[float], but returns float instead echo p(s).len # the bug was: p(s) should return seq[float], but returns float instead
# Test overloading and code generation when
# downcasting is required for generic types:
var d = makeDerived(10)
setBaseValue(d, 20)

View file

@ -0,0 +1,154 @@
discard """
output: '''
main started: a=10, b=inner-b, c=10, d=some-d, x=16, z=20
exiting: a=12, b=overriden-b, c=100, msg=bye bye, x=16
'''
"""
import macros, tables
template scopeHolder =
0 # scope revision number
type
BindingsSet = Table[string, NimNode]
proc actualBody(n: NimNode): NimNode =
# skip over the double StmtList node introduced in `mergeScopes`
result = n.body
if result.kind == nnkStmtList and result[0].kind == nnkStmtList:
result = result[0]
iterator bindings(n: NimNode, skip = 0): (string, NimNode) =
for i in skip ..< n.len:
let child = n[i]
if child.kind in {nnkAsgn, nnkExprEqExpr}:
let name = $child[0]
let value = child[1]
yield (name, value)
proc scopeRevision(scopeHolder: NimNode): int =
# get the revision number from a scopeHolder sym
assert scopeHolder.kind == nnkSym
var revisionNode = scopeHolder.getImpl.actualBody[0]
result = int(revisionNode.intVal)
proc lastScopeHolder(scopeHolders: NimNode): NimNode =
# get the most recent scopeHolder from a symChoice node
if scopeHolders.kind in {nnkClosedSymChoice, nnkOpenSymChoice}:
var bestScopeRev = 0
assert scopeHolders.len > 0
for scope in scopeHolders:
let rev = scope.scopeRevision
if result == nil or rev > bestScopeRev:
result = scope
bestScopeRev = rev
else:
result = scopeHolders
assert result.kind == nnkSym
macro mergeScopes(scopeHolders: typed, newBindings: untyped): untyped =
var
bestScope = scopeHolders.lastScopeHolder
bestScopeRev = bestScope.scopeRevision
var finalBindings = initTable[string, NimNode]()
for k, v in bindings(bestScope.getImpl.actualBody, skip = 1):
finalBindings[k] = v
for k, v in bindings(newBindings):
finalBindings[k] = v
var newScopeDefinition = newStmtList(newLit(bestScopeRev + 1))
for k, v in finalBindings:
newScopeDefinition.add newAssignment(newIdentNode(k), v)
result = quote:
template scopeHolder = `newScopeDefinition`
template scope(newBindings: untyped) {.dirty.} =
mergeScopes(bindSym"scopeHolder", newBindings)
type
TextLogRecord = object
line: string
StdoutLogRecord = object
template setProperty(r: var TextLogRecord, key: string, val: string, isFirst: bool) =
if not first: r.line.add ", "
r.line.add key
r.line.add "="
r.line.add val
template setEventName(r: var StdoutLogRecord, name: string) =
stdout.write(name & ": ")
template setProperty(r: var StdoutLogRecord, key: string, val: auto, isFirst: bool) =
when not isFirst: stdout.write ", "
stdout.write key
stdout.write "="
stdout.write $val
template flushRecord(r: var StdoutLogRecord) =
stdout.write "\n"
stdout.flushFile
macro logImpl(scopeHolders: typed,
logStmtProps: varargs[untyped]): untyped =
let lexicalScope = scopeHolders.lastScopeHolder.getImpl.actualBody
var finalBindings = initOrderedTable[string, NimNode]()
for k, v in bindings(lexicalScope, skip = 1):
finalBindings[k] = v
for k, v in bindings(logStmtProps, skip = 1):
finalBindings[k] = v
finalBindings.sort(system.cmp)
let eventName = logStmtProps[0]
assert eventName.kind in {nnkStrLit}
let record = genSym(nskVar, "record")
result = quote:
var `record`: StdoutLogRecord
setEventName(`record`, `eventName`)
var isFirst = true
for k, v in finalBindings:
result.add newCall(newIdentNode"setProperty",
record, newLit(k), v, newLit(isFirst))
isFirst = false
result.add newCall(newIdentNode"flushRecord", record)
template log(props: varargs[untyped]) {.dirty.} =
logImpl(bindSym"scopeHolder", props)
scope:
a = 12
b = "original-b"
scope:
x = 16
b = "overriden-b"
scope:
c = 100
proc main =
scope:
c = 10
scope:
z = 20
log("main started", a = 10, b = "inner-b", d = "some-d")
main()
log("exiting", msg = "bye bye")

View file

@ -0,0 +1,173 @@
import
macros, algorithm, strutils
proc normalProc(x: int) =
echo x
template templateWithtouParams =
echo 10
proc overloadedProc(x: int) =
echo x
proc overloadedProc(x: string) =
echo x
proc overloadedProc[T](x: T) =
echo x
template normalTemplate(x: int) =
echo x
template overloadedTemplate(x: int) =
echo x
template overloadedTemplate(x: string) =
echo x
macro normalMacro(x: int): untyped =
discard
macro macroWithoutParams: untyped =
discard
macro inspectSymbol(sym: typed, expected: static[string]): untyped =
if sym.kind == nnkSym:
echo "Symbol node:"
let res = sym.getImpl.repr & "\n"
echo res
# echo "|", res, "|"
# echo "|", expected, "|"
if expected.len > 0: assert res == expected
elif sym.kind in {nnkClosedSymChoice, nnkOpenSymChoice}:
echo "Begin sym choice:"
var results = newSeq[string](0)
for innerSym in sym:
results.add innerSym.getImpl.repr
sort(results, cmp[string])
let res = results.join("\n") & "\n"
echo res
if expected.len > 0: assert res == expected
echo "End symchoice."
else:
echo "Non-symbol node: ", sym.kind
if expected.len > 0: assert $sym.kind == expected
macro inspectUntyped(sym: untyped, expected: static[string]): untyped =
let res = sym.repr
echo "Untyped node: ", res
assert res == expected
inspectSymbol templateWithtouParams, "nnkCommand"
# this template is expanded, because bindSym was not used
# the end result is the template body (nnkCommand)
inspectSymbol bindSym("templateWithtouParams"), """
template templateWithtouParams() =
echo 10
"""
inspectSymbol macroWithoutParams, "nnkEmpty"
# Just like the template above, the macro was expanded
inspectSymbol bindSym("macroWithoutParams"), """
macro macroWithoutParams(): untyped =
discard
"""
inspectSymbol normalMacro, """
macro normalMacro(x: int): untyped =
discard
"""
# Since the normalMacro has params, it's automatically
# treated as a symbol here (no need for `bindSym`)
inspectSymbol bindSym("normalMacro"), """
macro normalMacro(x: int): untyped =
discard
"""
inspectSymbol normalTemplate, """
template normalTemplate(x: int) =
echo x
"""
inspectSymbol bindSym("normalTemplate"), """
template normalTemplate(x: int) =
echo x
"""
inspectSymbol overloadedTemplate, """
template overloadedTemplate(x: int) =
echo x
template overloadedTemplate(x: string) =
echo x
"""
inspectSymbol bindSym("overloadedTemplate"), """
template overloadedTemplate(x: int) =
echo x
template overloadedTemplate(x: string) =
echo x
"""
inspectUntyped bindSym("overloadedTemplate"), """bindSym("overloadedTemplate")"""
# binSym is active only in the presense of `typed` params.
# `untyped` params still get the raw AST
inspectSymbol normalProc, """
proc normalProc(x: int) =
echo [x]
"""
inspectSymbol bindSym("normalProc"), """
proc normalProc(x: int) =
echo [x]
"""
inspectSymbol overloadedProc, """
proc overloadedProc(x: int) =
echo [x]
proc overloadedProc(x: string) =
echo [x]
proc overloadedProc[T](x: T) =
echo x
"""
# XXX: There seems to be a repr rendering problem above.
# Notice that `echo [x]`
inspectSymbol overloadedProc[float], """
proc overloadedProc(x: T) =
echo [x]
"""
# As expected, when we select a specific generic, the
# AST is no longer a symChoice
inspectSymbol bindSym("overloadedProc"), """
proc overloadedProc(x: int) =
echo [x]
proc overloadedProc(x: string) =
echo [x]
proc overloadedProc[T](x: T) =
echo x
"""

View file

@ -0,0 +1,4 @@
var v*: int
proc p* = echo "proc p called"
template t* = echo "template t expanded"

View file

@ -0,0 +1,3 @@
import definitions
export definitions except p

View file

@ -6,6 +6,10 @@ output: '''baz
a a
b b
c c
x: 1, y: test 1
x: 2, y: test 2
x: 10, y: test 3
x: 4, y: test 4
''' '''
""" """
@ -35,3 +39,15 @@ templateForwarding fooVarargs, "test".len > 3, Foo(x: 10), Foo(x: 100), Foo(x: 1
procForwarding "a", "b", "c" procForwarding "a", "b", "c"
proc hasKeywordArgs(x = 10, y = "y") =
echo "x: ", x, ", y: ", y
proc hasRegularArgs(x: int, y: string) =
echo "x: ", x, ", y: ", y
templateForwarding(hasRegularArgs, true, 1, "test 1")
templateForwarding hasKeywordArgs, true, 2, "test 2"
templateForwarding(hasKeywordArgs, true, y = "test 3")
templateForwarding hasKeywordArgs, true, y = "test 4", x = 4

View file

@ -0,0 +1,78 @@
discard """
output: '''
10
1111
1222
3030303
3060606
6060606
6121212
3030903
3061206
3031503
3061806
5050505
5101010
'''
"""
import typetraits
var tls1 {.threadvar.}: int
var g0: int
var g1 {.global.}: int
proc customInc(x: var int, delta: int) =
x += delta
customInc(tls1, 10)
echo tls1
proc nonGenericProc: int =
var local: int
var nonGenericTls {.threadvar.}: int
var nonGenericGlobal {.global.}: int
var nonGenericMixedPragmas {.global, threadvar.}: int
customInc local, 1000
customInc nonGenericTls, 1
customInc nonGenericGlobal, 10
customInc nonGenericMixedPragmas, 100
return local + nonGenericTls + nonGenericGlobal + nonGenericMixedPragmas
proc genericProc(T: typedesc): int =
var local: int
var genericTls {.threadvar.}: int
var genericGlobal {.global.}: int
var genericMixedPragmas {.global, threadvar.}: int
customInc local, T.name.len * 1000000
customInc genericTls, T.name.len * 1
customInc genericGlobal, T.name.len * 100
customInc genericMixedPragmas, T.name.len * 10000
return local + genericTls + genericGlobal + genericMixedPragmas
echo nonGenericProc()
echo nonGenericProc()
echo genericProc(int)
echo genericProc(int)
echo genericProc(string)
echo genericProc(string)
proc echoInThread[T]() {.thread.} =
echo genericProc(T)
echo genericProc(T)
proc newEchoThread(T: typedesc) =
var t: Thread[void]
createThread(t, echoInThread[T])
joinThreads(t)
newEchoThread int
newEchoThread int
newEchoThread float

12
tests/vm/tref.nim Normal file
View file

@ -0,0 +1,12 @@
static:
var
a: ref string
b: ref string
new a
a[] = "Hello world"
b = a
b[5] = 'c'
doAssert a[] == "Hellocworld"
doAssert b[] == "Hellocworld"

View file

@ -187,12 +187,14 @@ when defined(windows):
proc main() = proc main() =
when defined(windows): when defined(windows):
let desiredPath = expandFilename(getCurrentDir() / "bin") let nimDesiredPath = expandFilename(getCurrentDir() / "bin")
let nimbleDesiredPath = expandFilename(getEnv("USERPROFILE") / ".nimble" / "bin")
let p = tryGetUnicodeValue(r"Environment", "Path", let p = tryGetUnicodeValue(r"Environment", "Path",
HKEY_CURRENT_USER) & ";" & tryGetUnicodeValue( HKEY_CURRENT_USER) & ";" & tryGetUnicodeValue(
r"System\CurrentControlSet\Control\Session Manager\Environment", "Path", r"System\CurrentControlSet\Control\Session Manager\Environment", "Path",
HKEY_LOCAL_MACHINE) HKEY_LOCAL_MACHINE)
var alreadyInPath = false var nimAlreadyInPath = false
var nimbleAlreadyInPath = false
var mingWchoices: seq[string] = @[] var mingWchoices: seq[string] = @[]
var incompat: seq[string] = @[] var incompat: seq[string] = @[]
for x in p.split(';'): for x in p.split(';'):
@ -200,18 +202,29 @@ proc main() =
let y = try: expandFilename(if x[0] == '"' and x[^1] == '"': let y = try: expandFilename(if x[0] == '"' and x[^1] == '"':
substr(x, 1, x.len-2) else: x) substr(x, 1, x.len-2) else: x)
except: "" except: ""
if y.cmpIgnoreCase(desiredPath) == 0: alreadyInPath = true if y.cmpIgnoreCase(nimDesiredPath) == 0:
if y.toLowerAscii.contains("mingw"): nimAlreadyInPath = true
elif y.cmpIgnoreCase(nimbleDesiredPath) == 0:
nimbleAlreadyInPath = true
elif y.toLowerAscii.contains("mingw"):
if dirExists(y): if dirExists(y):
if checkGccArch(y): mingWchoices.add y if checkGccArch(y): mingWchoices.add y
else: incompat.add y else: incompat.add y
if alreadyInPath: if nimAlreadyInPath:
echo "bin/nim.exe is already in your PATH [Skipping]" echo "bin\\nim.exe is already in your PATH [Skipping]"
else: else:
if askBool("nim.exe is not in your PATH environment variable.\n" & if askBool("nim.exe is not in your PATH environment variable.\n" &
"Should it be added permanently? (y/n) "): "Should it be added permanently? (y/n) "):
addToPathEnv(desiredPath) addToPathEnv(nimDesiredPath)
if nimbleAlreadyInPath:
echo nimbleDesiredPath & " is already in your PATH [Skipping]"
else:
if askBool(nimbleDesiredPath & " is not in your PATH environment variable.\n" &
"Should it be added permanently? (y/n) "):
addToPathEnv(nimbleDesiredPath)
if mingWchoices.len == 0: if mingWchoices.len == 0:
# No mingw in path, so try a few locations: # No mingw in path, so try a few locations:
let alternative = tryDirs(incompat, defaultMingwLocations()) let alternative = tryDirs(incompat, defaultMingwLocations())