Merge branch 'devel' into sorted_deduplicate
This commit is contained in:
commit
a1bf9fd2b6
494 changed files with 2495 additions and 2643 deletions
|
|
@ -65,7 +65,7 @@ runnableExamples:
|
|||
let hasVowel = firstVowel.isSome()
|
||||
if hasVowel:
|
||||
let matchBounds = firstVowel.get().captureBounds[-1]
|
||||
doAssert matchBounds.get().a == 1
|
||||
doAssert matchBounds.a == 1
|
||||
|
||||
|
||||
# Type definitions {{{
|
||||
|
|
@ -167,14 +167,15 @@ type
|
|||
## - ``"abc".match(re"(?<letter>\w)").get.captures["letter"] == "a"``
|
||||
## - ``"abc".match(re"(\w)\w").get.captures[-1] == "ab"``
|
||||
##
|
||||
## ``captureBounds[]: Option[HSlice[int, int]]``
|
||||
## ``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.
|
||||
##
|
||||
## - ``"abc".match(re"(\w)").get.captureBounds[0].get == 0 .. 0``
|
||||
## - ``"abc".match(re"").get.captureBounds[-1].get == 0 .. -1``
|
||||
## - ``"abc".match(re"abc").get.captureBounds[-1].get == 0 .. 2``
|
||||
## - ``"abc".match(re"(\w)").get.captureBounds[0] == 0 .. 0``
|
||||
## - ``0 in "abc".match(re"(\w)").get.captureBounds == true``
|
||||
## - ``"abc".match(re"").get.captureBounds[-1] == 0 .. -1``
|
||||
## - ``"abc".match(re"abc").get.captureBounds[-1] == 0 .. 2``
|
||||
##
|
||||
## ``match: string``
|
||||
## the full text of the match.
|
||||
|
|
@ -227,9 +228,10 @@ runnableExamples:
|
|||
doAssert "abc".match(re"(?<letter>\w)").get.captures["letter"] == "a"
|
||||
doAssert "abc".match(re"(\w)\w").get.captures[-1] == "ab"
|
||||
|
||||
doAssert "abc".match(re"(\w)").get.captureBounds[0].get == 0 .. 0
|
||||
doAssert "abc".match(re"").get.captureBounds[-1].get == 0 .. -1
|
||||
doAssert "abc".match(re"abc").get.captureBounds[-1].get == 0 .. 2
|
||||
doAssert "abc".match(re"(\w)").get.captureBounds[0] == 0 .. 0
|
||||
doAssert 0 in "abc".match(re"(\w)").get.captureBounds == true
|
||||
doAssert "abc".match(re"").get.captureBounds[-1] == 0 .. -1
|
||||
doAssert "abc".match(re"abc").get.captureBounds[-1] == 0 .. 2
|
||||
# }}}
|
||||
|
||||
proc getinfo[T](pattern: Regex, opt: cint): T =
|
||||
|
|
@ -269,78 +271,99 @@ proc matchesCrLf(pattern: Regex): bool =
|
|||
# }}}
|
||||
|
||||
# Capture accessors {{{
|
||||
proc captureBounds*(pattern: RegexMatch): CaptureBounds = return CaptureBounds(pattern)
|
||||
func captureBounds*(pattern: RegexMatch): CaptureBounds = return CaptureBounds(pattern)
|
||||
|
||||
proc captures*(pattern: RegexMatch): Captures = return Captures(pattern)
|
||||
func captures*(pattern: RegexMatch): Captures = return Captures(pattern)
|
||||
|
||||
proc `[]`*(pattern: CaptureBounds, i: int): Option[HSlice[int, int]] =
|
||||
func contains*(pattern: CaptureBounds, i: int): bool =
|
||||
let pattern = RegexMatch(pattern)
|
||||
if pattern.pcreMatchBounds[i + 1].a != -1:
|
||||
let bounds = pattern.pcreMatchBounds[i + 1]
|
||||
return some(int(bounds.a) .. int(bounds.b-1))
|
||||
else:
|
||||
return none(HSlice[int, int])
|
||||
pattern.pcreMatchBounds[i + 1].a != -1
|
||||
|
||||
proc `[]`*(pattern: Captures, i: int): string =
|
||||
func contains*(pattern: Captures, i: int): bool =
|
||||
i in CaptureBounds(pattern)
|
||||
|
||||
func `[]`*(pattern: CaptureBounds, i: int): HSlice[int, int] =
|
||||
let pattern = RegexMatch(pattern)
|
||||
if not (i in pattern.captureBounds):
|
||||
raise newException(IndexError, "Group '" & $i & "' was not captured")
|
||||
|
||||
let bounds = pattern.pcreMatchBounds[i + 1]
|
||||
int(bounds.a)..int(bounds.b-1)
|
||||
|
||||
func `[]`*(pattern: Captures, i: int): string =
|
||||
let pattern = RegexMatch(pattern)
|
||||
let bounds = pattern.captureBounds[i]
|
||||
|
||||
if bounds.isSome:
|
||||
let bounds = bounds.get
|
||||
return pattern.str.substr(bounds.a, bounds.b)
|
||||
else:
|
||||
return ""
|
||||
pattern.str.substr(bounds.a, bounds.b)
|
||||
|
||||
proc match*(pattern: RegexMatch): string =
|
||||
func match*(pattern: RegexMatch): string =
|
||||
return pattern.captures[-1]
|
||||
|
||||
proc matchBounds*(pattern: RegexMatch): HSlice[int, int] =
|
||||
return pattern.captureBounds[-1].get
|
||||
func matchBounds*(pattern: RegexMatch): HSlice[int, int] =
|
||||
return pattern.captureBounds[-1]
|
||||
|
||||
proc `[]`*(pattern: CaptureBounds, name: string): Option[HSlice[int, int]] =
|
||||
func contains*(pattern: CaptureBounds, name: string): bool =
|
||||
let pattern = RegexMatch(pattern)
|
||||
return pattern.captureBounds[pattern.pattern.captureNameToId.fget(name)]
|
||||
let nameToId = pattern.pattern.captureNameToId
|
||||
if not (name in nameToId):
|
||||
return false
|
||||
nameToId[name] in pattern.captureBounds
|
||||
|
||||
proc `[]`*(pattern: Captures, name: string): string =
|
||||
func contains*(pattern: Captures, name: string): bool =
|
||||
name in CaptureBounds(pattern)
|
||||
|
||||
func checkNamedCaptured(pattern: RegexMatch, name: string): void =
|
||||
if not (name in pattern.captureBounds):
|
||||
raise newException(KeyError, "Group '" & name & "' was not captured")
|
||||
|
||||
func `[]`*(pattern: CaptureBounds, name: string): HSlice[int, int] =
|
||||
let pattern = RegexMatch(pattern)
|
||||
return pattern.captures[pattern.pattern.captureNameToId.fget(name)]
|
||||
checkNamedCaptured(pattern, name)
|
||||
pattern.captureBounds[pattern.pattern.captureNameToId[name]]
|
||||
|
||||
template toTableImpl(cond: untyped) {.dirty.} =
|
||||
func `[]`*(pattern: Captures, name: string): string =
|
||||
let pattern = RegexMatch(pattern)
|
||||
checkNamedCaptured(pattern, name)
|
||||
return pattern.captures[pattern.pattern.captureNameToId[name]]
|
||||
|
||||
template toTableImpl() {.dirty.} =
|
||||
for key in RegexMatch(pattern).pattern.captureNameId.keys:
|
||||
let nextVal = pattern[key]
|
||||
if cond:
|
||||
result[key] = default
|
||||
else:
|
||||
result[key] = nextVal
|
||||
if key in pattern:
|
||||
result[key] = pattern[key]
|
||||
|
||||
proc toTable*(pattern: Captures, default: string = ""): Table[string, string] =
|
||||
func toTable*(pattern: Captures): Table[string, string] =
|
||||
result = initTable[string, string]()
|
||||
toTableImpl(nextVal.len == 0)
|
||||
toTableImpl()
|
||||
|
||||
proc toTable*(pattern: CaptureBounds, default = none(HSlice[int, int])):
|
||||
Table[string, Option[HSlice[int, int]]] =
|
||||
result = initTable[string, Option[HSlice[int, int]]]()
|
||||
toTableImpl(nextVal.isNone)
|
||||
func toTable*(pattern: CaptureBounds): Table[string, HSlice[int, int]] =
|
||||
result = initTable[string, HSlice[int, int]]()
|
||||
toTableImpl()
|
||||
|
||||
template itemsImpl(cond: untyped) {.dirty.} =
|
||||
template itemsImpl() {.dirty.} =
|
||||
for i in 0 ..< RegexMatch(pattern).pattern.captureCount:
|
||||
let nextVal = pattern[i]
|
||||
# done in this roundabout way to avoid multiple yields (potential code
|
||||
# bloat)
|
||||
let nextYieldVal = if cond: default else: nextVal
|
||||
let nextYieldVal = if i in pattern:
|
||||
some(pattern[i])
|
||||
else:
|
||||
default
|
||||
|
||||
yield nextYieldVal
|
||||
|
||||
iterator items*(pattern: CaptureBounds,
|
||||
default = none(HSlice[int, int])): Option[HSlice[int, int]] =
|
||||
itemsImpl()
|
||||
|
||||
iterator items*(pattern: CaptureBounds, default = none(HSlice[int, int])): Option[HSlice[int, int]] =
|
||||
itemsImpl(nextVal.isNone)
|
||||
iterator items*(pattern: Captures,
|
||||
default: Option[string] = none(string)): Option[string] =
|
||||
itemsImpl()
|
||||
|
||||
iterator items*(pattern: Captures, default: string = ""): string =
|
||||
itemsImpl(nextVal.len == 0)
|
||||
|
||||
proc toSeq*(pattern: CaptureBounds, default = none(HSlice[int, int])): seq[Option[HSlice[int, int]]] =
|
||||
proc toSeq*(pattern: CaptureBounds,
|
||||
default = none(HSlice[int, int])): seq[Option[HSlice[int, int]]] =
|
||||
accumulateResult(pattern.items(default))
|
||||
|
||||
proc toSeq*(pattern: Captures, default: string = ""): seq[string] =
|
||||
proc toSeq*(pattern: Captures,
|
||||
default: Option[string] = none(string)): seq[Option[string]] =
|
||||
accumulateResult(pattern.items(default))
|
||||
|
||||
proc `$`*(pattern: RegexMatch): string =
|
||||
|
|
@ -652,7 +675,8 @@ proc split*(str: string, pattern: Regex, maxSplit = -1, start = 0): seq[string]
|
|||
|
||||
for cap in match.captures:
|
||||
# if there are captures, include them in the result
|
||||
result.add(cap)
|
||||
if cap.isSome:
|
||||
result.add(cap.get)
|
||||
|
||||
if splits == maxSplit - 1:
|
||||
break
|
||||
|
|
@ -706,7 +730,8 @@ proc replace*(str: string, pattern: Regex,
|
|||
## - ``$#`` - first capture
|
||||
## - ``$0`` - full match
|
||||
##
|
||||
## If a given capture is missing, a ``ValueError`` exception is thrown.
|
||||
## If a given capture is missing, ``IndexError`` thrown for un-named captures
|
||||
## and ``KeyError`` for named captures.
|
||||
replaceImpl(str, pattern, subproc(match))
|
||||
|
||||
proc replace*(str: string, pattern: Regex,
|
||||
|
|
|
|||
|
|
@ -1,17 +1,9 @@
|
|||
## INTERNAL FILE FOR USE ONLY BY nre.nim.
|
||||
import tables
|
||||
|
||||
proc fget*[K, V](self: Table[K, V], key: K): V =
|
||||
if self.hasKey(key):
|
||||
return self[key]
|
||||
else:
|
||||
raise newException(KeyError, "Key does not exist in table: " & $key)
|
||||
|
||||
const Ident = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\128'..'\255'}
|
||||
const StartIdent = Ident - {'0'..'9'}
|
||||
|
||||
template checkNil(arg: string): string = arg
|
||||
|
||||
template formatStr*(howExpr, namegetter, idgetter): untyped =
|
||||
let how = howExpr
|
||||
var val = newStringOfCap(how.len)
|
||||
|
|
@ -28,7 +20,7 @@ template formatStr*(howExpr, namegetter, idgetter): untyped =
|
|||
i += 2
|
||||
elif how[i + 1] == '#':
|
||||
var id {.inject.} = lastNum
|
||||
val.add(checkNil(idgetter))
|
||||
val.add(idgetter)
|
||||
lastNum += 1
|
||||
i += 2
|
||||
elif how[i + 1] in {'0'..'9'}:
|
||||
|
|
@ -37,7 +29,7 @@ template formatStr*(howExpr, namegetter, idgetter): untyped =
|
|||
while i < how.len and how[i] in {'0'..'9'}:
|
||||
id += (id * 10) + (ord(how[i]) - ord('0'))
|
||||
i += 1
|
||||
val.add(checkNil(idgetter))
|
||||
val.add(idgetter)
|
||||
lastNum = id + 1
|
||||
elif how[i + 1] in StartIdent:
|
||||
i += 1
|
||||
|
|
@ -45,7 +37,7 @@ template formatStr*(howExpr, namegetter, idgetter): untyped =
|
|||
while i < how.len and how[i] in Ident:
|
||||
name.add(how[i])
|
||||
i += 1
|
||||
val.add(checkNil(namegetter))
|
||||
val.add(namegetter)
|
||||
elif how[i + 1] == '{':
|
||||
i += 2
|
||||
var name {.inject.} = ""
|
||||
|
|
@ -53,7 +45,7 @@ template formatStr*(howExpr, namegetter, idgetter): untyped =
|
|||
name.add(how[i])
|
||||
i += 1
|
||||
i += 1
|
||||
val.add(checkNil(namegetter))
|
||||
val.add(namegetter)
|
||||
else:
|
||||
raise newException(Exception, "Syntax error in format string at " & $i)
|
||||
val
|
||||
|
|
|
|||
|
|
@ -1513,8 +1513,217 @@ proc poll*(timeout = 500) =
|
|||
## `epoll`:idx: or `kqueue`:idx: primitive only once.
|
||||
discard runOnce(timeout)
|
||||
|
||||
# Common procedures between current and upcoming asyncdispatch
|
||||
include includes/asynccommon
|
||||
template createAsyncNativeSocketImpl(domain, sockType, protocol) =
|
||||
let handle = newNativeSocket(domain, sockType, protocol)
|
||||
if handle == osInvalidSocket:
|
||||
return osInvalidSocket.AsyncFD
|
||||
handle.setBlocking(false)
|
||||
when defined(macosx) and not defined(nimdoc):
|
||||
handle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1)
|
||||
result = handle.AsyncFD
|
||||
register(result)
|
||||
|
||||
proc createAsyncNativeSocket*(domain: cint, sockType: cint,
|
||||
protocol: cint): AsyncFD =
|
||||
createAsyncNativeSocketImpl(domain, sockType, protocol)
|
||||
|
||||
proc createAsyncNativeSocket*(domain: Domain = Domain.AF_INET,
|
||||
sockType: SockType = SOCK_STREAM,
|
||||
protocol: Protocol = IPPROTO_TCP): AsyncFD =
|
||||
createAsyncNativeSocketImpl(domain, sockType, protocol)
|
||||
|
||||
proc newAsyncNativeSocket*(domain: cint, sockType: cint,
|
||||
protocol: cint): AsyncFD {.deprecated: "use createAsyncNativeSocket instead".} =
|
||||
createAsyncNativeSocketImpl(domain, sockType, protocol)
|
||||
|
||||
proc newAsyncNativeSocket*(domain: Domain = Domain.AF_INET,
|
||||
sockType: SockType = SOCK_STREAM,
|
||||
protocol: Protocol = IPPROTO_TCP): AsyncFD
|
||||
{.deprecated: "use createAsyncNativeSocket instead".} =
|
||||
createAsyncNativeSocketImpl(domain, sockType, protocol)
|
||||
|
||||
when defined(windows) or defined(nimdoc):
|
||||
proc bindToDomain(handle: SocketHandle, domain: Domain) =
|
||||
# Extracted into a separate proc, because connect() on Windows requires
|
||||
# the socket to be initially bound.
|
||||
template doBind(saddr) =
|
||||
if bindAddr(handle, cast[ptr SockAddr](addr(saddr)),
|
||||
sizeof(saddr).SockLen) < 0'i32:
|
||||
raiseOSError(osLastError())
|
||||
|
||||
if domain == Domain.AF_INET6:
|
||||
var saddr: Sockaddr_in6
|
||||
saddr.sin6_family = uint16(toInt(domain))
|
||||
doBind(saddr)
|
||||
else:
|
||||
var saddr: Sockaddr_in
|
||||
saddr.sin_family = uint16(toInt(domain))
|
||||
doBind(saddr)
|
||||
|
||||
proc doConnect(socket: AsyncFD, addrInfo: ptr AddrInfo): Future[void] =
|
||||
let retFuture = newFuture[void]("doConnect")
|
||||
result = retFuture
|
||||
|
||||
var ol = PCustomOverlapped()
|
||||
GC_ref(ol)
|
||||
ol.data = CompletionData(fd: socket, cb:
|
||||
proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
|
||||
if not retFuture.finished:
|
||||
if errcode == OSErrorCode(-1):
|
||||
retFuture.complete()
|
||||
else:
|
||||
retFuture.fail(newException(OSError, osErrorMsg(errcode)))
|
||||
)
|
||||
|
||||
let ret = connectEx(socket.SocketHandle, addrInfo.ai_addr,
|
||||
cint(addrInfo.ai_addrlen), nil, 0, nil,
|
||||
cast[POVERLAPPED](ol))
|
||||
if ret:
|
||||
# Request to connect completed immediately.
|
||||
retFuture.complete()
|
||||
# We don't deallocate ``ol`` here because even though this completed
|
||||
# immediately poll will still be notified about its completion and it
|
||||
# will free ``ol``.
|
||||
else:
|
||||
let lastError = osLastError()
|
||||
if lastError.int32 != ERROR_IO_PENDING:
|
||||
# With ERROR_IO_PENDING ``ol`` will be deallocated in ``poll``,
|
||||
# and the future will be completed/failed there, too.
|
||||
GC_unref(ol)
|
||||
retFuture.fail(newException(OSError, osErrorMsg(lastError)))
|
||||
else:
|
||||
proc doConnect(socket: AsyncFD, addrInfo: ptr AddrInfo): Future[void] =
|
||||
let retFuture = newFuture[void]("doConnect")
|
||||
result = retFuture
|
||||
|
||||
proc cb(fd: AsyncFD): bool =
|
||||
let ret = SocketHandle(fd).getSockOptInt(
|
||||
cint(SOL_SOCKET), cint(SO_ERROR))
|
||||
if ret == 0:
|
||||
# We have connected.
|
||||
retFuture.complete()
|
||||
return true
|
||||
elif ret == EINTR:
|
||||
# interrupted, keep waiting
|
||||
return false
|
||||
else:
|
||||
retFuture.fail(newException(OSError, osErrorMsg(OSErrorCode(ret))))
|
||||
return true
|
||||
|
||||
let ret = connect(socket.SocketHandle,
|
||||
addrInfo.ai_addr,
|
||||
addrInfo.ai_addrlen.Socklen)
|
||||
if ret == 0:
|
||||
# Request to connect completed immediately.
|
||||
retFuture.complete()
|
||||
else:
|
||||
let lastError = osLastError()
|
||||
if lastError.int32 == EINTR or lastError.int32 == EINPROGRESS:
|
||||
addWrite(socket, cb)
|
||||
else:
|
||||
retFuture.fail(newException(OSError, osErrorMsg(lastError)))
|
||||
|
||||
template asyncAddrInfoLoop(addrInfo: ptr AddrInfo, fd: untyped,
|
||||
protocol: Protocol = IPPROTO_RAW) =
|
||||
## Iterates through the AddrInfo linked list asynchronously
|
||||
## until the connection can be established.
|
||||
const shouldCreateFd = not declared(fd)
|
||||
|
||||
when shouldCreateFd:
|
||||
let sockType = protocol.toSockType()
|
||||
|
||||
var fdPerDomain: array[low(Domain).ord..high(Domain).ord, AsyncFD]
|
||||
for i in low(fdPerDomain)..high(fdPerDomain):
|
||||
fdPerDomain[i] = osInvalidSocket.AsyncFD
|
||||
template closeUnusedFds(domainToKeep = -1) {.dirty.} =
|
||||
for i, fd in fdPerDomain:
|
||||
if fd != osInvalidSocket.AsyncFD and i != domainToKeep:
|
||||
fd.closeSocket()
|
||||
|
||||
var lastException: ref Exception
|
||||
var curAddrInfo = addrInfo
|
||||
var domain: Domain
|
||||
when shouldCreateFd:
|
||||
var curFd: AsyncFD
|
||||
else:
|
||||
var curFd = fd
|
||||
proc tryNextAddrInfo(fut: Future[void]) {.gcsafe.} =
|
||||
if fut == nil or fut.failed:
|
||||
if fut != nil:
|
||||
lastException = fut.readError()
|
||||
|
||||
while curAddrInfo != nil:
|
||||
let domainOpt = curAddrInfo.ai_family.toKnownDomain()
|
||||
if domainOpt.isSome:
|
||||
domain = domainOpt.unsafeGet()
|
||||
break
|
||||
curAddrInfo = curAddrInfo.ai_next
|
||||
|
||||
if curAddrInfo == nil:
|
||||
freeAddrInfo(addrInfo)
|
||||
when shouldCreateFd:
|
||||
closeUnusedFds()
|
||||
if lastException != nil:
|
||||
retFuture.fail(lastException)
|
||||
else:
|
||||
retFuture.fail(newException(
|
||||
IOError, "Couldn't resolve address: " & address))
|
||||
return
|
||||
|
||||
when shouldCreateFd:
|
||||
curFd = fdPerDomain[ord(domain)]
|
||||
if curFd == osInvalidSocket.AsyncFD:
|
||||
try:
|
||||
curFd = newAsyncNativeSocket(domain, sockType, protocol)
|
||||
except:
|
||||
freeAddrInfo(addrInfo)
|
||||
closeUnusedFds()
|
||||
raise getCurrentException()
|
||||
when defined(windows):
|
||||
curFd.SocketHandle.bindToDomain(domain)
|
||||
fdPerDomain[ord(domain)] = curFd
|
||||
|
||||
doConnect(curFd, curAddrInfo).callback = tryNextAddrInfo
|
||||
curAddrInfo = curAddrInfo.ai_next
|
||||
else:
|
||||
freeAddrInfo(addrInfo)
|
||||
when shouldCreateFd:
|
||||
closeUnusedFds(ord(domain))
|
||||
retFuture.complete(curFd)
|
||||
else:
|
||||
retFuture.complete()
|
||||
|
||||
tryNextAddrInfo(nil)
|
||||
|
||||
proc dial*(address: string, port: Port,
|
||||
protocol: Protocol = IPPROTO_TCP): Future[AsyncFD] =
|
||||
## Establishes connection to the specified ``address``:``port`` pair via the
|
||||
## specified protocol. The procedure iterates through possible
|
||||
## resolutions of the ``address`` until it succeeds, meaning that it
|
||||
## seamlessly works with both IPv4 and IPv6.
|
||||
## Returns the async file descriptor, registered in the dispatcher of
|
||||
## the current thread, ready to send or receive data.
|
||||
let retFuture = newFuture[AsyncFD]("dial")
|
||||
result = retFuture
|
||||
let sockType = protocol.toSockType()
|
||||
|
||||
let aiList = getAddrInfo(address, port, Domain.AF_UNSPEC, sockType, protocol)
|
||||
asyncAddrInfoLoop(aiList, noFD, protocol)
|
||||
|
||||
proc connect*(socket: AsyncFD, address: string, port: Port,
|
||||
domain = Domain.AF_INET): Future[void] =
|
||||
let retFuture = newFuture[void]("connect")
|
||||
result = retFuture
|
||||
|
||||
when defined(windows):
|
||||
verifyPresence(socket)
|
||||
else:
|
||||
assert getSockDomain(socket.SocketHandle) == domain
|
||||
|
||||
let aiList = getAddrInfo(address, port, domain)
|
||||
when defined(windows):
|
||||
socket.SocketHandle.bindToDomain(domain)
|
||||
asyncAddrInfoLoop(aiList, socket)
|
||||
|
||||
proc sleepAsync*(ms: int | float): Future[void] =
|
||||
## Suspends the execution of the current async procedure for the next
|
||||
|
|
|
|||
|
|
@ -140,11 +140,26 @@ proc contains*[T](L: SomeLinkedCollection[T], value: T): bool {.inline.} =
|
|||
## exist, true otherwise.
|
||||
result = find(L, value) != nil
|
||||
|
||||
proc append*[T](L: var SinglyLinkedList[T],
|
||||
n: SinglyLinkedNode[T]) {.inline.} =
|
||||
## appends a node `n` to `L`. Efficiency: O(1).
|
||||
n.next = nil
|
||||
if L.tail != nil:
|
||||
assert(L.tail.next == nil)
|
||||
L.tail.next = n
|
||||
L.tail = n
|
||||
if L.head == nil: L.head = n
|
||||
|
||||
proc append*[T](L: var SinglyLinkedList[T], value: T) {.inline.} =
|
||||
## appends a value to `L`. Efficiency: O(1).
|
||||
append(L, newSinglyLinkedNode(value))
|
||||
|
||||
proc prepend*[T](L: var SinglyLinkedList[T],
|
||||
n: SinglyLinkedNode[T]) {.inline.} =
|
||||
## prepends a node to `L`. Efficiency: O(1).
|
||||
n.next = L.head
|
||||
L.head = n
|
||||
if L.tail == nil: L.tail = n
|
||||
|
||||
proc prepend*[T](L: var SinglyLinkedList[T], value: T) {.inline.} =
|
||||
## prepends a node to `L`. Efficiency: O(1).
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@
|
|||
type
|
||||
UncheckedCharArray = UncheckedArray[char]
|
||||
|
||||
import system/helpers2
|
||||
|
||||
type
|
||||
Buffer = ptr object
|
||||
refcount: int
|
||||
|
|
@ -49,11 +51,11 @@ proc len*(s: SharedString): int = s.len
|
|||
|
||||
proc `[]`*(s: SharedString; i: Natural): char =
|
||||
if i < s.len: result = s.buffer.data[i+s.first]
|
||||
else: raise newException(IndexError, "index out of bounds")
|
||||
else: raise newException(IndexError, formatErrorIndexBound(i, s.len-1))
|
||||
|
||||
proc `[]=`*(s: var SharedString; i: Natural; value: char) =
|
||||
if i < s.len: s.buffer.data[i+s.first] = value
|
||||
else: raise newException(IndexError, "index out of bounds")
|
||||
else: raise newException(IndexError, formatErrorIndexBound(i, s.len-1))
|
||||
|
||||
proc `[]`*(s: SharedString; ab: HSlice[int, int]): SharedString =
|
||||
#incRef(src.buffer)
|
||||
|
|
|
|||
|
|
@ -1,211 +0,0 @@
|
|||
template createAsyncNativeSocketImpl(domain, sockType, protocol) =
|
||||
let handle = newNativeSocket(domain, sockType, protocol)
|
||||
if handle == osInvalidSocket:
|
||||
return osInvalidSocket.AsyncFD
|
||||
handle.setBlocking(false)
|
||||
when defined(macosx) and not defined(nimdoc):
|
||||
handle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1)
|
||||
result = handle.AsyncFD
|
||||
register(result)
|
||||
|
||||
proc createAsyncNativeSocket*(domain: cint, sockType: cint,
|
||||
protocol: cint): AsyncFD =
|
||||
createAsyncNativeSocketImpl(domain, sockType, protocol)
|
||||
|
||||
proc createAsyncNativeSocket*(domain: Domain = Domain.AF_INET,
|
||||
sockType: SockType = SOCK_STREAM,
|
||||
protocol: Protocol = IPPROTO_TCP): AsyncFD =
|
||||
createAsyncNativeSocketImpl(domain, sockType, protocol)
|
||||
|
||||
proc newAsyncNativeSocket*(domain: cint, sockType: cint,
|
||||
protocol: cint): AsyncFD {.deprecated: "use createAsyncNativeSocket instead".} =
|
||||
createAsyncNativeSocketImpl(domain, sockType, protocol)
|
||||
|
||||
proc newAsyncNativeSocket*(domain: Domain = Domain.AF_INET,
|
||||
sockType: SockType = SOCK_STREAM,
|
||||
protocol: Protocol = IPPROTO_TCP): AsyncFD
|
||||
{.deprecated: "use createAsyncNativeSocket instead".} =
|
||||
createAsyncNativeSocketImpl(domain, sockType, protocol)
|
||||
|
||||
when defined(windows) or defined(nimdoc):
|
||||
proc bindToDomain(handle: SocketHandle, domain: Domain) =
|
||||
# Extracted into a separate proc, because connect() on Windows requires
|
||||
# the socket to be initially bound.
|
||||
template doBind(saddr) =
|
||||
if bindAddr(handle, cast[ptr SockAddr](addr(saddr)),
|
||||
sizeof(saddr).SockLen) < 0'i32:
|
||||
raiseOSError(osLastError())
|
||||
|
||||
if domain == Domain.AF_INET6:
|
||||
var saddr: Sockaddr_in6
|
||||
saddr.sin6_family = uint16(toInt(domain))
|
||||
doBind(saddr)
|
||||
else:
|
||||
var saddr: Sockaddr_in
|
||||
saddr.sin_family = uint16(toInt(domain))
|
||||
doBind(saddr)
|
||||
|
||||
proc doConnect(socket: AsyncFD, addrInfo: ptr AddrInfo): Future[void] =
|
||||
let retFuture = newFuture[void]("doConnect")
|
||||
result = retFuture
|
||||
|
||||
var ol = PCustomOverlapped()
|
||||
GC_ref(ol)
|
||||
ol.data = CompletionData(fd: socket, cb:
|
||||
proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
|
||||
if not retFuture.finished:
|
||||
if errcode == OSErrorCode(-1):
|
||||
retFuture.complete()
|
||||
else:
|
||||
retFuture.fail(newException(OSError, osErrorMsg(errcode)))
|
||||
)
|
||||
|
||||
let ret = connectEx(socket.SocketHandle, addrInfo.ai_addr,
|
||||
cint(addrInfo.ai_addrlen), nil, 0, nil,
|
||||
cast[POVERLAPPED](ol))
|
||||
if ret:
|
||||
# Request to connect completed immediately.
|
||||
retFuture.complete()
|
||||
# We don't deallocate ``ol`` here because even though this completed
|
||||
# immediately poll will still be notified about its completion and it
|
||||
# will free ``ol``.
|
||||
else:
|
||||
let lastError = osLastError()
|
||||
if lastError.int32 != ERROR_IO_PENDING:
|
||||
# With ERROR_IO_PENDING ``ol`` will be deallocated in ``poll``,
|
||||
# and the future will be completed/failed there, too.
|
||||
GC_unref(ol)
|
||||
retFuture.fail(newException(OSError, osErrorMsg(lastError)))
|
||||
else:
|
||||
proc doConnect(socket: AsyncFD, addrInfo: ptr AddrInfo): Future[void] =
|
||||
let retFuture = newFuture[void]("doConnect")
|
||||
result = retFuture
|
||||
|
||||
proc cb(fd: AsyncFD): bool =
|
||||
let ret = SocketHandle(fd).getSockOptInt(
|
||||
cint(SOL_SOCKET), cint(SO_ERROR))
|
||||
if ret == 0:
|
||||
# We have connected.
|
||||
retFuture.complete()
|
||||
return true
|
||||
elif ret == EINTR:
|
||||
# interrupted, keep waiting
|
||||
return false
|
||||
else:
|
||||
retFuture.fail(newException(OSError, osErrorMsg(OSErrorCode(ret))))
|
||||
return true
|
||||
|
||||
let ret = connect(socket.SocketHandle,
|
||||
addrInfo.ai_addr,
|
||||
addrInfo.ai_addrlen.Socklen)
|
||||
if ret == 0:
|
||||
# Request to connect completed immediately.
|
||||
retFuture.complete()
|
||||
else:
|
||||
let lastError = osLastError()
|
||||
if lastError.int32 == EINTR or lastError.int32 == EINPROGRESS:
|
||||
addWrite(socket, cb)
|
||||
else:
|
||||
retFuture.fail(newException(OSError, osErrorMsg(lastError)))
|
||||
|
||||
template asyncAddrInfoLoop(addrInfo: ptr AddrInfo, fd: untyped,
|
||||
protocol: Protocol = IPPROTO_RAW) =
|
||||
## Iterates through the AddrInfo linked list asynchronously
|
||||
## until the connection can be established.
|
||||
const shouldCreateFd = not declared(fd)
|
||||
|
||||
when shouldCreateFd:
|
||||
let sockType = protocol.toSockType()
|
||||
|
||||
var fdPerDomain: array[low(Domain).ord..high(Domain).ord, AsyncFD]
|
||||
for i in low(fdPerDomain)..high(fdPerDomain):
|
||||
fdPerDomain[i] = osInvalidSocket.AsyncFD
|
||||
template closeUnusedFds(domainToKeep = -1) {.dirty.} =
|
||||
for i, fd in fdPerDomain:
|
||||
if fd != osInvalidSocket.AsyncFD and i != domainToKeep:
|
||||
fd.closeSocket()
|
||||
|
||||
var lastException: ref Exception
|
||||
var curAddrInfo = addrInfo
|
||||
var domain: Domain
|
||||
when shouldCreateFd:
|
||||
var curFd: AsyncFD
|
||||
else:
|
||||
var curFd = fd
|
||||
proc tryNextAddrInfo(fut: Future[void]) {.gcsafe.} =
|
||||
if fut == nil or fut.failed:
|
||||
if fut != nil:
|
||||
lastException = fut.readError()
|
||||
|
||||
while curAddrInfo != nil:
|
||||
let domainOpt = curAddrInfo.ai_family.toKnownDomain()
|
||||
if domainOpt.isSome:
|
||||
domain = domainOpt.unsafeGet()
|
||||
break
|
||||
curAddrInfo = curAddrInfo.ai_next
|
||||
|
||||
if curAddrInfo == nil:
|
||||
freeAddrInfo(addrInfo)
|
||||
when shouldCreateFd:
|
||||
closeUnusedFds()
|
||||
if lastException != nil:
|
||||
retFuture.fail(lastException)
|
||||
else:
|
||||
retFuture.fail(newException(
|
||||
IOError, "Couldn't resolve address: " & address))
|
||||
return
|
||||
|
||||
when shouldCreateFd:
|
||||
curFd = fdPerDomain[ord(domain)]
|
||||
if curFd == osInvalidSocket.AsyncFD:
|
||||
try:
|
||||
curFd = newAsyncNativeSocket(domain, sockType, protocol)
|
||||
except:
|
||||
freeAddrInfo(addrInfo)
|
||||
closeUnusedFds()
|
||||
raise getCurrentException()
|
||||
when defined(windows):
|
||||
curFd.SocketHandle.bindToDomain(domain)
|
||||
fdPerDomain[ord(domain)] = curFd
|
||||
|
||||
doConnect(curFd, curAddrInfo).callback = tryNextAddrInfo
|
||||
curAddrInfo = curAddrInfo.ai_next
|
||||
else:
|
||||
freeAddrInfo(addrInfo)
|
||||
when shouldCreateFd:
|
||||
closeUnusedFds(ord(domain))
|
||||
retFuture.complete(curFd)
|
||||
else:
|
||||
retFuture.complete()
|
||||
|
||||
tryNextAddrInfo(nil)
|
||||
|
||||
proc dial*(address: string, port: Port,
|
||||
protocol: Protocol = IPPROTO_TCP): Future[AsyncFD] =
|
||||
## Establishes connection to the specified ``address``:``port`` pair via the
|
||||
## specified protocol. The procedure iterates through possible
|
||||
## resolutions of the ``address`` until it succeeds, meaning that it
|
||||
## seamlessly works with both IPv4 and IPv6.
|
||||
## Returns the async file descriptor, registered in the dispatcher of
|
||||
## the current thread, ready to send or receive data.
|
||||
let retFuture = newFuture[AsyncFD]("dial")
|
||||
result = retFuture
|
||||
let sockType = protocol.toSockType()
|
||||
|
||||
let aiList = getAddrInfo(address, port, Domain.AF_UNSPEC, sockType, protocol)
|
||||
asyncAddrInfoLoop(aiList, noFD, protocol)
|
||||
|
||||
proc connect*(socket: AsyncFD, address: string, port: Port,
|
||||
domain = Domain.AF_INET): Future[void] =
|
||||
let retFuture = newFuture[void]("connect")
|
||||
result = retFuture
|
||||
|
||||
when defined(windows):
|
||||
verifyPresence(socket)
|
||||
else:
|
||||
assert getSockDomain(socket.SocketHandle) == domain
|
||||
|
||||
let aiList = getAddrInfo(address, port, domain)
|
||||
when defined(windows):
|
||||
socket.SocketHandle.bindToDomain(domain)
|
||||
asyncAddrInfoLoop(aiList, socket)
|
||||
|
|
@ -59,7 +59,8 @@ proc raiseOSError*(errorCode: OSErrorCode; additionalInfo = "") {.noinline.} =
|
|||
e.errorCode = errorCode.int32
|
||||
e.msg = osErrorMsg(errorCode)
|
||||
if additionalInfo.len > 0:
|
||||
e.msg.add "; Additional info: "
|
||||
if e.msg[^1] != '\n': e.msg.add '\n'
|
||||
e.msg.add "Additional info: "
|
||||
e.msg.addQuoted additionalInfo
|
||||
if e.msg == "":
|
||||
e.msg = "unknown OS error"
|
||||
|
|
|
|||
|
|
@ -36,11 +36,12 @@ type
|
|||
size*: int ## size of the memory mapped file
|
||||
|
||||
when defined(windows):
|
||||
fHandle: Handle
|
||||
mapHandle: Handle
|
||||
wasOpened: bool ## only close if wasOpened
|
||||
fHandle*: Handle ## **Caution**: Windows specific public field to allow
|
||||
## even more low level trickery.
|
||||
mapHandle*: Handle ## **Caution**: Windows specific public field.
|
||||
wasOpened*: bool ## **Caution**: Windows specific public field.
|
||||
else:
|
||||
handle: cint
|
||||
handle*: cint ## **Caution**: Posix specific public field.
|
||||
|
||||
proc mapMem*(m: var MemFile, mode: FileMode = fmRead,
|
||||
mappedSize = -1, offset = 0): pointer =
|
||||
|
|
@ -281,6 +282,35 @@ proc flush*(f: var MemFile; attempts: Natural = 3) =
|
|||
if lastErr != EBUSY.OSErrorCode:
|
||||
raiseOSError(lastErr, "error flushing mapping")
|
||||
|
||||
when defined(posix) or defined(nimdoc):
|
||||
proc resize*(f: var MemFile, newFileSize: int) {.raises: [IOError, OSError].} =
|
||||
## resize and re-map the file underlying an ``allowRemap MemFile``.
|
||||
## **Note**: this assumes the entire file is mapped read-write at offset zero.
|
||||
## Also, the value of ``.mem`` will probably change.
|
||||
## **Note**: This is not (yet) avaiable on Windows.
|
||||
when defined(posix):
|
||||
if f.handle == -1:
|
||||
raise newException(IOError,
|
||||
"Cannot resize MemFile opened with allowRemap=false")
|
||||
if ftruncate(f.handle, newFileSize) == -1:
|
||||
raiseOSError(osLastError())
|
||||
when defined(linux): #Maybe NetBSD, too?
|
||||
#On Linux this can be over 100 times faster than a munmap,mmap cycle.
|
||||
proc mremap(old: pointer; oldSize,newSize: csize; flags: cint): pointer {.
|
||||
importc: "mremap", header: "<sys/mman.h>" .}
|
||||
let newAddr = mremap(f.mem, csize(f.size), csize(newFileSize), cint(1))
|
||||
if newAddr == cast[pointer](MAP_FAILED):
|
||||
raiseOSError(osLastError())
|
||||
else:
|
||||
if munmap(f.mem, f.size) != 0:
|
||||
raiseOSError(osLastError())
|
||||
let newAddr = mmap(nil, newFileSize, PROT_READ or PROT_WRITE,
|
||||
MAP_SHARED or MAP_POPULATE, f.handle, 0)
|
||||
if newAddr == cast[pointer](MAP_FAILED):
|
||||
raiseOSError(osLastError())
|
||||
f.mem = newAddr
|
||||
f.size = newFileSize
|
||||
|
||||
proc close*(f: var MemFile) =
|
||||
## closes the memory mapped file `f`. All changes are written back to the
|
||||
## file system, if `f` was opened with write access.
|
||||
|
|
|
|||
|
|
@ -39,17 +39,18 @@
|
|||
##
|
||||
## .. code-block:: nim
|
||||
##
|
||||
## try:
|
||||
## assert("abc".find('c').get() == 2) # Immediately extract the value
|
||||
## except UnpackError: # If there is no value
|
||||
## assert false # This will not be reached, because the value is present
|
||||
##
|
||||
## let found = "abc".find('c')
|
||||
## assert found.isSome and found.get() == 2
|
||||
##
|
||||
## The ``get`` operation demonstrated above returns the underlying value, or
|
||||
## raises ``UnpackError`` if there is no value. There is another option for
|
||||
## obtaining the value: ``unsafeGet``, but you must only use it when you are
|
||||
## absolutely sure the value is present (e.g. after checking ``isSome``). If
|
||||
## you do not care about the tiny overhead that ``get`` causes, you should
|
||||
## simply never use ``unsafeGet``.
|
||||
## raises ``UnpackError`` if there is no value. Note that ``UnpackError`` inherits
|
||||
## from ``system.Defect``, and should therefore never be catched. Instead, rely on
|
||||
## checking if the option contains a value with ``isSome`` and ``isNone``.
|
||||
##
|
||||
## There is another option for obtaining the value: ``unsafeGet``, but you must
|
||||
## only use it when you are absolutely sure the value is present (e.g. after
|
||||
## checking ``isSome``). If you do not care about the tiny overhead that ``get``
|
||||
## causes, you should simply never use ``unsafeGet``.
|
||||
##
|
||||
## How to deal with an absence of a value:
|
||||
##
|
||||
|
|
@ -61,12 +62,7 @@
|
|||
## assert(result == none(int))
|
||||
## # It has no value:
|
||||
## assert(result.isNone)
|
||||
##
|
||||
## try:
|
||||
## echo result.get()
|
||||
## assert(false) # This will not be reached
|
||||
## except UnpackError: # Because an exception is raised
|
||||
## discard
|
||||
|
||||
import typetraits
|
||||
|
||||
type
|
||||
|
|
@ -81,7 +77,7 @@ type
|
|||
val: T
|
||||
has: bool
|
||||
|
||||
UnpackError* = ref object of ValueError
|
||||
UnpackError* = object of Defect
|
||||
|
||||
proc some*[T](val: T): Option[T] =
|
||||
## Returns a ``Option`` that has this value.
|
||||
|
|
@ -129,7 +125,7 @@ proc get*[T](self: Option[T]): T =
|
|||
## Returns contents of the Option. If it is none, then an exception is
|
||||
## thrown.
|
||||
if self.isNone:
|
||||
raise UnpackError(msg: "Can't obtain a value from a `none`")
|
||||
raise newException(UnpackError, "Can't obtain a value from a `none`")
|
||||
self.val
|
||||
|
||||
proc get*[T](self: Option[T], otherwise: T): T =
|
||||
|
|
@ -143,7 +139,7 @@ proc get*[T](self: var Option[T]): var T =
|
|||
## Returns contents of the Option. If it is none, then an exception is
|
||||
## thrown.
|
||||
if self.isNone:
|
||||
raise UnpackError(msg: "Can't obtain a value from a `none`")
|
||||
raise newException(UnpackError, "Can't obtain a value from a `none`")
|
||||
return self.val
|
||||
|
||||
proc map*[T](self: Option[T], callback: proc (input: T)) =
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
from strutils import parseInt, cmpIgnoreStyle, Digits
|
||||
include "system/inclrtl"
|
||||
|
||||
import system/helpers2
|
||||
|
||||
proc findNormalized(x: string, inArray: openarray[string]): int =
|
||||
var i = 0
|
||||
|
|
@ -85,7 +85,7 @@ proc getFormatArg(p: var FormatParser, a: openArray[string]): int =
|
|||
result = parseInt(a[result])-1
|
||||
else:
|
||||
raiseInvalidFormat("'#', '$', number or identifier expected")
|
||||
if result >=% a.len: raiseInvalidFormat("index out of bounds: " & $result)
|
||||
if result >=% a.len: raiseInvalidFormat(formatErrorIndexBound(result, a.len))
|
||||
p.i = i
|
||||
|
||||
proc scanDollar(p: var FormatParser, a: openarray[string], s: var string) {.
|
||||
|
|
|
|||
387
lib/std/diff.nim
Normal file
387
lib/std/diff.nim
Normal file
|
|
@ -0,0 +1,387 @@
|
|||
#
|
||||
#
|
||||
# Nim's Runtime Library
|
||||
# (c) Copyright 2018 Nim contributors
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## This module implements an algorithm to compute the
|
||||
## `diff`:idx: between two sequences of lines.
|
||||
|
||||
# code owner: Arne Döring
|
||||
#
|
||||
# This is based on C# code written by Matthias Hertel, http://www.mathertel.de
|
||||
#
|
||||
# This Class implements the Difference Algorithm published in
|
||||
# "An O(ND) Difference Algorithm and its Variations" by Eugene Myers
|
||||
# Algorithmica Vol. 1 No. 2, 1986, p 251.
|
||||
|
||||
import tables, strutils
|
||||
|
||||
type
|
||||
Item* = object ## An Item in the list of differences.
|
||||
startA*: int ## Start Line number in Data A.
|
||||
startB*: int ## Start Line number in Data B.
|
||||
deletedA*: int ## Number of changes in Data A.
|
||||
insertedB*: int ## Number of changes in Data B.
|
||||
|
||||
DiffData = object ## Data on one input file being compared.
|
||||
data: seq[int] ## Buffer of numbers that will be compared.
|
||||
modified: seq[bool] ## Array of booleans that flag for modified
|
||||
## data. This is the result of the diff.
|
||||
## This means deletedA in the first Data or
|
||||
## inserted in the second Data.
|
||||
|
||||
Smsrd = object
|
||||
x, y: int
|
||||
|
||||
# template to avoid a seq copy. Required until ``sink`` parameters are ready.
|
||||
template newDiffData(initData: seq[int]; L: int): DiffData =
|
||||
DiffData(
|
||||
data: initData,
|
||||
modified: newSeq[bool](L + 2)
|
||||
)
|
||||
|
||||
proc len(d: DiffData): int {.inline.} = d.data.len
|
||||
|
||||
proc diffCodes(aText: string; h: var Table[string, int]): DiffData =
|
||||
## This function converts all textlines of the text into unique numbers for every unique textline
|
||||
## so further work can work only with simple numbers.
|
||||
## ``aText`` the input text
|
||||
## ``h`` This extern initialized hashtable is used for storing all ever used textlines.
|
||||
## ``trimSpace`` ignore leading and trailing space characters
|
||||
## Returns a array of integers.
|
||||
var lastUsedCode = h.len
|
||||
result.data = newSeq[int]()
|
||||
for s in aText.splitLines:
|
||||
if h.contains s:
|
||||
result.data.add h[s]
|
||||
else:
|
||||
inc lastUsedCode
|
||||
h[s] = lastUsedCode
|
||||
result.data.add lastUsedCode
|
||||
result.modified = newSeq[bool](result.data.len + 2)
|
||||
|
||||
proc optimize(data: var DiffData) =
|
||||
## If a sequence of modified lines starts with a line that contains the same content
|
||||
## as the line that appends the changes, the difference sequence is modified so that the
|
||||
## appended line and not the starting line is marked as modified.
|
||||
## This leads to more readable diff sequences when comparing text files.
|
||||
var startPos = 0
|
||||
while startPos < data.len:
|
||||
while startPos < data.len and not data.modified[startPos]:
|
||||
inc startPos
|
||||
var endPos = startPos
|
||||
while endPos < data.len and data.modified[endPos]:
|
||||
inc endPos
|
||||
|
||||
if endPos < data.len and data.data[startPos] == data.data[endPos]:
|
||||
data.modified[startPos] = false
|
||||
data.modified[endPos] = true
|
||||
else:
|
||||
startPos = endPos
|
||||
|
||||
proc sms(dataA: var DiffData; lowerA, upperA: int; dataB: DiffData; lowerB, upperB: int;
|
||||
downVector, upVector: var openArray[int]): Smsrd =
|
||||
## This is the algorithm to find the Shortest Middle Snake (sms).
|
||||
## ``dataA`` sequence A
|
||||
## ``lowerA`` lower bound of the actual range in dataA
|
||||
## ``upperA`` upper bound of the actual range in dataA (exclusive)
|
||||
## ``dataB`` sequence B
|
||||
## ``lowerB`` lower bound of the actual range in dataB
|
||||
## ``upperB`` upper bound of the actual range in dataB (exclusive)
|
||||
## ``downVector`` a vector for the (0,0) to (x,y) search. Passed as a parameter for speed reasons.
|
||||
## ``upVector`` a vector for the (u,v) to (N,M) search. Passed as a parameter for speed reasons.
|
||||
## Returns a MiddleSnakeData record containing x,y and u,v.
|
||||
|
||||
let max = dataA.len + dataB.len + 1
|
||||
|
||||
let downK = lowerA - lowerB # the k-line to start the forward search
|
||||
let upK = upperA - upperB # the k-line to start the reverse search
|
||||
|
||||
let delta = (upperA - lowerA) - (upperB - lowerB)
|
||||
let oddDelta = (delta and 1) != 0
|
||||
|
||||
# The vectors in the publication accepts negative indexes. the vectors implemented here are 0-based
|
||||
# and are access using a specific offset: upOffset upVector and downOffset for downVector
|
||||
let downOffset = max - downK
|
||||
let upOffset = max - upK
|
||||
|
||||
let maxD = ((upperA - lowerA + upperB - lowerB) div 2) + 1
|
||||
|
||||
downVector[downOffset + downK + 1] = lowerA
|
||||
upVector[upOffset + upK - 1] = upperA
|
||||
|
||||
for D in 0 .. maxD:
|
||||
# Extend the forward path.
|
||||
for k in countUp(downK - D, downK + D, 2):
|
||||
# find the only or better starting point
|
||||
var x: int
|
||||
if k == downK - D:
|
||||
x = downVector[downOffset + k + 1] # down
|
||||
else:
|
||||
x = downVector[downOffset + k - 1] + 1 # a step to the right
|
||||
if k < downK + D and downVector[downOffset + k + 1] >= x:
|
||||
x = downVector[downOffset + k + 1] # down
|
||||
|
||||
var y = x - k
|
||||
|
||||
# find the end of the furthest reaching forward D-path in diagonal k.
|
||||
while x < upperA and y < upperB and dataA.data[x] == dataB.data[y]:
|
||||
inc x
|
||||
inc y
|
||||
|
||||
downVector[downOffset + k] = x
|
||||
|
||||
# overlap ?
|
||||
if oddDelta and upK - D < k and k < upK + D:
|
||||
if upVector[upOffset + k] <= downVector[downOffset + k]:
|
||||
return Smsrd(x: downVector[downOffset + k],
|
||||
y: downVector[downOffset + k] - k)
|
||||
|
||||
# Extend the reverse path.
|
||||
for k in countUp(upK - D, upK + D, 2):
|
||||
# find the only or better starting point
|
||||
var x: int
|
||||
if k == upK + D:
|
||||
x = upVector[upOffset + k - 1] # up
|
||||
else:
|
||||
x = upVector[upOffset + k + 1] - 1 # left
|
||||
if k > upK - D and upVector[upOffset + k - 1] < x:
|
||||
x = upVector[upOffset + k - 1] # up
|
||||
|
||||
var y = x - k
|
||||
while x > lowerA and y > lowerB and dataA.data[x - 1] == dataB.data[y - 1]:
|
||||
dec x
|
||||
dec y
|
||||
|
||||
upVector[upOffset + k] = x
|
||||
|
||||
# overlap ?
|
||||
if not oddDelta and downK-D <= k and k <= downK+D:
|
||||
if upVector[upOffset + k] <= downVector[downOffset + k]:
|
||||
return Smsrd(x: downVector[downOffset + k],
|
||||
y: downVector[downOffset + k] - k)
|
||||
|
||||
assert false, "the algorithm should never come here."
|
||||
|
||||
proc lcs(dataA: var DiffData; lowerA, upperA: int; dataB: var DiffData; lowerB, upperB: int;
|
||||
downVector, upVector: var openArray[int]) =
|
||||
## This is the divide-and-conquer implementation of the longes common-subsequence (lcs)
|
||||
## algorithm.
|
||||
## The published algorithm passes recursively parts of the A and B sequences.
|
||||
## To avoid copying these arrays the lower and upper bounds are passed while the sequences stay constant.
|
||||
## ``dataA`` sequence A
|
||||
## ``lowerA`` lower bound of the actual range in dataA
|
||||
## ``upperA`` upper bound of the actual range in dataA (exclusive)
|
||||
## ``dataB`` sequence B
|
||||
## ``lowerB`` lower bound of the actual range in dataB
|
||||
## ``upperB`` upper bound of the actual range in dataB (exclusive)
|
||||
## ``downVector`` a vector for the (0,0) to (x,y) search. Passed as a parameter for speed reasons.
|
||||
## ``upVector`` a vector for the (u,v) to (N,M) search. Passed as a parameter for speed reasons.
|
||||
|
||||
# make mutable copy:
|
||||
var lowerA = lowerA
|
||||
var lowerB = lowerB
|
||||
var upperA = upperA
|
||||
var upperB = upperB
|
||||
|
||||
# Fast walkthrough equal lines at the start
|
||||
while lowerA < upperA and lowerB < upperB and dataA.data[lowerA] == dataB.data[lowerB]:
|
||||
inc lowerA
|
||||
inc lowerB
|
||||
|
||||
# Fast walkthrough equal lines at the end
|
||||
while lowerA < upperA and lowerB < upperB and dataA.data[upperA - 1] == dataB.data[upperB - 1]:
|
||||
dec upperA
|
||||
dec upperB
|
||||
|
||||
if lowerA == upperA:
|
||||
# mark as inserted lines.
|
||||
while lowerB < upperB:
|
||||
dataB.modified[lowerB] = true
|
||||
inc lowerB
|
||||
|
||||
elif lowerB == upperB:
|
||||
# mark as deleted lines.
|
||||
while lowerA < upperA:
|
||||
dataA.modified[lowerA] = true
|
||||
inc lowerA
|
||||
|
||||
else:
|
||||
# Find the middle snakea and length of an optimal path for A and B
|
||||
let smsrd = sms(dataA, lowerA, upperA, dataB, lowerB, upperB, downVector, upVector)
|
||||
# Debug.Write(2, "MiddleSnakeData", String.Format("{0},{1}", smsrd.x, smsrd.y))
|
||||
|
||||
# The path is from LowerX to (x,y) and (x,y) to UpperX
|
||||
lcs(dataA, lowerA, smsrd.x, dataB, lowerB, smsrd.y, downVector, upVector)
|
||||
lcs(dataA, smsrd.x, upperA, dataB, smsrd.y, upperB, downVector, upVector) # 2002.09.20: no need for 2 points
|
||||
|
||||
proc createDiffs(dataA, dataB: DiffData): seq[Item] =
|
||||
## Scan the tables of which lines are inserted and deleted,
|
||||
## producing an edit script in forward order.
|
||||
var startA = 0
|
||||
var startB = 0
|
||||
var lineA = 0
|
||||
var lineB = 0
|
||||
while lineA < dataA.len or lineB < dataB.len:
|
||||
if lineA < dataA.len and not dataA.modified[lineA] and
|
||||
lineB < dataB.len and not dataB.modified[lineB]:
|
||||
# equal lines
|
||||
inc lineA
|
||||
inc lineB
|
||||
else:
|
||||
# maybe deleted and/or inserted lines
|
||||
startA = lineA
|
||||
startB = lineB
|
||||
|
||||
while lineA < dataA.len and (lineB >= dataB.len or dataA.modified[lineA]):
|
||||
inc lineA
|
||||
|
||||
while lineB < dataB.len and (lineA >= dataA.len or dataB.modified[lineB]):
|
||||
inc lineB
|
||||
|
||||
if (startA < lineA) or (startB < lineB):
|
||||
result.add Item(startA: startA,
|
||||
startB: startB,
|
||||
deletedA: lineA - startA,
|
||||
insertedB: lineB - startB)
|
||||
|
||||
|
||||
proc diffInt*(arrayA, arrayB: openArray[int]): seq[Item] =
|
||||
## Find the difference in 2 arrays of integers.
|
||||
## ``arrayA`` A-version of the numbers (usualy the old one)
|
||||
## ``arrayB`` B-version of the numbers (usualy the new one)
|
||||
## Returns a array of Items that describe the differences.
|
||||
|
||||
# The A-Version of the data (original data) to be compared.
|
||||
var dataA = newDiffData(@arrayA, arrayA.len)
|
||||
|
||||
# The B-Version of the data (modified data) to be compared.
|
||||
var dataB = newDiffData(@arrayB, arrayB.len)
|
||||
|
||||
let max = dataA.len + dataB.len + 1
|
||||
## vector for the (0,0) to (x,y) search
|
||||
var downVector = newSeq[int](2 * max + 2)
|
||||
## vector for the (u,v) to (N,M) search
|
||||
var upVector = newSeq[int](2 * max + 2)
|
||||
|
||||
lcs(dataA, 0, dataA.len, dataB, 0, dataB.len, downVector, upVector)
|
||||
result = createDiffs(dataA, dataB)
|
||||
|
||||
proc diffText*(textA, textB: string): seq[Item] =
|
||||
## Find the difference in 2 text documents, comparing by textlines.
|
||||
## The algorithm itself is comparing 2 arrays of numbers so when comparing 2 text documents
|
||||
## each line is converted into a (hash) number. This hash-value is computed by storing all
|
||||
## textlines into a common hashtable so i can find dublicates in there, and generating a
|
||||
## new number each time a new textline is inserted.
|
||||
## ``TextA`` A-version of the text (usualy the old one)
|
||||
## ``TextB`` B-version of the text (usualy the new one)
|
||||
## ``trimSpace`` When set to true, all leading and trailing whitespace characters are stripped out before the comparation is done.
|
||||
## ``ignoreSpace`` When set to true, all whitespace characters are converted to a single space character before the comparation is done.
|
||||
## ``ignoreCase`` When set to true, all characters are converted to their lowercase equivivalence before the comparation is done.
|
||||
## Returns a seq of Items that describe the differences.
|
||||
|
||||
# prepare the input-text and convert to comparable numbers.
|
||||
var h = initTable[string, int]() # TextA.len + TextB.len <- probably wrong initial size
|
||||
# The A-Version of the data (original data) to be compared.
|
||||
var dataA = diffCodes(textA, h)
|
||||
|
||||
# The B-Version of the data (modified data) to be compared.
|
||||
var dataB = diffCodes(textB, h)
|
||||
|
||||
h.clear # free up hashtable memory (maybe)
|
||||
|
||||
let max = dataA.len + dataB.len + 1
|
||||
## vector for the (0,0) to (x,y) search
|
||||
var downVector = newSeq[int](2 * max + 2)
|
||||
## vector for the (u,v) to (N,M) search
|
||||
var upVector = newSeq[int](2 * max + 2)
|
||||
|
||||
lcs(dataA, 0, dataA.len, dataB, 0, dataB.len, downVector, upVector)
|
||||
|
||||
optimize(dataA)
|
||||
optimize(dataB)
|
||||
result = createDiffs(dataA, dataB)
|
||||
|
||||
when isMainModule:
|
||||
|
||||
proc testHelper(f: seq[Item]): string =
|
||||
for it in f:
|
||||
result.add(
|
||||
$it.deletedA & "." & $it.insertedB & "." & $it.startA & "." & $it.startB & "*"
|
||||
)
|
||||
|
||||
proc main() =
|
||||
var a, b: string
|
||||
|
||||
stdout.writeLine("Diff Self Test...")
|
||||
|
||||
# test all changes
|
||||
a = "a,b,c,d,e,f,g,h,i,j,k,l".replace(',', '\n')
|
||||
b = "0,1,2,3,4,5,6,7,8,9".replace(',', '\n')
|
||||
assert(testHelper(diffText(a, b)) ==
|
||||
"12.10.0.0*",
|
||||
"all-changes test failed.")
|
||||
stdout.writeLine("all-changes test passed.")
|
||||
# test all same
|
||||
a = "a,b,c,d,e,f,g,h,i,j,k,l".replace(',', '\n')
|
||||
b = a
|
||||
assert(testHelper(diffText(a, b)) ==
|
||||
"",
|
||||
"all-same test failed.")
|
||||
stdout.writeLine("all-same test passed.")
|
||||
|
||||
# test snake
|
||||
a = "a,b,c,d,e,f".replace(',', '\n')
|
||||
b = "b,c,d,e,f,x".replace(',', '\n')
|
||||
assert(testHelper(diffText(a, b)) ==
|
||||
"1.0.0.0*0.1.6.5*",
|
||||
"snake test failed.")
|
||||
stdout.writeLine("snake test passed.")
|
||||
|
||||
# 2002.09.20 - repro
|
||||
a = "c1,a,c2,b,c,d,e,g,h,i,j,c3,k,l".replace(',', '\n')
|
||||
b = "C1,a,C2,b,c,d,e,I1,e,g,h,i,j,C3,k,I2,l".replace(',', '\n')
|
||||
assert(testHelper(diffText(a, b)) ==
|
||||
"1.1.0.0*1.1.2.2*0.2.7.7*1.1.11.13*0.1.13.15*",
|
||||
"repro20020920 test failed.")
|
||||
stdout.writeLine("repro20020920 test passed.")
|
||||
|
||||
# 2003.02.07 - repro
|
||||
a = "F".replace(',', '\n')
|
||||
b = "0,F,1,2,3,4,5,6,7".replace(',', '\n')
|
||||
assert(testHelper(diffText(a, b)) ==
|
||||
"0.1.0.0*0.7.1.2*",
|
||||
"repro20030207 test failed.")
|
||||
stdout.writeLine("repro20030207 test passed.")
|
||||
|
||||
# Muegel - repro
|
||||
a = "HELLO\nWORLD"
|
||||
b = "\n\nhello\n\n\n\nworld\n"
|
||||
assert(testHelper(diffText(a, b)) ==
|
||||
"2.8.0.0*",
|
||||
"repro20030409 test failed.")
|
||||
stdout.writeLine("repro20030409 test passed.")
|
||||
|
||||
# test some differences
|
||||
a = "a,b,-,c,d,e,f,f".replace(',', '\n')
|
||||
b = "a,b,x,c,e,f".replace(',', '\n')
|
||||
assert(testHelper(diffText(a, b)) ==
|
||||
"1.1.2.2*1.0.4.4*1.0.7.6*",
|
||||
"some-changes test failed.")
|
||||
stdout.writeLine("some-changes test passed.")
|
||||
|
||||
# test one change within long chain of repeats
|
||||
a = "a,a,a,a,a,a,a,a,a,a".replace(',', '\n')
|
||||
b = "a,a,a,a,-,a,a,a,a,a".replace(',', '\n')
|
||||
assert(testHelper(diffText(a, b)) ==
|
||||
"0.1.4.4*1.0.9.10*",
|
||||
"long chain of repeats test failed.")
|
||||
|
||||
stdout.writeLine("End.")
|
||||
stdout.flushFile
|
||||
|
||||
main()
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
#
|
||||
|
||||
# Implementation of some runtime checks.
|
||||
import system/helpers2
|
||||
|
||||
proc raiseRangeError(val: BiggestInt) {.compilerproc, noinline.} =
|
||||
when hostOS == "standalone":
|
||||
|
|
@ -15,6 +16,12 @@ proc raiseRangeError(val: BiggestInt) {.compilerproc, noinline.} =
|
|||
else:
|
||||
sysFatal(RangeError, "value out of range: ", $val)
|
||||
|
||||
proc raiseIndexError3(i, a, b: int) {.compilerproc, noinline.} =
|
||||
sysFatal(IndexError, formatErrorIndexBound(i, a, b))
|
||||
|
||||
proc raiseIndexError2(i, n: int) {.compilerproc, noinline.} =
|
||||
sysFatal(IndexError, formatErrorIndexBound(i, n))
|
||||
|
||||
proc raiseIndexError() {.compilerproc, noinline.} =
|
||||
sysFatal(IndexError, "index out of bounds")
|
||||
|
||||
|
|
@ -25,7 +32,7 @@ proc chckIndx(i, a, b: int): int =
|
|||
if i >= a and i <= b:
|
||||
return i
|
||||
else:
|
||||
raiseIndexError()
|
||||
raiseIndexError3(i, a, b)
|
||||
|
||||
proc chckRange(i, a, b: int): int =
|
||||
if i >= a and i <= b:
|
||||
|
|
|
|||
|
|
@ -454,16 +454,21 @@ when not defined(gcDestructors):
|
|||
shallowCopy(result, e.trace)
|
||||
|
||||
when defined(nimRequiresNimFrame):
|
||||
proc stackOverflow() {.noinline.} =
|
||||
const nimCallDepthLimit {.intdefine.} = 2000
|
||||
|
||||
proc callDepthLimitReached() {.noinline.} =
|
||||
writeStackTrace()
|
||||
showErrorMessage("Stack overflow\n")
|
||||
showErrorMessage("Error: call depth limit reached in a debug build (" &
|
||||
$nimCallDepthLimit & " function calls). You can change it with " &
|
||||
"-d:nimCallDepthLimit=<int> but really try to avoid deep " &
|
||||
"recursions instead.\n")
|
||||
quitOrDebug()
|
||||
|
||||
proc nimFrame(s: PFrame) {.compilerRtl, inl, exportc: "nimFrame".} =
|
||||
s.calldepth = if framePtr == nil: 0 else: framePtr.calldepth+1
|
||||
s.prev = framePtr
|
||||
framePtr = s
|
||||
if s.calldepth == 2000: stackOverflow()
|
||||
if s.calldepth == nimCallDepthLimit: callDepthLimitReached()
|
||||
else:
|
||||
proc pushFrame(s: PFrame) {.compilerRtl, inl, exportc: "nimFrame".} =
|
||||
# XXX only for backwards compatibility
|
||||
|
|
|
|||
5
lib/system/helpers2.nim
Normal file
5
lib/system/helpers2.nim
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
template formatErrorIndexBound*[T](i, a, b: T): string =
|
||||
"index out of bounds: (a:" & $a & ") <= (i:" & $i & ") <= (b:" & $b & ") "
|
||||
|
||||
template formatErrorIndexBound*[T](i, n: T): string =
|
||||
"index out of bounds: (i:" & $i & ") <= (n:" & $n & ") "
|
||||
Loading…
Add table
Add a link
Reference in a new issue