Merge branch 'devel' into gogc

This commit is contained in:
Stefan Talpalaru 2015-06-15 08:44:02 +02:00
commit bdac85f2cf
212 changed files with 8304 additions and 7492 deletions

0
bootstrap.sh Normal file → Executable file
View file

View file

@ -1,6 +1,6 @@
[Package] [Package]
name = "compiler" name = "compiler"
version = "0.10.3" version = "0.11.3"
author = "Andreas Rumpf" author = "Andreas Rumpf"
description = "Compiler package providing the compiler sources as a library." description = "Compiler package providing the compiler sources as a library."
license = "MIT" license = "MIT"
@ -8,4 +8,4 @@ license = "MIT"
InstallDirs = "doc, compiler" InstallDirs = "doc, compiler"
[Deps] [Deps]
Requires: "nim >= 0.10.3" Requires: "nim >= 0.11.3"

View file

@ -10,7 +10,7 @@
# abstract syntax tree + symbol table # abstract syntax tree + symbol table
import import
msgs, hashes, nversion, options, strutils, crc, ropes, idents, lists, msgs, hashes, nversion, options, strutils, securehash, ropes, idents, lists,
intsets, idgen intsets, idgen
type type
@ -423,6 +423,7 @@ type
# but unfortunately it has measurable impact for compilation # but unfortunately it has measurable impact for compilation
# efficiency # efficiency
nfTransf, # node has been transformed nfTransf, # node has been transformed
nfNoRewrite # node should not be transformed anymore
nfSem # node has been checked for semantics nfSem # node has been checked for semantics
nfLL # node has gone through lambda lifting nfLL # node has gone through lambda lifting
nfDotField # the call can use a dot operator nfDotField # the call can use a dot operator
@ -538,22 +539,21 @@ type
mIncl, mExcl, mCard, mChr, mIncl, mExcl, mCard, mChr,
mGCref, mGCunref, mGCref, mGCunref,
mAddI, mSubI, mMulI, mDivI, mModI, mAddI64, mSubI64, mMulI64, mAddI, mSubI, mMulI, mDivI, mModI,
mDivI64, mModI64, mSucc, mPred, mSucc, mPred,
mAddF64, mSubF64, mMulF64, mDivF64, mAddF64, mSubF64, mMulF64, mDivF64,
mShrI, mShlI, mBitandI, mBitorI, mBitxorI, mMinI, mMaxI, mShrI, mShlI, mBitandI, mBitorI, mBitxorI, mMinI, mMaxI,
mShrI64, mShlI64, mBitandI64, mBitorI64, mBitxorI64,
mMinF64, mMaxF64, mAddU, mSubU, mMulU, mMinF64, mMaxF64, mAddU, mSubU, mMulU,
mDivU, mModU, mEqI, mLeI, mDivU, mModU, mEqI, mLeI,
mLtI, mLtI,
mEqI64, mLeI64, mLtI64, mEqF64, mLeF64, mLtF64, mEqF64, mLeF64, mLtF64,
mLeU, mLtU, mLeU64, mLtU64, mLeU, mLtU, mLeU64, mLtU64,
mEqEnum, mLeEnum, mLtEnum, mEqCh, mLeCh, mLtCh, mEqB, mLeB, mLtB, mEqRef, mEqEnum, mLeEnum, mLtEnum, mEqCh, mLeCh, mLtCh, mEqB, mLeB, mLtB, mEqRef,
mEqUntracedRef, mLePtr, mLtPtr, mEqCString, mXor, mEqProc, mUnaryMinusI, mEqUntracedRef, mLePtr, mLtPtr, mEqCString, mXor, mEqProc, mUnaryMinusI,
mUnaryMinusI64, mAbsI, mAbsI64, mNot, mUnaryMinusI64, mAbsI, mNot,
mUnaryPlusI, mBitnotI, mUnaryPlusI, mBitnotI,
mBitnotI64, mUnaryPlusF64, mUnaryMinusF64, mAbsF64, mZe8ToI, mZe8ToI64, mUnaryPlusF64, mUnaryMinusF64, mAbsF64, mZe8ToI, mZe8ToI64,
mZe16ToI, mZe16ToI64, mZe32ToI64, mZeIToI64, mToU8, mToU16, mToU32, mZe16ToI, mZe16ToI64, mZe32ToI64, mZeIToI64, mToU8, mToU16, mToU32,
mToFloat, mToBiggestFloat, mToInt, mToBiggestInt, mCharToStr, mBoolToStr, mToFloat, mToBiggestFloat, mToInt, mToBiggestInt, mCharToStr, mBoolToStr,
mIntToStr, mInt64ToStr, mFloatToStr, mCStrToStr, mStrToStr, mEnumToStr, mIntToStr, mInt64ToStr, mFloatToStr, mCStrToStr, mStrToStr, mEnumToStr,
@ -593,20 +593,19 @@ const
mPred, mInc, mDec, mOrd, mLengthOpenArray, mPred, mInc, mDec, mOrd, mLengthOpenArray,
mLengthStr, mLengthArray, mLengthSeq, mXLenStr, mXLenSeq, mLengthStr, mLengthArray, mLengthSeq, mXLenStr, mXLenSeq,
mIncl, mExcl, mCard, mChr, mIncl, mExcl, mCard, mChr,
mAddI, mSubI, mMulI, mDivI, mModI, mAddI64, mSubI64, mMulI64, mAddI, mSubI, mMulI, mDivI, mModI,
mDivI64, mModI64, mAddF64, mSubF64, mMulF64, mDivF64, mAddF64, mSubF64, mMulF64, mDivF64,
mShrI, mShlI, mBitandI, mBitorI, mBitxorI, mMinI, mMaxI, mShrI, mShlI, mBitandI, mBitorI, mBitxorI, mMinI, mMaxI,
mShrI64, mShlI64, mBitandI64, mBitorI64, mBitxorI64,
mMinF64, mMaxF64, mAddU, mSubU, mMulU, mMinF64, mMaxF64, mAddU, mSubU, mMulU,
mDivU, mModU, mEqI, mLeI, mDivU, mModU, mEqI, mLeI,
mLtI, mLtI,
mEqI64, mLeI64, mLtI64, mEqF64, mLeF64, mLtF64, mEqF64, mLeF64, mLtF64,
mLeU, mLtU, mLeU64, mLtU64, mLeU, mLtU, mLeU64, mLtU64,
mEqEnum, mLeEnum, mLtEnum, mEqCh, mLeCh, mLtCh, mEqB, mLeB, mLtB, mEqRef, mEqEnum, mLeEnum, mLtEnum, mEqCh, mLeCh, mLtCh, mEqB, mLeB, mLtB, mEqRef,
mEqProc, mEqUntracedRef, mLePtr, mLtPtr, mEqCString, mXor, mUnaryMinusI, mEqProc, mEqUntracedRef, mLePtr, mLtPtr, mEqCString, mXor, mUnaryMinusI,
mUnaryMinusI64, mAbsI, mAbsI64, mNot, mUnaryMinusI64, mAbsI, mNot,
mUnaryPlusI, mBitnotI, mUnaryPlusI, mBitnotI,
mBitnotI64, mUnaryPlusF64, mUnaryMinusF64, mAbsF64, mZe8ToI, mZe8ToI64, mUnaryPlusF64, mUnaryMinusF64, mAbsF64, mZe8ToI, mZe8ToI64,
mZe16ToI, mZe16ToI64, mZe32ToI64, mZeIToI64, mToU8, mToU16, mToU32, mZe16ToI, mZe16ToI64, mZe32ToI64, mZeIToI64, mToU8, mToU16, mToU32,
mToFloat, mToBiggestFloat, mToInt, mToBiggestInt, mCharToStr, mBoolToStr, mToFloat, mToBiggestFloat, mToInt, mToBiggestInt, mCharToStr, mBoolToStr,
mIntToStr, mInt64ToStr, mFloatToStr, mCStrToStr, mStrToStr, mEnumToStr, mIntToStr, mInt64ToStr, mFloatToStr, mCStrToStr, mStrToStr, mEnumToStr,
@ -842,7 +841,7 @@ type
data*: TIdNodePairSeq data*: TIdNodePairSeq
TNodePair* = object TNodePair* = object
h*: THash # because it is expensive to compute! h*: Hash # because it is expensive to compute!
key*: PNode key*: PNode
val*: int val*: int
@ -947,6 +946,9 @@ proc add*(father, son: PNode) =
proc `[]`*(n: PNode, i: int): PNode {.inline.} = proc `[]`*(n: PNode, i: int): PNode {.inline.} =
result = n.sons[i] result = n.sons[i]
template `-|`*(b, s: expr): expr =
(if b >= 0: b else: s.len + b)
# son access operators with support for negative indices # son access operators with support for negative indices
template `{}`*(n: PNode, i: int): expr = n[i -| n] template `{}`*(n: PNode, i: int): expr = n[i -| n]
template `{}=`*(n: PNode, i: int, s: PNode): stmt = template `{}=`*(n: PNode, i: int, s: PNode): stmt =
@ -1345,7 +1347,7 @@ proc propagateToOwner*(owner, elem: PType) =
owner.flags.incl tfHasAsgn owner.flags.incl tfHasAsgn
if owner.kind notin {tyProc, tyGenericInst, tyGenericBody, if owner.kind notin {tyProc, tyGenericInst, tyGenericBody,
tyGenericInvocation}: tyGenericInvocation, tyPtr}:
let elemB = elem.skipTypes({tyGenericInst}) let elemB = elem.skipTypes({tyGenericInst})
if elemB.isGCedMem or tfHasGCedMem in elemB.flags: if elemB.isGCedMem or tfHasGCedMem in elemB.flags:
# for simplicity, we propagate this flag even to generics. We then # for simplicity, we propagate this flag even to generics. We then

View file

@ -14,7 +14,7 @@
import import
ast, hashes, intsets, strutils, options, msgs, ropes, idents, rodutils ast, hashes, intsets, strutils, options, msgs, ropes, idents, rodutils
proc hashNode*(p: RootRef): THash proc hashNode*(p: RootRef): Hash
proc treeToYaml*(n: PNode, indent: int = 0, maxRecDepth: int = - 1): Rope proc treeToYaml*(n: PNode, indent: int = 0, maxRecDepth: int = - 1): Rope
# Convert a tree into its YAML representation; this is used by the # Convert a tree into its YAML representation; this is used by the
# YAML code generator and it is invaluable for debugging purposes. # YAML code generator and it is invaluable for debugging purposes.
@ -49,7 +49,7 @@ proc strTableGet*(t: TStrTable, name: PIdent): PSym
type type
TTabIter*{.final.} = object # consider all fields here private TTabIter*{.final.} = object # consider all fields here private
h*: THash # current hash h*: Hash # current hash
proc initTabIter*(ti: var TTabIter, tab: TStrTable): PSym proc initTabIter*(ti: var TTabIter, tab: TStrTable): PSym
proc nextIter*(ti: var TTabIter, tab: TStrTable): PSym proc nextIter*(ti: var TTabIter, tab: TStrTable): PSym
@ -65,7 +65,7 @@ proc nextIter*(ti: var TTabIter, tab: TStrTable): PSym
type type
TIdentIter*{.final.} = object # iterator over all syms with same identifier TIdentIter*{.final.} = object # iterator over all syms with same identifier
h*: THash # current hash h*: Hash # current hash
name*: PIdent name*: PIdent
@ -94,7 +94,7 @@ proc getSymFromList*(list: PNode, ident: PIdent, start: int = 0): PSym
proc lookupInRecord*(n: PNode, field: PIdent): PSym proc lookupInRecord*(n: PNode, field: PIdent): PSym
proc getModule*(s: PSym): PSym proc getModule*(s: PSym): PSym
proc mustRehash*(length, counter: int): bool proc mustRehash*(length, counter: int): bool
proc nextTry*(h, maxHash: THash): THash {.inline.} proc nextTry*(h, maxHash: Hash): Hash {.inline.}
# ------------- table[int, int] --------------------------------------------- # ------------- table[int, int] ---------------------------------------------
const const
@ -196,7 +196,7 @@ proc getSymFromList(list: PNode, ident: PIdent, start: int = 0): PSym =
else: internalError(list.info, "getSymFromList") else: internalError(list.info, "getSymFromList")
result = nil result = nil
proc hashNode(p: RootRef): THash = proc hashNode(p: RootRef): Hash =
result = hash(cast[pointer](p)) result = hash(cast[pointer](p))
proc mustRehash(length, counter: int): bool = proc mustRehash(length, counter: int): bool =
@ -466,7 +466,7 @@ proc debug(n: PNode) =
const const
EmptySeq = @[] EmptySeq = @[]
proc nextTry(h, maxHash: THash): THash = proc nextTry(h, maxHash: Hash): Hash =
result = ((5 * h) + 1) and maxHash result = ((5 * h) + 1) and maxHash
# For any initial h in range(maxHash), repeating that maxHash times # For any initial h in range(maxHash), repeating that maxHash times
# generates each int in range(maxHash) exactly once (see any text on # generates each int in range(maxHash) exactly once (see any text on
@ -474,7 +474,7 @@ proc nextTry(h, maxHash: THash): THash =
proc objectSetContains(t: TObjectSet, obj: RootRef): bool = proc objectSetContains(t: TObjectSet, obj: RootRef): bool =
# returns true whether n is in t # returns true whether n is in t
var h: THash = hashNode(obj) and high(t.data) # start with real hash value var h: Hash = hashNode(obj) and high(t.data) # start with real hash value
while t.data[h] != nil: while t.data[h] != nil:
if t.data[h] == obj: if t.data[h] == obj:
return true return true
@ -482,7 +482,7 @@ proc objectSetContains(t: TObjectSet, obj: RootRef): bool =
result = false result = false
proc objectSetRawInsert(data: var TObjectSeq, obj: RootRef) = proc objectSetRawInsert(data: var TObjectSeq, obj: RootRef) =
var h: THash = hashNode(obj) and high(data) var h: Hash = hashNode(obj) and high(data)
while data[h] != nil: while data[h] != nil:
assert(data[h] != obj) assert(data[h] != obj)
h = nextTry(h, high(data)) h = nextTry(h, high(data))
@ -503,7 +503,7 @@ proc objectSetIncl(t: var TObjectSet, obj: RootRef) =
proc objectSetContainsOrIncl(t: var TObjectSet, obj: RootRef): bool = proc objectSetContainsOrIncl(t: var TObjectSet, obj: RootRef): bool =
# returns true if obj is already in the string table: # returns true if obj is already in the string table:
var h: THash = hashNode(obj) and high(t.data) var h: Hash = hashNode(obj) and high(t.data)
while true: while true:
var it = t.data[h] var it = t.data[h]
if it == nil: break if it == nil: break
@ -520,7 +520,7 @@ proc objectSetContainsOrIncl(t: var TObjectSet, obj: RootRef): bool =
result = false result = false
proc tableRawGet(t: TTable, key: RootRef): int = proc tableRawGet(t: TTable, key: RootRef): int =
var h: THash = hashNode(key) and high(t.data) # start with real hash value var h: Hash = hashNode(key) and high(t.data) # start with real hash value
while t.data[h].key != nil: while t.data[h].key != nil:
if t.data[h].key == key: if t.data[h].key == key:
return h return h
@ -529,7 +529,7 @@ proc tableRawGet(t: TTable, key: RootRef): int =
proc tableSearch(t: TTable, key, closure: RootRef, proc tableSearch(t: TTable, key, closure: RootRef,
comparator: TCmpProc): RootRef = comparator: TCmpProc): RootRef =
var h: THash = hashNode(key) and high(t.data) # start with real hash value var h: Hash = hashNode(key) and high(t.data) # start with real hash value
while t.data[h].key != nil: while t.data[h].key != nil:
if t.data[h].key == key: if t.data[h].key == key:
if comparator(t.data[h].val, closure): if comparator(t.data[h].val, closure):
@ -544,7 +544,7 @@ proc tableGet(t: TTable, key: RootRef): RootRef =
else: result = nil else: result = nil
proc tableRawInsert(data: var TPairSeq, key, val: RootRef) = proc tableRawInsert(data: var TPairSeq, key, val: RootRef) =
var h: THash = hashNode(key) and high(data) var h: Hash = hashNode(key) and high(data)
while data[h].key != nil: while data[h].key != nil:
assert(data[h].key != key) assert(data[h].key != key)
h = nextTry(h, high(data)) h = nextTry(h, high(data))
@ -569,7 +569,7 @@ proc tablePut(t: var TTable, key, val: RootRef) =
inc(t.counter) inc(t.counter)
proc strTableContains(t: TStrTable, n: PSym): bool = proc strTableContains(t: TStrTable, n: PSym): bool =
var h: THash = n.name.h and high(t.data) # start with real hash value var h: Hash = n.name.h and high(t.data) # start with real hash value
while t.data[h] != nil: while t.data[h] != nil:
if (t.data[h] == n): if (t.data[h] == n):
return true return true
@ -577,7 +577,7 @@ proc strTableContains(t: TStrTable, n: PSym): bool =
result = false result = false
proc strTableRawInsert(data: var TSymSeq, n: PSym) = proc strTableRawInsert(data: var TSymSeq, n: PSym) =
var h: THash = n.name.h and high(data) var h: Hash = n.name.h and high(data)
if sfImmediate notin n.flags: if sfImmediate notin n.flags:
# fast path: # fast path:
while data[h] != nil: while data[h] != nil:
@ -606,7 +606,7 @@ proc strTableRawInsert(data: var TSymSeq, n: PSym) =
proc symTabReplaceRaw(data: var TSymSeq, prevSym: PSym, newSym: PSym) = proc symTabReplaceRaw(data: var TSymSeq, prevSym: PSym, newSym: PSym) =
assert prevSym.name.h == newSym.name.h assert prevSym.name.h == newSym.name.h
var h: THash = prevSym.name.h and high(data) var h: Hash = prevSym.name.h and high(data)
while data[h] != nil: while data[h] != nil:
if data[h] == prevSym: if data[h] == prevSym:
data[h] = newSym data[h] = newSym
@ -640,7 +640,7 @@ proc strTableIncl*(t: var TStrTable, n: PSym): bool {.discardable.} =
# It is essential that `n` is written nevertheless! # It is essential that `n` is written nevertheless!
# This way the newest redefinition is picked by the semantic analyses! # This way the newest redefinition is picked by the semantic analyses!
assert n.name != nil assert n.name != nil
var h: THash = n.name.h and high(t.data) var h: Hash = n.name.h and high(t.data)
var replaceSlot = -1 var replaceSlot = -1
while true: while true:
var it = t.data[h] var it = t.data[h]
@ -666,7 +666,7 @@ proc strTableIncl*(t: var TStrTable, n: PSym): bool {.discardable.} =
result = false result = false
proc strTableGet(t: TStrTable, name: PIdent): PSym = proc strTableGet(t: TStrTable, name: PIdent): PSym =
var h: THash = name.h and high(t.data) var h: Hash = name.h and high(t.data)
while true: while true:
result = t.data[h] result = t.data[h]
if result == nil: break if result == nil: break
@ -694,7 +694,7 @@ proc nextIdentIter(ti: var TIdentIter, tab: TStrTable): PSym =
proc nextIdentExcluding*(ti: var TIdentIter, tab: TStrTable, proc nextIdentExcluding*(ti: var TIdentIter, tab: TStrTable,
excluding: IntSet): PSym = excluding: IntSet): PSym =
var h: THash = ti.h and high(tab.data) var h: Hash = ti.h and high(tab.data)
var start = h var start = h
result = tab.data[h] result = tab.data[h]
while result != nil: while result != nil:
@ -743,7 +743,7 @@ proc hasEmptySlot(data: TIdPairSeq): bool =
result = false result = false
proc idTableRawGet(t: TIdTable, key: int): int = proc idTableRawGet(t: TIdTable, key: int): int =
var h: THash var h: Hash
h = key and high(t.data) # start with real hash value h = key and high(t.data) # start with real hash value
while t.data[h].key != nil: while t.data[h].key != nil:
if t.data[h].key.id == key: if t.data[h].key.id == key:
@ -772,7 +772,7 @@ iterator pairs*(t: TIdTable): tuple[key: int, value: RootRef] =
yield (t.data[i].key.id, t.data[i].val) yield (t.data[i].key.id, t.data[i].val)
proc idTableRawInsert(data: var TIdPairSeq, key: PIdObj, val: RootRef) = proc idTableRawInsert(data: var TIdPairSeq, key: PIdObj, val: RootRef) =
var h: THash var h: Hash
h = key.id and high(data) h = key.id and high(data)
while data[h].key != nil: while data[h].key != nil:
assert(data[h].key.id != key.id) assert(data[h].key.id != key.id)
@ -805,7 +805,7 @@ iterator idTablePairs*(t: TIdTable): tuple[key: PIdObj, val: RootRef] =
if not isNil(t.data[i].key): yield (t.data[i].key, t.data[i].val) if not isNil(t.data[i].key): yield (t.data[i].key, t.data[i].val)
proc idNodeTableRawGet(t: TIdNodeTable, key: PIdObj): int = proc idNodeTableRawGet(t: TIdNodeTable, key: PIdObj): int =
var h: THash var h: Hash
h = key.id and high(t.data) # start with real hash value h = key.id and high(t.data) # start with real hash value
while t.data[h].key != nil: while t.data[h].key != nil:
if t.data[h].key.id == key.id: if t.data[h].key.id == key.id:
@ -824,7 +824,7 @@ proc idNodeTableGetLazy*(t: TIdNodeTable, key: PIdObj): PNode =
result = idNodeTableGet(t, key) result = idNodeTableGet(t, key)
proc idNodeTableRawInsert(data: var TIdNodePairSeq, key: PIdObj, val: PNode) = proc idNodeTableRawInsert(data: var TIdNodePairSeq, key: PIdObj, val: PNode) =
var h: THash var h: Hash
h = key.id and high(data) h = key.id and high(data)
while data[h].key != nil: while data[h].key != nil:
assert(data[h].key.id != key.id) assert(data[h].key.id != key.id)
@ -863,7 +863,7 @@ proc initIITable(x: var TIITable) =
for i in countup(0, StartSize - 1): x.data[i].key = InvalidKey for i in countup(0, StartSize - 1): x.data[i].key = InvalidKey
proc iiTableRawGet(t: TIITable, key: int): int = proc iiTableRawGet(t: TIITable, key: int): int =
var h: THash var h: Hash
h = key and high(t.data) # start with real hash value h = key and high(t.data) # start with real hash value
while t.data[h].key != InvalidKey: while t.data[h].key != InvalidKey:
if t.data[h].key == key: return h if t.data[h].key == key: return h
@ -876,7 +876,7 @@ proc iiTableGet(t: TIITable, key: int): int =
else: result = InvalidKey else: result = InvalidKey
proc iiTableRawInsert(data: var TIIPairSeq, key, val: int) = proc iiTableRawInsert(data: var TIIPairSeq, key, val: int) =
var h: THash var h: Hash
h = key and high(data) h = key and high(data)
while data[h].key != InvalidKey: while data[h].key != InvalidKey:
assert(data[h].key != key) assert(data[h].key != key)

View file

@ -503,13 +503,13 @@ proc binaryArithOverflow(p: BProc, e: PNode, d: var TLoc, m: TMagic) =
"$# = #addInt($#, $#);$n", "$# = #subInt($#, $#);$n", "$# = #addInt($#, $#);$n", "$# = #subInt($#, $#);$n",
"$# = #mulInt($#, $#);$n", "$# = #divInt($#, $#);$n", "$# = #mulInt($#, $#);$n", "$# = #divInt($#, $#);$n",
"$# = #modInt($#, $#);$n", "$# = #modInt($#, $#);$n",
"$# = #addInt($#, $#);$n", "$# = #subInt($#, $#);$n"]
prc64: array[mAddI..mPred, string] = [
"$# = #addInt64($#, $#);$n", "$# = #subInt64($#, $#);$n", "$# = #addInt64($#, $#);$n", "$# = #subInt64($#, $#);$n",
"$# = #mulInt64($#, $#);$n", "$# = #divInt64($#, $#);$n", "$# = #mulInt64($#, $#);$n", "$# = #divInt64($#, $#);$n",
"$# = #modInt64($#, $#);$n", "$# = #modInt64($#, $#);$n",
"$# = #addInt($#, $#);$n", "$# = #subInt($#, $#);$n"] "$# = #addInt64($#, $#);$n", "$# = #subInt64($#, $#);$n"]
opr: array[mAddI..mPred, string] = [ opr: array[mAddI..mPred, string] = [
"($#)($# + $#)", "($#)($# - $#)", "($#)($# * $#)",
"($#)($# / $#)", "($#)($# % $#)",
"($#)($# + $#)", "($#)($# - $#)", "($#)($# * $#)", "($#)($# + $#)", "($#)($# - $#)", "($#)($# * $#)",
"($#)($# / $#)", "($#)($# % $#)", "($#)($# / $#)", "($#)($# % $#)",
"($#)($# + $#)", "($#)($# - $#)"] "($#)($# + $#)", "($#)($# - $#)"]
@ -525,16 +525,16 @@ proc binaryArithOverflow(p: BProc, e: PNode, d: var TLoc, m: TMagic) =
let res = opr[m] % [getTypeDesc(p.module, t), rdLoc(a), rdLoc(b)] let res = opr[m] % [getTypeDesc(p.module, t), rdLoc(a), rdLoc(b)]
putIntoDest(p, d, e.typ, res) putIntoDest(p, d, e.typ, res)
else: else:
let res = binaryArithOverflowRaw(p, t, a, b, prc[m]) let res = binaryArithOverflowRaw(p, t, a, b,
if t.kind == tyInt64: prc64[m] else: prc[m])
putIntoDest(p, d, e.typ, "($#)($#)" % [getTypeDesc(p.module, t), res]) putIntoDest(p, d, e.typ, "($#)($#)" % [getTypeDesc(p.module, t), res])
proc unaryArithOverflow(p: BProc, e: PNode, d: var TLoc, m: TMagic) = proc unaryArithOverflow(p: BProc, e: PNode, d: var TLoc, m: TMagic) =
const const
opr: array[mUnaryMinusI..mAbsI64, string] = [ opr: array[mUnaryMinusI..mAbsI, string] = [
mUnaryMinusI: "((NI$2)-($1))", mUnaryMinusI: "((NI$2)-($1))",
mUnaryMinusI64: "-($1)", mUnaryMinusI64: "-($1)",
mAbsI: "($1 > 0? ($1) : -($1))", mAbsI: "($1 > 0? ($1) : -($1))"]
mAbsI64: "($1 > 0? ($1) : -($1))"]
var var
a: TLoc a: TLoc
t: PType t: PType
@ -561,11 +561,6 @@ proc binaryArith(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
"($4)($1 ^ $2)", # BitxorI "($4)($1 ^ $2)", # BitxorI
"(($1 <= $2) ? $1 : $2)", # MinI "(($1 <= $2) ? $1 : $2)", # MinI
"(($1 >= $2) ? $1 : $2)", # MaxI "(($1 >= $2) ? $1 : $2)", # MaxI
"($4)((NU64)($1) >> (NU64)($2))", # ShrI64
"($4)((NU64)($1) << (NU64)($2))", # ShlI64
"($4)($1 & $2)", # BitandI64
"($4)($1 | $2)", # BitorI64
"($4)($1 ^ $2)", # BitxorI64
"(($1 <= $2) ? $1 : $2)", # MinF64 "(($1 <= $2) ? $1 : $2)", # MinF64
"(($1 >= $2) ? $1 : $2)", # MaxF64 "(($1 >= $2) ? $1 : $2)", # MaxF64
"($4)((NU$3)($1) + (NU$3)($2))", # AddU "($4)((NU$3)($1) + (NU$3)($2))", # AddU
@ -576,9 +571,6 @@ proc binaryArith(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
"($1 == $2)", # EqI "($1 == $2)", # EqI
"($1 <= $2)", # LeI "($1 <= $2)", # LeI
"($1 < $2)", # LtI "($1 < $2)", # LtI
"($1 == $2)", # EqI64
"($1 <= $2)", # LeI64
"($1 < $2)", # LtI64
"($1 == $2)", # EqF64 "($1 == $2)", # EqF64
"($1 <= $2)", # LeF64 "($1 <= $2)", # LeF64
"($1 < $2)", # LtF64 "($1 < $2)", # LtF64
@ -638,7 +630,6 @@ proc unaryArith(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
unArithTab: array[mNot..mToBiggestInt, string] = ["!($1)", # Not unArithTab: array[mNot..mToBiggestInt, string] = ["!($1)", # Not
"$1", # UnaryPlusI "$1", # UnaryPlusI
"($3)((NU$2) ~($1))", # BitnotI "($3)((NU$2) ~($1))", # BitnotI
"($3)((NU$2) ~($1))", # BitnotI64
"$1", # UnaryPlusF64 "$1", # UnaryPlusF64
"-($1)", # UnaryMinusF64 "-($1)", # UnaryMinusF64
"($1 > 0? ($1) : -($1))", # AbsF64; BUGFIX: fabs() makes problems "($1 > 0? ($1) : -($1))", # AbsF64; BUGFIX: fabs() makes problems
@ -1661,7 +1652,7 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
case op case op
of mOr, mAnd: genAndOr(p, e, d, op) of mOr, mAnd: genAndOr(p, e, d, op)
of mNot..mToBiggestInt: unaryArith(p, e, d, op) of mNot..mToBiggestInt: unaryArith(p, e, d, op)
of mUnaryMinusI..mAbsI64: unaryArithOverflow(p, e, d, op) of mUnaryMinusI..mAbsI: unaryArithOverflow(p, e, d, op)
of mAddF64..mDivF64: binaryFloatArith(p, e, d, op) of mAddF64..mDivF64: binaryFloatArith(p, e, d, op)
of mShrI..mXor: binaryArith(p, e, d, op) of mShrI..mXor: binaryArith(p, e, d, op)
of mEqProc: genEqProc(p, e, d) of mEqProc: genEqProc(p, e, d)
@ -2150,7 +2141,7 @@ proc genNamedConstExpr(p: BProc, n: PNode): Rope =
proc genConstSimpleList(p: BProc, n: PNode): Rope = proc genConstSimpleList(p: BProc, n: PNode): Rope =
var length = sonsLen(n) var length = sonsLen(n)
result = rope("{") result = rope("{")
for i in countup(0, length - 2): for i in countup(ord(n.kind == nkObjConstr), length - 2):
addf(result, "$1,$n", [genNamedConstExpr(p, n.sons[i])]) addf(result, "$1,$n", [genNamedConstExpr(p, n.sons[i])])
if length > 0: add(result, genNamedConstExpr(p, n.sons[length - 1])) if length > 0: add(result, genNamedConstExpr(p, n.sons[length - 1]))
addf(result, "}$n", []) addf(result, "}$n", [])

View file

@ -10,12 +10,11 @@
## This module implements the C code generator. ## This module implements the C code generator.
import import
ast, astalgo, hashes, trees, platform, magicsys, extccomp, ast, astalgo, hashes, trees, platform, magicsys, extccomp, options, intsets,
options, intsets, nversion, nimsets, msgs, securehash, bitsets, idents, lists, types,
nversion, nimsets, msgs, crc, bitsets, idents, lists, types, ccgutils, os, ccgutils, os, ropes, math, passes, rodread, wordrecg, treetab, cgmeth,
ropes, math, passes, rodread, wordrecg, treetab, cgmeth, condsyms, condsyms, rodutils, renderer, idgen, cgendata, ccgmerge, semfold, aliases,
rodutils, renderer, idgen, cgendata, ccgmerge, semfold, aliases, lowerings, lowerings, semparallel
semparallel
import strutils except `%` # collides with ropes.`%` import strutils except `%` # collides with ropes.`%`
@ -721,6 +720,8 @@ proc genProcPrototype(m: BModule, sym: PSym) =
getTypeDesc(m, sym.loc.t), mangleDynLibProc(sym))) getTypeDesc(m, sym.loc.t), mangleDynLibProc(sym)))
elif not containsOrIncl(m.declaredProtos, sym.id): elif not containsOrIncl(m.declaredProtos, sym.id):
var header = genProcHeader(m, sym) var header = genProcHeader(m, sym)
if sfNoReturn in sym.flags and hasDeclspec in extccomp.CC[cCompiler].props:
header = "__declspec(noreturn) " & header
if sym.typ.callConv != ccInline and crossesCppBoundary(m, sym): if sym.typ.callConv != ccInline and crossesCppBoundary(m, sym):
header = "extern \"C\" " & header header = "extern \"C\" " & header
if sfPure in sym.flags and hasAttribute in CC[cCompiler].props: if sfPure in sym.flags and hasAttribute in CC[cCompiler].props:

View file

@ -537,6 +537,9 @@ proc processSwitch(switch, arg: string, pass: TCmdLinePass, info: TLineInfo) =
of "genscript": of "genscript":
expectNoArg(switch, arg, pass, info) expectNoArg(switch, arg, pass, info)
incl(gGlobalOptions, optGenScript) incl(gGlobalOptions, optGenScript)
of "usecolors":
expectNoArg(switch, arg, pass, info)
incl(gGlobalOptions, optUseColors)
of "lib": of "lib":
expectArg(switch, arg, pass, info) expectArg(switch, arg, pass, info)
libpath = processPath(arg, notRelativeToProj=true) libpath = processPath(arg, notRelativeToProj=true)

View file

@ -88,3 +88,4 @@ proc initDefines*() =
defineSymbol("nimalias") defineSymbol("nimalias")
defineSymbol("nimlocks") defineSymbol("nimlocks")
defineSymbol("nimnode") defineSymbol("nimnode")
defineSymbol("nimnomagic64")

View file

@ -1,147 +0,0 @@
#
#
# The Nim Compiler
# (c) Copyright 2012 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
import
strutils
type
TCrc32* = int32
const
InitCrc32* = TCrc32(- 1)
InitAdler32* = int32(1)
proc updateCrc32*(val: int8, crc: TCrc32): TCrc32 {.inline.}
proc updateCrc32*(val: char, crc: TCrc32): TCrc32 {.inline.}
proc crcFromBuf*(buf: pointer, length: int): TCrc32
proc strCrc32*(s: string): TCrc32
proc crcFromFile*(filename: string): TCrc32
proc updateAdler32*(adler: int32, buf: pointer, length: int): int32
# implementation
type
TCRC_TabEntry = int
const
crc32table: array[0..255, TCRC_TabEntry] = [0, 1996959894, - 301047508,
- 1727442502, 124634137, 1886057615, - 379345611, - 1637575261, 249268274,
2044508324, - 522852066, - 1747789432, 162941995, 2125561021, - 407360249,
- 1866523247, 498536548, 1789927666, - 205950648, - 2067906082, 450548861,
1843258603, - 187386543, - 2083289657, 325883990, 1684777152, - 43845254,
- 1973040660, 335633487, 1661365465, - 99664541, - 1928851979, 997073096,
1281953886, - 715111964, - 1570279054, 1006888145, 1258607687, - 770865667,
- 1526024853, 901097722, 1119000684, - 608450090, - 1396901568, 853044451,
1172266101, - 589951537, - 1412350631, 651767980, 1373503546, - 925412992,
- 1076862698, 565507253, 1454621731, - 809855591, - 1195530993, 671266974,
1594198024, - 972236366, - 1324619484, 795835527, 1483230225, - 1050600021,
- 1234817731, 1994146192, 31158534, - 1731059524, - 271249366, 1907459465,
112637215, - 1614814043, - 390540237, 2013776290, 251722036, - 1777751922,
- 519137256, 2137656763, 141376813, - 1855689577, - 429695999, 1802195444,
476864866, - 2056965928, - 228458418, 1812370925, 453092731, - 2113342271,
- 183516073, 1706088902, 314042704, - 1950435094, - 54949764, 1658658271,
366619977, - 1932296973, - 69972891, 1303535960, 984961486, - 1547960204,
- 725929758, 1256170817, 1037604311, - 1529756563, - 740887301, 1131014506,
879679996, - 1385723834, - 631195440, 1141124467, 855842277, - 1442165665,
- 586318647, 1342533948, 654459306, - 1106571248, - 921952122, 1466479909,
544179635, - 1184443383, - 832445281, 1591671054, 702138776, - 1328506846,
- 942167884, 1504918807, 783551873, - 1212326853, - 1061524307, - 306674912,
- 1698712650, 62317068, 1957810842, - 355121351, - 1647151185, 81470997,
1943803523, - 480048366, - 1805370492, 225274430, 2053790376, - 468791541,
- 1828061283, 167816743, 2097651377, - 267414716, - 2029476910, 503444072,
1762050814, - 144550051, - 2140837941, 426522225, 1852507879, - 19653770,
- 1982649376, 282753626, 1742555852, - 105259153, - 1900089351, 397917763,
1622183637, - 690576408, - 1580100738, 953729732, 1340076626, - 776247311,
- 1497606297, 1068828381, 1219638859, - 670225446, - 1358292148, 906185462,
1090812512, - 547295293, - 1469587627, 829329135, 1181335161, - 882789492,
- 1134132454, 628085408, 1382605366, - 871598187, - 1156888829, 570562233,
1426400815, - 977650754, - 1296233688, 733239954, 1555261956, - 1026031705,
- 1244606671, 752459403, 1541320221, - 1687895376, - 328994266, 1969922972,
40735498, - 1677130071, - 351390145, 1913087877, 83908371, - 1782625662,
- 491226604, 2075208622, 213261112, - 1831694693, - 438977011, 2094854071,
198958881, - 2032938284, - 237706686, 1759359992, 534414190, - 2118248755,
- 155638181, 1873836001, 414664567, - 2012718362, - 15766928, 1711684554,
285281116, - 1889165569, - 127750551, 1634467795, 376229701, - 1609899400,
- 686959890, 1308918612, 956543938, - 1486412191, - 799009033, 1231636301,
1047427035, - 1362007478, - 640263460, 1088359270, 936918000, - 1447252397,
- 558129467, 1202900863, 817233897, - 1111625188, - 893730166, 1404277552,
615818150, - 1160759803, - 841546093, 1423857449, 601450431, - 1285129682,
- 1000256840, 1567103746, 711928724, - 1274298825, - 1022587231, 1510334235,
755167117]
proc updateCrc32(val: int8, crc: TCrc32): TCrc32 =
result = TCrc32(crc32table[(int(crc) xor (int(val) and 0x000000FF)) and
0x000000FF]) xor (crc shr TCrc32(8))
proc updateCrc32(val: char, crc: TCrc32): TCrc32 =
result = updateCrc32(toU8(ord(val)), crc)
proc strCrc32(s: string): TCrc32 =
result = InitCrc32
for i in countup(0, len(s) - 1): result = updateCrc32(s[i], result)
proc `><`*(c: TCrc32, s: string): TCrc32 =
result = c
for i in 0..len(s)-1: result = updateCrc32(s[i], result)
type
TByteArray = array[0..10000000, int8]
PByteArray = ref TByteArray
proc crcFromBuf(buf: pointer, length: int): TCrc32 =
var p = cast[PByteArray](buf)
result = InitCrc32
for i in countup(0, length - 1): result = updateCrc32(p[i], result)
proc crcFromFile(filename: string): TCrc32 =
const
bufSize = 8000 # don't use 8K for the memory allocator!
var
bin: File
result = InitCrc32
if not open(bin, filename):
return # not equal if file does not exist
var buf = alloc(bufSize)
var p = cast[PByteArray](buf)
while true:
var readBytes = readBuffer(bin, buf, bufSize)
for i in countup(0, readBytes - 1): result = updateCrc32(p[i], result)
if readBytes != bufSize: break
dealloc(buf)
close(bin)
const
base = int32(65521) # largest prime smaller than 65536
# NMAX = 5552; original code with unsigned 32 bit integer
# NMAX is the largest n
# such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1
nmax = 3854 # code with signed 32 bit integer
# NMAX is the largest n such that
# 255n(n+1)/2 + (n+1)(BASE-1) <= 2^31-1
# The penalty is the time loss in the extra MOD-calls.
proc updateAdler32(adler: int32, buf: pointer, length: int): int32 =
var
s1, s2: int32
L, k, b: int
s1 = adler and int32(0x0000FFFF)
s2 = (adler shr int32(16)) and int32(0x0000FFFF)
L = length
b = 0
while (L > 0):
if L < nmax: k = L
else: k = nmax
dec(L, k)
while (k > 0):
s1 = s1 +% int32((cast[cstring](buf))[b])
s2 = s2 +% s1
inc(b)
dec(k)
s1 = `%%`(s1, base)
s2 = `%%`(s2, base)
result = (s2 shl int32(16)) or s1

View file

@ -18,7 +18,7 @@ import
type type
TSections = array[TSymKind, Rope] TSections = array[TSymKind, Rope]
TDocumentor = object of rstgen.TRstGenerator TDocumentor = object of rstgen.RstGenerator
modDesc: Rope # module description modDesc: Rope # module description
id: int # for generating IDs id: int # for generating IDs
toc, section: TSections toc, section: TSections
@ -29,7 +29,7 @@ type
PDoc* = ref TDocumentor ## Alias to type less. PDoc* = ref TDocumentor ## Alias to type less.
proc compilerMsgHandler(filename: string, line, col: int, proc compilerMsgHandler(filename: string, line, col: int,
msgKind: rst.TMsgKind, arg: string) {.procvar.} = msgKind: rst.MsgKind, arg: string) {.procvar.} =
# translate msg kind: # translate msg kind:
var k: msgs.TMsgKind var k: msgs.TMsgKind
case msgKind case msgKind
@ -53,7 +53,7 @@ proc docgenFindFile(s: string): string {.procvar.} =
proc parseRst(text, filename: string, proc parseRst(text, filename: string,
line, column: int, hasToc: var bool, line, column: int, hasToc: var bool,
rstOptions: TRstParseOptions): PRstNode = rstOptions: RstParseOptions): PRstNode =
result = rstParse(text, filename, line, column, hasToc, rstOptions, result = rstParse(text, filename, line, column, hasToc, rstOptions,
docgenFindFile, compilerMsgHandler) docgenFindFile, compilerMsgHandler)

View file

@ -13,7 +13,8 @@
# nim files. # nim files.
import import
lists, ropes, os, strutils, osproc, platform, condsyms, options, msgs, crc lists, ropes, os, strutils, osproc, platform, condsyms, options, msgs,
securehash
type type
TSystemCC* = enum TSystemCC* = enum
@ -572,26 +573,24 @@ proc getCompileCFileCmd*(cfilename: string, isExternal = false): string =
"nim", quoteShell(getPrefixDir()), "nim", quoteShell(getPrefixDir()),
"lib", quoteShell(libpath)]) "lib", quoteShell(libpath)])
proc footprint(filename: string): TCrc32 = proc footprint(filename: string): SecureHash =
# note, '><' further modifies a crc value with a string. result = secureHash(
result = crcFromFile(filename) >< $secureHashFile(filename) &
platform.OS[targetOS].name >< platform.OS[targetOS].name &
platform.CPU[targetCPU].name >< platform.CPU[targetCPU].name &
extccomp.CC[extccomp.cCompiler].name >< extccomp.CC[extccomp.cCompiler].name &
getCompileCFileCmd(filename, true) getCompileCFileCmd(filename, true))
proc externalFileChanged(filename: string): bool = proc externalFileChanged(filename: string): bool =
if gCmd notin {cmdCompileToC, cmdCompileToCpp, cmdCompileToOC, cmdCompileToLLVM}: if gCmd notin {cmdCompileToC, cmdCompileToCpp, cmdCompileToOC, cmdCompileToLLVM}:
return false return false
var crcFile = toGeneratedFile(filename.withPackageName, "crc") var crcFile = toGeneratedFile(filename.withPackageName, "crc")
var currentCrc = int(footprint(filename)) var currentCrc = footprint(filename)
var f: File var f: File
if open(f, crcFile, fmRead): if open(f, crcFile, fmRead):
var line = newStringOfCap(40) let oldCrc = parseSecureHash(f.readLine())
if not f.readLine(line): line = "0"
close(f) close(f)
var oldCrc = parseInt(line)
result = oldCrc != currentCrc result = oldCrc != currentCrc
else: else:
result = true result = true

View file

@ -12,9 +12,9 @@
import ast, astalgo import ast, astalgo
const const
someCmp = {mEqI, mEqI64, mEqF64, mEqEnum, mEqCh, mEqB, mEqRef, mEqProc, someCmp = {mEqI, mEqF64, mEqEnum, mEqCh, mEqB, mEqRef, mEqProc,
mEqUntracedRef, mLeI, mLeI64, mLeF64, mLeU, mLeU64, mLeEnum, mEqUntracedRef, mLeI, mLeF64, mLeU, mLeU64, mLeEnum,
mLeCh, mLeB, mLePtr, mLtI, mLtI64, mLtF64, mLtU, mLtU64, mLtEnum, mLeCh, mLeB, mLePtr, mLtI, mLtF64, mLtU, mLtU64, mLtEnum,
mLtCh, mLtB, mLtPtr} mLtCh, mLtB, mLtPtr}
proc isCounter(s: PSym): bool {.inline.} = proc isCounter(s: PSym): bool {.inline.} =

View file

@ -13,13 +13,13 @@ import ast, astalgo, msgs, magicsys, nimsets, trees, types, renderer, idents,
saturate saturate
const const
someEq = {mEqI, mEqI64, mEqF64, mEqEnum, mEqCh, mEqB, mEqRef, mEqProc, someEq = {mEqI, mEqF64, mEqEnum, mEqCh, mEqB, mEqRef, mEqProc,
mEqUntracedRef, mEqStr, mEqSet, mEqCString} mEqUntracedRef, mEqStr, mEqSet, mEqCString}
# set excluded here as the semantics are vastly different: # set excluded here as the semantics are vastly different:
someLe = {mLeI, mLeI64, mLeF64, mLeU, mLeU64, mLeEnum, someLe = {mLeI, mLeF64, mLeU, mLeU64, mLeEnum,
mLeCh, mLeB, mLePtr, mLeStr} mLeCh, mLeB, mLePtr, mLeStr}
someLt = {mLtI, mLtI64, mLtF64, mLtU, mLtU64, mLtEnum, someLt = {mLtI, mLtF64, mLtU, mLtU64, mLtEnum,
mLtCh, mLtB, mLtPtr, mLtStr} mLtCh, mLtB, mLtPtr, mLtStr}
someLen = {mLengthOpenArray, mLengthStr, mLengthArray, mLengthSeq, someLen = {mLengthOpenArray, mLengthStr, mLengthArray, mLengthSeq,
@ -30,11 +30,11 @@ const
someHigh = {mHigh} someHigh = {mHigh}
# we don't list unsigned here because wrap around semantics suck for # we don't list unsigned here because wrap around semantics suck for
# proving anything: # proving anything:
someAdd = {mAddI, mAddI64, mAddF64, mSucc} someAdd = {mAddI, mAddF64, mSucc}
someSub = {mSubI, mSubI64, mSubF64, mPred} someSub = {mSubI, mSubF64, mPred}
someMul = {mMulI, mMulI64, mMulF64} someMul = {mMulI, mMulF64}
someDiv = {mDivI, mDivI64, mDivF64} someDiv = {mDivI, mDivF64}
someMod = {mModI, mModI64} someMod = {mModI}
someMax = {mMaxI, mMaxF64} someMax = {mMaxI, mMaxF64}
someMin = {mMinI, mMinF64} someMin = {mMinI, mMinF64}

View file

@ -12,7 +12,7 @@
# id. This module is essential for the compiler's performance. # id. This module is essential for the compiler's performance.
import import
hashes, strutils hashes, strutils, etcpriv
type type
TIdObj* = object of RootObj TIdObj* = object of RootObj
@ -23,7 +23,7 @@ type
TIdent*{.acyclic.} = object of TIdObj TIdent*{.acyclic.} = object of TIdObj
s*: string s*: string
next*: PIdent # for hash-table chaining next*: PIdent # for hash-table chaining
h*: THash # hash value of s h*: Hash # hash value of s
var firstCharIsCS*: bool = true var firstCharIsCS*: bool = true
var buckets*: array[0..4096 * 2 - 1, PIdent] var buckets*: array[0..4096 * 2 - 1, PIdent]
@ -37,6 +37,8 @@ proc cmpIgnoreStyle(a, b: cstring, blen: int): int =
while j < blen: while j < blen:
while a[i] == '_': inc(i) while a[i] == '_': inc(i)
while b[j] == '_': inc(j) while b[j] == '_': inc(j)
while isMagicIdentSeparatorRune(a, i): inc(i, magicIdentSeparatorRuneByteWidth)
while isMagicIdentSeparatorRune(b, j): inc(j, magicIdentSeparatorRuneByteWidth)
# tolower inlined: # tolower inlined:
var aa = a[i] var aa = a[i]
var bb = b[j] var bb = b[j]
@ -65,7 +67,7 @@ proc cmpExact(a, b: cstring, blen: int): int =
var wordCounter = 1 var wordCounter = 1
proc getIdent*(identifier: cstring, length: int, h: THash): PIdent = proc getIdent*(identifier: cstring, length: int, h: Hash): PIdent =
var idx = h and high(buckets) var idx = h and high(buckets)
result = buckets[idx] result = buckets[idx]
var last: PIdent = nil var last: PIdent = nil
@ -99,7 +101,7 @@ proc getIdent*(identifier: string): PIdent =
result = getIdent(cstring(identifier), len(identifier), result = getIdent(cstring(identifier), len(identifier),
hashIgnoreStyle(identifier)) hashIgnoreStyle(identifier))
proc getIdent*(identifier: string, h: THash): PIdent = proc getIdent*(identifier: string, h: Hash): PIdent =
result = getIdent(cstring(identifier), len(identifier), h) result = getIdent(cstring(identifier), len(identifier), h)
proc identEq*(id: PIdent, name: string): bool = proc identEq*(id: PIdent, name: string): bool =

View file

@ -99,6 +99,7 @@ Files: "lib/pure/concurrency/*.nim"
Files: "lib/pure/unidecode/*.nim" Files: "lib/pure/unidecode/*.nim"
Files: "lib/pure/concurrency/*.cfg" Files: "lib/pure/concurrency/*.cfg"
Files: "lib/impure/*.nim" Files: "lib/impure/*.nim"
Files: "lib/impure/nre/private/*.nim"
Files: "lib/wrappers/*.nim" Files: "lib/wrappers/*.nim"
Files: "lib/wrappers/readline/*.nim" Files: "lib/wrappers/readline/*.nim"

View file

@ -30,8 +30,8 @@ implements the required case distinction.
import import
ast, astalgo, strutils, hashes, trees, platform, magicsys, extccomp, ast, astalgo, strutils, hashes, trees, platform, magicsys, extccomp, options,
options, nversion, nimsets, msgs, crc, bitsets, idents, lists, types, os, nversion, nimsets, msgs, securehash, bitsets, idents, lists, types, os,
times, ropes, math, passes, ccgutils, wordrecg, renderer, rodread, rodutils, times, ropes, math, passes, ccgutils, wordrecg, renderer, rodread, rodutils,
intsets, cgmeth, lowerings intsets, cgmeth, lowerings
@ -258,11 +258,6 @@ const # magic checked op; magic unchecked op; checked op; unchecked op
["mulInt", "", "mulInt($1, $2)", "($1 * $2)"], # MulI ["mulInt", "", "mulInt($1, $2)", "($1 * $2)"], # MulI
["divInt", "", "divInt($1, $2)", "Math.floor($1 / $2)"], # DivI ["divInt", "", "divInt($1, $2)", "Math.floor($1 / $2)"], # DivI
["modInt", "", "modInt($1, $2)", "Math.floor($1 % $2)"], # ModI ["modInt", "", "modInt($1, $2)", "Math.floor($1 % $2)"], # ModI
["addInt64", "", "addInt64($1, $2)", "($1 + $2)"], # AddI64
["subInt64", "", "subInt64($1, $2)", "($1 - $2)"], # SubI64
["mulInt64", "", "mulInt64($1, $2)", "($1 * $2)"], # MulI64
["divInt64", "", "divInt64($1, $2)", "Math.floor($1 / $2)"], # DivI64
["modInt64", "", "modInt64($1, $2)", "Math.floor($1 % $2)"], # ModI64
["addInt", "", "addInt($1, $2)", "($1 + $2)"], # Succ ["addInt", "", "addInt($1, $2)", "($1 + $2)"], # Succ
["subInt", "", "subInt($1, $2)", "($1 - $2)"], # Pred ["subInt", "", "subInt($1, $2)", "($1 - $2)"], # Pred
["", "", "($1 + $2)", "($1 + $2)"], # AddF64 ["", "", "($1 + $2)", "($1 + $2)"], # AddF64
@ -276,11 +271,6 @@ const # magic checked op; magic unchecked op; checked op; unchecked op
["", "", "($1 ^ $2)", "($1 ^ $2)"], # BitxorI ["", "", "($1 ^ $2)", "($1 ^ $2)"], # BitxorI
["nimMin", "nimMin", "nimMin($1, $2)", "nimMin($1, $2)"], # MinI ["nimMin", "nimMin", "nimMin($1, $2)", "nimMin($1, $2)"], # MinI
["nimMax", "nimMax", "nimMax($1, $2)", "nimMax($1, $2)"], # MaxI ["nimMax", "nimMax", "nimMax($1, $2)", "nimMax($1, $2)"], # MaxI
["", "", "($1 >>> $2)", "($1 >>> $2)"], # ShrI64
["", "", "($1 << $2)", "($1 << $2)"], # ShlI64
["", "", "($1 & $2)", "($1 & $2)"], # BitandI64
["", "", "($1 | $2)", "($1 | $2)"], # BitorI64
["", "", "($1 ^ $2)", "($1 ^ $2)"], # BitxorI64
["nimMin", "nimMin", "nimMin($1, $2)", "nimMin($1, $2)"], # MinF64 ["nimMin", "nimMin", "nimMin($1, $2)", "nimMin($1, $2)"], # MinF64
["nimMax", "nimMax", "nimMax($1, $2)", "nimMax($1, $2)"], # MaxF64 ["nimMax", "nimMax", "nimMax($1, $2)", "nimMax($1, $2)"], # MaxF64
["addU", "addU", "addU($1, $2)", "addU($1, $2)"], # addU ["addU", "addU", "addU($1, $2)", "addU($1, $2)"], # addU
@ -291,9 +281,6 @@ const # magic checked op; magic unchecked op; checked op; unchecked op
["", "", "($1 == $2)", "($1 == $2)"], # EqI ["", "", "($1 == $2)", "($1 == $2)"], # EqI
["", "", "($1 <= $2)", "($1 <= $2)"], # LeI ["", "", "($1 <= $2)", "($1 <= $2)"], # LeI
["", "", "($1 < $2)", "($1 < $2)"], # LtI ["", "", "($1 < $2)", "($1 < $2)"], # LtI
["", "", "($1 == $2)", "($1 == $2)"], # EqI64
["", "", "($1 <= $2)", "($1 <= $2)"], # LeI64
["", "", "($1 < $2)", "($1 < $2)"], # LtI64
["", "", "($1 == $2)", "($1 == $2)"], # EqF64 ["", "", "($1 == $2)", "($1 == $2)"], # EqF64
["", "", "($1 <= $2)", "($1 <= $2)"], # LeF64 ["", "", "($1 <= $2)", "($1 <= $2)"], # LeF64
["", "", "($1 < $2)", "($1 < $2)"], # LtF64 ["", "", "($1 < $2)", "($1 < $2)"], # LtF64
@ -320,11 +307,9 @@ const # magic checked op; magic unchecked op; checked op; unchecked op
["negInt", "", "negInt($1)", "-($1)"], # UnaryMinusI ["negInt", "", "negInt($1)", "-($1)"], # UnaryMinusI
["negInt64", "", "negInt64($1)", "-($1)"], # UnaryMinusI64 ["negInt64", "", "negInt64($1)", "-($1)"], # UnaryMinusI64
["absInt", "", "absInt($1)", "Math.abs($1)"], # AbsI ["absInt", "", "absInt($1)", "Math.abs($1)"], # AbsI
["absInt64", "", "absInt64($1)", "Math.abs($1)"], # AbsI64
["", "", "!($1)", "!($1)"], # Not ["", "", "!($1)", "!($1)"], # Not
["", "", "+($1)", "+($1)"], # UnaryPlusI ["", "", "+($1)", "+($1)"], # UnaryPlusI
["", "", "~($1)", "~($1)"], # BitnotI ["", "", "~($1)", "~($1)"], # BitnotI
["", "", "~($1)", "~($1)"], # BitnotI64
["", "", "+($1)", "+($1)"], # UnaryPlusF64 ["", "", "+($1)", "+($1)"], # UnaryPlusF64
["", "", "-($1)", "-($1)"], # UnaryMinusF64 ["", "", "-($1)", "-($1)"], # UnaryMinusF64
["", "", "Math.abs($1)", "Math.abs($1)"], # AbsF64 ["", "", "Math.abs($1)", "Math.abs($1)"], # AbsF64
@ -357,11 +342,6 @@ const # magic checked op; magic unchecked op; checked op; unchecked op
["mulInt", "", "mulInt($1, $2)", "($1 * $2)"], # MulI ["mulInt", "", "mulInt($1, $2)", "($1 * $2)"], # MulI
["divInt", "", "divInt($1, $2)", "Math.floor($1 / $2)"], # DivI ["divInt", "", "divInt($1, $2)", "Math.floor($1 / $2)"], # DivI
["modInt", "", "modInt($1, $2)", "Math.floor($1 % $2)"], # ModI ["modInt", "", "modInt($1, $2)", "Math.floor($1 % $2)"], # ModI
["addInt64", "", "addInt64($1, $2)", "($1 + $2)"], # AddI64
["subInt64", "", "subInt64($1, $2)", "($1 - $2)"], # SubI64
["mulInt64", "", "mulInt64($1, $2)", "($1 * $2)"], # MulI64
["divInt64", "", "divInt64($1, $2)", "Math.floor($1 / $2)"], # DivI64
["modInt64", "", "modInt64($1, $2)", "Math.floor($1 % $2)"], # ModI64
["addInt", "", "addInt($1, $2)", "($1 + $2)"], # Succ ["addInt", "", "addInt($1, $2)", "($1 + $2)"], # Succ
["subInt", "", "subInt($1, $2)", "($1 - $2)"], # Pred ["subInt", "", "subInt($1, $2)", "($1 - $2)"], # Pred
["", "", "($1 + $2)", "($1 + $2)"], # AddF64 ["", "", "($1 + $2)", "($1 + $2)"], # AddF64
@ -375,11 +355,6 @@ const # magic checked op; magic unchecked op; checked op; unchecked op
["", "", "($1 ^ $2)", "($1 ^ $2)"], # BitxorI ["", "", "($1 ^ $2)", "($1 ^ $2)"], # BitxorI
["nimMin", "nimMin", "nimMin($1, $2)", "nimMin($1, $2)"], # MinI ["nimMin", "nimMin", "nimMin($1, $2)", "nimMin($1, $2)"], # MinI
["nimMax", "nimMax", "nimMax($1, $2)", "nimMax($1, $2)"], # MaxI ["nimMax", "nimMax", "nimMax($1, $2)", "nimMax($1, $2)"], # MaxI
["", "", "($1 >>> $2)", "($1 >>> $2)"], # ShrI64
["", "", "($1 << $2)", "($1 << $2)"], # ShlI64
["", "", "($1 & $2)", "($1 & $2)"], # BitandI64
["", "", "($1 | $2)", "($1 | $2)"], # BitorI64
["", "", "($1 ^ $2)", "($1 ^ $2)"], # BitxorI64
["nimMin", "nimMin", "nimMin($1, $2)", "nimMin($1, $2)"], # MinF64 ["nimMin", "nimMin", "nimMin($1, $2)", "nimMin($1, $2)"], # MinF64
["nimMax", "nimMax", "nimMax($1, $2)", "nimMax($1, $2)"], # MaxF64 ["nimMax", "nimMax", "nimMax($1, $2)", "nimMax($1, $2)"], # MaxF64
["addU", "addU", "addU($1, $2)", "addU($1, $2)"], # addU ["addU", "addU", "addU($1, $2)", "addU($1, $2)"], # addU
@ -390,9 +365,6 @@ const # magic checked op; magic unchecked op; checked op; unchecked op
["", "", "($1 == $2)", "($1 == $2)"], # EqI ["", "", "($1 == $2)", "($1 == $2)"], # EqI
["", "", "($1 <= $2)", "($1 <= $2)"], # LeI ["", "", "($1 <= $2)", "($1 <= $2)"], # LeI
["", "", "($1 < $2)", "($1 < $2)"], # LtI ["", "", "($1 < $2)", "($1 < $2)"], # LtI
["", "", "($1 == $2)", "($1 == $2)"], # EqI64
["", "", "($1 <= $2)", "($1 <= $2)"], # LeI64
["", "", "($1 < $2)", "($1 < $2)"], # LtI64
["", "", "($1 == $2)", "($1 == $2)"], # EqF64 ["", "", "($1 == $2)", "($1 == $2)"], # EqF64
["", "", "($1 <= $2)", "($1 <= $2)"], # LeF64 ["", "", "($1 <= $2)", "($1 <= $2)"], # LeF64
["", "", "($1 < $2)", "($1 < $2)"], # LtF64 ["", "", "($1 < $2)", "($1 < $2)"], # LtF64
@ -419,11 +391,9 @@ const # magic checked op; magic unchecked op; checked op; unchecked op
["negInt", "", "negInt($1)", "-($1)"], # UnaryMinusI ["negInt", "", "negInt($1)", "-($1)"], # UnaryMinusI
["negInt64", "", "negInt64($1)", "-($1)"], # UnaryMinusI64 ["negInt64", "", "negInt64($1)", "-($1)"], # UnaryMinusI64
["absInt", "", "absInt($1)", "Math.abs($1)"], # AbsI ["absInt", "", "absInt($1)", "Math.abs($1)"], # AbsI
["absInt64", "", "absInt64($1)", "Math.abs($1)"], # AbsI64
["", "", "not ($1)", "not ($1)"], # Not ["", "", "not ($1)", "not ($1)"], # Not
["", "", "+($1)", "+($1)"], # UnaryPlusI ["", "", "+($1)", "+($1)"], # UnaryPlusI
["", "", "~($1)", "~($1)"], # BitnotI ["", "", "~($1)", "~($1)"], # BitnotI
["", "", "~($1)", "~($1)"], # BitnotI64
["", "", "+($1)", "+($1)"], # UnaryPlusF64 ["", "", "+($1)", "+($1)"], # UnaryPlusF64
["", "", "-($1)", "-($1)"], # UnaryMinusF64 ["", "", "-($1)", "-($1)"], # UnaryMinusF64
["", "", "Math.abs($1)", "Math.abs($1)"], # AbsF64 ["", "", "Math.abs($1)", "Math.abs($1)"], # AbsF64
@ -817,7 +787,7 @@ proc genAsgnAux(p: PProc, x, y: PNode, noCopyNeeded: bool) =
addf(p.body, "$1 = $2;$n", [a.rdLoc, b.rdLoc]) addf(p.body, "$1 = $2;$n", [a.rdLoc, b.rdLoc])
else: else:
useMagic(p, "nimCopy") useMagic(p, "nimCopy")
addf(p.body, "$1 = nimCopy($2, $3);$n", addf(p.body, "$1 = nimCopy($1, $2, $3);$n",
[a.res, b.res, genTypeInfo(p, y.typ)]) [a.res, b.res, genTypeInfo(p, y.typ)])
of etyBaseIndex: of etyBaseIndex:
if a.typ != etyBaseIndex or b.typ != etyBaseIndex: if a.typ != etyBaseIndex or b.typ != etyBaseIndex:
@ -976,16 +946,28 @@ proc genAddr(p: PProc, n: PNode, r: var TCompRes) =
genFieldAccess(p, n.sons[0], r) genFieldAccess(p, n.sons[0], r)
of nkBracketExpr: of nkBracketExpr:
var ty = skipTypes(n.sons[0].typ, abstractVarRange) var ty = skipTypes(n.sons[0].typ, abstractVarRange)
if ty.kind in {tyRef, tyPtr}: ty = skipTypes(ty.lastSon, abstractVarRange) if ty.kind in MappedToObject:
case ty.kind gen(p, n.sons[0], r)
of tyArray, tyArrayConstr, tyOpenArray, tySequence, tyString, tyCString, else:
tyVarargs, tyChar: let kindOfIndexedExpr = skipTypes(n.sons[0].sons[0].typ, abstractVarRange).kind
genArrayAddr(p, n.sons[0], r) case kindOfIndexedExpr
of tyTuple: of tyArray, tyArrayConstr, tyOpenArray, tySequence, tyString, tyCString,
genFieldAddr(p, n.sons[0], r) tyVarargs:
else: internalError(n.sons[0].info, "expr(nkBracketExpr, " & $ty.kind & ')') genArrayAddr(p, n.sons[0], r)
of tyTuple:
genFieldAddr(p, n.sons[0], r)
else: internalError(n.sons[0].info, "expr(nkBracketExpr, " & $kindOfIndexedExpr & ')')
else: internalError(n.sons[0].info, "genAddr") else: internalError(n.sons[0].info, "genAddr")
proc genProcForSymIfNeeded(p: PProc, s: PSym) =
if not p.g.generatedSyms.containsOrIncl(s.id):
let newp = genProc(p, s)
var owner = p
while owner != nil and owner.prc != s.owner:
owner = owner.up
if owner != nil: add(owner.locals, newp)
else: add(p.g.code, newp)
proc genSym(p: PProc, n: PNode, r: var TCompRes) = proc genSym(p: PProc, n: PNode, r: var TCompRes) =
var s = n.sym var s = n.sym
case s.kind case s.kind
@ -1021,13 +1003,8 @@ proc genSym(p: PProc, n: PNode, r: var TCompRes) =
discard discard
elif sfForward in s.flags: elif sfForward in s.flags:
p.g.forwarded.add(s) p.g.forwarded.add(s)
elif not p.g.generatedSyms.containsOrIncl(s.id): else:
let newp = genProc(p, s) genProcForSymIfNeeded(p, s)
var owner = p
while owner != nil and owner.prc != s.owner:
owner = owner.up
if owner != nil: add(owner.locals, newp)
else: add(p.g.code, newp)
else: else:
if s.loc.r == nil: if s.loc.r == nil:
internalError(n.info, "symbol has no generated name: " & s.name.s) internalError(n.info, "symbol has no generated name: " & s.name.s)
@ -1202,7 +1179,7 @@ proc genVarInit(p: PProc, v: PSym, n: PNode) =
s = a.res s = a.res
else: else:
useMagic(p, "nimCopy") useMagic(p, "nimCopy")
s = "nimCopy($1, $2)" % [a.res, genTypeInfo(p, n.typ)] s = "nimCopy(null, $1, $2)" % [a.res, genTypeInfo(p, n.typ)]
of etyBaseIndex: of etyBaseIndex:
if (a.typ != etyBaseIndex): internalError(n.info, "genVarInit") if (a.typ != etyBaseIndex): internalError(n.info, "genVarInit")
if {sfAddrTaken, sfGlobal} * v.flags != {}: if {sfAddrTaken, sfGlobal} * v.flags != {}:
@ -1394,6 +1371,9 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) =
of mCopyStrLast: ternaryExpr(p, n, r, "", "($1.slice($2, ($3)+1).concat(0))") of mCopyStrLast: ternaryExpr(p, n, r, "", "($1.slice($2, ($3)+1).concat(0))")
of mNewString: unaryExpr(p, n, r, "mnewString", "mnewString($1)") of mNewString: unaryExpr(p, n, r, "mnewString", "mnewString($1)")
of mNewStringOfCap: unaryExpr(p, n, r, "mnewString", "mnewString(0)") of mNewStringOfCap: unaryExpr(p, n, r, "mnewString", "mnewString(0)")
of mDotDot:
genProcForSymIfNeeded(p, n.sons[0].sym)
genCall(p, n, r)
else: else:
genCall(p, n, r) genCall(p, n, r)
#else internalError(e.info, 'genMagic: ' + magicToStr[op]); #else internalError(e.info, 'genMagic: ' + magicToStr[op]);

View file

@ -946,7 +946,11 @@ proc transformOuterProc(o: POuterContext, n: PNode; it: TIter): PNode =
proc liftLambdas*(fn: PSym, body: PNode): PNode = proc liftLambdas*(fn: PSym, body: PNode): PNode =
# XXX gCmd == cmdCompileToJS does not suffice! The compiletime stuff needs # XXX gCmd == cmdCompileToJS does not suffice! The compiletime stuff needs
# the transformation even when compiling to JS ... # the transformation even when compiling to JS ...
if body.kind == nkEmpty or gCmd == cmdCompileToJS or
# However we can do lifting for the stuff which is *only* compiletime.
let isCompileTime = sfCompileTime in fn.flags or fn.kind == skMacro
if body.kind == nkEmpty or (gCmd == cmdCompileToJS and not isCompileTime) or
fn.skipGenericOwner.kind != skModule: fn.skipGenericOwner.kind != skModule:
# ignore forward declaration: # ignore forward declaration:
result = body result = body

View file

@ -17,7 +17,7 @@
import import
hashes, options, msgs, strutils, platform, idents, nimlexbase, llstream, hashes, options, msgs, strutils, platform, idents, nimlexbase, llstream,
wordrecg wordrecg, etcpriv
const const
MaxLineLength* = 80 # lines longer than this lead to a warning MaxLineLength* = 80 # lines longer than this lead to a warning
@ -140,10 +140,12 @@ proc isKeyword*(kind: TTokType): bool =
proc isNimIdentifier*(s: string): bool = proc isNimIdentifier*(s: string): bool =
if s[0] in SymStartChars: if s[0] in SymStartChars:
var i = 1 var i = 1
while i < s.len: var sLen = s.len
while i < sLen:
if s[i] == '_': if s[i] == '_':
inc(i) inc(i)
if s[i] notin SymChars: return elif isMagicIdentSeparatorRune(cstring s, i):
inc(i, magicIdentSeparatorRuneByteWidth)
if s[i] notin SymChars: return if s[i] notin SymChars: return
inc(i) inc(i)
result = true result = true
@ -229,23 +231,6 @@ proc lexMessagePos(L: var TLexer, msg: TMsgKind, pos: int, arg = "") =
var info = newLineInfo(L.fileIdx, L.lineNumber, pos - L.lineStart) var info = newLineInfo(L.fileIdx, L.lineNumber, pos - L.lineStart)
L.dispMessage(info, msg, arg) L.dispMessage(info, msg, arg)
proc matchUnderscoreChars(L: var TLexer, tok: var TToken, chars: set[char]) =
var pos = L.bufpos # use registers for pos, buf
var buf = L.buf
while true:
if buf[pos] in chars:
add(tok.literal, buf[pos])
inc(pos)
else:
break
if buf[pos] == '_':
if buf[pos+1] notin chars:
lexMessage(L, errInvalidToken, "_")
break
add(tok.literal, '_')
inc(pos)
L.bufpos = pos
proc matchTwoChars(L: TLexer, first: char, second: set[char]): bool = proc matchTwoChars(L: TLexer, first: char, second: set[char]): bool =
result = (L.buf[L.bufpos] == first) and (L.buf[L.bufpos + 1] in second) result = (L.buf[L.bufpos] == first) and (L.buf[L.bufpos + 1] in second)
@ -268,136 +253,200 @@ proc unsafeParseUInt(s: string, b: var BiggestInt, start = 0): int =
result = i - start result = i - start
{.pop.} # overflowChecks {.pop.} # overflowChecks
template eatChar(L: var TLexer, t: var TToken, replacementChar: char) =
add(t.literal, replacementChar)
inc(L.bufpos)
template eatChar(L: var TLexer, t: var TToken) =
add(t.literal, L.buf[L.bufpos])
inc(L.bufpos)
proc getNumber(L: var TLexer): TToken = proc getNumber(L: var TLexer): TToken =
proc matchUnderscoreChars(L: var TLexer, tok: var TToken, chars: set[char]) =
var pos = L.bufpos # use registers for pos, buf
var buf = L.buf
while true:
if buf[pos] in chars:
add(tok.literal, buf[pos])
inc(pos)
else:
break
if buf[pos] == '_':
if buf[pos+1] notin chars:
lexMessage(L, errInvalidToken, "_")
break
add(tok.literal, '_')
inc(pos)
L.bufpos = pos
proc matchChars(L: var TLexer, tok: var TToken, chars: set[char]) =
var pos = L.bufpos # use registers for pos, buf
var buf = L.buf
while buf[pos] in chars:
add(tok.literal, buf[pos])
inc(pos)
L.bufpos = pos
proc lexMessageLitNum(L: var TLexer, msg: TMsgKind, startpos: int) =
# Used to get slightly human friendlier err messages.
# Note: the erroneous 'O' char in the character set is intentional
const literalishChars = {'A'..'F', 'a'..'f', '0'..'9', 'X', 'x', 'o', 'O',
'c', 'C', 'b', 'B', '_', '.', '\'', 'd', 'i', 'u'}
var msgPos = L.bufpos
var t: TToken
t.literal = ""
L.bufpos = startpos # Use L.bufpos as pos because of matchChars
matchChars(L, t, literalishChars)
# We must verify +/- specifically so that we're not past the literal
if L.buf[L.bufpos] in {'+', '-'} and
L.buf[L.bufpos - 1] in {'e', 'E'}:
add(t.literal, L.buf[L.bufpos])
inc(L.bufpos)
matchChars(L, t, literalishChars)
if L.buf[L.bufpos] in {'\'', 'f', 'F', 'd', 'D', 'i', 'I', 'u', 'U'}:
inc(L.bufpos)
add(t.literal, L.buf[L.bufpos])
matchChars(L, t, {'0'..'9'})
L.bufpos = msgPos
lexMessage(L, msg, t.literal)
var var
pos, endpos: int startpos, endpos: int
xi: BiggestInt xi: BiggestInt
# get the base: isBase10 = true
const
baseCodeChars = {'X', 'x', 'o', 'c', 'C', 'b', 'B'}
literalishChars = baseCodeChars + {'A'..'F', 'a'..'f', '0'..'9', '_', '\''}
floatTypes = {tkFloatLit, tkFloat32Lit, tkFloat64Lit, tkFloat128Lit}
result.tokType = tkIntLit # int literal until we know better result.tokType = tkIntLit # int literal until we know better
result.literal = "" result.literal = ""
result.base = base10 # BUGFIX result.base = base10
pos = L.bufpos # make sure the literal is correct for error messages: startpos = L.bufpos
var eallowed = false
if L.buf[pos] == '0' and L.buf[pos+1] in {'X', 'x'}: # First stage: find out base, make verifications, build token literal string
matchUnderscoreChars(L, result, {'A'..'F', 'a'..'f', '0'..'9', 'X', 'x'}) if L.buf[L.bufpos] == '0' and L.buf[L.bufpos + 1] in baseCodeChars + {'O'}:
isBase10 = false
eatChar(L, result, '0')
case L.buf[L.bufpos]
of 'O':
lexMessageLitNum(L, errInvalidNumberOctalCode, startpos)
of 'x', 'X':
eatChar(L, result, 'x')
matchUnderscoreChars(L, result, {'0'..'9', 'a'..'f', 'A'..'F'})
of 'o', 'c', 'C':
eatChar(L, result, 'c')
matchUnderscoreChars(L, result, {'0'..'7'})
of 'b', 'B':
eatChar(L, result, 'b')
matchUnderscoreChars(L, result, {'0'..'1'})
else:
internalError(getLineInfo(L), "getNumber")
else: else:
matchUnderscoreChars(L, result, {'0'..'9', 'b', 'B', 'o', 'c', 'C'})
eallowed = true
if (L.buf[L.bufpos] == '.') and (L.buf[L.bufpos + 1] in {'0'..'9'}):
add(result.literal, '.')
inc(L.bufpos)
matchUnderscoreChars(L, result, {'0'..'9'})
eallowed = true
if eallowed and L.buf[L.bufpos] in {'e', 'E'}:
add(result.literal, 'e')
inc(L.bufpos)
if L.buf[L.bufpos] in {'+', '-'}:
add(result.literal, L.buf[L.bufpos])
inc(L.bufpos)
matchUnderscoreChars(L, result, {'0'..'9'}) matchUnderscoreChars(L, result, {'0'..'9'})
if (L.buf[L.bufpos] == '.') and (L.buf[L.bufpos + 1] in {'0'..'9'}):
result.tokType = tkFloat64Lit
eatChar(L, result, '.')
matchUnderscoreChars(L, result, {'0'..'9'})
if L.buf[L.bufpos] in {'e', 'E'}:
result.tokType = tkFloat64Lit
eatChar(L, result, 'e')
if L.buf[L.bufpos] in {'+', '-'}:
eatChar(L, result)
matchUnderscoreChars(L, result, {'0'..'9'})
endpos = L.bufpos endpos = L.bufpos
if L.buf[endpos] in {'\'', 'f', 'F', 'i', 'I', 'u', 'U'}:
if L.buf[endpos] == '\'': inc(endpos) # Second stage, find out if there's a datatype suffix and handle it
L.bufpos = pos # restore position var postPos = endpos
case L.buf[endpos] if L.buf[postPos] in {'\'', 'f', 'F', 'd', 'D', 'i', 'I', 'u', 'U'}:
if L.buf[postPos] == '\'':
inc(postPos)
case L.buf[postPos]
of 'f', 'F': of 'f', 'F':
inc(endpos) inc(postPos)
if (L.buf[endpos] == '3') and (L.buf[endpos + 1] == '2'): if (L.buf[postPos] == '3') and (L.buf[postPos + 1] == '2'):
result.tokType = tkFloat32Lit result.tokType = tkFloat32Lit
inc(endpos, 2) inc(postPos, 2)
elif (L.buf[endpos] == '6') and (L.buf[endpos + 1] == '4'): elif (L.buf[postPos] == '6') and (L.buf[postPos + 1] == '4'):
result.tokType = tkFloat64Lit result.tokType = tkFloat64Lit
inc(endpos, 2) inc(postPos, 2)
elif (L.buf[endpos] == '1') and elif (L.buf[postPos] == '1') and
(L.buf[endpos + 1] == '2') and (L.buf[postPos + 1] == '2') and
(L.buf[endpos + 2] == '8'): (L.buf[postPos + 2] == '8'):
result.tokType = tkFloat128Lit result.tokType = tkFloat128Lit
inc(endpos, 3) inc(postPos, 3)
else: else: # "f" alone defaults to float32
lexMessage(L, errInvalidNumber, result.literal & "'f" & L.buf[endpos]) result.tokType = tkFloat32Lit
of 'd', 'D': # ad hoc convenience shortcut for f64
inc(postPos)
result.tokType = tkFloat64Lit
of 'i', 'I': of 'i', 'I':
inc(endpos) inc(postPos)
if (L.buf[endpos] == '6') and (L.buf[endpos + 1] == '4'): if (L.buf[postPos] == '6') and (L.buf[postPos + 1] == '4'):
result.tokType = tkInt64Lit result.tokType = tkInt64Lit
inc(endpos, 2) inc(postPos, 2)
elif (L.buf[endpos] == '3') and (L.buf[endpos + 1] == '2'): elif (L.buf[postPos] == '3') and (L.buf[postPos + 1] == '2'):
result.tokType = tkInt32Lit result.tokType = tkInt32Lit
inc(endpos, 2) inc(postPos, 2)
elif (L.buf[endpos] == '1') and (L.buf[endpos + 1] == '6'): elif (L.buf[postPos] == '1') and (L.buf[postPos + 1] == '6'):
result.tokType = tkInt16Lit result.tokType = tkInt16Lit
inc(endpos, 2) inc(postPos, 2)
elif (L.buf[endpos] == '8'): elif (L.buf[postPos] == '8'):
result.tokType = tkInt8Lit result.tokType = tkInt8Lit
inc(endpos) inc(postPos)
else: else:
lexMessage(L, errInvalidNumber, result.literal & "'i" & L.buf[endpos]) lexMessageLitNum(L, errInvalidNumber, startpos)
of 'u', 'U': of 'u', 'U':
inc(endpos) inc(postPos)
if (L.buf[endpos] == '6') and (L.buf[endpos + 1] == '4'): if (L.buf[postPos] == '6') and (L.buf[postPos + 1] == '4'):
result.tokType = tkUInt64Lit result.tokType = tkUInt64Lit
inc(endpos, 2) inc(postPos, 2)
elif (L.buf[endpos] == '3') and (L.buf[endpos + 1] == '2'): elif (L.buf[postPos] == '3') and (L.buf[postPos + 1] == '2'):
result.tokType = tkUInt32Lit result.tokType = tkUInt32Lit
inc(endpos, 2) inc(postPos, 2)
elif (L.buf[endpos] == '1') and (L.buf[endpos + 1] == '6'): elif (L.buf[postPos] == '1') and (L.buf[postPos + 1] == '6'):
result.tokType = tkUInt16Lit result.tokType = tkUInt16Lit
inc(endpos, 2) inc(postPos, 2)
elif (L.buf[endpos] == '8'): elif (L.buf[postPos] == '8'):
result.tokType = tkUInt8Lit result.tokType = tkUInt8Lit
inc(endpos) inc(postPos)
else: else:
result.tokType = tkUIntLit result.tokType = tkUIntLit
else: lexMessage(L, errInvalidNumber, result.literal & "'" & L.buf[endpos]) else:
else: lexMessageLitNum(L, errInvalidNumber, startpos)
L.bufpos = pos # restore position
# Is there still a literalish char awaiting? Then it's an error!
if L.buf[postPos] in literalishChars or
(L.buf[postPos] == '.' and L.buf[postPos + 1] in {'0'..'9'}):
lexMessageLitNum(L, errInvalidNumber, startpos)
# Third stage, extract actual number
L.bufpos = startpos # restore position
var pos: int = startpos
try: try:
if (L.buf[pos] == '0') and if (L.buf[pos] == '0') and (L.buf[pos + 1] in baseCodeChars):
(L.buf[pos + 1] in {'x', 'X', 'b', 'B', 'o', 'O', 'c', 'C'}):
inc(pos, 2) inc(pos, 2)
xi = 0 # it may be a base prefix xi = 0 # it is a base prefix
case L.buf[pos - 1] # now look at the optional type suffix:
case L.buf[pos - 1]
of 'b', 'B': of 'b', 'B':
result.base = base2 result.base = base2
while true: while pos < endpos:
case L.buf[pos] if L.buf[pos] != '_':
of '2'..'9', '.':
lexMessage(L, errInvalidNumber, result.literal)
inc(pos)
of '_':
if L.buf[pos+1] notin {'0'..'1'}:
lexMessage(L, errInvalidToken, "_")
break
inc(pos)
of '0', '1':
xi = `shl`(xi, 1) or (ord(L.buf[pos]) - ord('0')) xi = `shl`(xi, 1) or (ord(L.buf[pos]) - ord('0'))
inc(pos) inc(pos)
else: break
of 'o', 'c', 'C': of 'o', 'c', 'C':
result.base = base8 result.base = base8
while true: while pos < endpos:
case L.buf[pos] if L.buf[pos] != '_':
of '8'..'9', '.':
lexMessage(L, errInvalidNumber, result.literal)
inc(pos)
of '_':
if L.buf[pos+1] notin {'0'..'7'}:
lexMessage(L, errInvalidToken, "_")
break
inc(pos)
of '0'..'7':
xi = `shl`(xi, 3) or (ord(L.buf[pos]) - ord('0')) xi = `shl`(xi, 3) or (ord(L.buf[pos]) - ord('0'))
inc(pos) inc(pos)
else: break
of 'O':
lexMessage(L, errInvalidNumber, result.literal)
of 'x', 'X': of 'x', 'X':
result.base = base16 result.base = base16
while true: while pos < endpos:
case L.buf[pos] case L.buf[pos]
of '_': of '_':
if L.buf[pos+1] notin {'0'..'9', 'a'..'f', 'A'..'F'}:
lexMessage(L, errInvalidToken, "_")
break
inc(pos) inc(pos)
of '0'..'9': of '0'..'9':
xi = `shl`(xi, 4) or (ord(L.buf[pos]) - ord('0')) xi = `shl`(xi, 4) or (ord(L.buf[pos]) - ord('0'))
@ -408,51 +457,81 @@ proc getNumber(L: var TLexer): TToken =
of 'A'..'F': of 'A'..'F':
xi = `shl`(xi, 4) or (ord(L.buf[pos]) - ord('A') + 10) xi = `shl`(xi, 4) or (ord(L.buf[pos]) - ord('A') + 10)
inc(pos) inc(pos)
else: break else:
else: internalError(getLineInfo(L), "getNumber") break
else:
internalError(getLineInfo(L), "getNumber")
case result.tokType case result.tokType
of tkIntLit, tkInt64Lit: result.iNumber = xi of tkIntLit, tkInt64Lit: result.iNumber = xi
of tkInt8Lit: result.iNumber = BiggestInt(int8(toU8(int(xi)))) of tkInt8Lit: result.iNumber = BiggestInt(int8(toU8(int(xi))))
of tkInt16Lit: result.iNumber = BiggestInt(toU16(int(xi))) of tkInt16Lit: result.iNumber = BiggestInt(int16(toU16(int(xi))))
of tkInt32Lit: result.iNumber = BiggestInt(toU32(xi)) of tkInt32Lit: result.iNumber = BiggestInt(int32(toU32(int64(xi))))
of tkUIntLit, tkUInt64Lit: result.iNumber = xi of tkUIntLit, tkUInt64Lit: result.iNumber = xi
of tkUInt8Lit: result.iNumber = BiggestInt(int8(toU8(int(xi)))) of tkUInt8Lit: result.iNumber = BiggestInt(uint8(toU8(int(xi))))
of tkUInt16Lit: result.iNumber = BiggestInt(toU16(int(xi))) of tkUInt16Lit: result.iNumber = BiggestInt(uint16(toU16(int(xi))))
of tkUInt32Lit: result.iNumber = BiggestInt(toU32(xi)) of tkUInt32Lit: result.iNumber = BiggestInt(uint32(toU32(int64(xi))))
of tkFloat32Lit: of tkFloat32Lit:
result.fNumber = (cast[PFloat32](addr(xi)))[] result.fNumber = (cast[PFloat32](addr(xi)))[]
# note: this code is endian neutral! # note: this code is endian neutral!
# XXX: Test this on big endian machine! # XXX: Test this on big endian machine!
of tkFloat64Lit: result.fNumber = (cast[PFloat64](addr(xi)))[] of tkFloat64Lit: result.fNumber = (cast[PFloat64](addr(xi)))[]
else: internalError(getLineInfo(L), "getNumber") else: internalError(getLineInfo(L), "getNumber")
elif isFloatLiteral(result.literal) or (result.tokType == tkFloat32Lit) or
(result.tokType == tkFloat64Lit): # Bounds checks. Non decimal literals are allowed to overflow the range of
result.fNumber = parseFloat(result.literal) # the datatype as long as their pattern don't overflow _bitwise_, hence
if result.tokType == tkIntLit: result.tokType = tkFloatLit # below checks of signed sizes against uint*.high is deliberate:
elif result.tokType == tkUint64Lit: # (0x80'u8 = 128, 0x80'i8 = -128, etc == OK)
xi = 0 if result.tokType notin floatTypes:
let len = unsafeParseUInt(result.literal, xi) let outOfRange = case result.tokType:
if len != result.literal.len or len == 0: of tkUInt8Lit, tkUInt16Lit, tkUInt32Lit: result.iNumber != xi
raise newException(ValueError, "invalid integer: " & $xi) of tkInt8Lit: (xi > BiggestInt(uint8.high))
result.iNumber = xi of tkInt16Lit: (xi > BiggestInt(uint16.high))
of tkInt32Lit: (xi > BiggestInt(uint32.high))
else: false
if outOfRange:
#echo "out of range num: ", result.iNumber, " vs ", xi
lexMessageLitNum(L, errNumberOutOfRange, startpos)
else: else:
result.iNumber = parseBiggestInt(result.literal) case result.tokType
of floatTypes:
result.fNumber = parseFloat(result.literal)
of tkUint64Lit:
xi = 0
let len = unsafeParseUInt(result.literal, xi)
if len != result.literal.len or len == 0:
raise newException(ValueError, "invalid integer: " & $xi)
result.iNumber = xi
else:
result.iNumber = parseBiggestInt(result.literal)
# Explicit bounds checks
let outOfRange = case result.tokType:
of tkInt8Lit: (result.iNumber < int8.low or result.iNumber > int8.high)
of tkUInt8Lit: (result.iNumber < BiggestInt(uint8.low) or
result.iNumber > BiggestInt(uint8.high))
of tkInt16Lit: (result.iNumber < int16.low or result.iNumber > int16.high)
of tkUInt16Lit: (result.iNumber < BiggestInt(uint16.low) or
result.iNumber > BiggestInt(uint16.high))
of tkInt32Lit: (result.iNumber < int32.low or result.iNumber > int32.high)
of tkUInt32Lit: (result.iNumber < BiggestInt(uint32.low) or
result.iNumber > BiggestInt(uint32.high))
else: false
if outOfRange: lexMessageLitNum(L, errNumberOutOfRange, startpos)
# Promote int literal to int64? Not always necessary, but more consistent
if result.tokType == tkIntLit:
if (result.iNumber < low(int32)) or (result.iNumber > high(int32)): if (result.iNumber < low(int32)) or (result.iNumber > high(int32)):
if result.tokType == tkIntLit: result.tokType = tkInt64Lit
result.tokType = tkInt64Lit
elif result.tokType in {tkInt8Lit, tkInt16Lit, tkInt32Lit}:
lexMessage(L, errNumberOutOfRange, result.literal)
elif result.tokType == tkInt8Lit and
(result.iNumber < int8.low or result.iNumber > int8.high):
lexMessage(L, errNumberOutOfRange, result.literal)
elif result.tokType == tkInt16Lit and
(result.iNumber < int16.low or result.iNumber > int16.high):
lexMessage(L, errNumberOutOfRange, result.literal)
except ValueError: except ValueError:
lexMessage(L, errInvalidNumber, result.literal) lexMessageLitNum(L, errInvalidNumber, startpos)
except OverflowError, RangeError: except OverflowError, RangeError:
lexMessage(L, errNumberOutOfRange, result.literal) lexMessageLitNum(L, errNumberOutOfRange, startpos)
L.bufpos = endpos L.bufpos = postPos
proc handleHexChar(L: var TLexer, xi: var int) = proc handleHexChar(L: var TLexer, xi: var int) =
case L.buf[L.bufpos] case L.buf[L.bufpos]
@ -625,23 +704,34 @@ proc getCharacter(L: var TLexer, tok: var TToken) =
inc(L.bufpos) # skip ' inc(L.bufpos) # skip '
proc getSymbol(L: var TLexer, tok: var TToken) = proc getSymbol(L: var TLexer, tok: var TToken) =
var h: THash = 0 var h: Hash = 0
var pos = L.bufpos var pos = L.bufpos
var buf = L.buf var buf = L.buf
while true: while true:
var c = buf[pos] var c = buf[pos]
case c case c
of 'a'..'z', '0'..'9', '\x80'..'\xFF': of 'a'..'z', '0'..'9', '\x80'..'\xFF':
h = h !& ord(c) if c == '\226' and
buf[pos+1] == '\128' and
buf[pos+2] == '\147': # It's a 'magic separator' en-dash Unicode
if buf[pos + magicIdentSeparatorRuneByteWidth] notin SymChars:
lexMessage(L, errInvalidToken, "–")
break
inc(pos, magicIdentSeparatorRuneByteWidth)
else:
h = h !& ord(c)
inc(pos)
of 'A'..'Z': of 'A'..'Z':
c = chr(ord(c) + (ord('a') - ord('A'))) # toLower() c = chr(ord(c) + (ord('a') - ord('A'))) # toLower()
h = h !& ord(c) h = h !& ord(c)
inc(pos)
of '_': of '_':
if buf[pos+1] notin SymChars: if buf[pos+1] notin SymChars:
lexMessage(L, errInvalidToken, "_") lexMessage(L, errInvalidToken, "_")
break break
inc(pos)
else: break else: break
inc(pos)
h = !$h h = !$h
tok.ident = getIdent(addr(L.buf[L.bufpos]), pos - L.bufpos, h) tok.ident = getIdent(addr(L.buf[L.bufpos]), pos - L.bufpos, h)
L.bufpos = pos L.bufpos = pos
@ -652,7 +742,7 @@ proc getSymbol(L: var TLexer, tok: var TToken) =
tok.tokType = TTokType(tok.ident.id + ord(tkSymbol)) tok.tokType = TTokType(tok.ident.id + ord(tkSymbol))
proc endOperator(L: var TLexer, tok: var TToken, pos: int, proc endOperator(L: var TLexer, tok: var TToken, pos: int,
hash: THash) {.inline.} = hash: Hash) {.inline.} =
var h = !$hash var h = !$hash
tok.ident = getIdent(addr(L.buf[L.bufpos]), pos - L.bufpos, h) tok.ident = getIdent(addr(L.buf[L.bufpos]), pos - L.bufpos, h)
if (tok.ident.id < oprLow) or (tok.ident.id > oprHigh): tok.tokType = tkOpr if (tok.ident.id < oprLow) or (tok.ident.id > oprHigh): tok.tokType = tkOpr
@ -662,7 +752,7 @@ proc endOperator(L: var TLexer, tok: var TToken, pos: int,
proc getOperator(L: var TLexer, tok: var TToken) = proc getOperator(L: var TLexer, tok: var TToken) =
var pos = L.bufpos var pos = L.bufpos
var buf = L.buf var buf = L.buf
var h: THash = 0 var h: Hash = 0
while true: while true:
var c = buf[pos] var c = buf[pos]
if c notin OpChars: break if c notin OpChars: break

View file

@ -10,7 +10,7 @@
## implements the module handling ## implements the module handling
import import
ast, astalgo, magicsys, crc, rodread, msgs, cgendata, sigmatch, options, ast, astalgo, magicsys, securehash, rodread, msgs, cgendata, sigmatch, options,
idents, os, lexer, idgen, passes, syntaxes, llstream idents, os, lexer, idgen, passes, syntaxes, llstream
type type
@ -19,7 +19,7 @@ type
TModuleInMemory* = object TModuleInMemory* = object
compiledAt*: float compiledAt*: float
crc*: TCrc32 crc*: SecureHash
deps*: seq[int32] ## XXX: slurped files are currently not tracked deps*: seq[int32] ## XXX: slurped files are currently not tracked
needsRecompile*: TNeedRecompile needsRecompile*: TNeedRecompile
crcStatus*: TCrcStatus crcStatus*: TCrcStatus
@ -51,19 +51,19 @@ proc crcChanged(fileIdx: int32): bool =
of crcNotChanged: of crcNotChanged:
result = false result = false
of crcCached: of crcCached:
let newCrc = crcFromFile(fileIdx.toFilename) let newCrc = secureHashFile(fileIdx.toFullPath)
result = newCrc != gMemCacheData[fileIdx].crc result = newCrc != gMemCacheData[fileIdx].crc
gMemCacheData[fileIdx].crc = newCrc gMemCacheData[fileIdx].crc = newCrc
updateStatus() updateStatus()
of crcNotTaken: of crcNotTaken:
gMemCacheData[fileIdx].crc = crcFromFile(fileIdx.toFilename) gMemCacheData[fileIdx].crc = secureHashFile(fileIdx.toFullPath)
result = true result = true
updateStatus() updateStatus()
proc doCRC(fileIdx: int32) = proc doCRC(fileIdx: int32) =
if gMemCacheData[fileIdx].crcStatus == crcNotTaken: if gMemCacheData[fileIdx].crcStatus == crcNotTaken:
# echo "FIRST CRC: ", fileIdx.ToFilename # echo "FIRST CRC: ", fileIdx.ToFilename
gMemCacheData[fileIdx].crc = crcFromFile(fileIdx.toFilename) gMemCacheData[fileIdx].crc = secureHashFile(fileIdx.toFullPath)
proc addDep(x: PSym, dep: int32) = proc addDep(x: PSym, dep: int32) =
growCache gMemCacheData, dep growCache gMemCacheData, dep

View file

@ -8,7 +8,7 @@
# #
import import
options, strutils, os, tables, ropes, platform options, strutils, os, tables, ropes, platform, terminal
type type
TMsgKind* = enum TMsgKind* = enum
@ -17,10 +17,9 @@ type
errIntLiteralExpected, errInvalidCharacterConstant, errIntLiteralExpected, errInvalidCharacterConstant,
errClosingTripleQuoteExpected, errClosingQuoteExpected, errClosingTripleQuoteExpected, errClosingQuoteExpected,
errTabulatorsAreNotAllowed, errInvalidToken, errLineTooLong, errTabulatorsAreNotAllowed, errInvalidToken, errLineTooLong,
errInvalidNumber, errNumberOutOfRange, errNnotAllowedInCharacter, errInvalidNumber, errInvalidNumberOctalCode, errNumberOutOfRange,
errClosingBracketExpected, errMissingFinalQuote, errIdentifierExpected, errNnotAllowedInCharacter, errClosingBracketExpected, errMissingFinalQuote,
errNewlineExpected, errIdentifierExpected, errNewlineExpected, errInvalidModuleName,
errInvalidModuleName,
errOperatorExpected, errTokenExpected, errStringAfterIncludeExpected, errOperatorExpected, errTokenExpected, errStringAfterIncludeExpected,
errRecursiveDependencyX, errOnOrOffExpected, errNoneSpeedOrSizeExpected, errRecursiveDependencyX, errOnOrOffExpected, errNoneSpeedOrSizeExpected,
errInvalidPragma, errUnknownPragma, errInvalidDirectiveX, errInvalidPragma, errUnknownPragma, errInvalidDirectiveX,
@ -35,7 +34,9 @@ type
errNoneSpeedOrSizeExpectedButXFound, errGuiConsoleOrLibExpectedButXFound, errNoneSpeedOrSizeExpectedButXFound, errGuiConsoleOrLibExpectedButXFound,
errUnknownOS, errUnknownCPU, errGenOutExpectedButXFound, errUnknownOS, errUnknownCPU, errGenOutExpectedButXFound,
errArgsNeedRunOption, errInvalidMultipleAsgn, errColonOrEqualsExpected, errArgsNeedRunOption, errInvalidMultipleAsgn, errColonOrEqualsExpected,
errExprExpected, errUndeclaredIdentifier, errUseQualifier, errTypeExpected, errExprExpected, errUndeclaredIdentifier, errUndeclaredField,
errUndeclaredRoutine, errUseQualifier,
errTypeExpected,
errSystemNeeds, errExecutionOfProgramFailed, errNotOverloadable, errSystemNeeds, errExecutionOfProgramFailed, errNotOverloadable,
errInvalidArgForX, errStmtHasNoEffect, errXExpectsTypeOrValue, errInvalidArgForX, errStmtHasNoEffect, errXExpectsTypeOrValue,
errXExpectsArrayType, errIteratorCannotBeInstantiated, errExprXAmbiguous, errXExpectsArrayType, errIteratorCannotBeInstantiated, errExprXAmbiguous,
@ -143,6 +144,7 @@ const
errInvalidToken: "invalid token: $1", errInvalidToken: "invalid token: $1",
errLineTooLong: "line too long", errLineTooLong: "line too long",
errInvalidNumber: "$1 is not a valid number", errInvalidNumber: "$1 is not a valid number",
errInvalidNumberOctalCode: "$1 is not a valid number; did you mean octal? Then use one of '0o', '0c' or '0C'.",
errNumberOutOfRange: "number $1 out of valid range", errNumberOutOfRange: "number $1 out of valid range",
errNnotAllowedInCharacter: "\\n not allowed in character literal", errNnotAllowedInCharacter: "\\n not allowed in character literal",
errClosingBracketExpected: "closing ']' expected, but end of file reached", errClosingBracketExpected: "closing ']' expected, but end of file reached",
@ -190,6 +192,8 @@ const
errColonOrEqualsExpected: "\':\' or \'=\' expected, but found \'$1\'", errColonOrEqualsExpected: "\':\' or \'=\' expected, but found \'$1\'",
errExprExpected: "expression expected, but found \'$1\'", errExprExpected: "expression expected, but found \'$1\'",
errUndeclaredIdentifier: "undeclared identifier: \'$1\'", errUndeclaredIdentifier: "undeclared identifier: \'$1\'",
errUndeclaredField: "undeclared field: \'$1\'",
errUndeclaredRoutine: "attempting to call undeclared routine: \'$1\'",
errUseQualifier: "ambiguous identifier: \'$1\' -- use a qualifier", errUseQualifier: "ambiguous identifier: \'$1\' -- use a qualifier",
errTypeExpected: "type expected", errTypeExpected: "type expected",
errSystemNeeds: "system module needs \'$1\'", errSystemNeeds: "system module needs \'$1\'",
@ -601,13 +605,13 @@ proc suggestQuit*() =
# this format is understood by many text editors: it is the same that # this format is understood by many text editors: it is the same that
# Borland and Freepascal use # Borland and Freepascal use
const const
PosErrorFormat* = "$1($2, $3) Error: $4" PosErrorFormat* = "$1($2, $3) Error: "
PosWarningFormat* = "$1($2, $3) Warning: $4" PosWarningFormat* = "$1($2, $3) Warning: "
PosHintFormat* = "$1($2, $3) Hint: $4" PosHintFormat* = "$1($2, $3) Hint: "
PosContextFormat = "$1($2, $3) Info: $4" PosContextFormat = "$1($2, $3) Info: "
RawErrorFormat* = "Error: $1" RawError* = "Error: "
RawWarningFormat* = "Warning: $1" RawWarning* = "Warning: "
RawHintFormat* = "Hint: $1" RawHint* = "Hint: "
proc getInfoContextLen*(): int = return msgContext.len proc getInfoContextLen*(): int = return msgContext.len
proc setInfoContextLen*(L: int) = setLen(msgContext, L) proc setInfoContextLen*(L: int) = setLen(msgContext, L)
@ -682,17 +686,27 @@ proc outWriteln*(s: string) =
## Writes to stdout. Always. ## Writes to stdout. Always.
if eStdOut in errorOutputs: writeln(stdout, s) if eStdOut in errorOutputs: writeln(stdout, s)
proc msgWriteln*(s: string) = proc msgWriteln*(s: string, color: ForegroundColor = fgWhite, coloredText: string = "") =
## Writes to stdout. If --stdout option is given, writes to stderr instead. ## Writes to stdout. If --stderr option is given, writes to stderr instead.
#if gCmd == cmdIdeTools and optCDebug notin gGlobalOptions: return #if gCmd == cmdIdeTools and optCDebug notin gGlobalOptions: return
var hasColor = optUseColors in gGlobalOptions
if not isNil(writelnHook): if not isNil(writelnHook):
writelnHook(s) writelnHook(coloredText & s)
elif optStdout in gGlobalOptions:
if eStdErr in errorOutputs: writeln(stderr, s)
else: else:
if eStdOut in errorOutputs: writeln(stdout, s) if optStdout in gGlobalOptions:
if eStdErr in errorOutputs:
if hasColor: setForegroundColor(color)
write(stderr, coloredText)
if hasColor: resetAttributes()
writeln(stderr, s)
else:
if eStdOut in errorOutputs:
if hasColor: setForegroundColor(color)
write(stdout, coloredText)
if hasColor: resetAttributes()
writeln(stdout, s)
proc coordToStr(coord: int): string = proc coordToStr(coord: int): string =
if coord == -1: result = "???" if coord == -1: result = "???"
@ -714,7 +728,7 @@ proc handleError(msg: TMsgKind, eh: TErrorHandling, s: string) =
if stackTraceAvailable() and isNil(writelnHook): if stackTraceAvailable() and isNil(writelnHook):
writeStackTrace() writeStackTrace()
else: else:
msgWriteln("No stack traceback available\nTo create a stacktrace, rerun compilation with ./koch temp " & options.command & " <file>") msgWriteln("", fgRed, "No stack traceback available\nTo create a stacktrace, rerun compilation with ./koch temp " & options.command & " <file>")
quit 1 quit 1
if msg >= fatalMin and msg <= fatalMax: if msg >= fatalMin and msg <= fatalMax:
@ -737,34 +751,39 @@ proc writeContext(lastinfo: TLineInfo) =
for i in countup(0, len(msgContext) - 1): for i in countup(0, len(msgContext) - 1):
if msgContext[i] != lastinfo and msgContext[i] != info: if msgContext[i] != lastinfo and msgContext[i] != info:
msgWriteln(PosContextFormat % [toMsgFilename(msgContext[i]), msgWriteln(PosContextFormat % [toMsgFilename(msgContext[i]),
coordToStr(msgContext[i].line), coordToStr(msgContext[i].line),
coordToStr(msgContext[i].col+1), coordToStr(msgContext[i].col+1),
getMessageStr(errInstantiationFrom, "")]) getMessageStr(errInstantiationFrom, "")])
info = msgContext[i] info = msgContext[i]
proc ignoreMsgBecauseOfIdeTools(msg: TMsgKind): bool = proc ignoreMsgBecauseOfIdeTools(msg: TMsgKind): bool =
msg >= errGenerated and gCmd == cmdIdeTools and optIdeDebug notin gGlobalOptions msg >= errGenerated and gCmd == cmdIdeTools and optIdeDebug notin gGlobalOptions
proc rawMessage*(msg: TMsgKind, args: openArray[string]) = proc rawMessage*(msg: TMsgKind, args: openArray[string]) =
var frmt: string var
frmt: string
color: ForegroundColor
case msg case msg
of errMin..errMax: of errMin..errMax:
writeContext(unknownLineInfo()) writeContext(unknownLineInfo())
frmt = RawErrorFormat frmt = RawError
color = fgRed
of warnMin..warnMax: of warnMin..warnMax:
if optWarns notin gOptions: return if optWarns notin gOptions: return
if msg notin gNotes: return if msg notin gNotes: return
writeContext(unknownLineInfo()) writeContext(unknownLineInfo())
frmt = RawWarningFormat frmt = RawWarning
inc(gWarnCounter) inc(gWarnCounter)
color = fgYellow
of hintMin..hintMax: of hintMin..hintMax:
if optHints notin gOptions: return if optHints notin gOptions: return
if msg notin gNotes: return if msg notin gNotes: return
frmt = RawHintFormat frmt = RawHint
inc(gHintCounter) inc(gHintCounter)
let s = `%`(frmt, `%`(msgKindToString(msg), args)) color = fgGreen
let s = `%`(msgKindToString(msg), args)
if not ignoreMsgBecauseOfIdeTools(msg): if not ignoreMsgBecauseOfIdeTools(msg):
msgWriteln(s) msgWriteln(s, color, frmt)
handleError(msg, doAbort, s) handleError(msg, doAbort, s)
proc rawMessage*(msg: TMsgKind, arg: string) = proc rawMessage*(msg: TMsgKind, arg: string) =
@ -785,8 +804,10 @@ proc formatMsg*(info: TLineInfo, msg: TMsgKind, arg: string): string =
proc liMessage(info: TLineInfo, msg: TMsgKind, arg: string, proc liMessage(info: TLineInfo, msg: TMsgKind, arg: string,
eh: TErrorHandling) = eh: TErrorHandling) =
var frmt: string var
var ignoreMsg = false frmt: string
ignoreMsg = false
color: ForegroundColor
case msg case msg
of errMin..errMax: of errMin..errMax:
writeContext(info) writeContext(info)
@ -795,22 +816,26 @@ proc liMessage(info: TLineInfo, msg: TMsgKind, arg: string,
# in the same file and line are produced: # in the same file and line are produced:
#ignoreMsg = lastError == info and eh != doAbort #ignoreMsg = lastError == info and eh != doAbort
lastError = info lastError = info
color = fgRed
of warnMin..warnMax: of warnMin..warnMax:
ignoreMsg = optWarns notin gOptions or msg notin gNotes ignoreMsg = optWarns notin gOptions or msg notin gNotes
if not ignoreMsg: writeContext(info) if not ignoreMsg: writeContext(info)
frmt = PosWarningFormat frmt = PosWarningFormat
inc(gWarnCounter) inc(gWarnCounter)
color = fgYellow
of hintMin..hintMax: of hintMin..hintMax:
ignoreMsg = optHints notin gOptions or msg notin gNotes ignoreMsg = optHints notin gOptions or msg notin gNotes
frmt = PosHintFormat frmt = PosHintFormat
inc(gHintCounter) inc(gHintCounter)
color = fgGreen
# NOTE: currently line info line numbers start with 1, # NOTE: currently line info line numbers start with 1,
# but column numbers start with 0, however most editors expect # but column numbers start with 0, however most editors expect
# first column to be 1, so we need to +1 here # first column to be 1, so we need to +1 here
let s = frmt % [toMsgFilename(info), coordToStr(info.line), let x = frmt % [toMsgFilename(info), coordToStr(info.line),
coordToStr(info.col+1), getMessageStr(msg, arg)] coordToStr(info.col+1)]
let s = getMessageStr(msg, arg)
if not ignoreMsg and not ignoreMsgBecauseOfIdeTools(msg): if not ignoreMsg and not ignoreMsgBecauseOfIdeTools(msg):
msgWriteln(s) msgWriteln(s, color, x)
if optPrintSurroundingSrc and msg in errMin..errMax: if optPrintSurroundingSrc and msg in errMin..errMax:
info.writeSurroundingSrc info.writeSurroundingSrc
handleError(msg, eh, s) handleError(msg, eh, s)

View file

@ -10,8 +10,10 @@
## Nimfix is a tool that helps to convert old-style Nimrod code to Nim code. ## Nimfix is a tool that helps to convert old-style Nimrod code to Nim code.
import strutils, os, parseopt import strutils, os, parseopt
import options, commands, modules, sem, passes, passaux, pretty, msgs, nimconf, import compiler/options, compiler/commands, compiler/modules, compiler/sem,
extccomp, condsyms, lists compiler/passes, compiler/passaux, compiler/nimfix/pretty,
compiler/msgs, compiler/nimconf,
compiler/extccomp, compiler/condsyms, compiler/lists
const Usage = """ const Usage = """
Nimfix - Tool to patch Nim code Nimfix - Tool to patch Nim code
@ -24,7 +26,7 @@ Options:
--wholeProject overwrite every processed file. --wholeProject overwrite every processed file.
--checkExtern:on|off style check also extern names --checkExtern:on|off style check also extern names
--styleCheck:on|off|auto performs style checking for identifiers --styleCheck:on|off|auto performs style checking for identifiers
and suggests an alternative spelling; and suggests an alternative spelling;
'auto' corrects the spelling. 'auto' corrects the spelling.
--bestEffort try to fix the code even when there --bestEffort try to fix the code even when there
are errors. are errors.
@ -48,11 +50,11 @@ proc processCmdLine*(pass: TCmdLinePass, cmd: string) =
var p = parseopt.initOptParser(cmd) var p = parseopt.initOptParser(cmd)
var argsCount = 0 var argsCount = 0
gOnlyMainfile = true gOnlyMainfile = true
while true: while true:
parseopt.next(p) parseopt.next(p)
case p.kind case p.kind
of cmdEnd: break of cmdEnd: break
of cmdLongoption, cmdShortOption: of cmdLongoption, cmdShortOption:
case p.key.normalize case p.key.normalize
of "overwritefiles": of "overwritefiles":
case p.val.normalize case p.val.normalize

View file

@ -10,9 +10,11 @@
## This module implements the code "prettifier". This is part of the toolchain ## This module implements the code "prettifier". This is part of the toolchain
## to convert Nim code into a consistent style. ## to convert Nim code into a consistent style.
import import
strutils, os, options, ast, astalgo, msgs, ropes, idents, strutils, os, intsets, strtabs
intsets, strtabs, semdata, prettybase
import compiler/options, compiler/ast, compiler/astalgo, compiler/msgs,
compiler/semdata, compiler/nimfix/prettybase, compiler/ropes, compiler/idents
type type
StyleCheck* {.pure.} = enum None, Warn, Auto StyleCheck* {.pure.} = enum None, Warn, Auto
@ -92,7 +94,7 @@ proc beautifyName(s: string, k: TSymKind): string =
proc replaceInFile(info: TLineInfo; newName: string) = proc replaceInFile(info: TLineInfo; newName: string) =
loadFile(info) loadFile(info)
let line = gSourceFiles[info.fileIndex].lines[info.line-1] let line = gSourceFiles[info.fileIndex].lines[info.line-1]
var first = min(info.col.int, line.len) var first = min(info.col.int, line.len)
if first < 0: return if first < 0: return
@ -100,18 +102,18 @@ proc replaceInFile(info: TLineInfo; newName: string) =
while first > 0 and line[first-1] in prettybase.Letters: dec first while first > 0 and line[first-1] in prettybase.Letters: dec first
if first < 0: return if first < 0: return
if line[first] == '`': inc first if line[first] == '`': inc first
let last = first+identLen(line, first)-1 let last = first+identLen(line, first)-1
if differ(line, first, last, newName): if differ(line, first, last, newName):
# last-first+1 != newName.len or # last-first+1 != newName.len or
var x = line.substr(0, first-1) & newName & line.substr(last+1) var x = line.substr(0, first-1) & newName & line.substr(last+1)
system.shallowCopy(gSourceFiles[info.fileIndex].lines[info.line-1], x) system.shallowCopy(gSourceFiles[info.fileIndex].lines[info.line-1], x)
gSourceFiles[info.fileIndex].dirty = true gSourceFiles[info.fileIndex].dirty = true
proc checkStyle(info: TLineInfo, s: string, k: TSymKind; sym: PSym) = proc checkStyle(info: TLineInfo, s: string, k: TSymKind; sym: PSym) =
let beau = beautifyName(s, k) let beau = beautifyName(s, k)
if s != beau: if s != beau:
if gStyleCheck == StyleCheck.Auto: if gStyleCheck == StyleCheck.Auto:
sym.name = getIdent(beau) sym.name = getIdent(beau)
replaceInFile(info, beau) replaceInFile(info, beau)
else: else:
@ -137,7 +139,7 @@ proc styleCheckUseImpl(info: TLineInfo; s: PSym) =
if info.fileIndex < 0: return if info.fileIndex < 0: return
# we simply convert it to what it looks like in the definition # we simply convert it to what it looks like in the definition
# for consistency # for consistency
# operators stay as they are: # operators stay as they are:
if s.kind in {skResult, skTemp} or s.name.s[0] notin prettybase.Letters: if s.kind in {skResult, skTemp} or s.name.s[0] notin prettybase.Letters:
return return

View file

@ -7,7 +7,8 @@
# distribution, for details about the copyright. # distribution, for details about the copyright.
# #
import ast, msgs, strutils, idents, lexbase, streams import strutils, lexbase, streams
import compiler/ast, compiler/msgs, compiler/idents
from os import splitFile from os import splitFile
type type
@ -39,7 +40,7 @@ proc loadFile*(info: TLineInfo) =
var pos = lex.bufpos var pos = lex.bufpos
while true: while true:
case lex.buf[pos] case lex.buf[pos]
of '\c': of '\c':
gSourceFiles[i].newline = "\c\L" gSourceFiles[i].newline = "\c\L"
break break
of '\L', '\0': of '\L', '\0':
@ -70,7 +71,7 @@ proc replaceDeprecated*(info: TLineInfo; oldSym, newSym: PIdent) =
while first > 0 and line[first-1] in Letters: dec first while first > 0 and line[first-1] in Letters: dec first
if first < 0: return if first < 0: return
if line[first] == '`': inc first if line[first] == '`': inc first
let last = first+identLen(line, first)-1 let last = first+identLen(line, first)-1
if cmpIgnoreStyle(line[first..last], oldSym.s) == 0: if cmpIgnoreStyle(line[first..last], oldSym.s) == 0:
var x = line.substr(0, first-1) & newSym.s & line.substr(last+1) var x = line.substr(0, first-1) & newSym.s & line.substr(last+1)

View file

@ -7,330 +7,6 @@
# distribution, for details about the copyright. # distribution, for details about the copyright.
# #
## Nimsuggest is a tool that helps to give editors IDE like capabilities. ## Nimsuggest has been moved to https://github.com/nim-lang/nimsuggest
import strutils, os, parseopt, parseutils, sequtils, net {.error: "This project has moved to the following repo: https://github.com/nim-lang/nimsuggest".}
# Do NOT import suggest. It will lead to wierd bugs with
# suggestionResultHook, because suggest.nim is included by sigmatch.
# So we import that one instead.
import options, commands, modules, sem, passes, passaux, msgs, nimconf,
extccomp, condsyms, lists, net, rdstdin, sexp, sigmatch, ast
when defined(windows):
import winlean
else:
import posix
const Usage = """
Nimsuggest - Tool to give every editor IDE like capabilities for Nim
Usage:
nimsuggest [options] projectfile.nim
Options:
--port:PORT port, by default 6000
--address:HOST binds to that address, by default ""
--stdin read commands from stdin and write results to
stdout instead of using sockets
--epc use emacs epc mode
The server then listens to the connection and takes line-based commands.
In addition, all command line options of Nim that do not affect code generation
are supported.
"""
type
Mode = enum mstdin, mtcp, mepc
var
gPort = 6000.Port
gAddress = ""
gMode: Mode
const
seps = {':', ';', ' ', '\t'}
Help = "usage: sug|con|def|use file.nim[;dirtyfile.nim]:line:col\n"&
"type 'quit' to quit\n" &
"type 'debug' to toggle debug mode on/off\n" &
"type 'terse' to toggle terse mode on/off"
type
EUnexpectedCommand = object of Exception
proc parseQuoted(cmd: string; outp: var string; start: int): int =
var i = start
i += skipWhitespace(cmd, i)
if cmd[i] == '"':
i += parseUntil(cmd, outp, '"', i+1)+2
else:
i += parseUntil(cmd, outp, seps, i)
result = i
proc sexp(s: IdeCmd): SexpNode = sexp($s)
proc sexp(s: TSymKind): SexpNode = sexp($s)
proc sexp(s: Suggest): SexpNode =
# If you change the oder here, make sure to change it over in
# nim-mode.el too.
result = convertSexp([
s.section,
s.symkind,
s.qualifiedPath.map(newSString),
s.filePath,
s.forth,
s.line,
s.column,
s.doc
])
proc sexp(s: seq[Suggest]): SexpNode =
result = newSList()
for sug in s:
result.add(sexp(sug))
proc listEPC(): SexpNode =
let
argspecs = sexp("file line column dirtyfile".split(" ").map(newSSymbol))
docstring = sexp("line starts at 1, column at 0, dirtyfile is optional")
result = newSList()
for command in ["sug", "con", "def", "use"]:
let
cmd = sexp(command)
methodDesc = newSList()
methodDesc.add(cmd)
methodDesc.add(argspecs)
methodDesc.add(docstring)
result.add(methodDesc)
proc execute(cmd: IdeCmd, file, dirtyfile: string, line, col: int) =
gIdeCmd = cmd
if cmd == ideUse:
modules.resetAllModules()
var isKnownFile = true
let dirtyIdx = file.fileInfoIdx(isKnownFile)
if dirtyfile.len != 0: msgs.setDirtyFile(dirtyIdx, dirtyfile)
else: msgs.setDirtyFile(dirtyIdx, nil)
resetModule dirtyIdx
if dirtyIdx != gProjectMainIdx:
resetModule gProjectMainIdx
gTrackPos = newLineInfo(dirtyIdx, line, col)
gErrorCounter = 0
if not isKnownFile:
compileProject()
compileProject(dirtyIdx)
proc executeEPC(cmd: IdeCmd, args: SexpNode) =
let
file = args[0].getStr
line = args[1].getNum
column = args[2].getNum
var dirtyfile = ""
if len(args) > 3:
dirtyfile = args[3].getStr(nil)
execute(cmd, file, dirtyfile, int(line), int(column))
proc returnEPC(socket: var Socket, uid: BiggestInt, s: SexpNode, return_symbol = "return") =
let response = $convertSexp([newSSymbol(return_symbol), uid, s])
socket.send(toHex(len(response), 6))
socket.send(response)
proc connectToNextFreePort(server: Socket, host: string, start = 30000): int =
result = start
while true:
try:
server.bindaddr(Port(result), host)
return
except OsError:
when defined(windows):
let checkFor = WSAEADDRINUSE.OSErrorCode
else:
let checkFor = EADDRINUSE.OSErrorCode
if osLastError() != checkFor:
raise getCurrentException()
else:
result += 1
proc parseCmdLine(cmd: string) =
template toggle(sw) =
if sw in gGlobalOptions:
excl(gGlobalOptions, sw)
else:
incl(gGlobalOptions, sw)
return
template err() =
echo Help
return
var opc = ""
var i = parseIdent(cmd, opc, 0)
case opc.normalize
of "sug": gIdeCmd = ideSug
of "con": gIdeCmd = ideCon
of "def": gIdeCmd = ideDef
of "use": gIdeCmd = ideUse
of "quit": quit()
of "debug": toggle optIdeDebug
of "terse": toggle optIdeTerse
else: err()
var dirtyfile = ""
var orig = ""
i = parseQuoted(cmd, orig, i)
if cmd[i] == ';':
i = parseQuoted(cmd, dirtyfile, i+1)
i += skipWhile(cmd, seps, i)
var line = -1
var col = 0
i += parseInt(cmd, line, i)
i += skipWhile(cmd, seps, i)
i += parseInt(cmd, col, i)
execute(gIdeCmd, orig, dirtyfile, line, col-1)
proc serve() =
case gMode:
of mstdin:
echo Help
var line = ""
while readLineFromStdin("> ", line):
parseCmdLine line
echo ""
flushFile(stdout)
of mtcp:
var server = newSocket()
server.bindAddr(gPort, gAddress)
var inp = "".TaintedString
server.listen()
while true:
var stdoutSocket = newSocket()
msgs.writelnHook = proc (line: string) =
stdoutSocket.send(line & "\c\L")
accept(server, stdoutSocket)
stdoutSocket.readLine(inp)
parseCmdLine inp.string
stdoutSocket.send("\c\L")
stdoutSocket.close()
of mepc:
var server = newSocket()
let port = connectToNextFreePort(server, "localhost")
var inp = "".TaintedString
server.listen()
echo(port)
var client = newSocket()
# Wait for connection
accept(server, client)
while true:
var sizeHex = ""
if client.recv(sizeHex, 6) != 6:
raise newException(ValueError, "didn't get all the hexbytes")
var size = 0
if parseHex(sizeHex, size) == 0:
raise newException(ValueError, "invalid size hex: " & $sizeHex)
var messageBuffer = ""
if client.recv(messageBuffer, size) != size:
raise newException(ValueError, "didn't get all the bytes")
let
message = parseSexp($messageBuffer)
messageType = message[0].getSymbol
case messageType:
of "call":
var results: seq[Suggest] = @[]
suggestionResultHook = proc (s: Suggest) =
results.add(s)
let
uid = message[1].getNum
cmd = parseIdeCmd(message[2].getSymbol)
args = message[3]
executeEPC(cmd, args)
returnEPC(client, uid, sexp(results))
of "return":
raise newException(EUnexpectedCommand, "no return expected")
of "return-error":
raise newException(EUnexpectedCommand, "no return expected")
of "epc-error":
stderr.writeln("recieved epc error: " & $messageBuffer)
raise newException(IOError, "epc error")
of "methods":
returnEPC(client, message[1].getNum, listEPC())
else:
raise newException(EUnexpectedCommand, "unexpected call: " & messageType)
proc mainCommand =
registerPass verbosePass
registerPass semPass
gCmd = cmdIdeTools
incl gGlobalOptions, optCaasEnabled
isServing = true
wantMainModule()
appendStr(searchPaths, options.libpath)
if gProjectFull.len != 0:
# current path is always looked first for modules
prependStr(searchPaths, gProjectPath)
# do not stop after the first error:
msgs.gErrorMax = high(int)
compileProject()
serve()
proc processCmdLine*(pass: TCmdLinePass, cmd: string) =
var p = parseopt.initOptParser(cmd)
while true:
parseopt.next(p)
case p.kind
of cmdEnd: break
of cmdLongoption, cmdShortOption:
case p.key.normalize
of "port":
gPort = parseInt(p.val).Port
gMode = mtcp
of "address":
gAddress = p.val
gMode = mtcp
of "stdin": gMode = mstdin
of "epc":
gMode = mepc
gVerbosity = 0 # Port number gotta be first.
else: processSwitch(pass, p)
of cmdArgument:
options.gProjectName = unixToNativePath(p.key)
# if processArgument(pass, p, argsCount): break
proc handleCmdLine() =
if paramCount() == 0:
stdout.writeln(Usage)
else:
processCmdLine(passCmd1, "")
if gProjectName != "":
try:
gProjectFull = canonicalizePath(gProjectName)
except OSError:
gProjectFull = gProjectName
var p = splitFile(gProjectFull)
gProjectPath = p.dir
gProjectName = p.name
else:
gProjectPath = getCurrentDir()
loadConfigs(DefaultConfig) # load all config files
# now process command line arguments again, because some options in the
# command line can overwite the config file's settings
extccomp.initVars()
processCmdLine(passCmd2, "")
mainCommand()
when false:
proc quitCalled() {.noconv.} =
writeStackTrace()
addQuitProc(quitCalled)
condsyms.initDefines()
defineSymbol "nimsuggest"
handleCmdline()

View file

@ -1,17 +0,0 @@
# Special configuration file for the Nim project
gc:markAndSweep
hint[XDeclaredButNotUsed]:off
path:"$projectPath/../.."
path:"$lib/packages/docutils"
path:"../../compiler"
define:useStdoutAsStdmsg
define:nimsuggest
cs:partial
#define:useNodeIds
define:booting
#define:noDocgen

View file

@ -4,3 +4,5 @@ proc findNodeJs*(): string =
result = findExe("nodejs") result = findExe("nodejs")
if result == "": if result == "":
result = findExe("node") result = findExe("node")
if result == "":
result = findExe("iojs")

View file

@ -54,6 +54,7 @@ type # please make sure we have under 32 options
optSkipUserConfigFile, # skip the users's config file optSkipUserConfigFile, # skip the users's config file
optSkipParentConfigFiles, # skip parent dir's config files optSkipParentConfigFiles, # skip parent dir's config files
optNoMain, # do not generate a "main" proc optNoMain, # do not generate a "main" proc
optUseColors, # use colors for hints, warnings, and errors
optThreads, # support for multi-threading optThreads, # support for multi-threading
optStdout, # output to stdout optStdout, # output to stdout
optThreadAnalysis, # thread analysis pass optThreadAnalysis, # thread analysis pass
@ -145,6 +146,7 @@ const
var var
gConfigVars* = newStringTable(modeStyleInsensitive) gConfigVars* = newStringTable(modeStyleInsensitive)
gDllOverrides = newStringTable(modeCaseInsensitive) gDllOverrides = newStringTable(modeCaseInsensitive)
gPrefixDir* = "" # Overrides the default prefix dir in getPrefixDir proc.
libpath* = "" libpath* = ""
gProjectName* = "" # holds a name like 'nimrod' gProjectName* = "" # holds a name like 'nimrod'
gProjectPath* = "" # holds a path like /home/alice/projects/nimrod/compiler/ gProjectPath* = "" # holds a path like /home/alice/projects/nimrod/compiler/
@ -183,8 +185,13 @@ proc getOutFile*(filename, ext: string): string =
else: result = changeFileExt(filename, ext) else: result = changeFileExt(filename, ext)
proc getPrefixDir*(): string = proc getPrefixDir*(): string =
## gets the application directory ## Gets the prefix dir, usually the parent directory where the binary resides.
result = splitPath(getAppDir()).head ##
## This is overrided by some tools (namely nimsuggest) via the ``gPrefixDir``
## global.
if gPrefixDir != "": result = gPrefixDir
else:
result = splitPath(getAppDir()).head
proc canonicalizePath*(path: string): string = proc canonicalizePath*(path: string): string =
when not FileSystemCaseSensitive: result = path.expandFilename.toLower when not FileSystemCaseSensitive: result = path.expandFilename.toLower

View file

@ -64,6 +64,7 @@ proc setBaseFlags*(n: PNode, base: TNumericalBase)
proc parseSymbol*(p: var TParser, allowNil = false): PNode proc parseSymbol*(p: var TParser, allowNil = false): PNode
proc parseTry(p: var TParser; isExpr: bool): PNode proc parseTry(p: var TParser; isExpr: bool): PNode
proc parseCase(p: var TParser): PNode proc parseCase(p: var TParser): PNode
proc parseStmtPragma(p: var TParser): PNode
# implementation # implementation
proc getTok(p: var TParser) = proc getTok(p: var TParser) =
@ -499,10 +500,13 @@ proc parsePar(p: var TParser): PNode =
#| parKeyw = 'discard' | 'include' | 'if' | 'while' | 'case' | 'try' #| parKeyw = 'discard' | 'include' | 'if' | 'while' | 'case' | 'try'
#| | 'finally' | 'except' | 'for' | 'block' | 'const' | 'let' #| | 'finally' | 'except' | 'for' | 'block' | 'const' | 'let'
#| | 'when' | 'var' | 'mixin' #| | 'when' | 'var' | 'mixin'
#| par = '(' optInd (&parKeyw complexOrSimpleStmt ^+ ';' #| par = '(' optInd
#| | simpleExpr ('=' expr (';' complexOrSimpleStmt ^+ ';' )? )? #| ( &parKeyw complexOrSimpleStmt ^+ ';'
#| | (':' expr)? (',' (exprColonEqExpr comma?)*)? )? #| | ';' complexOrSimpleStmt ^+ ';'
#| optPar ')' #| | pragmaStmt
#| | simpleExpr ( ('=' expr (';' complexOrSimpleStmt ^+ ';' )? )
#| | (':' expr (',' exprColonEqExpr ^+ ',' )? ) ) )
#| optPar ')'
# #
# unfortunately it's ambiguous: (expr: expr) vs (exprStmt); however a # unfortunately it's ambiguous: (expr: expr) vs (exprStmt); however a
# leading ';' could be used to enforce a 'stmt' context ... # leading ';' could be used to enforce a 'stmt' context ...
@ -521,6 +525,8 @@ proc parsePar(p: var TParser): PNode =
getTok(p) getTok(p)
optInd(p, result) optInd(p, result)
semiStmtList(p, result) semiStmtList(p, result)
elif p.tok.tokType == tkCurlyDotLe:
result.add(parseStmtPragma(p))
elif p.tok.tokType != tkParRi: elif p.tok.tokType != tkParRi:
var a = simpleExpr(p) var a = simpleExpr(p)
if p.tok.tokType == tkEquals: if p.tok.tokType == tkEquals:

View file

@ -130,7 +130,9 @@ proc matchNested(c: PPatternContext, p, n: PNode, rpn: bool): bool =
proc matches(c: PPatternContext, p, n: PNode): bool = proc matches(c: PPatternContext, p, n: PNode): bool =
# hidden conversions (?) # hidden conversions (?)
if isPatternParam(c, p): if nfNoRewrite in n.flags:
result = false
elif isPatternParam(c, p):
result = bindOrCheck(c, p.sym, n) result = bindOrCheck(c, p.sym, n)
elif n.kind == nkSym and p.kind == nkIdent: elif n.kind == nkSym and p.kind == nkIdent:
result = p.ident.id == n.sym.name.id result = p.ident.id == n.sym.name.id

View file

@ -9,7 +9,8 @@
## The builtin 'system.locals' implemented as a plugin. ## The builtin 'system.locals' implemented as a plugin.
import plugins, ast, astalgo, magicsys, lookups, semdata, lowerings import compiler/plugins, compiler/ast, compiler/astalgo, compiler/magicsys,
compiler/lookups, compiler/semdata, compiler/lowerings
proc semLocals(c: PContext, n: PNode): PNode = proc semLocals(c: PContext, n: PNode): PNode =
var counter = 0 var counter = 0

View file

@ -37,7 +37,7 @@ const
wImportc, wExportc, wNodecl, wMagic, wDeprecated, wBorrow, wExtern, wImportc, wExportc, wNodecl, wMagic, wDeprecated, wBorrow, wExtern,
wImportCpp, wImportObjC, wError, wDiscardable, wGensym, wInject, wRaises, wImportCpp, wImportObjC, wError, wDiscardable, wGensym, wInject, wRaises,
wTags, wLocks, wGcSafe} wTags, wLocks, wGcSafe}
exprPragmas* = {wLine, wLocks} exprPragmas* = {wLine, wLocks, wNoRewrite}
stmtPragmas* = {wChecks, wObjChecks, wFieldChecks, wRangechecks, stmtPragmas* = {wChecks, wObjChecks, wFieldChecks, wRangechecks,
wBoundchecks, wOverflowchecks, wNilchecks, wAssertions, wWarnings, wHints, wBoundchecks, wOverflowchecks, wNilchecks, wAssertions, wWarnings, wHints,
wLinedir, wStacktrace, wLinetrace, wOptimization, wHint, wWarning, wError, wLinedir, wStacktrace, wLinetrace, wOptimization, wHint, wWarning, wError,
@ -859,6 +859,8 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int,
c.module.flags.incl sfExperimental c.module.flags.incl sfExperimental
else: else:
localError(it.info, "'experimental' pragma only valid as toplevel statement") localError(it.info, "'experimental' pragma only valid as toplevel statement")
of wNoRewrite:
noVal(it)
else: invalidPragma(it) else: invalidPragma(it)
else: invalidPragma(it) else: invalidPragma(it)
else: processNote(c, it) else: processNote(c, it)

View file

@ -90,7 +90,7 @@
import import
os, options, strutils, nversion, ast, astalgo, msgs, platform, condsyms, os, options, strutils, nversion, ast, astalgo, msgs, platform, condsyms,
ropes, idents, crc, idgen, types, rodutils, memfiles ropes, idents, securehash, idgen, types, rodutils, memfiles
type type
TReasonForRecompile* = enum ## all the reasons that can trigger recompilation TReasonForRecompile* = enum ## all the reasons that can trigger recompilation
@ -538,10 +538,11 @@ proc cmdChangeTriggersRecompilation(old, new: TCommands): bool =
# else: trigger recompilation: # else: trigger recompilation:
result = true result = true
proc processRodFile(r: PRodReader, crc: TCrc32) = proc processRodFile(r: PRodReader, crc: SecureHash) =
var var
w: string w: string
d, inclCrc: int d: int
var inclCrc: SecureHash
while r.s[r.pos] != '\0': while r.s[r.pos] != '\0':
var section = rdWord(r) var section = rdWord(r)
if r.reason != rrNone: if r.reason != rrNone:
@ -549,7 +550,8 @@ proc processRodFile(r: PRodReader, crc: TCrc32) =
case section case section
of "CRC": of "CRC":
inc(r.pos) # skip ':' inc(r.pos) # skip ':'
if int(crc) != decodeVInt(r.s, r.pos): r.reason = rrCrcChange if crc != parseSecureHash(decodeStr(r.s, r.pos)):
r.reason = rrCrcChange
of "ID": of "ID":
inc(r.pos) # skip ':' inc(r.pos) # skip ':'
r.moduleID = decodeVInt(r.s, r.pos) r.moduleID = decodeVInt(r.s, r.pos)
@ -596,9 +598,9 @@ proc processRodFile(r: PRodReader, crc: TCrc32) =
while r.s[r.pos] != ')': while r.s[r.pos] != ')':
w = r.files[decodeVInt(r.s, r.pos)].toFullPath w = r.files[decodeVInt(r.s, r.pos)].toFullPath
inc(r.pos) # skip ' ' inc(r.pos) # skip ' '
inclCrc = decodeVInt(r.s, r.pos) inclCrc = parseSecureHash(decodeStr(r.s, r.pos))
if r.reason == rrNone: if r.reason == rrNone:
if not existsFile(w) or (inclCrc != int(crcFromFile(w))): if not existsFile(w) or (inclCrc != secureHashFile(w)):
r.reason = rrInclDeps r.reason = rrInclDeps
if r.s[r.pos] == '\x0A': if r.s[r.pos] == '\x0A':
inc(r.pos) inc(r.pos)
@ -649,7 +651,7 @@ proc startsWith(buf: cstring, token: string, pos = 0): bool =
while s < token.len and buf[pos+s] == token[s]: inc s while s < token.len and buf[pos+s] == token[s]: inc s
result = s == token.len result = s == token.len
proc newRodReader(modfilename: string, crc: TCrc32, proc newRodReader(modfilename: string, crc: SecureHash,
readerIndex: int): PRodReader = readerIndex: int): PRodReader =
new(result) new(result)
try: try:
@ -701,7 +703,7 @@ type
filename*: string filename*: string
reason*: TReasonForRecompile reason*: TReasonForRecompile
rd*: PRodReader rd*: PRodReader
crc*: TCrc32 crc*: SecureHash
crcDone*: bool crcDone*: bool
TFileModuleMap = seq[TFileModuleRec] TFileModuleMap = seq[TFileModuleRec]
@ -794,13 +796,13 @@ proc loadMethods(r: PRodReader) =
r.methods.add(rrGetSym(r, d, unknownLineInfo())) r.methods.add(rrGetSym(r, d, unknownLineInfo()))
if r.s[r.pos] == ' ': inc(r.pos) if r.s[r.pos] == ' ': inc(r.pos)
proc getCRC*(fileIdx: int32): TCrc32 = proc getCRC*(fileIdx: int32): SecureHash =
internalAssert fileIdx >= 0 and fileIdx < gMods.len internalAssert fileIdx >= 0 and fileIdx < gMods.len
if gMods[fileIdx].crcDone: if gMods[fileIdx].crcDone:
return gMods[fileIdx].crc return gMods[fileIdx].crc
result = crcFromFile(fileIdx.toFilename) result = secureHashFile(fileIdx.toFilename)
gMods[fileIdx].crc = result gMods[fileIdx].crc = result
template growCache*(cache, pos) = template growCache*(cache, pos) =
@ -1017,7 +1019,7 @@ proc writeType(f: File; t: PType) =
f.write("]\n") f.write("]\n")
proc viewFile(rodfile: string) = proc viewFile(rodfile: string) =
var r = newRodReader(rodfile, 0, 0) var r = newRodReader(rodfile, secureHash(""), 0)
if r == nil: if r == nil:
rawMessage(errGenerated, "cannot open file (or maybe wrong version):" & rawMessage(errGenerated, "cannot open file (or maybe wrong version):" &
rodfile) rodfile)

View file

@ -13,14 +13,15 @@
import import
intsets, os, options, strutils, nversion, ast, astalgo, msgs, platform, intsets, os, options, strutils, nversion, ast, astalgo, msgs, platform,
condsyms, ropes, idents, crc, rodread, passes, importer, idgen, rodutils condsyms, ropes, idents, securehash, rodread, passes, importer, idgen,
rodutils
# implementation # implementation
type type
TRodWriter = object of TPassContext TRodWriter = object of TPassContext
module: PSym module: PSym
crc: TCrc32 crc: SecureHash
options: TOptions options: TOptions
defines: string defines: string
inclDeps: string inclDeps: string
@ -38,7 +39,7 @@ type
PRodWriter = ref TRodWriter PRodWriter = ref TRodWriter
proc newRodWriter(crc: TCrc32, module: PSym): PRodWriter proc newRodWriter(crc: SecureHash, module: PSym): PRodWriter
proc addModDep(w: PRodWriter, dep: string) proc addModDep(w: PRodWriter, dep: string)
proc addInclDep(w: PRodWriter, dep: string) proc addInclDep(w: PRodWriter, dep: string)
proc addInterfaceSym(w: PRodWriter, s: PSym) proc addInterfaceSym(w: PRodWriter, s: PSym)
@ -62,7 +63,7 @@ proc fileIdx(w: PRodWriter, filename: string): int =
template filename*(w: PRodWriter): string = template filename*(w: PRodWriter): string =
w.module.filename w.module.filename
proc newRodWriter(crc: TCrc32, module: PSym): PRodWriter = proc newRodWriter(crc: SecureHash, module: PSym): PRodWriter =
new(result) new(result)
result.sstack = @[] result.sstack = @[]
result.tstack = @[] result.tstack = @[]
@ -96,7 +97,7 @@ proc addInclDep(w: PRodWriter, dep: string) =
var resolved = dep.findModule(w.module.info.toFullPath) var resolved = dep.findModule(w.module.info.toFullPath)
encodeVInt(fileIdx(w, dep), w.inclDeps) encodeVInt(fileIdx(w, dep), w.inclDeps)
add(w.inclDeps, " ") add(w.inclDeps, " ")
encodeVInt(crcFromFile(resolved), w.inclDeps) encodeStr($secureHashFile(resolved), w.inclDeps)
add(w.inclDeps, rodNL) add(w.inclDeps, rodNL)
proc pushType(w: PRodWriter, t: PType) = proc pushType(w: PRodWriter, t: PType) =
@ -440,7 +441,7 @@ proc writeRod(w: PRodWriter) =
f.write(rodNL) f.write(rodNL)
var crc = "CRC:" var crc = "CRC:"
encodeVInt(w.crc, crc) encodeStr($w.crc, crc)
f.write(crc) f.write(crc)
f.write(rodNL) f.write(rodNL)

199
compiler/securehash.nim Normal file
View file

@ -0,0 +1,199 @@
#
#
# The Nim Compiler
# (c) Copyright 2015 Nim Contributers
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
import
strutils, unsigned
const Sha1DigestSize = 20
type
Sha1Digest = array[0 .. Sha1DigestSize-1, uint8]
SecureHash* = distinct Sha1Digest
proc sha1(src: string) : Sha1Digest
proc secureHash*(str: string): SecureHash = SecureHash(sha1(str))
proc secureHashFile*(filename: string): SecureHash = secureHash(readFile(filename))
proc `$`*(self: SecureHash): string =
result = ""
for v in Sha1Digest(self):
result.add(toHex(int(v), 2))
proc parseSecureHash*(hash: string): SecureHash =
for i in 0.. <Sha1DigestSize:
Sha1Digest(result)[i] = uint8(parseHexInt(hash[i*2] & hash[i*2 + 1]))
proc `==`*(a, b: SecureHash): bool =
# Not a constant-time comparison, but that's acceptable in this context
Sha1Digest(a) == Sha1Digest(b)
when isMainModule:
let hash1 = secureHash("a93tgj0p34jagp9[agjp98ajrhp9aej]")
doAssert hash1 == hash1
doAssert parseSecureHash($hash1) == hash1
# Copyright (c) 2011, Micael Hildenborg
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of Micael Hildenborg nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY Micael Hildenborg ''AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL Micael Hildenborg BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# Ported to Nim by Erik O'Leary
type
Sha1State = array[0 .. 5-1, uint32]
Sha1Buffer = array[0 .. 80-1, uint32]
template clearBuffer(w: Sha1Buffer, len = 16) =
zeroMem(addr(w), len * sizeof(uint32))
proc init(result: var Sha1State) =
result[0] = 0x67452301'u32
result[1] = 0xefcdab89'u32
result[2] = 0x98badcfe'u32
result[3] = 0x10325476'u32
result[4] = 0xc3d2e1f0'u32
proc innerHash(state: var Sha1State, w: var Sha1Buffer) =
var
a = state[0]
b = state[1]
c = state[2]
d = state[3]
e = state[4]
var round = 0
template rot(value, bits: uint32): uint32 {.immediate.} =
(value shl bits) or (value shr (32 - bits))
template sha1(fun, val: uint32): stmt =
let t = rot(a, 5) + fun + e + val + w[round]
e = d
d = c
c = rot(b, 30)
b = a
a = t
template process(body: stmt): stmt =
w[round] = rot(w[round - 3] xor w[round - 8] xor w[round - 14] xor w[round - 16], 1)
body
inc(round)
template wrap(dest, value: expr): stmt {.immediate.} =
let v = dest + value
dest = v
while round < 16:
sha1((b and c) or (not b and d), 0x5a827999'u32)
inc(round)
while round < 20:
process:
sha1((b and c) or (not b and d), 0x5a827999'u32)
while round < 40:
process:
sha1(b xor c xor d, 0x6ed9eba1'u32)
while round < 60:
process:
sha1((b and c) or (b and d) or (c and d), 0x8f1bbcdc'u32)
while round < 80:
process:
sha1(b xor c xor d, 0xca62c1d6'u32)
wrap state[0], a
wrap state[1], b
wrap state[2], c
wrap state[3], d
wrap state[4], e
template computeInternal(src: expr): stmt {.immediate.} =
#Initialize state
var state: Sha1State
init(state)
#Create w buffer
var w: Sha1Buffer
#Loop through all complete 64byte blocks.
let byteLen = src.len
let endOfFullBlocks = byteLen - 64
var endCurrentBlock = 0
var currentBlock = 0
while currentBlock <= endOfFullBlocks:
endCurrentBlock = currentBlock + 64
var i = 0
while currentBlock < endCurrentBlock:
w[i] = uint32(src[currentBlock+3]) or
uint32(src[currentBlock+2]) shl 8'u32 or
uint32(src[currentBlock+1]) shl 16'u32 or
uint32(src[currentBlock]) shl 24'u32
currentBlock += 4
inc(i)
innerHash(state, w)
#Handle last and not full 64 byte block if existing
endCurrentBlock = byteLen - currentBlock
clearBuffer(w)
var lastBlockBytes = 0
while lastBlockBytes < endCurrentBlock:
var value = uint32(src[lastBlockBytes + currentBlock]) shl
((3'u32 - (lastBlockBytes and 3)) shl 3)
w[lastBlockBytes shr 2] = w[lastBlockBytes shr 2] or value
inc(lastBlockBytes)
w[lastBlockBytes shr 2] = w[lastBlockBytes shr 2] or (
0x80'u32 shl ((3'u32 - (lastBlockBytes and 3)) shl 3)
)
if endCurrentBlock >= 56:
innerHash(state, w)
clearBuffer(w)
w[15] = uint32(byteLen) shl 3
innerHash(state, w)
# Store hash in result pointer, and make sure we get in in the correct order
# on both endian models.
for i in 0 .. Sha1DigestSize-1:
result[i] = uint8((int(state[i shr 2]) shr ((3-(i and 3)) * 8)) and 255)
proc sha1(src: string) : Sha1Digest =
## Calculate SHA1 from input string
computeInternal(src)

View file

@ -209,7 +209,10 @@ proc resolveOverloads(c: PContext, n, orig: PNode,
pickBest(callOp) pickBest(callOp)
if overloadsState == csEmpty and result.state == csEmpty: if overloadsState == csEmpty and result.state == csEmpty:
localError(n.info, errUndeclaredIdentifier, considerQuotedIdent(f).s) if nfDotField in n.flags and nfExplicitCall notin n.flags:
localError(n.info, errUndeclaredField, considerQuotedIdent(f).s)
else:
localError(n.info, errUndeclaredRoutine, considerQuotedIdent(f).s)
return return
elif result.state != csMatch: elif result.state != csMatch:
if nfExprCall in n.flags: if nfExprCall in n.flags:

View file

@ -179,7 +179,7 @@ proc getIntervalType*(m: TMagic, n: PNode): PType =
else: else:
result = makeRangeF(a, abs(getFloat(a.n.sons[1])), result = makeRangeF(a, abs(getFloat(a.n.sons[1])),
abs(getFloat(a.n.sons[0]))) abs(getFloat(a.n.sons[0])))
of mAbsI, mAbsI64: of mAbsI:
let a = n.sons[1].typ let a = n.sons[1].typ
if isIntRange(a): if isIntRange(a):
if a.n[0].intVal <= 0: if a.n[0].intVal <= 0:
@ -200,13 +200,13 @@ proc getIntervalType*(m: TMagic, n: PNode): PType =
if isIntRange(a) and isIntLit(b): if isIntRange(a) and isIntLit(b):
result = makeRange(a, pickMinInt(n.sons[1]) |-| pickMinInt(n.sons[2]), result = makeRange(a, pickMinInt(n.sons[1]) |-| pickMinInt(n.sons[2]),
pickMaxInt(n.sons[1]) |-| pickMaxInt(n.sons[2])) pickMaxInt(n.sons[1]) |-| pickMaxInt(n.sons[2]))
of mAddI, mAddI64, mAddU: of mAddI, mAddU:
commutativeOp(`|+|`) commutativeOp(`|+|`)
of mMulI, mMulI64, mMulU: of mMulI, mMulU:
commutativeOp(`|*|`) commutativeOp(`|*|`)
of mSubI, mSubI64, mSubU: of mSubI, mSubU:
binaryOp(`|-|`) binaryOp(`|-|`)
of mBitandI, mBitandI64: of mBitandI:
# since uint64 is still not even valid for 'range' (since it's no ordinal # since uint64 is still not even valid for 'range' (since it's no ordinal
# yet), we exclude it from the list (see bug #1638) for now: # yet), we exclude it from the list (see bug #1638) for now:
var a = n.sons[1] var a = n.sons[1]
@ -225,7 +225,7 @@ proc getIntervalType*(m: TMagic, n: PNode): PType =
result = makeRange(a.typ, 0, b.intVal-1) result = makeRange(a.typ, 0, b.intVal-1)
else: else:
result = makeRange(a.typ, b.intVal+1, 0) result = makeRange(a.typ, b.intVal+1, 0)
of mModI, mModI64: of mModI:
# so ... if you ever wondered about modulo's signedness; this defines it: # so ... if you ever wondered about modulo's signedness; this defines it:
let a = n.sons[1] let a = n.sons[1]
let b = n.sons[2] let b = n.sons[2]
@ -234,7 +234,7 @@ proc getIntervalType*(m: TMagic, n: PNode): PType =
result = makeRange(a.typ, -(b.intVal-1), b.intVal-1) result = makeRange(a.typ, -(b.intVal-1), b.intVal-1)
else: else:
result = makeRange(a.typ, b.intVal+1, -(b.intVal+1)) result = makeRange(a.typ, b.intVal+1, -(b.intVal+1))
of mDivI, mDivI64, mDivU: of mDivI, mDivU:
binaryOp(`|div|`) binaryOp(`|div|`)
of mMinI: of mMinI:
commutativeOp(min) commutativeOp(min)
@ -243,8 +243,8 @@ proc getIntervalType*(m: TMagic, n: PNode): PType =
else: discard else: discard
discard """ discard """
mShlI, mShlI64, mShlI,
mShrI, mShrI64, mAddF64, mSubF64, mMulF64, mDivF64, mMaxF64, mMinF64 mShrI, mAddF64, mSubF64, mMulF64, mDivF64, mMaxF64, mMinF64
""" """
proc evalIs(n, a: PNode): PNode = proc evalIs(n, a: PNode): PNode =
@ -285,7 +285,7 @@ proc evalOp(m: TMagic, n, a, b, c: PNode): PNode =
of mUnaryMinusF64: result = newFloatNodeT(- getFloat(a), n) of mUnaryMinusF64: result = newFloatNodeT(- getFloat(a), n)
of mNot: result = newIntNodeT(1 - getInt(a), n) of mNot: result = newIntNodeT(1 - getInt(a), n)
of mCard: result = newIntNodeT(nimsets.cardSet(a), n) of mCard: result = newIntNodeT(nimsets.cardSet(a), n)
of mBitnotI, mBitnotI64: result = newIntNodeT(not getInt(a), n) of mBitnotI: result = newIntNodeT(not getInt(a), n)
of mLengthStr, mXLenStr: of mLengthStr, mXLenStr:
if a.kind == nkNilLit: result = newIntNodeT(0, n) if a.kind == nkNilLit: result = newIntNodeT(0, n)
else: result = newIntNodeT(len(getStr(a)), n) else: result = newIntNodeT(len(getStr(a)), n)
@ -298,7 +298,7 @@ proc evalOp(m: TMagic, n, a, b, c: PNode): PNode =
result = newFloatNodeT(toFloat(int(getInt(a))), n) result = newFloatNodeT(toFloat(int(getInt(a))), n)
of mToInt, mToBiggestInt: result = newIntNodeT(system.toInt(getFloat(a)), n) of mToInt, mToBiggestInt: result = newIntNodeT(system.toInt(getFloat(a)), n)
of mAbsF64: result = newFloatNodeT(abs(getFloat(a)), n) of mAbsF64: result = newFloatNodeT(abs(getFloat(a)), n)
of mAbsI, mAbsI64: of mAbsI:
if getInt(a) >= 0: result = a if getInt(a) >= 0: result = a
else: result = newIntNodeT(- getInt(a), n) else: result = newIntNodeT(- getInt(a), n)
of mZe8ToI, mZe8ToI64, mZe16ToI, mZe16ToI64, mZe32ToI64, mZeIToI64: of mZe8ToI, mZe8ToI64, mZe16ToI, mZe16ToI64, mZe32ToI64, mZeIToI64:
@ -310,16 +310,16 @@ proc evalOp(m: TMagic, n, a, b, c: PNode): PNode =
of mUnaryLt: result = newIntNodeT(getOrdValue(a) - 1, n) of mUnaryLt: result = newIntNodeT(getOrdValue(a) - 1, n)
of mSucc: result = newIntNodeT(getOrdValue(a) + getInt(b), n) of mSucc: result = newIntNodeT(getOrdValue(a) + getInt(b), n)
of mPred: result = newIntNodeT(getOrdValue(a) - getInt(b), n) of mPred: result = newIntNodeT(getOrdValue(a) - getInt(b), n)
of mAddI, mAddI64: result = newIntNodeT(getInt(a) + getInt(b), n) of mAddI: result = newIntNodeT(getInt(a) + getInt(b), n)
of mSubI, mSubI64: result = newIntNodeT(getInt(a) - getInt(b), n) of mSubI: result = newIntNodeT(getInt(a) - getInt(b), n)
of mMulI, mMulI64: result = newIntNodeT(getInt(a) * getInt(b), n) of mMulI: result = newIntNodeT(getInt(a) * getInt(b), n)
of mMinI: of mMinI:
if getInt(a) > getInt(b): result = newIntNodeT(getInt(b), n) if getInt(a) > getInt(b): result = newIntNodeT(getInt(b), n)
else: result = newIntNodeT(getInt(a), n) else: result = newIntNodeT(getInt(a), n)
of mMaxI: of mMaxI:
if getInt(a) > getInt(b): result = newIntNodeT(getInt(a), n) if getInt(a) > getInt(b): result = newIntNodeT(getInt(a), n)
else: result = newIntNodeT(getInt(b), n) else: result = newIntNodeT(getInt(b), n)
of mShlI, mShlI64: of mShlI:
case skipTypes(n.typ, abstractRange).kind case skipTypes(n.typ, abstractRange).kind
of tyInt8: result = newIntNodeT(int8(getInt(a)) shl int8(getInt(b)), n) of tyInt8: result = newIntNodeT(int8(getInt(a)) shl int8(getInt(b)), n)
of tyInt16: result = newIntNodeT(int16(getInt(a)) shl int16(getInt(b)), n) of tyInt16: result = newIntNodeT(int16(getInt(a)) shl int16(getInt(b)), n)
@ -327,7 +327,7 @@ proc evalOp(m: TMagic, n, a, b, c: PNode): PNode =
of tyInt64, tyInt, tyUInt..tyUInt64: of tyInt64, tyInt, tyUInt..tyUInt64:
result = newIntNodeT(`shl`(getInt(a), getInt(b)), n) result = newIntNodeT(`shl`(getInt(a), getInt(b)), n)
else: internalError(n.info, "constant folding for shl") else: internalError(n.info, "constant folding for shl")
of mShrI, mShrI64: of mShrI:
case skipTypes(n.typ, abstractRange).kind case skipTypes(n.typ, abstractRange).kind
of tyInt8: result = newIntNodeT(int8(getInt(a)) shr int8(getInt(b)), n) of tyInt8: result = newIntNodeT(int8(getInt(a)) shr int8(getInt(b)), n)
of tyInt16: result = newIntNodeT(int16(getInt(a)) shr int16(getInt(b)), n) of tyInt16: result = newIntNodeT(int16(getInt(a)) shr int16(getInt(b)), n)
@ -335,11 +335,11 @@ proc evalOp(m: TMagic, n, a, b, c: PNode): PNode =
of tyInt64, tyInt, tyUInt..tyUInt64: of tyInt64, tyInt, tyUInt..tyUInt64:
result = newIntNodeT(`shr`(getInt(a), getInt(b)), n) result = newIntNodeT(`shr`(getInt(a), getInt(b)), n)
else: internalError(n.info, "constant folding for shr") else: internalError(n.info, "constant folding for shr")
of mDivI, mDivI64: of mDivI:
let y = getInt(b) let y = getInt(b)
if y != 0: if y != 0:
result = newIntNodeT(getInt(a) div y, n) result = newIntNodeT(getInt(a) div y, n)
of mModI, mModI64: of mModI:
let y = getInt(b) let y = getInt(b)
if y != 0: if y != 0:
result = newIntNodeT(getInt(a) mod y, n) result = newIntNodeT(getInt(a) mod y, n)
@ -359,11 +359,11 @@ proc evalOp(m: TMagic, n, a, b, c: PNode): PNode =
if getFloat(a) > getFloat(b): result = newFloatNodeT(getFloat(b), n) if getFloat(a) > getFloat(b): result = newFloatNodeT(getFloat(b), n)
else: result = newFloatNodeT(getFloat(a), n) else: result = newFloatNodeT(getFloat(a), n)
of mIsNil: result = newIntNodeT(ord(a.kind == nkNilLit), n) of mIsNil: result = newIntNodeT(ord(a.kind == nkNilLit), n)
of mLtI, mLtI64, mLtB, mLtEnum, mLtCh: of mLtI, mLtB, mLtEnum, mLtCh:
result = newIntNodeT(ord(getOrdValue(a) < getOrdValue(b)), n) result = newIntNodeT(ord(getOrdValue(a) < getOrdValue(b)), n)
of mLeI, mLeI64, mLeB, mLeEnum, mLeCh: of mLeI, mLeB, mLeEnum, mLeCh:
result = newIntNodeT(ord(getOrdValue(a) <= getOrdValue(b)), n) result = newIntNodeT(ord(getOrdValue(a) <= getOrdValue(b)), n)
of mEqI, mEqI64, mEqB, mEqEnum, mEqCh: of mEqI, mEqB, mEqEnum, mEqCh:
result = newIntNodeT(ord(getOrdValue(a) == getOrdValue(b)), n) result = newIntNodeT(ord(getOrdValue(a) == getOrdValue(b)), n)
of mLtF64: result = newIntNodeT(ord(getFloat(a) < getFloat(b)), n) of mLtF64: result = newIntNodeT(ord(getFloat(a) < getFloat(b)), n)
of mLeF64: result = newIntNodeT(ord(getFloat(a) <= getFloat(b)), n) of mLeF64: result = newIntNodeT(ord(getFloat(a) <= getFloat(b)), n)
@ -375,9 +375,9 @@ proc evalOp(m: TMagic, n, a, b, c: PNode): PNode =
result = newIntNodeT(ord(`<%`(getOrdValue(a), getOrdValue(b))), n) result = newIntNodeT(ord(`<%`(getOrdValue(a), getOrdValue(b))), n)
of mLeU, mLeU64: of mLeU, mLeU64:
result = newIntNodeT(ord(`<=%`(getOrdValue(a), getOrdValue(b))), n) result = newIntNodeT(ord(`<=%`(getOrdValue(a), getOrdValue(b))), n)
of mBitandI, mBitandI64, mAnd: result = newIntNodeT(a.getInt and b.getInt, n) of mBitandI, mAnd: result = newIntNodeT(a.getInt and b.getInt, n)
of mBitorI, mBitorI64, mOr: result = newIntNodeT(getInt(a) or getInt(b), n) of mBitorI, mOr: result = newIntNodeT(getInt(a) or getInt(b), n)
of mBitxorI, mBitxorI64, mXor: result = newIntNodeT(a.getInt xor b.getInt, n) of mBitxorI, mXor: result = newIntNodeT(a.getInt xor b.getInt, n)
of mAddU: result = newIntNodeT(`+%`(getInt(a), getInt(b)), n) of mAddU: result = newIntNodeT(`+%`(getInt(a), getInt(b)), n)
of mSubU: result = newIntNodeT(`-%`(getInt(a), getInt(b)), n) of mSubU: result = newIntNodeT(`-%`(getInt(a), getInt(b)), n)
of mMulU: result = newIntNodeT(`*%`(getInt(a), getInt(b)), n) of mMulU: result = newIntNodeT(`*%`(getInt(a), getInt(b)), n)

View file

@ -207,9 +207,9 @@ proc markGcUnsafe(a: PEffects; reason: PNode) =
a.owner.gcUnsafetyReason = newSym(skUnknown, getIdent("<unknown>"), a.owner.gcUnsafetyReason = newSym(skUnknown, getIdent("<unknown>"),
a.owner, reason.info) a.owner, reason.info)
proc listGcUnsafety(s: PSym; onlyWarning: bool) = proc listGcUnsafety(s: PSym; onlyWarning: bool; cycleCheck: var IntSet) =
let u = s.gcUnsafetyReason let u = s.gcUnsafetyReason
if u != nil: if u != nil and not cycleCheck.containsOrIncl(u.id):
let msgKind = if onlyWarning: warnGcUnsafe2 else: errGenerated let msgKind = if onlyWarning: warnGcUnsafe2 else: errGenerated
if u.kind in {skLet, skVar}: if u.kind in {skLet, skVar}:
message(s.info, msgKind, message(s.info, msgKind,
@ -218,7 +218,7 @@ proc listGcUnsafety(s: PSym; onlyWarning: bool) =
elif u.kind in routineKinds: elif u.kind in routineKinds:
# recursive call *always* produces only a warning so the full error # recursive call *always* produces only a warning so the full error
# message is printed: # message is printed:
listGcUnsafety(u, true) listGcUnsafety(u, true, cycleCheck)
message(s.info, msgKind, message(s.info, msgKind,
"'$#' is not GC-safe as it calls '$#'" % "'$#' is not GC-safe as it calls '$#'" %
[s.name.s, u.name.s]) [s.name.s, u.name.s])
@ -227,6 +227,10 @@ proc listGcUnsafety(s: PSym; onlyWarning: bool) =
message(u.info, msgKind, message(u.info, msgKind,
"'$#' is not GC-safe as it performs an indirect call here" % s.name.s) "'$#' is not GC-safe as it performs an indirect call here" % s.name.s)
proc listGcUnsafety(s: PSym; onlyWarning: bool) =
var cycleCheck = initIntSet()
listGcUnsafety(s, onlyWarning, cycleCheck)
proc useVar(a: PEffects, n: PNode) = proc useVar(a: PEffects, n: PNode) =
let s = n.sym let s = n.sym
if isLocalVar(a, s): if isLocalVar(a, s):

View file

@ -1268,6 +1268,8 @@ proc semPragmaBlock(c: PContext, n: PNode): PNode =
of wLocks: of wLocks:
result = n result = n
result.typ = n.sons[1].typ result.typ = n.sons[1].typ
of wNoRewrite:
incl(result.flags, nfNoRewrite)
else: discard else: discard
proc semStaticStmt(c: PContext, n: PNode): PNode = proc semStaticStmt(c: PContext, n: PNode): PNode =

View file

@ -12,7 +12,7 @@
import import
hashes, ast, astalgo, types hashes, ast, astalgo, types
proc hashTree(n: PNode): THash = proc hashTree(n: PNode): Hash =
if n == nil: return if n == nil: return
result = ord(n.kind) result = ord(n.kind)
case n.kind case n.kind
@ -53,8 +53,8 @@ proc treesEquivalent(a, b: PNode): bool =
result = true result = true
if result: result = sameTypeOrNil(a.typ, b.typ) if result: result = sameTypeOrNil(a.typ, b.typ)
proc nodeTableRawGet(t: TNodeTable, k: THash, key: PNode): int = proc nodeTableRawGet(t: TNodeTable, k: Hash, key: PNode): int =
var h: THash = k and high(t.data) var h: Hash = k and high(t.data)
while t.data[h].key != nil: while t.data[h].key != nil:
if (t.data[h].h == k) and treesEquivalent(t.data[h].key, key): if (t.data[h].h == k) and treesEquivalent(t.data[h].key, key):
return h return h
@ -66,9 +66,9 @@ proc nodeTableGet*(t: TNodeTable, key: PNode): int =
if index >= 0: result = t.data[index].val if index >= 0: result = t.data[index].val
else: result = low(int) else: result = low(int)
proc nodeTableRawInsert(data: var TNodePairSeq, k: THash, key: PNode, proc nodeTableRawInsert(data: var TNodePairSeq, k: Hash, key: PNode,
val: int) = val: int) =
var h: THash = k and high(data) var h: Hash = k and high(data)
while data[h].key != nil: h = nextTry(h, high(data)) while data[h].key != nil: h = nextTry(h, high(data))
assert(data[h].key == nil) assert(data[h].key == nil)
data[h].h = k data[h].h = k
@ -77,7 +77,7 @@ proc nodeTableRawInsert(data: var TNodePairSeq, k: THash, key: PNode,
proc nodeTablePut*(t: var TNodeTable, key: PNode, val: int) = proc nodeTablePut*(t: var TNodeTable, key: PNode, val: int) =
var n: TNodePairSeq var n: TNodePairSeq
var k: THash = hashTree(key) var k: Hash = hashTree(key)
var index = nodeTableRawGet(t, k, key) var index = nodeTableRawGet(t, k, key)
if index >= 0: if index >= 0:
assert(t.data[index].key != nil) assert(t.data[index].key != nil)
@ -94,7 +94,7 @@ proc nodeTablePut*(t: var TNodeTable, key: PNode, val: int) =
proc nodeTableTestOrSet*(t: var TNodeTable, key: PNode, val: int): int = proc nodeTableTestOrSet*(t: var TNodeTable, key: PNode, val: int): int =
var n: TNodePairSeq var n: TNodePairSeq
var k: THash = hashTree(key) var k: Hash = hashTree(key)
var index = nodeTableRawGet(t, k, key) var index = nodeTableRawGet(t, k, key)
if index >= 0: if index >= 0:
assert(t.data[index].key != nil) assert(t.data[index].key != nil)

View file

@ -682,11 +682,15 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
of opcLtu: of opcLtu:
decodeBC(rkInt) decodeBC(rkInt)
regs[ra].intVal = ord(regs[rb].intVal <% regs[rc].intVal) regs[ra].intVal = ord(regs[rb].intVal <% regs[rc].intVal)
of opcEqRef, opcEqNimrodNode: of opcEqRef:
decodeBC(rkInt) decodeBC(rkInt)
regs[ra].intVal = ord((regs[rb].node.kind == nkNilLit and regs[ra].intVal = ord((regs[rb].node.kind == nkNilLit and
regs[rc].node.kind == nkNilLit) or regs[rc].node.kind == nkNilLit) or
regs[rb].node == regs[rc].node) regs[rb].node == regs[rc].node)
of opcEqNimrodNode:
decodeBC(rkInt)
regs[ra].intVal =
ord(exprStructuralEquivalent(regs[rb].node, regs[rc].node))
of opcXor: of opcXor:
decodeBC(rkInt) decodeBC(rkInt)
regs[ra].intVal = ord(regs[rb].intVal != regs[rc].intVal) regs[ra].intVal = ord(regs[rb].intVal != regs[rc].intVal)

View file

@ -710,9 +710,9 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) =
if dest < 0: dest = c.getTemp(n.typ) if dest < 0: dest = c.getTemp(n.typ)
c.gABI(n, opcSubImmInt, dest, tmp, 1) c.gABI(n, opcSubImmInt, dest, tmp, 1)
c.freeTemp(tmp) c.freeTemp(tmp)
of mPred, mSubI, mSubI64: of mPred, mSubI:
c.genAddSubInt(n, dest, opcSubInt) c.genAddSubInt(n, dest, opcSubInt)
of mSucc, mAddI, mAddI64: of mSucc, mAddI:
c.genAddSubInt(n, dest, opcAddInt) c.genAddSubInt(n, dest, opcAddInt)
of mInc, mDec: of mInc, mDec:
unused(n, dest) unused(n, dest)
@ -759,28 +759,28 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) =
c.freeTemp(d) c.freeTemp(d)
c.freeTemp(tmp) c.freeTemp(tmp)
of mCard: genCard(c, n, dest) of mCard: genCard(c, n, dest)
of mMulI, mMulI64: genBinaryABCnarrow(c, n, dest, opcMulInt) of mMulI: genBinaryABCnarrow(c, n, dest, opcMulInt)
of mDivI, mDivI64: genBinaryABCnarrow(c, n, dest, opcDivInt) of mDivI: genBinaryABCnarrow(c, n, dest, opcDivInt)
of mModI, mModI64: genBinaryABCnarrow(c, n, dest, opcModInt) of mModI: genBinaryABCnarrow(c, n, dest, opcModInt)
of mAddF64: genBinaryABC(c, n, dest, opcAddFloat) of mAddF64: genBinaryABC(c, n, dest, opcAddFloat)
of mSubF64: genBinaryABC(c, n, dest, opcSubFloat) of mSubF64: genBinaryABC(c, n, dest, opcSubFloat)
of mMulF64: genBinaryABC(c, n, dest, opcMulFloat) of mMulF64: genBinaryABC(c, n, dest, opcMulFloat)
of mDivF64: genBinaryABC(c, n, dest, opcDivFloat) of mDivF64: genBinaryABC(c, n, dest, opcDivFloat)
of mShrI, mShrI64: genBinaryABCnarrowU(c, n, dest, opcShrInt) of mShrI: genBinaryABCnarrowU(c, n, dest, opcShrInt)
of mShlI, mShlI64: genBinaryABCnarrowU(c, n, dest, opcShlInt) of mShlI: genBinaryABCnarrowU(c, n, dest, opcShlInt)
of mBitandI, mBitandI64: genBinaryABCnarrowU(c, n, dest, opcBitandInt) of mBitandI: genBinaryABCnarrowU(c, n, dest, opcBitandInt)
of mBitorI, mBitorI64: genBinaryABCnarrowU(c, n, dest, opcBitorInt) of mBitorI: genBinaryABCnarrowU(c, n, dest, opcBitorInt)
of mBitxorI, mBitxorI64: genBinaryABCnarrowU(c, n, dest, opcBitxorInt) of mBitxorI: genBinaryABCnarrowU(c, n, dest, opcBitxorInt)
of mAddU: genBinaryABCnarrowU(c, n, dest, opcAddu) of mAddU: genBinaryABCnarrowU(c, n, dest, opcAddu)
of mSubU: genBinaryABCnarrowU(c, n, dest, opcSubu) of mSubU: genBinaryABCnarrowU(c, n, dest, opcSubu)
of mMulU: genBinaryABCnarrowU(c, n, dest, opcMulu) of mMulU: genBinaryABCnarrowU(c, n, dest, opcMulu)
of mDivU: genBinaryABCnarrowU(c, n, dest, opcDivu) of mDivU: genBinaryABCnarrowU(c, n, dest, opcDivu)
of mModU: genBinaryABCnarrowU(c, n, dest, opcModu) of mModU: genBinaryABCnarrowU(c, n, dest, opcModu)
of mEqI, mEqI64, mEqB, mEqEnum, mEqCh: of mEqI, mEqB, mEqEnum, mEqCh:
genBinaryABC(c, n, dest, opcEqInt) genBinaryABC(c, n, dest, opcEqInt)
of mLeI, mLeI64, mLeEnum, mLeCh, mLeB: of mLeI, mLeEnum, mLeCh, mLeB:
genBinaryABC(c, n, dest, opcLeInt) genBinaryABC(c, n, dest, opcLeInt)
of mLtI, mLtI64, mLtEnum, mLtCh, mLtB: of mLtI, mLtEnum, mLtCh, mLtB:
genBinaryABC(c, n, dest, opcLtInt) genBinaryABC(c, n, dest, opcLtInt)
of mEqF64: genBinaryABC(c, n, dest, opcEqFloat) of mEqF64: genBinaryABC(c, n, dest, opcEqFloat)
of mLeF64: genBinaryABC(c, n, dest, opcLeFloat) of mLeF64: genBinaryABC(c, n, dest, opcLeFloat)
@ -796,7 +796,7 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) =
genNarrow(c, n, dest) genNarrow(c, n, dest)
of mUnaryMinusF64: genUnaryABC(c, n, dest, opcUnaryMinusFloat) of mUnaryMinusF64: genUnaryABC(c, n, dest, opcUnaryMinusFloat)
of mUnaryPlusI, mUnaryPlusF64: gen(c, n.sons[1], dest) of mUnaryPlusI, mUnaryPlusF64: gen(c, n.sons[1], dest)
of mBitnotI, mBitnotI64: of mBitnotI:
genUnaryABC(c, n, dest, opcBitnotInt) genUnaryABC(c, n, dest, opcBitnotInt)
genNarrowU(c, n, dest) genNarrowU(c, n, dest)
of mZe8ToI, mZe8ToI64, mZe16ToI, mZe16ToI64, mZe32ToI64, mZeIToI64, of mZe8ToI, mZe8ToI64, mZe16ToI, mZe16ToI64, mZe32ToI64, mZeIToI64,
@ -1013,7 +1013,7 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) =
c.gABC(n, opcCallSite, dest) c.gABC(n, opcCallSite, dest)
of mNGenSym: genBinaryABC(c, n, dest, opcGenSym) of mNGenSym: genBinaryABC(c, n, dest, opcGenSym)
of mMinI, mMaxI, mAbsF64, mMinF64, mMaxF64, mAbsI, of mMinI, mMaxI, mAbsF64, mMinF64, mMaxF64, mAbsI,
mAbsI64, mDotDot: mDotDot:
c.genCall(n, dest) c.genCall(n, dest)
of mExpandToAst: of mExpandToAst:
if n.len != 2: if n.len != 2:

View file

@ -55,7 +55,7 @@ type
wFloatchecks, wNanChecks, wInfChecks, wFloatchecks, wNanChecks, wInfChecks,
wAssertions, wPatterns, wWarnings, wAssertions, wPatterns, wWarnings,
wHints, wOptimization, wRaises, wWrites, wReads, wSize, wEffects, wTags, wHints, wOptimization, wRaises, wWrites, wReads, wSize, wEffects, wTags,
wDeadCodeElim, wSafecode, wNoForward, wDeadCodeElim, wSafecode, wNoForward, wNoRewrite,
wPragma, wPragma,
wCompileTime, wNoInit, wCompileTime, wNoInit,
wPassc, wPassl, wBorrow, wDiscardable, wPassc, wPassl, wBorrow, wDiscardable,
@ -139,7 +139,7 @@ const
"assertions", "patterns", "warnings", "hints", "assertions", "patterns", "warnings", "hints",
"optimization", "raises", "writes", "reads", "size", "effects", "tags", "optimization", "raises", "writes", "reads", "size", "effects", "tags",
"deadcodeelim", "safecode", "noforward", "deadcodeelim", "safecode", "noforward", "norewrite",
"pragma", "pragma",
"compiletime", "noinit", "compiletime", "noinit",
"passc", "passl", "borrow", "discardable", "fieldchecks", "passc", "passl", "borrow", "discardable", "fieldchecks",

View file

@ -11,8 +11,8 @@
* `FloatInvalidOpError <system.html#FloatInvalidOpError>`_ * `FloatInvalidOpError <system.html#FloatInvalidOpError>`_
* `FloatOverflowError <system.html#FloatOverflowError>`_ * `FloatOverflowError <system.html#FloatOverflowError>`_
* `FloatUnderflowError <system.html#FloatUnderflowError>`_ * `FloatUnderflowError <system.html#FloatUnderflowError>`_
* `FieldError <system.html#InvalidFieldError>`_ * `FieldError <system.html#FieldError>`_
* `IndexError <system.html#InvalidIndexError>`_ * `IndexError <system.html#IndexError>`_
* `ObjectAssignmentError <system.html#ObjectAssignmentError>`_ * `ObjectAssignmentError <system.html#ObjectAssignmentError>`_
* `ObjectConversionError <system.html#ObjectConversionError>`_ * `ObjectConversionError <system.html#ObjectConversionError>`_
* `ValueError <system.html#ValueError>`_ * `ValueError <system.html#ValueError>`_

View file

@ -164,11 +164,13 @@ Alternatively, the ``distinct`` type modifier can be applied to the type class
to allow each param matching the type class to bind to a different type. to allow each param matching the type class to bind to a different type.
If a proc param doesn't have a type specified, Nim will use the If a proc param doesn't have a type specified, Nim will use the
``distinct auto`` type class (also known as ``any``): ``distinct auto`` type class (also known as ``any``). Note this behavior is
deprecated for procs; templates, however, support them:
.. code-block:: nim .. code-block:: nim
# allow any combination of param types # allow any combination of param types
proc concat(a, b): string = $a & $b proc concat(a, b): string = $a & $b # deprecated
proc concat(a, b: any): string = $a & $b # preferred
Procs written with the implicitly generic style will often need to refer to the Procs written with the implicitly generic style will often need to refer to the
type parameters of the matched generic type. They can be easily accessed using type parameters of the matched generic type. They can be easily accessed using

View file

@ -101,25 +101,28 @@ Two identifiers are considered equal if the following algorithm returns true:
.. code-block:: nim .. code-block:: nim
proc sameIdentifier(a, b: string): bool = proc sameIdentifier(a, b: string): bool =
a[0] == b[0] and a.replace("_", "").toLower == b.replace("_", "").toLower a[0] == b[0] and
a.replace(re"_|–", "").toLower == b.replace(re"_|–", "").toLower
That means only the first letters are compared in a case sensitive manner. Other That means only the first letters are compared in a case sensitive manner. Other
letters are compared case insensitively and underscores are ignored. letters are compared case insensitively and underscores and en-dash (Unicode
point U+2013) are ignored.
This rather strange way to do identifier comparisons is called This rather unorthodox way to do identifier comparisons is called
`partial case insensitivity`:idx: and has some advantages over the conventional `partial case insensitivity`:idx: and has some advantages over the conventional
case sensitivity: case sensitivity:
It allows programmers to mostly use their own preferred It allows programmers to mostly use their own preferred
spelling style and libraries written by different programmers cannot use spelling style, be it humpStyle, snake_style or dash–style and libraries written
incompatible conventions. A Nim-aware editor or IDE can show the identifiers as by different programmers cannot use incompatible conventions.
preferred. Another advantage is that it frees the programmer from remembering A Nim-aware editor or IDE can show the identifiers as preferred.
Another advantage is that it frees the programmer from remembering
the exact spelling of an identifier. The exception with respect to the first the exact spelling of an identifier. The exception with respect to the first
letter allows common code like ``var foo: Foo`` to be parsed unambiguously. letter allows common code like ``var foo: Foo`` to be parsed unambiguously.
Historically, Nim was a `style-insensitive`:idx: language. This means that it Historically, Nim was a fully `style-insensitive`:idx: language. This meant that
was not case-sensitive and underscores were ignored and there was no distinction it was not case-sensitive and underscores were ignored and there was no even a
between ``foo`` and ``Foo``. distinction between ``foo`` and ``Foo``.
String literals String literals
@ -276,7 +279,7 @@ Numerical constants are of a single type and have the form::
bindigit = '0'..'1' bindigit = '0'..'1'
HEX_LIT = '0' ('x' | 'X' ) hexdigit ( ['_'] hexdigit )* HEX_LIT = '0' ('x' | 'X' ) hexdigit ( ['_'] hexdigit )*
DEC_LIT = digit ( ['_'] digit )* DEC_LIT = digit ( ['_'] digit )*
OCT_LIT = '0o' octdigit ( ['_'] octdigit )* OCT_LIT = '0' ('o' | 'c' | 'C') octdigit ( ['_'] octdigit )*
BIN_LIT = '0' ('b' | 'B' ) bindigit ( ['_'] bindigit )* BIN_LIT = '0' ('b' | 'B' ) bindigit ( ['_'] bindigit )*
INT_LIT = HEX_LIT INT_LIT = HEX_LIT
@ -297,15 +300,17 @@ Numerical constants are of a single type and have the form::
exponent = ('e' | 'E' ) ['+' | '-'] digit ( ['_'] digit )* exponent = ('e' | 'E' ) ['+' | '-'] digit ( ['_'] digit )*
FLOAT_LIT = digit (['_'] digit)* (('.' (['_'] digit)* [exponent]) |exponent) FLOAT_LIT = digit (['_'] digit)* (('.' (['_'] digit)* [exponent]) |exponent)
FLOAT32_LIT = HEX_LIT '\'' ('f'|'F') '32' FLOAT32_SUFFIX = ('f' | 'F') ['32']
| (FLOAT_LIT | DEC_LIT | OCT_LIT | BIN_LIT) ['\''] ('f'|'F') '32' FLOAT32_LIT = HEX_LIT '\'' FLOAT32_SUFFIX
FLOAT64_LIT = HEX_LIT '\'' ('f'|'F') '64' | (FLOAT_LIT | DEC_LIT | OCT_LIT | BIN_LIT) ['\''] FLOAT32_SUFFIX
| (FLOAT_LIT | DEC_LIT | OCT_LIT | BIN_LIT) ['\''] ('f'|'F') '64' FLOAT64_SUFFIX = ( ('f' | 'F') '64' ) | 'd' | 'D'
FLOAT64_LIT = HEX_LIT '\'' FLOAT64_SUFFIX
| (FLOAT_LIT | DEC_LIT | OCT_LIT | BIN_LIT) ['\''] FLOAT64_SUFFIX
As can be seen in the productions, numerical constants can contain underscores As can be seen in the productions, numerical constants can contain underscores
for readability. Integer and floating point literals may be given in decimal (no for readability. Integer and floating point literals may be given in decimal (no
prefix), binary (prefix ``0b``), octal (prefix ``0o``) and hexadecimal prefix), binary (prefix ``0b``), octal (prefix ``0o`` or ``0c``) and hexadecimal
(prefix ``0x``) notation. (prefix ``0x``) notation.
There exists a literal for each numerical type that is There exists a literal for each numerical type that is
@ -331,8 +336,11 @@ The type suffixes are:
``'u16`` uint16 ``'u16`` uint16
``'u32`` uint32 ``'u32`` uint32
``'u64`` uint64 ``'u64`` uint64
``'f`` float32
``'d`` float64
``'f32`` float32 ``'f32`` float32
``'f64`` float64 ``'f64`` float64
``'f128`` float128
================= ========================= ================= =========================
Floating point literals may also be in binary, octal or hexadecimal Floating point literals may also be in binary, octal or hexadecimal
@ -340,12 +348,18 @@ notation:
``0B0_10001110100_0000101001000111101011101111111011000101001101001001'f64`` ``0B0_10001110100_0000101001000111101011101111111011000101001101001001'f64``
is approximately 1.72826e35 according to the IEEE floating point standard. is approximately 1.72826e35 according to the IEEE floating point standard.
Literals are bounds checked so that they fit the datatype. Non base-10
literals are used mainly for flags and bit pattern representations, therefore
bounds checking is done on bit width, not value range. If the literal fits in
the bit width of the datatype, it is accepted.
Hence: 0b10000000'u8 == 0x80'u8 == 128, but, 0b10000000'i8 == 0x80'i8 == -1
instead of causing an overflow error.
Operators Operators
--------- ---------
In Nim one can define his own operators. An operator is any Nim allows user defined operators. An operator is any combination of the
combination of the following characters:: following characters::
= + - * / < > = + - * / < >
@ $ ~ & % | @ $ ~ & % |
@ -355,7 +369,7 @@ These keywords are also operators:
``and or not xor shl shr div mod in notin is isnot of``. ``and or not xor shl shr div mod in notin is isnot of``.
`=`:tok:, `:`:tok:, `::`:tok: are not available as general operators; they `=`:tok:, `:`:tok:, `::`:tok: are not available as general operators; they
are used for other notational purposes. are used for other notational purposes.
``*:`` is as a special case the two tokens `*`:tok: and `:`:tok: ``*:`` is as a special case the two tokens `*`:tok: and `:`:tok:
(to support ``var v*: T``). (to support ``var v*: T``).

View file

@ -146,6 +146,14 @@ modules don't need to import a module's dependencies:
var x: MyObject var x: MyObject
echo($x) echo($x)
Note on paths
-----------
In module related statements, if any part of the module name /
path begins with a number, you may have to quote it in double quotes.
In the following example, it would be seen as a literal number '3.0' of type
'float64' if not quoted, if uncertain - quote it:
.. code-block:: nim
import "gfx/3d/somemodule"
Scope rules Scope rules
----------- -----------

View file

@ -404,7 +404,7 @@ dispatch.
result.a = a result.a = a
result.b = b result.b = b
echo eval(newPlus(newPlus(newLit(1), newLit(2)), newLit(4))) echo eval(newPlus(newPlus(newLit(1), newLit(2)), newLit(4)))
In the example the constructors ``newLit`` and ``newPlus`` are procs In the example the constructors ``newLit`` and ``newPlus`` are procs
because they should use static binding, but ``eval`` is a method because it because they should use static binding, but ``eval`` is a method because it

View file

@ -9,10 +9,10 @@ should be in your ``$PATH`` (most likely the case). Note that some few Linux
distributions do not ship with a GCC compiler preinstalled - then you have to distributions do not ship with a GCC compiler preinstalled - then you have to
install it. install it.
Install Nim by downloading the appropriate ``.zip`` file and extracting it Install Nim by downloading the appropriate ``.zip`` file and extracting it
to a directory of your choice. The Nim Compiler will stay in this to a directory of your choice. The Nim Compiler will stay in this
directory (unless you copy it somewhere else). The compiler does not need directory (unless you copy it somewhere else). The compiler does not need
write access to its directory, so copying the nim folder to ``/opt`` write access to its directory, so copying the nim folder to ``/opt``
works. works.
Then run the following command:: Then run the following command::
@ -26,7 +26,7 @@ manually. An alternative is to create a symbolic link in ``/usr/bin``::
[sudo] ln -s $your_install_dir/bin/nim /usr/bin/nim [sudo] ln -s $your_install_dir/bin/nim /usr/bin/nim
There are also ``install.sh`` and ``deinstall.sh`` scripts for distributing There are also ``install.sh`` and ``deinstall.sh`` scripts for distributing
the files over the UNIX hierarchy. However, updating your Nim installation the files over the UNIX hierarchy. However, updating your Nim installation
is more cumbersome then. is more cumbersome then.
@ -35,8 +35,8 @@ Installation on the Macintosh
----------------------------- -----------------------------
Only MacOS X is supported. Only MacOS X is supported.
Since MacOS X is UNIX based too, it works like the installation on Linux. Since MacOS X is UNIX based too, it works like the installation on Linux.
However, for unknown reasons the symbolic link method does not work on MacOS X. However, for unknown reasons the symbolic link method does not work on MacOS X.
You need to install Apple's developer's tools for the GNU Compiler Collection. You need to install Apple's developer's tools for the GNU Compiler Collection.
@ -64,5 +64,5 @@ However, most testing is done with GCC.
Bootstrapping from Github Bootstrapping from Github
------------------------- -------------------------
Take a look at the readme file on github `here <https://github.com/Araq/Nim#readme>`_ Take a look at the readme file on github `here <https://github.com/nim-lang/Nim#readme>`_
for instructions. for instructions.

View file

@ -333,6 +333,9 @@ proc tests(args: string) =
# we compile the tester with taintMode:on to have a basic # we compile the tester with taintMode:on to have a basic
# taint mode test :-) # taint mode test :-)
exec "nim cc --taintMode:on tests/testament/tester" exec "nim cc --taintMode:on tests/testament/tester"
# Since tests take a long time (on my machine), and we want to defy Murhpys
# law - lets make sure the compiler really is freshly compiled!
exec "nim c --lib:lib -d:release --opt:speed compiler/nim.nim"
let tester = quoteShell(getCurrentDir() / "tests/testament/tester".exe) let tester = quoteShell(getCurrentDir() / "tests/testament/tester".exe)
let success = tryExec tester & " " & (args|"all") let success = tryExec tester & " " & (args|"all")
exec tester & " html" exec tester & " html"

View file

@ -12,44 +12,46 @@
include "system/syslocks" include "system/syslocks"
type type
TLock* = TSysLock ## Nim lock; whether this is re-entrant Lock* = SysLock ## Nim lock; whether this is re-entrant
## or not is unspecified! ## or not is unspecified!
TCond* = TSysCond ## Nim condition variable
proc initLock*(lock: var TLock) {.inline.} = Cond* = SysCond ## Nim condition variable
{.deprecated: [TLock: Lock, TCond: Cond].}
proc initLock*(lock: var Lock) {.inline.} =
## Initializes the given lock. ## Initializes the given lock.
initSysLock(lock) initSysLock(lock)
proc deinitLock*(lock: var TLock) {.inline.} = proc deinitLock*(lock: var Lock) {.inline.} =
## Frees the resources associated with the lock. ## Frees the resources associated with the lock.
deinitSys(lock) deinitSys(lock)
proc tryAcquire*(lock: var TLock): bool = proc tryAcquire*(lock: var Lock): bool =
## Tries to acquire the given lock. Returns `true` on success. ## Tries to acquire the given lock. Returns `true` on success.
result = tryAcquireSys(lock) result = tryAcquireSys(lock)
proc acquire*(lock: var TLock) = proc acquire*(lock: var Lock) =
## Acquires the given lock. ## Acquires the given lock.
acquireSys(lock) acquireSys(lock)
proc release*(lock: var TLock) = proc release*(lock: var Lock) =
## Releases the given lock. ## Releases the given lock.
releaseSys(lock) releaseSys(lock)
proc initCond*(cond: var TCond) {.inline.} = proc initCond*(cond: var Cond) {.inline.} =
## Initializes the given condition variable. ## Initializes the given condition variable.
initSysCond(cond) initSysCond(cond)
proc deinitCond*(cond: var TCond) {.inline.} = proc deinitCond*(cond: var Cond) {.inline.} =
## Frees the resources associated with the lock. ## Frees the resources associated with the lock.
deinitSysCond(cond) deinitSysCond(cond)
proc wait*(cond: var TCond, lock: var TLock) {.inline.} = proc wait*(cond: var Cond, lock: var Lock) {.inline.} =
## waits on the condition variable `cond`. ## waits on the condition variable `cond`.
waitSysCond(cond, lock) waitSysCond(cond, lock)
proc signal*(cond: var TCond) {.inline.} = proc signal*(cond: var Cond) {.inline.} =
## sends a signal to the condition variable `cond`. ## sends a signal to the condition variable `cond`.
signalSysCond(cond) signalSysCond(cond)

View file

@ -9,7 +9,7 @@
## This module implements an interface to Nim's `runtime type information`:idx: ## This module implements an interface to Nim's `runtime type information`:idx:
## (`RTTI`:idx:). ## (`RTTI`:idx:).
## Note that even though ``TAny`` and its operations hide the nasty low level ## Note that even though ``Any`` and its operations hide the nasty low level
## details from its clients, it remains inherently unsafe! ## details from its clients, it remains inherently unsafe!
## ##
## See the `marshal <marshal.html>`_ module for what this module allows you ## See the `marshal <marshal.html>`_ module for what this module allows you
@ -23,7 +23,7 @@ include "system/hti.nim"
{.pop.} {.pop.}
type type
TAnyKind* = enum ## what kind of ``any`` it is AnyKind* = enum ## what kind of ``any`` it is
akNone = 0, ## invalid any akNone = 0, ## invalid any
akBool = 1, ## any represents a ``bool`` akBool = 1, ## any represents a ``bool``
akChar = 2, ## any represents a ``char`` akChar = 2, ## any represents a ``char``
@ -55,9 +55,9 @@ type
akUInt32 = 43, ## any represents an unsigned int32 akUInt32 = 43, ## any represents an unsigned int32
akUInt64 = 44, ## any represents an unsigned int64 akUInt64 = 44, ## any represents an unsigned int64
TAny* = object ## can represent any nim value; NOTE: the wrapped Any* = object ## can represent any nim value; NOTE: the wrapped
## value can be modified with its wrapper! This means ## value can be modified with its wrapper! This means
## that ``TAny`` keeps a non-traced pointer to its ## that ``Any`` keeps a non-traced pointer to its
## wrapped value and **must not** live longer than ## wrapped value and **must not** live longer than
## its wrapped value. ## its wrapped value.
value: pointer value: pointer
@ -69,6 +69,7 @@ type
TGenericSeq {.importc.} = object TGenericSeq {.importc.} = object
len, space, elemSize: int len, space, elemSize: int
PGenSeq = ptr TGenericSeq PGenSeq = ptr TGenericSeq
{.deprecated: [TAny: Any, TAnyKind: AnyKind].}
const const
GenericSeqSize = (3 * sizeof(int)) GenericSeqSize = (3 * sizeof(int))
@ -103,58 +104,58 @@ proc selectBranch(aa: pointer, n: ptr TNimNode): ptr TNimNode =
else: else:
result = n.sons[n.len] result = n.sons[n.len]
proc newAny(value: pointer, rawType: PNimType): TAny = proc newAny(value: pointer, rawType: PNimType): Any =
result.value = value result.value = value
result.rawType = rawType result.rawType = rawType
when declared(system.TVarSlot): when declared(system.VarSlot):
proc toAny*(x: TVarSlot): TAny {.inline.} = proc toAny*(x: VarSlot): Any {.inline.} =
## constructs a ``TAny`` object from a variable slot ``x``. ## constructs a ``Any`` object from a variable slot ``x``.
## This captures `x`'s address, so `x` can be modified with its ## This captures `x`'s address, so `x` can be modified with its
## ``TAny`` wrapper! The client needs to ensure that the wrapper ## ``Any`` wrapper! The client needs to ensure that the wrapper
## **does not** live longer than `x`! ## **does not** live longer than `x`!
## This is provided for easier reflection capabilities of a debugger. ## This is provided for easier reflection capabilities of a debugger.
result.value = x.address result.value = x.address
result.rawType = x.typ result.rawType = x.typ
proc toAny*[T](x: var T): TAny {.inline.} = proc toAny*[T](x: var T): Any {.inline.} =
## constructs a ``TAny`` object from `x`. This captures `x`'s address, so ## constructs a ``Any`` object from `x`. This captures `x`'s address, so
## `x` can be modified with its ``TAny`` wrapper! The client needs to ensure ## `x` can be modified with its ``Any`` wrapper! The client needs to ensure
## that the wrapper **does not** live longer than `x`! ## that the wrapper **does not** live longer than `x`!
result.value = addr(x) result.value = addr(x)
result.rawType = cast[PNimType](getTypeInfo(x)) result.rawType = cast[PNimType](getTypeInfo(x))
proc kind*(x: TAny): TAnyKind {.inline.} = proc kind*(x: Any): AnyKind {.inline.} =
## get the type kind ## get the type kind
result = TAnyKind(ord(x.rawType.kind)) result = AnyKind(ord(x.rawType.kind))
proc size*(x: TAny): int {.inline.} = proc size*(x: Any): int {.inline.} =
## returns the size of `x`'s type. ## returns the size of `x`'s type.
result = x.rawType.size result = x.rawType.size
proc baseTypeKind*(x: TAny): TAnyKind {.inline.} = proc baseTypeKind*(x: Any): AnyKind {.inline.} =
## get the base type's kind; ``akNone`` is returned if `x` has no base type. ## get the base type's kind; ``akNone`` is returned if `x` has no base type.
if x.rawType.base != nil: if x.rawType.base != nil:
result = TAnyKind(ord(x.rawType.base.kind)) result = AnyKind(ord(x.rawType.base.kind))
proc baseTypeSize*(x: TAny): int {.inline.} = proc baseTypeSize*(x: Any): int {.inline.} =
## returns the size of `x`'s basetype. ## returns the size of `x`'s basetype.
if x.rawType.base != nil: if x.rawType.base != nil:
result = x.rawType.base.size result = x.rawType.base.size
proc invokeNew*(x: TAny) = proc invokeNew*(x: Any) =
## performs ``new(x)``. `x` needs to represent a ``ref``. ## performs ``new(x)``. `x` needs to represent a ``ref``.
assert x.rawType.kind == tyRef assert x.rawType.kind == tyRef
var z = newObj(x.rawType, x.rawType.base.size) var z = newObj(x.rawType, x.rawType.base.size)
genericAssign(x.value, addr(z), x.rawType) genericAssign(x.value, addr(z), x.rawType)
proc invokeNewSeq*(x: TAny, len: int) = proc invokeNewSeq*(x: Any, len: int) =
## performs ``newSeq(x, len)``. `x` needs to represent a ``seq``. ## performs ``newSeq(x, len)``. `x` needs to represent a ``seq``.
assert x.rawType.kind == tySequence assert x.rawType.kind == tySequence
var z = newSeq(x.rawType, len) var z = newSeq(x.rawType, len)
genericShallowAssign(x.value, addr(z), x.rawType) genericShallowAssign(x.value, addr(z), x.rawType)
proc extendSeq*(x: TAny) = proc extendSeq*(x: Any) =
## performs ``setLen(x, x.len+1)``. `x` needs to represent a ``seq``. ## performs ``setLen(x, x.len+1)``. `x` needs to represent a ``seq``.
assert x.rawType.kind == tySequence assert x.rawType.kind == tySequence
var y = cast[ptr PGenSeq](x.value)[] var y = cast[ptr PGenSeq](x.value)[]
@ -164,7 +165,7 @@ proc extendSeq*(x: TAny) =
cast[ppointer](x.value)[] = z cast[ppointer](x.value)[] = z
#genericShallowAssign(x.value, addr(z), x.rawType) #genericShallowAssign(x.value, addr(z), x.rawType)
proc setObjectRuntimeType*(x: TAny) = proc setObjectRuntimeType*(x: Any) =
## this needs to be called to set `x`'s runtime object type field. ## this needs to be called to set `x`'s runtime object type field.
assert x.rawType.kind == tyObject assert x.rawType.kind == tyObject
objectInit(x.value, x.rawType) objectInit(x.value, x.rawType)
@ -173,7 +174,7 @@ proc skipRange(x: PNimType): PNimType {.inline.} =
result = x result = x
if result.kind == tyRange: result = result.base if result.kind == tyRange: result = result.base
proc `[]`*(x: TAny, i: int): TAny = proc `[]`*(x: Any, i: int): Any =
## accessor for an any `x` that represents an array or a sequence. ## accessor for an any `x` that represents an array or a sequence.
case x.rawType.kind case x.rawType.kind
of tyArray: of tyArray:
@ -190,7 +191,7 @@ proc `[]`*(x: TAny, i: int): TAny =
return newAny(s +!! (GenericSeqSize+i*bs), x.rawType.base) return newAny(s +!! (GenericSeqSize+i*bs), x.rawType.base)
else: assert false else: assert false
proc `[]=`*(x: TAny, i: int, y: TAny) = proc `[]=`*(x: Any, i: int, y: Any) =
## accessor for an any `x` that represents an array or a sequence. ## accessor for an any `x` that represents an array or a sequence.
case x.rawType.kind case x.rawType.kind
of tyArray: of tyArray:
@ -209,7 +210,7 @@ proc `[]=`*(x: TAny, i: int, y: TAny) =
genericAssign(s +!! (GenericSeqSize+i*bs), y.value, y.rawType) genericAssign(s +!! (GenericSeqSize+i*bs), y.value, y.rawType)
else: assert false else: assert false
proc len*(x: TAny): int = proc len*(x: Any): int =
## len for an any `x` that represents an array or a sequence. ## len for an any `x` that represents an array or a sequence.
case x.rawType.kind case x.rawType.kind
of tyArray: result = x.rawType.size div x.rawType.base.size of tyArray: result = x.rawType.size div x.rawType.base.size
@ -217,20 +218,20 @@ proc len*(x: TAny): int =
else: assert false else: assert false
proc base*(x: TAny): TAny = proc base*(x: Any): Any =
## returns base TAny (useful for inherited object types). ## returns base Any (useful for inherited object types).
result.rawType = x.rawType.base result.rawType = x.rawType.base
result.value = x.value result.value = x.value
proc isNil*(x: TAny): bool = proc isNil*(x: Any): bool =
## `isNil` for an any `x` that represents a sequence, string, cstring, ## `isNil` for an any `x` that represents a sequence, string, cstring,
## proc or some pointer type. ## proc or some pointer type.
assert x.rawType.kind in {tyString, tyCString, tyRef, tyPtr, tyPointer, assert x.rawType.kind in {tyString, tyCString, tyRef, tyPtr, tyPointer,
tySequence, tyProc} tySequence, tyProc}
result = isNil(cast[ppointer](x.value)[]) result = isNil(cast[ppointer](x.value)[])
proc getPointer*(x: TAny): pointer = proc getPointer*(x: Any): pointer =
## retrieve the pointer value out of `x`. ``x`` needs to be of kind ## retrieve the pointer value out of `x`. ``x`` needs to be of kind
## ``akString``, ``akCString``, ``akProc``, ``akRef``, ``akPtr``, ## ``akString``, ``akCString``, ``akProc``, ``akRef``, ``akPtr``,
## ``akPointer``, ``akSequence``. ## ``akPointer``, ``akSequence``.
@ -238,7 +239,7 @@ proc getPointer*(x: TAny): pointer =
tySequence, tyProc} tySequence, tyProc}
result = cast[ppointer](x.value)[] result = cast[ppointer](x.value)[]
proc setPointer*(x: TAny, y: pointer) = proc setPointer*(x: Any, y: pointer) =
## sets the pointer value of `x`. ``x`` needs to be of kind ## sets the pointer value of `x`. ``x`` needs to be of kind
## ``akString``, ``akCString``, ``akProc``, ``akRef``, ``akPtr``, ## ``akString``, ``akCString``, ``akProc``, ``akRef``, ``akPtr``,
## ``akPointer``, ``akSequence``. ## ``akPointer``, ``akSequence``.
@ -247,7 +248,7 @@ proc setPointer*(x: TAny, y: pointer) =
cast[ppointer](x.value)[] = y cast[ppointer](x.value)[] = y
proc fieldsAux(p: pointer, n: ptr TNimNode, proc fieldsAux(p: pointer, n: ptr TNimNode,
ret: var seq[tuple[name: cstring, any: TAny]]) = ret: var seq[tuple[name: cstring, any: Any]]) =
case n.kind case n.kind
of nkNone: assert(false) of nkNone: assert(false)
of nkSlot: of nkSlot:
@ -260,7 +261,7 @@ proc fieldsAux(p: pointer, n: ptr TNimNode,
ret.add((n.name, newAny(p +!! n.offset, n.typ))) ret.add((n.name, newAny(p +!! n.offset, n.typ)))
if m != nil: fieldsAux(p, m, ret) if m != nil: fieldsAux(p, m, ret)
iterator fields*(x: TAny): tuple[name: string, any: TAny] = iterator fields*(x: Any): tuple[name: string, any: Any] =
## iterates over every active field of the any `x` that represents an object ## iterates over every active field of the any `x` that represents an object
## or a tuple. ## or a tuple.
assert x.rawType.kind in {tyTuple, tyObject} assert x.rawType.kind in {tyTuple, tyObject}
@ -269,7 +270,7 @@ iterator fields*(x: TAny): tuple[name: string, any: TAny] =
# XXX BUG: does not work yet, however is questionable anyway # XXX BUG: does not work yet, however is questionable anyway
when false: when false:
if x.rawType.kind == tyObject: t = cast[ptr PNimType](x.value)[] if x.rawType.kind == tyObject: t = cast[ptr PNimType](x.value)[]
var ret: seq[tuple[name: cstring, any: TAny]] = @[] var ret: seq[tuple[name: cstring, any: Any]] = @[]
if t.kind == tyObject: if t.kind == tyObject:
while true: while true:
fieldsAux(p, t.node, ret) fieldsAux(p, t.node, ret)
@ -314,7 +315,7 @@ proc getFieldNode(p: pointer, n: ptr TNimNode,
var m = selectBranch(p, n) var m = selectBranch(p, n)
if m != nil: result = getFieldNode(p, m, name) if m != nil: result = getFieldNode(p, m, name)
proc `[]=`*(x: TAny, fieldName: string, value: TAny) = proc `[]=`*(x: Any, fieldName: string, value: Any) =
## sets a field of `x`; `x` represents an object or a tuple. ## sets a field of `x`; `x` represents an object or a tuple.
var t = x.rawType var t = x.rawType
# XXX BUG: does not work yet, however is questionable anyway # XXX BUG: does not work yet, however is questionable anyway
@ -328,7 +329,7 @@ proc `[]=`*(x: TAny, fieldName: string, value: TAny) =
else: else:
raise newException(ValueError, "invalid field name: " & fieldName) raise newException(ValueError, "invalid field name: " & fieldName)
proc `[]`*(x: TAny, fieldName: string): TAny = proc `[]`*(x: Any, fieldName: string): Any =
## gets a field of `x`; `x` represents an object or a tuple. ## gets a field of `x`; `x` represents an object or a tuple.
var t = x.rawType var t = x.rawType
# XXX BUG: does not work yet, however is questionable anyway # XXX BUG: does not work yet, however is questionable anyway
@ -339,47 +340,49 @@ proc `[]`*(x: TAny, fieldName: string): TAny =
if n != nil: if n != nil:
result.value = x.value +!! n.offset result.value = x.value +!! n.offset
result.rawType = n.typ result.rawType = n.typ
elif x.rawType.kind == tyObject and x.rawType.base != nil:
return `[]`(TAny(value: x.value, rawType: x.rawType.base), fieldName)
else: else:
raise newException(ValueError, "invalid field name: " & fieldName) raise newException(ValueError, "invalid field name: " & fieldName)
proc `[]`*(x: TAny): TAny = proc `[]`*(x: Any): Any =
## dereference operation for the any `x` that represents a ptr or a ref. ## dereference operation for the any `x` that represents a ptr or a ref.
assert x.rawType.kind in {tyRef, tyPtr} assert x.rawType.kind in {tyRef, tyPtr}
result.value = cast[ppointer](x.value)[] result.value = cast[ppointer](x.value)[]
result.rawType = x.rawType.base result.rawType = x.rawType.base
proc `[]=`*(x, y: TAny) = proc `[]=`*(x, y: Any) =
## dereference operation for the any `x` that represents a ptr or a ref. ## dereference operation for the any `x` that represents a ptr or a ref.
assert x.rawType.kind in {tyRef, tyPtr} assert x.rawType.kind in {tyRef, tyPtr}
assert y.rawType == x.rawType.base assert y.rawType == x.rawType.base
genericAssign(cast[ppointer](x.value)[], y.value, y.rawType) genericAssign(cast[ppointer](x.value)[], y.value, y.rawType)
proc getInt*(x: TAny): int = proc getInt*(x: Any): int =
## retrieve the int value out of `x`. `x` needs to represent an int. ## retrieve the int value out of `x`. `x` needs to represent an int.
assert skipRange(x.rawType).kind == tyInt assert skipRange(x.rawType).kind == tyInt
result = cast[ptr int](x.value)[] result = cast[ptr int](x.value)[]
proc getInt8*(x: TAny): int8 = proc getInt8*(x: Any): int8 =
## retrieve the int8 value out of `x`. `x` needs to represent an int8. ## retrieve the int8 value out of `x`. `x` needs to represent an int8.
assert skipRange(x.rawType).kind == tyInt8 assert skipRange(x.rawType).kind == tyInt8
result = cast[ptr int8](x.value)[] result = cast[ptr int8](x.value)[]
proc getInt16*(x: TAny): int16 = proc getInt16*(x: Any): int16 =
## retrieve the int16 value out of `x`. `x` needs to represent an int16. ## retrieve the int16 value out of `x`. `x` needs to represent an int16.
assert skipRange(x.rawType).kind == tyInt16 assert skipRange(x.rawType).kind == tyInt16
result = cast[ptr int16](x.value)[] result = cast[ptr int16](x.value)[]
proc getInt32*(x: TAny): int32 = proc getInt32*(x: Any): int32 =
## retrieve the int32 value out of `x`. `x` needs to represent an int32. ## retrieve the int32 value out of `x`. `x` needs to represent an int32.
assert skipRange(x.rawType).kind == tyInt32 assert skipRange(x.rawType).kind == tyInt32
result = cast[ptr int32](x.value)[] result = cast[ptr int32](x.value)[]
proc getInt64*(x: TAny): int64 = proc getInt64*(x: Any): int64 =
## retrieve the int64 value out of `x`. `x` needs to represent an int64. ## retrieve the int64 value out of `x`. `x` needs to represent an int64.
assert skipRange(x.rawType).kind == tyInt64 assert skipRange(x.rawType).kind == tyInt64
result = cast[ptr int64](x.value)[] result = cast[ptr int64](x.value)[]
proc getBiggestInt*(x: TAny): BiggestInt = proc getBiggestInt*(x: Any): BiggestInt =
## retrieve the integer value out of `x`. `x` needs to represent ## retrieve the integer value out of `x`. `x` needs to represent
## some integer, a bool, a char, an enum or a small enough bit set. ## some integer, a bool, a char, an enum or a small enough bit set.
## The value might be sign-extended to ``BiggestInt``. ## The value might be sign-extended to ``BiggestInt``.
@ -405,7 +408,7 @@ proc getBiggestInt*(x: TAny): BiggestInt =
of tyUInt32: result = BiggestInt(cast[ptr uint32](x.value)[]) of tyUInt32: result = BiggestInt(cast[ptr uint32](x.value)[])
else: assert false else: assert false
proc setBiggestInt*(x: TAny, y: BiggestInt) = proc setBiggestInt*(x: Any, y: BiggestInt) =
## sets the integer value of `x`. `x` needs to represent ## sets the integer value of `x`. `x` needs to represent
## some integer, a bool, a char, an enum or a small enough bit set. ## some integer, a bool, a char, an enum or a small enough bit set.
var t = skipRange(x.rawType) var t = skipRange(x.rawType)
@ -430,36 +433,36 @@ proc setBiggestInt*(x: TAny, y: BiggestInt) =
of tyUInt32: cast[ptr uint32](x.value)[] = uint32(y) of tyUInt32: cast[ptr uint32](x.value)[] = uint32(y)
else: assert false else: assert false
proc getUInt*(x: TAny): uint = proc getUInt*(x: Any): uint =
## retrieve the uint value out of `x`, `x` needs to represent an uint. ## retrieve the uint value out of `x`, `x` needs to represent an uint.
assert skipRange(x.rawType).kind == tyUInt assert skipRange(x.rawType).kind == tyUInt
result = cast[ptr uint](x.value)[] result = cast[ptr uint](x.value)[]
proc getUInt8*(x: TAny): uint8 = proc getUInt8*(x: Any): uint8 =
## retrieve the uint8 value out of `x`, `x` needs to represent an ## retrieve the uint8 value out of `x`, `x` needs to represent an
## uint8. ## uint8.
assert skipRange(x.rawType).kind == tyUInt8 assert skipRange(x.rawType).kind == tyUInt8
result = cast[ptr uint8](x.value)[] result = cast[ptr uint8](x.value)[]
proc getUInt16*(x: TAny): uint16 = proc getUInt16*(x: Any): uint16 =
## retrieve the uint16 value out of `x`, `x` needs to represent an ## retrieve the uint16 value out of `x`, `x` needs to represent an
## uint16. ## uint16.
assert skipRange(x.rawType).kind == tyUInt16 assert skipRange(x.rawType).kind == tyUInt16
result = cast[ptr uint16](x.value)[] result = cast[ptr uint16](x.value)[]
proc getUInt32*(x: TAny): uint32 = proc getUInt32*(x: Any): uint32 =
## retrieve the uint32 value out of `x`, `x` needs to represent an ## retrieve the uint32 value out of `x`, `x` needs to represent an
## uint32. ## uint32.
assert skipRange(x.rawType).kind == tyUInt32 assert skipRange(x.rawType).kind == tyUInt32
result = cast[ptr uint32](x.value)[] result = cast[ptr uint32](x.value)[]
proc getUInt64*(x: TAny): uint64 = proc getUInt64*(x: Any): uint64 =
## retrieve the uint64 value out of `x`, `x` needs to represent an ## retrieve the uint64 value out of `x`, `x` needs to represent an
## uint64. ## uint64.
assert skipRange(x.rawType).kind == tyUInt64 assert skipRange(x.rawType).kind == tyUInt64
result = cast[ptr uint64](x.value)[] result = cast[ptr uint64](x.value)[]
proc getBiggestUint*(x: TAny): uint64 = proc getBiggestUint*(x: Any): uint64 =
## retrieve the unsigned integer value out of `x`. `x` needs to ## retrieve the unsigned integer value out of `x`. `x` needs to
## represent an unsigned integer. ## represent an unsigned integer.
var t = skipRange(x.rawType) var t = skipRange(x.rawType)
@ -471,7 +474,7 @@ proc getBiggestUint*(x: TAny): uint64 =
of tyUInt64: result = uint64(cast[ptr uint64](x.value)[]) of tyUInt64: result = uint64(cast[ptr uint64](x.value)[])
else: assert false else: assert false
proc setBiggestUint*(x: TAny; y: uint64) = proc setBiggestUint*(x: Any; y: uint64) =
## sets the unsigned integer value of `c`. `c` needs to represent an ## sets the unsigned integer value of `c`. `c` needs to represent an
## unsigned integer. ## unsigned integer.
var t = skipRange(x.rawType) var t = skipRange(x.rawType)
@ -483,25 +486,25 @@ proc setBiggestUint*(x: TAny; y: uint64) =
of tyUInt64: cast[ptr uint64](x.value)[] = uint64(y) of tyUInt64: cast[ptr uint64](x.value)[] = uint64(y)
else: assert false else: assert false
proc getChar*(x: TAny): char = proc getChar*(x: Any): char =
## retrieve the char value out of `x`. `x` needs to represent a char. ## retrieve the char value out of `x`. `x` needs to represent a char.
var t = skipRange(x.rawType) var t = skipRange(x.rawType)
assert t.kind == tyChar assert t.kind == tyChar
result = cast[ptr char](x.value)[] result = cast[ptr char](x.value)[]
proc getBool*(x: TAny): bool = proc getBool*(x: Any): bool =
## retrieve the bool value out of `x`. `x` needs to represent a bool. ## retrieve the bool value out of `x`. `x` needs to represent a bool.
var t = skipRange(x.rawType) var t = skipRange(x.rawType)
assert t.kind == tyBool assert t.kind == tyBool
result = cast[ptr bool](x.value)[] result = cast[ptr bool](x.value)[]
proc skipRange*(x: TAny): TAny = proc skipRange*(x: Any): Any =
## skips the range information of `x`. ## skips the range information of `x`.
assert x.rawType.kind == tyRange assert x.rawType.kind == tyRange
result.rawType = x.rawType.base result.rawType = x.rawType.base
result.value = x.value result.value = x.value
proc getEnumOrdinal*(x: TAny, name: string): int = proc getEnumOrdinal*(x: Any, name: string): int =
## gets the enum field ordinal from `name`. `x` needs to represent an enum ## gets the enum field ordinal from `name`. `x` needs to represent an enum
## but is only used to access the type information. In case of an error ## but is only used to access the type information. In case of an error
## ``low(int)`` is returned. ## ``low(int)`` is returned.
@ -517,7 +520,7 @@ proc getEnumOrdinal*(x: TAny, name: string): int =
return s[i].offset return s[i].offset
result = low(int) result = low(int)
proc getEnumField*(x: TAny, ordinalValue: int): string = proc getEnumField*(x: Any, ordinalValue: int): string =
## gets the enum field name as a string. `x` needs to represent an enum ## gets the enum field name as a string. `x` needs to represent an enum
## but is only used to access the type information. The field name of ## but is only used to access the type information. The field name of
## `ordinalValue` is returned. ## `ordinalValue` is returned.
@ -535,26 +538,26 @@ proc getEnumField*(x: TAny, ordinalValue: int): string =
if s[i].offset == e: return $s[i].name if s[i].offset == e: return $s[i].name
result = $e result = $e
proc getEnumField*(x: TAny): string = proc getEnumField*(x: Any): string =
## gets the enum field name as a string. `x` needs to represent an enum. ## gets the enum field name as a string. `x` needs to represent an enum.
result = getEnumField(x, getBiggestInt(x).int) result = getEnumField(x, getBiggestInt(x).int)
proc getFloat*(x: TAny): float = proc getFloat*(x: Any): float =
## retrieve the float value out of `x`. `x` needs to represent an float. ## retrieve the float value out of `x`. `x` needs to represent an float.
assert skipRange(x.rawType).kind == tyFloat assert skipRange(x.rawType).kind == tyFloat
result = cast[ptr float](x.value)[] result = cast[ptr float](x.value)[]
proc getFloat32*(x: TAny): float32 = proc getFloat32*(x: Any): float32 =
## retrieve the float32 value out of `x`. `x` needs to represent an float32. ## retrieve the float32 value out of `x`. `x` needs to represent an float32.
assert skipRange(x.rawType).kind == tyFloat32 assert skipRange(x.rawType).kind == tyFloat32
result = cast[ptr float32](x.value)[] result = cast[ptr float32](x.value)[]
proc getFloat64*(x: TAny): float64 = proc getFloat64*(x: Any): float64 =
## retrieve the float64 value out of `x`. `x` needs to represent an float64. ## retrieve the float64 value out of `x`. `x` needs to represent an float64.
assert skipRange(x.rawType).kind == tyFloat64 assert skipRange(x.rawType).kind == tyFloat64
result = cast[ptr float64](x.value)[] result = cast[ptr float64](x.value)[]
proc getBiggestFloat*(x: TAny): BiggestFloat = proc getBiggestFloat*(x: Any): BiggestFloat =
## retrieve the float value out of `x`. `x` needs to represent ## retrieve the float value out of `x`. `x` needs to represent
## some float. The value is extended to ``BiggestFloat``. ## some float. The value is extended to ``BiggestFloat``.
case skipRange(x.rawType).kind case skipRange(x.rawType).kind
@ -563,7 +566,7 @@ proc getBiggestFloat*(x: TAny): BiggestFloat =
of tyFloat64: result = BiggestFloat(cast[ptr float64](x.value)[]) of tyFloat64: result = BiggestFloat(cast[ptr float64](x.value)[])
else: assert false else: assert false
proc setBiggestFloat*(x: TAny, y: BiggestFloat) = proc setBiggestFloat*(x: Any, y: BiggestFloat) =
## sets the float value of `x`. `x` needs to represent ## sets the float value of `x`. `x` needs to represent
## some float. ## some float.
case skipRange(x.rawType).kind case skipRange(x.rawType).kind
@ -572,29 +575,29 @@ proc setBiggestFloat*(x: TAny, y: BiggestFloat) =
of tyFloat64: cast[ptr float64](x.value)[] = y of tyFloat64: cast[ptr float64](x.value)[] = y
else: assert false else: assert false
proc getString*(x: TAny): string = proc getString*(x: Any): string =
## retrieve the string value out of `x`. `x` needs to represent a string. ## retrieve the string value out of `x`. `x` needs to represent a string.
assert x.rawType.kind == tyString assert x.rawType.kind == tyString
if not isNil(cast[ptr pointer](x.value)[]): if not isNil(cast[ptr pointer](x.value)[]):
result = cast[ptr string](x.value)[] result = cast[ptr string](x.value)[]
proc setString*(x: TAny, y: string) = proc setString*(x: Any, y: string) =
## sets the string value of `x`. `x` needs to represent a string. ## sets the string value of `x`. `x` needs to represent a string.
assert x.rawType.kind == tyString assert x.rawType.kind == tyString
cast[ptr string](x.value)[] = y cast[ptr string](x.value)[] = y
proc getCString*(x: TAny): cstring = proc getCString*(x: Any): cstring =
## retrieve the cstring value out of `x`. `x` needs to represent a cstring. ## retrieve the cstring value out of `x`. `x` needs to represent a cstring.
assert x.rawType.kind == tyCString assert x.rawType.kind == tyCString
result = cast[ptr cstring](x.value)[] result = cast[ptr cstring](x.value)[]
proc assign*(x, y: TAny) = proc assign*(x, y: Any) =
## copies the value of `y` to `x`. The assignment operator for ``TAny`` ## copies the value of `y` to `x`. The assignment operator for ``Any``
## does NOT do this; it performs a shallow copy instead! ## does NOT do this; it performs a shallow copy instead!
assert y.rawType == x.rawType assert y.rawType == x.rawType
genericAssign(x.value, y.value, y.rawType) genericAssign(x.value, y.value, y.rawType)
iterator elements*(x: TAny): int = iterator elements*(x: Any): int =
## iterates over every element of `x` that represents a Nim bitset. ## iterates over every element of `x` that represents a Nim bitset.
assert x.rawType.kind == tySet assert x.rawType.kind == tySet
var typ = x.rawType var typ = x.rawType
@ -616,7 +619,7 @@ iterator elements*(x: TAny): int =
if (u and (1'i64 shl int64(i))) != 0'i64: if (u and (1'i64 shl int64(i))) != 0'i64:
yield i+typ.node.len yield i+typ.node.len
proc inclSetElement*(x: TAny, elem: int) = proc inclSetElement*(x: Any, elem: int) =
## includes an element `elem` in `x`. `x` needs to represent a Nim bitset. ## includes an element `elem` in `x`. `x` needs to represent a Nim bitset.
assert x.rawType.kind == tySet assert x.rawType.kind == tySet
var typ = x.rawType var typ = x.rawType

View file

@ -13,27 +13,31 @@
import strutils, mysql import strutils, mysql
type type
TDbConn* = PMySQL ## encapsulates a database connection DbConn* = PMySQL ## encapsulates a database connection
TRow* = seq[string] ## a row of a dataset. NULL database values will be Row* = seq[string] ## a row of a dataset. NULL database values will be
## transformed always to the empty string. ## transformed always to the empty string.
InstantRow* = tuple[row: cstringArray, len: int] ## a handle that can be
## used to get a row's
## column text on demand
EDb* = object of IOError ## exception that is raised if a database error occurs EDb* = object of IOError ## exception that is raised if a database error occurs
TSqlQuery* = distinct string ## an SQL query string SqlQuery* = distinct string ## an SQL query string
FDb* = object of IOEffect ## effect that denotes a database operation FDb* = object of IOEffect ## effect that denotes a database operation
FReadDb* = object of FDb ## effect that denotes a read operation FReadDb* = object of FDb ## effect that denotes a read operation
FWriteDb* = object of FDb ## effect that denotes a write operation FWriteDb* = object of FDb ## effect that denotes a write operation
{.deprecated: [TRow: Row, TSqlQuery: SqlQuery, TDbConn: DbConn].}
proc sql*(query: string): TSqlQuery {.noSideEffect, inline.} = proc sql*(query: string): SqlQuery {.noSideEffect, inline.} =
## constructs a TSqlQuery from the string `query`. This is supposed to be ## constructs a SqlQuery from the string `query`. This is supposed to be
## used as a raw-string-literal modifier: ## used as a raw-string-literal modifier:
## ``sql"update user set counter = counter + 1"`` ## ``sql"update user set counter = counter + 1"``
## ##
## If assertions are turned off, it does nothing. If assertions are turned ## If assertions are turned off, it does nothing. If assertions are turned
## on, later versions will check the string for valid syntax. ## on, later versions will check the string for valid syntax.
result = TSqlQuery(query) result = SqlQuery(query)
proc dbError(db: TDbConn) {.noreturn.} = proc dbError(db: DbConn) {.noreturn.} =
## raises an EDb exception. ## raises an EDb exception.
var e: ref EDb var e: ref EDb
new(e) new(e)
@ -48,7 +52,7 @@ proc dbError*(msg: string) {.noreturn.} =
raise e raise e
when false: when false:
proc dbQueryOpt*(db: TDbConn, query: string, args: varargs[string, `$`]) = proc dbQueryOpt*(db: DbConn, query: string, args: varargs[string, `$`]) =
var stmt = mysql_stmt_init(db) var stmt = mysql_stmt_init(db)
if stmt == nil: dbError(db) if stmt == nil: dbError(db)
if mysql_stmt_prepare(stmt, query, len(query)) != 0: if mysql_stmt_prepare(stmt, query, len(query)) != 0:
@ -65,7 +69,7 @@ proc dbQuote*(s: string): string =
else: add(result, c) else: add(result, c)
add(result, '\'') add(result, '\'')
proc dbFormat(formatstr: TSqlQuery, args: varargs[string]): string = proc dbFormat(formatstr: SqlQuery, args: varargs[string]): string =
result = "" result = ""
var a = 0 var a = 0
for c in items(string(formatstr)): for c in items(string(formatstr)):
@ -78,23 +82,23 @@ proc dbFormat(formatstr: TSqlQuery, args: varargs[string]): string =
else: else:
add(result, c) add(result, c)
proc tryExec*(db: TDbConn, query: TSqlQuery, args: varargs[string, `$`]): bool {. proc tryExec*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): bool {.
tags: [FReadDB, FWriteDb].} = tags: [FReadDB, FWriteDb].} =
## tries to execute the query and returns true if successful, false otherwise. ## tries to execute the query and returns true if successful, false otherwise.
var q = dbFormat(query, args) var q = dbFormat(query, args)
return mysql.realQuery(db, q, q.len) == 0'i32 return mysql.realQuery(db, q, q.len) == 0'i32
proc rawExec(db: TDbConn, query: TSqlQuery, args: varargs[string, `$`]) = proc rawExec(db: DbConn, query: SqlQuery, args: varargs[string, `$`]) =
var q = dbFormat(query, args) var q = dbFormat(query, args)
if mysql.realQuery(db, q, q.len) != 0'i32: dbError(db) if mysql.realQuery(db, q, q.len) != 0'i32: dbError(db)
proc exec*(db: TDbConn, query: TSqlQuery, args: varargs[string, `$`]) {. proc exec*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]) {.
tags: [FReadDB, FWriteDb].} = tags: [FReadDB, FWriteDb].} =
## executes the query and raises EDB if not successful. ## executes the query and raises EDB if not successful.
var q = dbFormat(query, args) var q = dbFormat(query, args)
if mysql.realQuery(db, q, q.len) != 0'i32: dbError(db) if mysql.realQuery(db, q, q.len) != 0'i32: dbError(db)
proc newRow(L: int): TRow = proc newRow(L: int): Row =
newSeq(result, L) newSeq(result, L)
for i in 0..L-1: result[i] = "" for i in 0..L-1: result[i] = ""
@ -103,8 +107,8 @@ proc properFreeResult(sqlres: mysql.PRES, row: cstringArray) =
while mysql.fetchRow(sqlres) != nil: discard while mysql.fetchRow(sqlres) != nil: discard
mysql.freeResult(sqlres) mysql.freeResult(sqlres)
iterator fastRows*(db: TDbConn, query: TSqlQuery, iterator fastRows*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): TRow {.tags: [FReadDB].} = args: varargs[string, `$`]): Row {.tags: [FReadDB].} =
## executes the query and iterates over the result dataset. This is very ## executes the query and iterates over the result dataset. This is very
## fast, but potenially dangerous: If the for-loop-body executes another ## fast, but potenially dangerous: If the for-loop-body executes another
## query, the results can be undefined. For MySQL this is the case!. ## query, the results can be undefined. For MySQL this is the case!.
@ -126,10 +130,34 @@ iterator fastRows*(db: TDbConn, query: TSqlQuery,
yield result yield result
properFreeResult(sqlres, row) properFreeResult(sqlres, row)
proc getRow*(db: TDbConn, query: TSqlQuery, iterator instantRows*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): TRow {.tags: [FReadDB].} = args: varargs[string, `$`]): InstantRow
{.tags: [FReadDb].} =
## same as fastRows but returns a handle that can be used to get column text
## on demand using []. Returned handle is valid only within interator body.
rawExec(db, query, args)
var sqlres = mysql.useResult(db)
if sqlres != nil:
let L = int(mysql.numFields(sqlres))
var row: cstringArray
while true:
row = mysql.fetchRow(sqlres)
if row == nil: break
yield (row: row, len: L)
properFreeResult(sqlres, row)
proc `[]`*(row: InstantRow, col: int): string {.inline.} =
## returns text for given column of the row
$row.row[col]
proc len*(row: InstantRow): int {.inline.} =
## returns number of columns in the row
row.len
proc getRow*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): Row {.tags: [FReadDB].} =
## retrieves a single row. If the query doesn't return any rows, this proc ## retrieves a single row. If the query doesn't return any rows, this proc
## will return a TRow with empty strings for each column. ## will return a Row with empty strings for each column.
rawExec(db, query, args) rawExec(db, query, args)
var sqlres = mysql.useResult(db) var sqlres = mysql.useResult(db)
if sqlres != nil: if sqlres != nil:
@ -145,8 +173,8 @@ proc getRow*(db: TDbConn, query: TSqlQuery,
add(result[i], row[i]) add(result[i], row[i])
properFreeResult(sqlres, row) properFreeResult(sqlres, row)
proc getAllRows*(db: TDbConn, query: TSqlQuery, proc getAllRows*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): seq[TRow] {.tags: [FReadDB].} = args: varargs[string, `$`]): seq[Row] {.tags: [FReadDB].} =
## executes the query and returns the whole result dataset. ## executes the query and returns the whole result dataset.
result = @[] result = @[]
rawExec(db, query, args) rawExec(db, query, args)
@ -168,12 +196,12 @@ proc getAllRows*(db: TDbConn, query: TSqlQuery,
inc(j) inc(j)
mysql.freeResult(sqlres) mysql.freeResult(sqlres)
iterator rows*(db: TDbConn, query: TSqlQuery, iterator rows*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): TRow {.tags: [FReadDB].} = args: varargs[string, `$`]): Row {.tags: [FReadDB].} =
## same as `fastRows`, but slower and safe. ## same as `fastRows`, but slower and safe.
for r in items(getAllRows(db, query, args)): yield r for r in items(getAllRows(db, query, args)): yield r
proc getValue*(db: TDbConn, query: TSqlQuery, proc getValue*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): string {.tags: [FReadDB].} = args: varargs[string, `$`]): string {.tags: [FReadDB].} =
## executes the query and returns the first column of the first row of the ## executes the query and returns the first column of the first row of the
## result dataset. Returns "" if the dataset contains no rows or the database ## result dataset. Returns "" if the dataset contains no rows or the database
@ -183,7 +211,7 @@ proc getValue*(db: TDbConn, query: TSqlQuery,
result = row[0] result = row[0]
break break
proc tryInsertId*(db: TDbConn, query: TSqlQuery, proc tryInsertId*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): int64 {.tags: [FWriteDb].} = args: varargs[string, `$`]): int64 {.tags: [FWriteDb].} =
## 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.
@ -193,14 +221,14 @@ proc tryInsertId*(db: TDbConn, query: TSqlQuery,
else: else:
result = mysql.insertId(db) result = mysql.insertId(db)
proc insertId*(db: TDbConn, query: TSqlQuery, proc insertId*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): int64 {.tags: [FWriteDb].} = args: varargs[string, `$`]): int64 {.tags: [FWriteDb].} =
## executes the query (typically "INSERT") and returns the ## executes the query (typically "INSERT") and returns the
## generated ID for the row. ## generated ID for the row.
result = tryInsertID(db, query, args) result = tryInsertID(db, query, args)
if result < 0: dbError(db) if result < 0: dbError(db)
proc execAffectedRows*(db: TDbConn, query: TSqlQuery, proc execAffectedRows*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): int64 {. args: varargs[string, `$`]): int64 {.
tags: [FReadDB, FWriteDb].} = tags: [FReadDB, FWriteDb].} =
## runs the query (typically "UPDATE") and returns the ## runs the query (typically "UPDATE") and returns the
@ -208,11 +236,11 @@ proc execAffectedRows*(db: TDbConn, query: TSqlQuery,
rawExec(db, query, args) rawExec(db, query, args)
result = mysql.affectedRows(db) result = mysql.affectedRows(db)
proc close*(db: TDbConn) {.tags: [FDb].} = proc close*(db: DbConn) {.tags: [FDb].} =
## closes the database connection. ## closes the database connection.
if db != nil: mysql.close(db) if db != nil: mysql.close(db)
proc open*(connection, user, password, database: string): TDbConn {. proc open*(connection, user, password, database: string): DbConn {.
tags: [FDb].} = tags: [FDb].} =
## opens a database connection. Raises `EDb` if the connection could not ## opens a database connection. Raises `EDb` if the connection could not
## be established. ## be established.
@ -230,8 +258,8 @@ proc open*(connection, user, password, database: string): TDbConn {.
db_mysql.close(result) db_mysql.close(result)
dbError(errmsg) dbError(errmsg)
proc setEncoding*(connection: TDbConn, encoding: string): bool {. proc setEncoding*(connection: DbConn, encoding: string): bool {.
tags: [FDb].} = tags: [FDb].} =
## sets the encoding of a database connection, returns true for ## sets the encoding of a database connection, returns true for
## success, false for failure. ## success, false for failure.
result = mysql.set_character_set(connection, encoding) == 0 result = mysql.set_character_set(connection, encoding) == 0

View file

@ -13,28 +13,33 @@
import strutils, postgres import strutils, postgres
type type
TDbConn* = PPGconn ## encapsulates a database connection DbConn* = PPGconn ## encapsulates a database connection
TRow* = seq[string] ## a row of a dataset. NULL database values will be Row* = seq[string] ## a row of a dataset. NULL database values will be
## transformed always to the empty string. ## transformed always to the empty string.
InstantRow* = tuple[res: PPGresult, line: int32] ## a handle that can be
## used to get a row's
## column text on demand
EDb* = object of IOError ## exception that is raised if a database error occurs EDb* = object of IOError ## exception that is raised if a database error occurs
TSqlQuery* = distinct string ## an SQL query string SqlQuery* = distinct string ## an SQL query string
TSqlPrepared* = distinct string ## a identifier for the prepared queries SqlPrepared* = distinct string ## a identifier for the prepared queries
FDb* = object of IOEffect ## effect that denotes a database operation FDb* = object of IOEffect ## effect that denotes a database operation
FReadDb* = object of FDb ## effect that denotes a read operation FReadDb* = object of FDb ## effect that denotes a read operation
FWriteDb* = object of FDb ## effect that denotes a write operation FWriteDb* = object of FDb ## effect that denotes a write operation
{.deprecated: [TRow: Row, TSqlQuery: SqlQuery, TDbConn: DbConn,
TSqlPrepared: SqlPrepared].}
proc sql*(query: string): TSqlQuery {.noSideEffect, inline.} = proc sql*(query: string): SqlQuery {.noSideEffect, inline.} =
## constructs a TSqlQuery from the string `query`. This is supposed to be ## constructs a SqlQuery from the string `query`. This is supposed to be
## used as a raw-string-literal modifier: ## used as a raw-string-literal modifier:
## ``sql"update user set counter = counter + 1"`` ## ``sql"update user set counter = counter + 1"``
## ##
## If assertions are turned off, it does nothing. If assertions are turned ## If assertions are turned off, it does nothing. If assertions are turned
## on, later versions will check the string for valid syntax. ## on, later versions will check the string for valid syntax.
result = TSqlQuery(query) result = SqlQuery(query)
proc dbError*(db: TDbConn) {.noreturn.} = proc dbError*(db: DbConn) {.noreturn.} =
## raises an EDb exception. ## raises an EDb exception.
var e: ref EDb var e: ref EDb
new(e) new(e)
@ -56,7 +61,7 @@ proc dbQuote*(s: string): string =
else: add(result, c) else: add(result, c)
add(result, '\'') add(result, '\'')
proc dbFormat(formatstr: TSqlQuery, args: varargs[string]): string = proc dbFormat(formatstr: SqlQuery, args: varargs[string]): string =
result = "" result = ""
var a = 0 var a = 0
for c in items(string(formatstr)): for c in items(string(formatstr)):
@ -69,7 +74,7 @@ proc dbFormat(formatstr: TSqlQuery, args: varargs[string]): string =
else: else:
add(result, c) add(result, c)
proc tryExec*(db: TDbConn, query: TSqlQuery, proc tryExec*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): bool {.tags: [FReadDB, FWriteDb].} = args: varargs[string, `$`]): bool {.tags: [FReadDB, FWriteDb].} =
## tries to execute the query and returns true if successful, false otherwise. ## tries to execute the query and returns true if successful, false otherwise.
var arr = allocCStringArray(args) var arr = allocCStringArray(args)
@ -79,7 +84,7 @@ proc tryExec*(db: TDbConn, query: TSqlQuery,
result = pqresultStatus(res) == PGRES_COMMAND_OK result = pqresultStatus(res) == PGRES_COMMAND_OK
pqclear(res) pqclear(res)
proc exec*(db: TDbConn, query: TSqlQuery, args: varargs[string, `$`]) {. proc exec*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]) {.
tags: [FReadDB, FWriteDb].} = tags: [FReadDB, FWriteDb].} =
## executes the query and raises EDB if not successful. ## executes the query and raises EDB if not successful.
var arr = allocCStringArray(args) var arr = allocCStringArray(args)
@ -89,7 +94,7 @@ proc exec*(db: TDbConn, query: TSqlQuery, args: varargs[string, `$`]) {.
if pqresultStatus(res) != PGRES_COMMAND_OK: dbError(db) if pqresultStatus(res) != PGRES_COMMAND_OK: dbError(db)
pqclear(res) pqclear(res)
proc exec*(db: TDbConn, stmtName: TSqlPrepared, proc exec*(db: DbConn, stmtName: SqlPrepared,
args: varargs[string]) {.tags: [FReadDB, FWriteDb].} = args: varargs[string]) {.tags: [FReadDB, FWriteDb].} =
var arr = allocCStringArray(args) var arr = allocCStringArray(args)
var res = pqexecPrepared(db, stmtName.string, int32(args.len), arr, var res = pqexecPrepared(db, stmtName.string, int32(args.len), arr,
@ -98,11 +103,11 @@ proc exec*(db: TDbConn, stmtName: TSqlPrepared,
if pqResultStatus(res) != PGRES_COMMAND_OK: dbError(db) if pqResultStatus(res) != PGRES_COMMAND_OK: dbError(db)
pqclear(res) pqclear(res)
proc newRow(L: int): TRow = proc newRow(L: int): Row =
newSeq(result, L) newSeq(result, L)
for i in 0..L-1: result[i] = "" for i in 0..L-1: result[i] = ""
proc setupQuery(db: TDbConn, query: TSqlQuery, proc setupQuery(db: DbConn, query: SqlQuery,
args: varargs[string]): PPGresult = args: varargs[string]): PPGresult =
var arr = allocCStringArray(args) var arr = allocCStringArray(args)
result = pqexecParams(db, query.string, int32(args.len), nil, arr, result = pqexecParams(db, query.string, int32(args.len), nil, arr,
@ -110,7 +115,7 @@ proc setupQuery(db: TDbConn, query: TSqlQuery,
deallocCStringArray(arr) deallocCStringArray(arr)
if pqResultStatus(result) != PGRES_TUPLES_OK: dbError(db) if pqResultStatus(result) != PGRES_TUPLES_OK: dbError(db)
proc setupQuery(db: TDbConn, stmtName: TSqlPrepared, proc setupQuery(db: DbConn, stmtName: SqlPrepared,
args: varargs[string]): PPGresult = args: varargs[string]): PPGresult =
var arr = allocCStringArray(args) var arr = allocCStringArray(args)
result = pqexecPrepared(db, stmtName.string, int32(args.len), arr, result = pqexecPrepared(db, stmtName.string, int32(args.len), arr,
@ -118,13 +123,13 @@ proc setupQuery(db: TDbConn, stmtName: TSqlPrepared,
deallocCStringArray(arr) deallocCStringArray(arr)
if pqResultStatus(result) != PGRES_TUPLES_OK: dbError(db) if pqResultStatus(result) != PGRES_TUPLES_OK: dbError(db)
proc prepare*(db: TDbConn; stmtName: string, query: TSqlQuery; proc prepare*(db: DbConn; stmtName: string, query: SqlQuery;
nParams: int): TSqlPrepared = nParams: int): SqlPrepared =
var res = pqprepare(db, stmtName, query.string, int32(nParams), nil) var res = pqprepare(db, stmtName, query.string, int32(nParams), nil)
if pqResultStatus(res) != PGRES_COMMAND_OK: dbError(db) if pqResultStatus(res) != PGRES_COMMAND_OK: dbError(db)
return TSqlPrepared(stmtName) return SqlPrepared(stmtName)
proc setRow(res: PPGresult, r: var TRow, line, cols: int32) = proc setRow(res: PPGresult, r: var Row, line, cols: int32) =
for col in 0..cols-1: for col in 0..cols-1:
setLen(r[col], 0) setLen(r[col], 0)
let x = pqgetvalue(res, line, col) let x = pqgetvalue(res, line, col)
@ -133,8 +138,8 @@ proc setRow(res: PPGresult, r: var TRow, line, cols: int32) =
else: else:
add(r[col], x) add(r[col], x)
iterator fastRows*(db: TDbConn, query: TSqlQuery, iterator fastRows*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): TRow {.tags: [FReadDB].} = args: varargs[string, `$`]): Row {.tags: [FReadDB].} =
## executes the query and iterates over the result dataset. This is very ## executes the query and iterates over the result dataset. This is very
## fast, but potenially dangerous: If the for-loop-body executes another ## fast, but potenially dangerous: If the for-loop-body executes another
## query, the results can be undefined. For Postgres it is safe though. ## query, the results can be undefined. For Postgres it is safe though.
@ -146,8 +151,8 @@ iterator fastRows*(db: TDbConn, query: TSqlQuery,
yield result yield result
pqclear(res) pqclear(res)
iterator fastRows*(db: TDbConn, stmtName: TSqlPrepared, iterator fastRows*(db: DbConn, stmtName: SqlPrepared,
args: varargs[string, `$`]): TRow {.tags: [FReadDB].} = args: varargs[string, `$`]): Row {.tags: [FReadDB].} =
## executes the prepared query and iterates over the result dataset. ## executes the prepared query and iterates over the result dataset.
var res = setupQuery(db, stmtName, args) var res = setupQuery(db, stmtName, args)
var L = pqNfields(res) var L = pqNfields(res)
@ -157,44 +162,62 @@ iterator fastRows*(db: TDbConn, stmtName: TSqlPrepared,
yield result yield result
pqClear(res) pqClear(res)
proc getRow*(db: TDbConn, query: TSqlQuery, iterator instantRows*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): TRow {.tags: [FReadDB].} = args: varargs[string, `$`]): InstantRow
{.tags: [FReadDb].} =
## same as fastRows but returns a handle that can be used to get column text
## on demand using []. Returned handle is valid only within interator body.
var res = setupQuery(db, query, args)
for i in 0..pqNtuples(res)-1:
yield (res: res, line: i)
pqClear(res)
proc `[]`*(row: InstantRow, col: int32): string {.inline.} =
## returns text for given column of the row
$pqgetvalue(row.res, row.line, col)
proc len*(row: InstantRow): int32 {.inline.} =
## returns number of columns in the row
pqNfields(row.res)
proc getRow*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): Row {.tags: [FReadDB].} =
## retrieves a single row. If the query doesn't return any rows, this proc ## retrieves a single row. If the query doesn't return any rows, this proc
## will return a TRow with empty strings for each column. ## will return a Row with empty strings for each column.
var res = setupQuery(db, query, args) var res = setupQuery(db, query, args)
var L = pqnfields(res) var L = pqnfields(res)
result = newRow(L) result = newRow(L)
setRow(res, result, 0, L) setRow(res, result, 0, L)
pqclear(res) pqclear(res)
proc getRow*(db: TDbConn, stmtName: TSqlPrepared, proc getRow*(db: DbConn, stmtName: SqlPrepared,
args: varargs[string, `$`]): TRow {.tags: [FReadDB].} = args: varargs[string, `$`]): Row {.tags: [FReadDB].} =
var res = setupQuery(db, stmtName, args) var res = setupQuery(db, stmtName, args)
var L = pqNfields(res) var L = pqNfields(res)
result = newRow(L) result = newRow(L)
setRow(res, result, 0, L) setRow(res, result, 0, L)
pqClear(res) pqClear(res)
proc getAllRows*(db: TDbConn, query: TSqlQuery, proc getAllRows*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): seq[TRow] {.tags: [FReadDB].} = args: varargs[string, `$`]): seq[Row] {.tags: [FReadDB].} =
## executes the query and returns the whole result dataset. ## executes the query and returns the whole result dataset.
result = @[] result = @[]
for r in fastRows(db, query, args): for r in fastRows(db, query, args):
result.add(r) result.add(r)
proc getAllRows*(db: TDbConn, stmtName: TSqlPrepared, proc getAllRows*(db: DbConn, stmtName: SqlPrepared,
args: varargs[string, `$`]): seq[TRow] {.tags: [FReadDB].} = args: varargs[string, `$`]): seq[Row] {.tags: [FReadDB].} =
## executes the prepared query and returns the whole result dataset. ## executes the prepared query and returns the whole result dataset.
result = @[] result = @[]
for r in fastRows(db, stmtName, args): for r in fastRows(db, stmtName, args):
result.add(r) result.add(r)
iterator rows*(db: TDbConn, query: TSqlQuery, iterator rows*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): TRow {.tags: [FReadDB].} = args: varargs[string, `$`]): Row {.tags: [FReadDB].} =
## same as `fastRows`, but slower and safe. ## same as `fastRows`, but slower and safe.
for r in items(getAllRows(db, query, args)): yield r for r in items(getAllRows(db, query, args)): yield r
proc getValue*(db: TDbConn, query: TSqlQuery, proc getValue*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): string {.tags: [FReadDB].} = args: varargs[string, `$`]): string {.tags: [FReadDB].} =
## executes the query and returns the first column of the first row of the ## executes the query and returns the first column of the first row of the
## result dataset. Returns "" if the dataset contains no rows or the database ## result dataset. Returns "" if the dataset contains no rows or the database
@ -202,20 +225,20 @@ proc getValue*(db: TDbConn, query: TSqlQuery,
var x = pqgetvalue(setupQuery(db, query, args), 0, 0) var x = pqgetvalue(setupQuery(db, query, args), 0, 0)
result = if isNil(x): "" else: $x result = if isNil(x): "" else: $x
proc tryInsertID*(db: TDbConn, query: TSqlQuery, proc tryInsertID*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): int64 {.tags: [FWriteDb].}= args: varargs[string, `$`]): int64 {.tags: [FWriteDb].}=
## 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. For Postgre this adds ## generated ID for the row or -1 in case of an error. For Postgre this adds
## ``RETURNING id`` to the query, so it only works if your primary key is ## ``RETURNING id`` to the query, so it only works if your primary key is
## named ``id``. ## named ``id``.
var x = pqgetvalue(setupQuery(db, TSqlQuery(string(query) & " RETURNING id"), var x = pqgetvalue(setupQuery(db, SqlQuery(string(query) & " RETURNING id"),
args), 0, 0) args), 0, 0)
if not isNil(x): if not isNil(x):
result = parseBiggestInt($x) result = parseBiggestInt($x)
else: else:
result = -1 result = -1
proc insertID*(db: TDbConn, query: TSqlQuery, proc insertID*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): int64 {.tags: [FWriteDb].} = args: varargs[string, `$`]): int64 {.tags: [FWriteDb].} =
## executes the query (typically "INSERT") and returns the ## executes the query (typically "INSERT") and returns the
## generated ID for the row. For Postgre this adds ## generated ID for the row. For Postgre this adds
@ -224,7 +247,7 @@ proc insertID*(db: TDbConn, query: TSqlQuery,
result = tryInsertID(db, query, args) result = tryInsertID(db, query, args)
if result < 0: dbError(db) if result < 0: dbError(db)
proc execAffectedRows*(db: TDbConn, query: TSqlQuery, proc execAffectedRows*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): int64 {.tags: [ args: varargs[string, `$`]): int64 {.tags: [
FReadDB, FWriteDb].} = FReadDB, FWriteDb].} =
## executes the query (typically "UPDATE") and returns the ## executes the query (typically "UPDATE") and returns the
@ -235,11 +258,11 @@ proc execAffectedRows*(db: TDbConn, query: TSqlQuery,
result = parseBiggestInt($pqcmdTuples(res)) result = parseBiggestInt($pqcmdTuples(res))
pqclear(res) pqclear(res)
proc close*(db: TDbConn) {.tags: [FDb].} = proc close*(db: DbConn) {.tags: [FDb].} =
## closes the database connection. ## closes the database connection.
if db != nil: pqfinish(db) if db != nil: pqfinish(db)
proc open*(connection, user, password, database: string): TDbConn {. proc open*(connection, user, password, database: string): DbConn {.
tags: [FDb].} = tags: [FDb].} =
## opens a database connection. Raises `EDb` if the connection could not ## opens a database connection. Raises `EDb` if the connection could not
## be established. ## be established.
@ -261,8 +284,8 @@ proc open*(connection, user, password, database: string): TDbConn {.
result = pqsetdbLogin(nil, nil, nil, nil, database, user, password) result = pqsetdbLogin(nil, nil, nil, nil, database, user, password)
if pqStatus(result) != CONNECTION_OK: dbError(result) # result = nil if pqStatus(result) != CONNECTION_OK: dbError(result) # result = nil
proc setEncoding*(connection: TDbConn, encoding: string): bool {. proc setEncoding*(connection: DbConn, encoding: string): bool {.
tags: [FDb].} = tags: [FDb].} =
## sets the encoding of a database connection, returns true for ## sets the encoding of a database connection, returns true for
## success, false for failure. ## success, false for failure.
return pqsetClientEncoding(connection, encoding) == 0 return pqsetClientEncoding(connection, encoding) == 0

View file

@ -13,27 +13,30 @@
import strutils, sqlite3 import strutils, sqlite3
type type
TDbConn* = PSqlite3 ## encapsulates a database connection DbConn* = PSqlite3 ## encapsulates a database connection
TRow* = seq[string] ## a row of a dataset. NULL database values will be Row* = seq[string] ## a row of a dataset. NULL database values will be
## transformed always to the empty string. ## transformed always to the empty string.
InstantRow* = Pstmt ## a handle that can be used to get a row's column
## text on demand
EDb* = object of IOError ## exception that is raised if a database error occurs EDb* = object of IOError ## exception that is raised if a database error occurs
TSqlQuery* = distinct string ## an SQL query string SqlQuery* = distinct string ## an SQL query string
FDb* = object of IOEffect ## effect that denotes a database operation FDb* = object of IOEffect ## effect that denotes a database operation
FReadDb* = object of FDb ## effect that denotes a read operation FReadDb* = object of FDb ## effect that denotes a read operation
FWriteDb* = object of FDb ## effect that denotes a write operation FWriteDb* = object of FDb ## effect that denotes a write operation
{.deprecated: [TRow: Row, TSqlQuery: SqlQuery, TDbConn: DbConn].}
proc sql*(query: string): TSqlQuery {.noSideEffect, inline.} = proc sql*(query: string): SqlQuery {.noSideEffect, inline.} =
## constructs a TSqlQuery from the string `query`. This is supposed to be ## constructs a SqlQuery from the string `query`. This is supposed to be
## used as a raw-string-literal modifier: ## used as a raw-string-literal modifier:
## ``sql"update user set counter = counter + 1"`` ## ``sql"update user set counter = counter + 1"``
## ##
## If assertions are turned off, it does nothing. If assertions are turned ## If assertions are turned off, it does nothing. If assertions are turned
## on, later versions will check the string for valid syntax. ## on, later versions will check the string for valid syntax.
result = TSqlQuery(query) result = SqlQuery(query)
proc dbError(db: TDbConn) {.noreturn.} = proc dbError(db: DbConn) {.noreturn.} =
## raises an EDb exception. ## raises an EDb exception.
var e: ref EDb var e: ref EDb
new(e) new(e)
@ -55,7 +58,7 @@ proc dbQuote(s: string): string =
else: add(result, c) else: add(result, c)
add(result, '\'') add(result, '\'')
proc dbFormat(formatstr: TSqlQuery, args: varargs[string]): string = proc dbFormat(formatstr: SqlQuery, args: varargs[string]): string =
result = "" result = ""
var a = 0 var a = 0
for c in items(string(formatstr)): for c in items(string(formatstr)):
@ -65,7 +68,7 @@ proc dbFormat(formatstr: TSqlQuery, args: varargs[string]): string =
else: else:
add(result, c) add(result, c)
proc tryExec*(db: TDbConn, query: TSqlQuery, proc tryExec*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): bool {.tags: [FReadDb, FWriteDb].} = args: varargs[string, `$`]): bool {.tags: [FReadDb, FWriteDb].} =
## tries to execute the query and returns true if successful, false otherwise. ## tries to execute the query and returns true if successful, false otherwise.
var q = dbFormat(query, args) var q = dbFormat(query, args)
@ -74,29 +77,29 @@ proc tryExec*(db: TDbConn, query: TSqlQuery,
if step(stmt) == SQLITE_DONE: if step(stmt) == SQLITE_DONE:
result = finalize(stmt) == SQLITE_OK result = finalize(stmt) == SQLITE_OK
proc exec*(db: TDbConn, query: TSqlQuery, args: varargs[string, `$`]) {. proc exec*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]) {.
tags: [FReadDb, FWriteDb].} = tags: [FReadDb, FWriteDb].} =
## executes the query and raises EDB if not successful. ## executes the query and raises EDB if not successful.
if not tryExec(db, query, args): dbError(db) if not tryExec(db, query, args): dbError(db)
proc newRow(L: int): TRow = proc newRow(L: int): Row =
newSeq(result, L) newSeq(result, L)
for i in 0..L-1: result[i] = "" for i in 0..L-1: result[i] = ""
proc setupQuery(db: TDbConn, query: TSqlQuery, proc setupQuery(db: DbConn, query: SqlQuery,
args: varargs[string]): Pstmt = args: varargs[string]): Pstmt =
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)
proc setRow(stmt: Pstmt, r: var TRow, cols: cint) = proc setRow(stmt: Pstmt, r: var Row, cols: cint) =
for col in 0..cols-1: for col in 0..cols-1:
setLen(r[col], column_bytes(stmt, col)) # set capacity setLen(r[col], column_bytes(stmt, col)) # set capacity
setLen(r[col], 0) setLen(r[col], 0)
let x = column_text(stmt, col) let x = column_text(stmt, col)
if not isNil(x): add(r[col], x) if not isNil(x): add(r[col], x)
iterator fastRows*(db: TDbConn, query: TSqlQuery, iterator fastRows*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): TRow {.tags: [FReadDb].} = args: varargs[string, `$`]): Row {.tags: [FReadDb].} =
## executes the query and iterates over the result dataset. This is very ## executes the query and iterates over the result dataset. This is very
## fast, but potenially dangerous: If the for-loop-body executes another ## fast, but potenially dangerous: If the for-loop-body executes another
## query, the results can be undefined. For Sqlite it is safe though. ## query, the results can be undefined. For Sqlite it is safe though.
@ -108,10 +111,28 @@ iterator fastRows*(db: TDbConn, query: TSqlQuery,
yield result yield result
if finalize(stmt) != SQLITE_OK: dbError(db) if finalize(stmt) != SQLITE_OK: dbError(db)
proc getRow*(db: TDbConn, query: TSqlQuery, iterator instantRows*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): TRow {.tags: [FReadDb].} = args: varargs[string, `$`]): InstantRow
{.tags: [FReadDb].} =
## same as fastRows but returns a handle that can be used to get column text
## on demand using []. Returned handle is valid only within interator body.
var stmt = setupQuery(db, query, args)
while step(stmt) == SQLITE_ROW:
yield stmt
if finalize(stmt) != SQLITE_OK: dbError(db)
proc `[]`*(row: InstantRow, col: int32): string {.inline.} =
## returns text for given column of the row
$column_text(row, col)
proc len*(row: InstantRow): int32 {.inline.} =
## returns number of columns in the row
column_count(row)
proc getRow*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): Row {.tags: [FReadDb].} =
## retrieves a single row. If the query doesn't return any rows, this proc ## retrieves a single row. If the query doesn't return any rows, this proc
## will return a TRow with empty strings for each column. ## will return a Row with empty strings for each column.
var stmt = setupQuery(db, query, args) var stmt = setupQuery(db, query, args)
var L = (column_count(stmt)) var L = (column_count(stmt))
result = newRow(L) result = newRow(L)
@ -119,19 +140,19 @@ proc getRow*(db: TDbConn, query: TSqlQuery,
setRow(stmt, result, L) setRow(stmt, result, L)
if finalize(stmt) != SQLITE_OK: dbError(db) if finalize(stmt) != SQLITE_OK: dbError(db)
proc getAllRows*(db: TDbConn, query: TSqlQuery, proc getAllRows*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): seq[TRow] {.tags: [FReadDb].} = args: varargs[string, `$`]): seq[Row] {.tags: [FReadDb].} =
## executes the query and returns the whole result dataset. ## executes the query and returns the whole result dataset.
result = @[] result = @[]
for r in fastRows(db, query, args): for r in fastRows(db, query, args):
result.add(r) result.add(r)
iterator rows*(db: TDbConn, query: TSqlQuery, iterator rows*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): TRow {.tags: [FReadDb].} = args: varargs[string, `$`]): Row {.tags: [FReadDb].} =
## same as `FastRows`, but slower and safe. ## same as `FastRows`, but slower and safe.
for r in fastRows(db, query, args): yield r for r in fastRows(db, query, args): yield r
proc getValue*(db: TDbConn, query: TSqlQuery, proc getValue*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): string {.tags: [FReadDb].} = args: varargs[string, `$`]): string {.tags: [FReadDb].} =
## executes the query and returns the first column of the first row of the ## executes the query and returns the first column of the first row of the
## result dataset. Returns "" if the dataset contains no rows or the database ## result dataset. Returns "" if the dataset contains no rows or the database
@ -148,7 +169,7 @@ proc getValue*(db: TDbConn, query: TSqlQuery,
result = "" result = ""
if finalize(stmt) != SQLITE_OK: dbError(db) if finalize(stmt) != SQLITE_OK: dbError(db)
proc tryInsertID*(db: TDbConn, query: TSqlQuery, proc tryInsertID*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): int64 args: varargs[string, `$`]): int64
{.tags: [FWriteDb], raises: [].} = {.tags: [FWriteDb], raises: [].} =
## executes the query (typically "INSERT") and returns the ## executes the query (typically "INSERT") and returns the
@ -162,7 +183,7 @@ proc tryInsertID*(db: TDbConn, query: TSqlQuery,
if finalize(stmt) != SQLITE_OK: if finalize(stmt) != SQLITE_OK:
result = -1 result = -1
proc insertID*(db: TDbConn, query: TSqlQuery, proc insertID*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): int64 {.tags: [FWriteDb].} = args: varargs[string, `$`]): int64 {.tags: [FWriteDb].} =
## executes the query (typically "INSERT") and returns the ## executes the query (typically "INSERT") and returns the
## generated ID for the row. For Postgre this adds ## generated ID for the row. For Postgre this adds
@ -171,7 +192,7 @@ proc insertID*(db: TDbConn, query: TSqlQuery,
result = tryInsertID(db, query, args) result = tryInsertID(db, query, args)
if result < 0: dbError(db) if result < 0: dbError(db)
proc execAffectedRows*(db: TDbConn, query: TSqlQuery, proc execAffectedRows*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): int64 {. args: varargs[string, `$`]): int64 {.
tags: [FReadDb, FWriteDb].} = tags: [FReadDb, FWriteDb].} =
## executes the query (typically "UPDATE") and returns the ## executes the query (typically "UPDATE") and returns the
@ -179,21 +200,21 @@ proc execAffectedRows*(db: TDbConn, query: TSqlQuery,
exec(db, query, args) exec(db, query, args)
result = changes(db) result = changes(db)
proc close*(db: TDbConn) {.tags: [FDb].} = proc close*(db: DbConn) {.tags: [FDb].} =
## closes the database connection. ## closes the database connection.
if sqlite3.close(db) != SQLITE_OK: dbError(db) if sqlite3.close(db) != SQLITE_OK: dbError(db)
proc open*(connection, user, password, database: string): TDbConn {. proc open*(connection, user, password, database: string): DbConn {.
tags: [FDb].} = tags: [FDb].} =
## opens a database connection. Raises `EDb` if the connection could not ## opens a database connection. Raises `EDb` if the connection could not
## be established. Only the ``connection`` parameter is used for ``sqlite``. ## be established. Only the ``connection`` parameter is used for ``sqlite``.
var db: TDbConn var db: DbConn
if sqlite3.open(connection, db) == SQLITE_OK: if sqlite3.open(connection, db) == SQLITE_OK:
result = db result = db
else: else:
dbError(db) dbError(db)
proc setEncoding*(connection: TDbConn, encoding: string): bool {. proc setEncoding*(connection: DbConn, encoding: string): bool {.
tags: [FDb].} = tags: [FDb].} =
## sets the encoding of a database connection, returns true for ## sets the encoding of a database connection, returns true for
## success, false for failure. ## success, false for failure.
@ -215,5 +236,7 @@ when not defined(testing) and isMainModule:
#db.query("insert into tbl1 values('goodbye', 20)") #db.query("insert into tbl1 values('goodbye', 20)")
for r in db.rows(sql"select * from tbl1", []): for r in db.rows(sql"select * from tbl1", []):
echo(r[0], r[1]) echo(r[0], r[1])
for r in db.instantRows(sql"select * from tbl1", []):
echo(r[0], r[1])
db_sqlite.close(db) db_sqlite.close(db)

View file

@ -17,23 +17,24 @@ from sdl import PSurface # Bug
from sdl_ttf import openFont, closeFont from sdl_ttf import openFont, closeFont
type type
TRect* = tuple[x, y, width, height: int] Rect* = tuple[x, y, width, height: int]
TPoint* = tuple[x, y: int] Point* = tuple[x, y: int]
PSurface* = ref TSurface ## a surface to draw onto PSurface* = ref Surface ## a surface to draw onto
TSurface* {.pure, final.} = object Surface* {.pure, final.} = object
w*, h*: Natural w*, h*: Natural
s*: sdl.PSurface s*: sdl.PSurface
EGraphics* = object of IOError EGraphics* = object of IOError
TFont {.pure, final.} = object Font {.pure, final.} = object
f: sdl_ttf.PFont f: sdl_ttf.PFont
color: sdl.TColor color: sdl.Color
PFont* = ref TFont ## represents a font PFont* = ref Font ## represents a font
{.deprecated: [TSurface: Surface, TFont: Font, TRect: Rect, TPoint: Point].}
proc toSdlColor*(c: Color): sdl.TColor = proc toSdlColor*(c: Color): sdl.Color =
## Convert colors.TColor to sdl.TColor ## Convert colors.Color to sdl.Color
var x = c.extractRGB var x = c.extractRGB
result.r = x.r and 0xff result.r = x.r and 0xff
result.g = x.g and 0xff result.g = x.g and 0xff
@ -45,8 +46,8 @@ proc createSdlColor*(sur: PSurface, c: Color, alpha: int = 0): int32 =
return sdl.mapRGBA(sur.s.format, x.r and 0xff, x.g and 0xff, return sdl.mapRGBA(sur.s.format, x.r and 0xff, x.g and 0xff,
x.b and 0xff, alpha and 0xff) x.b and 0xff, alpha and 0xff)
proc toSdlRect*(r: TRect): sdl.TRect = proc toSdlRect*(r: Rect): sdl.Rect =
## Convert ``graphics.TRect`` to ``sdl.TRect``. ## Convert ``graphics.Rect`` to ``sdl.Rect``.
result.x = int16(r.x) result.x = int16(r.x)
result.y = int16(r.y) result.y = int16(r.y)
result.w = uint16(r.width) result.w = uint16(r.width)
@ -103,8 +104,9 @@ proc writeToBMP*(sur: PSurface, filename: string) =
raise newException(IOError, "cannot write: " & filename) raise newException(IOError, "cannot write: " & filename)
type type
TPixels = array[0..1000_000-1, int32] Pixels = array[0..1000_000-1, int32]
PPixels = ptr TPixels PPixels = ptr Pixels
{.deprecated: [TPixels: Pixels].}
template setPix(video, pitch, x, y, col: expr): stmt = template setPix(video, pitch, x, y, col: expr): stmt =
video[y * pitch + x] = int32(col) video[y * pitch + x] = int32(col)
@ -128,7 +130,7 @@ proc setPixel(sur: PSurface, x, y: Natural, col: colors.Color) {.inline.} =
#pixs[y * (sur.s.pitch div colSize) + x] = int(col) #pixs[y * (sur.s.pitch div colSize) + x] = int(col)
setPix(pixs, sur.s.pitch.int div ColSize, x, y, col) setPix(pixs, sur.s.pitch.int div ColSize, x, y, col)
proc `[]`*(sur: PSurface, p: TPoint): Color = proc `[]`*(sur: PSurface, p: Point): Color =
## get pixel at position `p`. No range checking is done! ## get pixel at position `p`. No range checking is done!
result = getPixel(sur, p.x, p.y) result = getPixel(sur, p.x, p.y)
@ -136,7 +138,7 @@ proc `[]`*(sur: PSurface, x, y: int): Color =
## get pixel at position ``(x, y)``. No range checking is done! ## get pixel at position ``(x, y)``. No range checking is done!
result = getPixel(sur, x, y) result = getPixel(sur, x, y)
proc `[]=`*(sur: PSurface, p: TPoint, col: Color) = proc `[]=`*(sur: PSurface, p: Point, col: Color) =
## set the pixel at position `p`. No range checking is done! ## set the pixel at position `p`. No range checking is done!
setPixel(sur, p.x, p.y, col) setPixel(sur, p.x, p.y, col)
@ -144,10 +146,10 @@ proc `[]=`*(sur: PSurface, x, y: int, col: Color) =
## set the pixel at position ``(x, y)``. No range checking is done! ## set the pixel at position ``(x, y)``. No range checking is done!
setPixel(sur, x, y, col) setPixel(sur, x, y, col)
proc blit*(destSurf: PSurface, destRect: TRect, srcSurf: PSurface, proc blit*(destSurf: PSurface, destRect: Rect, srcSurf: PSurface,
srcRect: TRect) = srcRect: Rect) =
## Copies ``srcSurf`` into ``destSurf`` ## Copies ``srcSurf`` into ``destSurf``
var destTRect, srcTRect: sdl.TRect var destTRect, srcTRect: sdl.Rect
destTRect.x = int16(destRect.x) destTRect.x = int16(destRect.x)
destTRect.y = int16(destRect.y) destTRect.y = int16(destRect.y)
@ -168,7 +170,7 @@ proc textBounds*(text: string, font = defaultFont): tuple[width, height: int] =
result.width = int(w) result.width = int(w)
result.height = int(h) result.height = int(h)
proc drawText*(sur: PSurface, p: TPoint, text: string, font = defaultFont) = proc drawText*(sur: PSurface, p: Point, text: string, font = defaultFont) =
## Draws text with a transparent background, at location ``p`` with the given ## Draws text with a transparent background, at location ``p`` with the given
## font. ## font.
var textSur: PSurface # This surface will have the text drawn on it var textSur: PSurface # This surface will have the text drawn on it
@ -179,7 +181,7 @@ proc drawText*(sur: PSurface, p: TPoint, text: string, font = defaultFont) =
# Merge the text surface with sur # Merge the text surface with sur
sur.blit((p.x, p.y, sur.w, sur.h), textSur, (0, 0, sur.w, sur.h)) sur.blit((p.x, p.y, sur.w, sur.h), textSur, (0, 0, sur.w, sur.h))
proc drawText*(sur: PSurface, p: TPoint, text: string, proc drawText*(sur: PSurface, p: Point, text: string,
bg: Color, font = defaultFont) = bg: Color, font = defaultFont) =
## Draws text, at location ``p`` with font ``font``. ``bg`` ## Draws text, at location ``p`` with font ``font``. ``bg``
## is the background color. ## is the background color.
@ -189,7 +191,7 @@ proc drawText*(sur: PSurface, p: TPoint, text: string,
# Merge the text surface with sur # Merge the text surface with sur
sur.blit((p.x, p.y, sur.w, sur.h), textSur, (0, 0, sur.w, sur.h)) sur.blit((p.x, p.y, sur.w, sur.h), textSur, (0, 0, sur.w, sur.h))
proc drawCircle*(sur: PSurface, p: TPoint, r: Natural, color: Color) = proc drawCircle*(sur: PSurface, p: Point, r: Natural, color: Color) =
## draws a circle with center `p` and radius `r` with the given color ## draws a circle with center `p` and radius `r` with the given color
## onto the surface `sur`. ## onto the surface `sur`.
var video = cast[PPixels](sur.s.pixels) var video = cast[PPixels](sur.s.pixels)
@ -229,7 +231,7 @@ proc `>-<`(val: int, s: PSurface): int {.inline.} =
proc `>|<`(val: int, s: PSurface): int {.inline.} = proc `>|<`(val: int, s: PSurface): int {.inline.} =
return if val < 0: 0 elif val >= s.h: s.h-1 else: val return if val < 0: 0 elif val >= s.h: s.h-1 else: val
proc drawLine*(sur: PSurface, p1, p2: TPoint, color: Color) = proc drawLine*(sur: PSurface, p1, p2: Point, color: Color) =
## draws a line between the two points `p1` and `p2` with the given color ## draws a line between the two points `p1` and `p2` with the given color
## onto the surface `sur`. ## onto the surface `sur`.
var stepx, stepy: int = 0 var stepx, stepy: int = 0
@ -291,7 +293,7 @@ proc drawVerLine*(sur: PSurface, x, y, h: Natural, color: Color) =
for i in 0 .. min(sur.s.h-y, h)-1: for i in 0 .. min(sur.s.h-y, h)-1:
setPix(video, pitch, x, y + i, color) setPix(video, pitch, x, y + i, color)
proc fillCircle*(s: PSurface, p: TPoint, r: Natural, color: Color) = proc fillCircle*(s: PSurface, p: Point, r: Natural, color: Color) =
## draws a circle with center `p` and radius `r` with the given color ## draws a circle with center `p` and radius `r` with the given color
## onto the surface `sur` and fills it. ## onto the surface `sur` and fills it.
var a = 1 - r var a = 1 - r
@ -319,7 +321,7 @@ proc fillCircle*(s: PSurface, p: TPoint, r: Natural, color: Color) =
drawVerLine(s, x - py - 1, y - px, px, color) drawVerLine(s, x - py - 1, y - px, px, color)
px = px + 1 px = px + 1
proc drawRect*(sur: PSurface, r: TRect, color: Color) = proc drawRect*(sur: PSurface, r: Rect, color: Color) =
## draws a rectangle. ## draws a rectangle.
var video = cast[PPixels](sur.s.pixels) var video = cast[PPixels](sur.s.pixels)
var pitch = sur.s.pitch.int div ColSize var pitch = sur.s.pitch.int div ColSize
@ -337,7 +339,7 @@ proc drawRect*(sur: PSurface, r: TRect, color: Color) =
setPix(video, pitch, r.x, r.y + i, color) setPix(video, pitch, r.x, r.y + i, color)
setPix(video, pitch, r.x + minW - 1, r.y + i, color) # Draw right side setPix(video, pitch, r.x + minW - 1, r.y + i, color) # Draw right side
proc fillRect*(sur: PSurface, r: TRect, col: Color) = proc fillRect*(sur: PSurface, r: Rect, col: Color) =
## Fills a rectangle using sdl's ``FillRect`` function. ## Fills a rectangle using sdl's ``FillRect`` function.
var rect = toSdlRect(r) var rect = toSdlRect(r)
if sdl.fillRect(sur.s, addr(rect), sur.createSdlColor(col)) == -1: if sdl.fillRect(sur.s, addr(rect), sur.createSdlColor(col)) == -1:
@ -424,7 +426,7 @@ template cround(x: expr): expr = ipart(x + 0.5)
template fpart(x: expr): expr = x - ipart(x) template fpart(x: expr): expr = x - ipart(x)
template rfpart(x: expr): expr = 1.0 - fpart(x) template rfpart(x: expr): expr = 1.0 - fpart(x)
proc drawLineAA*(sur: PSurface, p1, p2: TPoint, color: Color) = proc drawLineAA*(sur: PSurface, p1, p2: Point, color: Color) =
## Draws a anti-aliased line from ``p1`` to ``p2``, using Xiaolin Wu's ## Draws a anti-aliased line from ``p1`` to ``p2``, using Xiaolin Wu's
## line algorithm ## line algorithm
var (x1, x2, y1, y2) = (p1.x.toFloat(), p2.x.toFloat(), var (x1, x2, y1, y2) = (p1.x.toFloat(), p2.x.toFloat(),
@ -490,9 +492,9 @@ proc fillSurface*(sur: PSurface, color: Color) =
template withEvents*(surf: PSurface, event: expr, actions: stmt): stmt {. template withEvents*(surf: PSurface, event: expr, actions: stmt): stmt {.
immediate.} = immediate.} =
## Simple template which creates an event loop. ``Event`` is the name of the ## Simple template which creates an event loop. ``Event`` is the name of the
## variable containing the TEvent object. ## variable containing the Event object.
while true: while true:
var event: sdl.TEvent var event: sdl.Event
if sdl.waitEvent(addr(event)) == 1: if sdl.waitEvent(addr(event)) == 1:
actions actions

682
lib/impure/nre.nim Normal file
View file

@ -0,0 +1,682 @@
#
# Nim's Runtime Library
# (c) Copyright 2015 Nim Contributers
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
from pcre import nil
import nre.private.util
import tables
import unsigned
from strutils import toLower, `%`
from math import ceil
import options
from unicode import runeLenAt
## What is NRE?
## ============
##
## A regular expression library for Nim using PCRE to do the hard work.
##
## Licencing
## ---------
##
## PCRE has some additional terms that you must comply with if you use this module.::
##
## > Copyright (c) 1997-2001 University of Cambridge
## >
## > Permission is granted to anyone to use this software for any purpose on any
## > computer system, and to redistribute it freely, subject to the following
## > restrictions:
## >
## > 1. This software is distributed in the hope that it will be useful,
## > but WITHOUT ANY WARRANTY; without even the implied warranty of
## > MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
## >
## > 2. The origin of this software must not be misrepresented, either by
## > explicit claim or by omission. In practice, this means that if you use
## > PCRE in software that you distribute to others, commercially or
## > otherwise, you must put a sentence like this
## >
## > Regular expression support is provided by the PCRE library package,
## > which is open source software, written by Philip Hazel, and copyright
## > by the University of Cambridge, England.
## >
## > somewhere reasonably visible in your documentation and in any relevant
## > files or online help data or similar. A reference to the ftp site for
## > the source, that is, to
## >
## > ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/
## >
## > should also be given in the documentation. However, this condition is not
## > intended to apply to whole chains of software. If package A includes PCRE,
## > it must acknowledge it, but if package B is software that includes package
## > A, the condition is not imposed on package B (unless it uses PCRE
## > independently).
## >
## > 3. Altered versions must be plainly marked as such, and must not be
## > misrepresented as being the original software.
## >
## > 4. If PCRE is embedded in any software that is released under the GNU
## > General Purpose Licence (GPL), or Lesser General Purpose Licence (LGPL),
## > then the terms of that licence shall supersede any condition above with
## > which it is incompatible.
# Type definitions {{{
type
Regex* = ref object
## Represents the pattern that things are matched against, constructed with
## ``re(string)``. Examples: ``re"foo"``, ``re(r"(*ANYCRLF)(?x)foo #
## comment".``
##
## ``pattern: string``
## the string that was used to create the pattern.
##
## ``captureCount: int``
## the number of captures that the pattern has.
##
## ``captureNameId: Table[string, int]``
## a table from the capture names to their numeric id.
##
##
## Options
## .......
##
## The following options may appear anywhere in the pattern, and they affect
## the rest of it.
##
## - ``(?i)`` - case insensitive
## - ``(?m)`` - multi-line: ``^`` and ``$`` match the beginning and end of
## lines, not of the subject string
## - ``(?s)`` - ``.`` also matches newline (*dotall*)
## - ``(?U)`` - expressions are not greedy by default. ``?`` can be added
## to a qualifier to make it greedy
## - ``(?x)`` - whitespace and comments (``#``) are ignored (*extended*)
## - ``(?X)`` - character escapes without special meaning (``\w`` vs.
## ``\a``) are errors (*extra*)
##
## One or a combination of these options may appear only at the beginning
## of the pattern:
##
## - ``(*UTF8)`` - treat both the pattern and subject as UTF-8
## - ``(*UCP)`` - Unicode character properties; ``\w`` matches ``я``
## - ``(*U)`` - a combination of the two options above
## - ``(*FIRSTLINE*)`` - fails if there is not a match on the first line
## - ``(*NO_AUTO_CAPTURE)`` - turn off auto-capture for groups;
## ``(?<name>...)`` can be used to capture
## - ``(*CR)`` - newlines are separated by ``\r``
## - ``(*LF)`` - newlines are separated by ``\n`` (UNIX default)
## - ``(*CRLF)`` - newlines are separated by ``\r\n`` (Windows default)
## - ``(*ANYCRLF)`` - newlines are separated by any of the above
## - ``(*ANY)`` - newlines are separated by any of the above and Unicode
## newlines:
##
## single characters VT (vertical tab, U+000B), FF (form feed, U+000C),
## NEL (next line, U+0085), LS (line separator, U+2028), and PS
## (paragraph separator, U+2029). For the 8-bit library, the last two
## are recognized only in UTF-8 mode.
## — man pcre
##
## - ``(*JAVASCRIPT_COMPAT)`` - JavaScript compatibility
## - ``(*NO_STUDY)`` - turn off studying; study is enabled by default
##
## For more details on the leading option groups, see the `Option
## Setting <http://man7.org/linux/man-pages/man3/pcresyntax.3.html#OPTION_SETTING>`__
## and the `Newline
## Convention <http://man7.org/linux/man-pages/man3/pcresyntax.3.html#NEWLINE_CONVENTION>`__
## sections of the `PCRE syntax
## manual <http://man7.org/linux/man-pages/man3/pcresyntax.3.html>`__.
pattern*: string ## not nil
pcreObj: ptr pcre.Pcre ## not nil
pcreExtra: ptr pcre.ExtraData ## nil
captureNameToId: Table[string, int]
RegexMatch* = object
## Usually seen as Option[RegexMatch], it represents the result of an
## execution. On failure, it is none, on success, it is some.
##
## ``pattern: Regex``
## the pattern that is being matched
##
## ``str: string``
## the string that was matched against
##
## ``captures[]: string``
## the string value of whatever was captured at that id. If the value
## is invalid, then behavior is undefined. If the id is ``-1``, then
## the whole match is returned. If the given capture was not matched,
## ``nil`` is returned.
##
## - ``"abc".match(re"(\w)").captures[0] == "a"``
## - ``"abc".match(re"(?<letter>\w)").captures["letter"] == "a"``
## - ``"abc".match(re"(\w)\w").captures[-1] == "ab"``
##
## ``captureBounds[]: Option[Slice[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)").captureBounds[0] == 0 .. 0``
## - ``"abc".match(re"").captureBounds[-1] == 0 .. -1``
## - ``"abc".match(re"abc").captureBounds[-1] == 0 .. 2``
##
## ``match: string``
## the full text of the match.
##
## ``matchBounds: Slice[int]``
## the bounds of the match, as in ``captureBounds[]``
##
## ``(captureBounds|captures).toTable``
## returns a table with each named capture as a key.
##
## ``(captureBounds|captures).toSeq``
## returns all the captures by their number.
##
## ``$: string``
## same as ``match``
pattern*: Regex ## The regex doing the matching.
## Not nil.
str*: string ## The string that was matched against.
## Not nil.
pcreMatchBounds: seq[Slice[cint]] ## First item is the bounds of the match
## Other items are the captures
## `a` is inclusive start, `b` is exclusive end
Captures* = distinct RegexMatch
CaptureBounds* = distinct RegexMatch
RegexError* = ref object of Exception
RegexInternalError* = ref object of RegexError
## Internal error in the module, this probably means that there is a bug
InvalidUnicodeError* = ref object of RegexError
## Thrown when matching fails due to invalid unicode in strings
pos*: int ## the location of the invalid unicode in bytes
SyntaxError* = ref object of RegexError
## Thrown when there is a syntax error in the
## regular expression string passed in
pos*: int ## the location of the syntax error in bytes
pattern*: string ## the pattern that caused the problem
StudyError* = ref object of RegexError
## Thrown when studying the regular expression failes
## for whatever reason. The message contains the error
## code.
# }}}
proc getinfo[T](pattern: Regex, opt: cint): T =
let retcode = pcre.fullinfo(pattern.pcreObj, pattern.pcreExtra, opt, addr result)
if retcode < 0:
# XXX Error message that doesn't expose implementation details
raise newException(FieldError, "Invalid getinfo for $1, errno $2" % [$opt, $retcode])
# Regex accessors {{{
proc captureCount*(pattern: Regex): int =
return getinfo[cint](pattern, pcre.INFO_CAPTURECOUNT)
proc captureNameId*(pattern: Regex): Table[string, int] =
return pattern.captureNameToId
proc matchesCrLf(pattern: Regex): bool =
let flags = uint32(getinfo[culong](pattern, pcre.INFO_OPTIONS))
let newlineFlags = flags and (pcre.NEWLINE_CRLF or
pcre.NEWLINE_ANY or
pcre.NEWLINE_ANYCRLF)
if newLineFlags > 0u32:
return true
# get flags from build config
var confFlags: cint
if pcre.config(pcre.CONFIG_NEWLINE, addr confFlags) != 0:
assert(false, "CONFIG_NEWLINE apparently got screwed up")
case confFlags
of 13: return false
of 10: return false
of (13 shl 8) or 10: return true
of -2: return true
of -1: return true
else: return false
# }}}
# Capture accessors {{{
proc captureBounds*(pattern: RegexMatch): CaptureBounds = return CaptureBounds(pattern)
proc captures*(pattern: RegexMatch): Captures = return Captures(pattern)
proc `[]`*(pattern: CaptureBounds, i: int): Option[Slice[int]] =
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(Slice[int])
proc `[]`*(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 nil
proc match*(pattern: RegexMatch): string =
return pattern.captures[-1]
proc matchBounds*(pattern: RegexMatch): Slice[int] =
return pattern.captureBounds[-1].get
proc `[]`*(pattern: CaptureBounds, name: string): Option[Slice[int]] =
let pattern = RegexMatch(pattern)
return pattern.captureBounds[pattern.pattern.captureNameToId.fget(name)]
proc `[]`*(pattern: Captures, name: string): string =
let pattern = RegexMatch(pattern)
return pattern.captures[pattern.pattern.captureNameToId.fget(name)]
template toTableImpl(cond: bool): stmt {.immediate, dirty.} =
for key in RegexMatch(pattern).pattern.captureNameId.keys:
let nextVal = pattern[key]
if cond:
result[key] = default
else:
result[key] = nextVal
proc toTable*(pattern: Captures, default: string = nil): Table[string, string] =
result = initTable[string, string]()
toTableImpl(nextVal == nil)
proc toTable*(pattern: CaptureBounds, default = none(Slice[int])):
Table[string, Option[Slice[int]]] =
result = initTable[string, Option[Slice[int]]]()
toTableImpl(nextVal.isNone)
template itemsImpl(cond: bool): stmt {.immediate, 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
yield nextYieldVal
iterator items*(pattern: CaptureBounds, default = none(Slice[int])): Option[Slice[int]] =
itemsImpl(nextVal.isNone)
iterator items*(pattern: Captures, default: string = nil): string =
itemsImpl(nextVal == nil)
proc toSeq*(pattern: CaptureBounds, default = none(Slice[int])): seq[Option[Slice[int]]] =
accumulateResult(pattern.items(default))
proc toSeq*(pattern: Captures, default: string = nil): seq[string] =
accumulateResult(pattern.items(default))
proc `$`*(pattern: RegexMatch): string =
return pattern.captures[-1]
proc `==`*(a, b: Regex): bool =
if not a.isNil and not b.isNil:
return a.pattern == b.pattern and
a.pcreObj == b.pcreObj and
a.pcreExtra == b.pcreExtra
else:
return system.`==`(a, b)
proc `==`*(a, b: RegexMatch): bool =
return a.pattern == b.pattern and
a.str == b.str
# }}}
# Creation & Destruction {{{
# PCRE Options {{{
const PcreOptions = {
"NEVER_UTF": pcre.NEVER_UTF,
"ANCHORED": pcre.ANCHORED,
"DOLLAR_ENDONLY": pcre.DOLLAR_ENDONLY,
"FIRSTLINE": pcre.FIRSTLINE,
"NO_AUTO_CAPTURE": pcre.NO_AUTO_CAPTURE,
"JAVASCRIPT_COMPAT": pcre.JAVASCRIPT_COMPAT,
"U": pcre.UTF8 or pcre.UCP
}.toTable
# Options that are supported inside regular expressions themselves
const SkipOptions = [
"LIMIT_MATCH=", "LIMIT_RECURSION=", "NO_AUTO_POSSESS", "NO_START_OPT",
"UTF8", "UTF16", "UTF32", "UTF", "UCP",
"CR", "LF", "CRLF", "ANYCRLF", "ANY", "BSR_ANYCRLF", "BSR_UNICODE"
]
proc extractOptions(pattern: string): tuple[pattern: string, flags: int, study: bool] =
result = ("", 0, true)
var optionStart = 0
var equals = false
for i, c in pattern:
if optionStart == i:
if c != '(':
break
optionStart = i
elif optionStart == i-1:
if c != '*':
break
elif c == ')':
let name = pattern[optionStart+2 .. i-1]
if equals or name in SkipOptions:
result.pattern.add pattern[optionStart .. i]
elif PcreOptions.hasKey name:
result.flags = result.flags or PcreOptions[name]
elif name == "NO_STUDY":
result.study = false
else:
break
optionStart = i+1
equals = false
elif not equals:
if c == '=':
equals = true
if pattern[optionStart+2 .. i] notin SkipOptions:
break
elif c notin {'A'..'Z', '0'..'9', '_'}:
break
result.pattern.add pattern[optionStart .. pattern.high]
# }}}
type UncheckedArray {.unchecked.}[T] = array[0 .. 0, T]
proc destroyRegex(pattern: Regex) =
pcre.free_substring(cast[cstring](pattern.pcreObj))
pattern.pcreObj = nil
if pattern.pcreExtra != nil:
pcre.free_study(pattern.pcreExtra)
proc getNameToNumberTable(pattern: Regex): Table[string, int] =
let entryCount = getinfo[cint](pattern, pcre.INFO_NAMECOUNT)
let entrySize = getinfo[cint](pattern, pcre.INFO_NAMEENTRYSIZE)
let table = cast[ptr UncheckedArray[uint8]](
getinfo[int](pattern, pcre.INFO_NAMETABLE))
result = initTable[string, int]()
for i in 0 .. <entryCount:
let pos = i * entrySize
let num = (int(table[pos]) shl 8) or int(table[pos + 1]) - 1
var name = ""
var idx = 2
while table[pos + idx] != 0:
name.add(char(table[pos + idx]))
idx += 1
result[name] = num
proc initRegex(pattern: string, flags: int, study = true): Regex =
new(result, destroyRegex)
result.pattern = pattern
var errorMsg: cstring
var errOffset: cint
result.pcreObj = pcre.compile(cstring(pattern),
# better hope int is at least 4 bytes..
cint(flags), addr errorMsg,
addr errOffset, nil)
if result.pcreObj == nil:
# failed to compile
raise SyntaxError(msg: $errorMsg, pos: errOffset, pattern: pattern)
if study:
# XXX investigate JIT
result.pcreExtra = pcre.study(result.pcreObj, 0x0, addr errorMsg)
if errorMsg != nil:
raise StudyError(msg: $errorMsg)
result.captureNameToId = result.getNameToNumberTable()
proc re*(pattern: string): Regex =
let (pattern, flags, study) = extractOptions(pattern)
initRegex(pattern, flags, study)
# }}}
# Operations {{{
proc matchImpl(str: string, pattern: Regex, start, endpos: int, flags: int): Option[RegexMatch] =
var myResult = RegexMatch(pattern : pattern, str : str)
# See PCRE man pages.
# 2x capture count to make room for start-end pairs
# 1x capture count as slack space for PCRE
let vecsize = (pattern.captureCount() + 1) * 3
# div 2 because each element is 2 cints long
myResult.pcreMatchBounds = newSeq[Slice[cint]](ceil(vecsize / 2).int)
myResult.pcreMatchBounds.setLen(vecsize div 3)
let strlen = if endpos == int.high: str.len else: endpos+1
doAssert(strlen <= str.len) # don't want buffer overflows
let execRet = pcre.exec(pattern.pcreObj,
pattern.pcreExtra,
cstring(str),
cint(strlen),
cint(start),
cint(flags),
cast[ptr cint](addr myResult.pcreMatchBounds[0]),
cint(vecsize))
if execRet >= 0:
return some(myResult)
case execRet:
of pcre.ERROR_NOMATCH:
return none(RegexMatch)
of pcre.ERROR_NULL:
raise newException(AccessViolationError, "Expected non-null parameters")
of pcre.ERROR_BADOPTION:
raise RegexInternalError(msg : "Unknown pattern flag. Either a bug or " &
"outdated PCRE.")
of pcre.ERROR_BADUTF8, pcre.ERROR_SHORTUTF8, pcre.ERROR_BADUTF8_OFFSET:
raise InvalidUnicodeError(msg : "Invalid unicode byte sequence",
pos : myResult.pcreMatchBounds[0].a)
else:
raise RegexInternalError(msg : "Unknown internal error: " & $execRet)
proc match*(str: string, pattern: Regex, start = 0, endpos = int.high): Option[RegexMatch] =
## Like ```find(...)`` <#proc-find>`__, but anchored to the start of the
## string. This means that ``"foo".match(re"f") == true``, but
## ``"foo".match(re"o") == false``.
return str.matchImpl(pattern, start, endpos, pcre.ANCHORED)
iterator findIter*(str: string, pattern: Regex, start = 0, endpos = int.high): RegexMatch =
## Works the same as ```find(...)`` <#proc-find>`__, but finds every
## non-overlapping match. ``"2222".find(re"22")`` is ``"22", "22"``, not
## ``"22", "22", "22"``.
##
## Arguments are the same as ```find(...)`` <#proc-find>`__
##
## Variants:
##
## - ``proc findAll(...)`` returns a ``seq[string]``
# see pcredemo for explaination
let matchesCrLf = pattern.matchesCrLf()
let unicode = uint32(getinfo[culong](pattern, pcre.INFO_OPTIONS) and
pcre.UTF8) > 0u32
let strlen = if endpos == int.high: str.len else: endpos+1
var offset = start
var match: Option[RegexMatch]
while true:
var flags = 0
if match.isSome and
match.get.matchBounds.a > match.get.matchBounds.b:
# 0-len match
flags = pcre.NOTEMPTY_ATSTART
match = str.matchImpl(pattern, offset, endpos, flags)
if match.isNone:
# either the end of the input or the string
# cannot be split here
if offset >= strlen:
break
if matchesCrLf and offset < (str.len - 1) and
str[offset] == '\r' and str[offset + 1] == '\L':
# if PCRE treats CrLf as newline, skip both at the same time
offset += 2
elif unicode:
# XXX what about invalid unicode?
offset += str.runeLenAt(offset)
assert(offset <= strlen)
else:
offset += 1
else:
offset = match.get.matchBounds.b + 1
yield match.get
proc find*(str: string, pattern: Regex, start = 0, endpos = int.high): Option[RegexMatch] =
## Finds the given pattern in the string between the end and start
## positions.
##
## ``start``
## The start point at which to start matching. ``|abc`` is ``0``;
## ``a|bc`` is ``1``
##
## ``endpos``
## The maximum index for a match; ``int.high`` means the end of the
## string, otherwise it’s an inclusive upper bound.
return str.matchImpl(pattern, start, endpos, 0)
proc findAll*(str: string, pattern: Regex, start = 0, endpos = int.high): seq[string] =
result = @[]
for match in str.findIter(pattern, start, endpos):
result.add(match.match)
proc split*(str: string, pattern: Regex, maxSplit = -1, start = 0): seq[string] =
## Splits the string with the given regex. This works according to the
## rules that Perl and Javascript use:
##
## - If the match is zero-width, then the string is still split:
## ``"123".split(r"") == @["1", "2", "3"]``.
##
## - If the pattern has a capture in it, it is added after the string
## split: ``"12".split(re"(\d)") == @["", "1", "", "2", ""]``.
##
## - If ``maxsplit != -1``, then the string will only be split
## ``maxsplit - 1`` times. This means that there will be ``maxsplit``
## strings in the output seq.
## ``"1.2.3".split(re"\.", maxsplit = 2) == @["1", "2.3"]``
##
## ``start`` behaves the same as in ```find(...)`` <#proc-find>`__.
result = @[]
var lastIdx = start
var splits = 0
var bounds = 0 .. 0
for match in str.findIter(pattern, start = start):
# bounds are inclusive:
#
# 0123456
# ^^^
# (1, 3)
bounds = match.matchBounds
# "12".split("") would be @["", "1", "2"], but
# if we skip an empty first match, it's the correct
# @["1", "2"]
if bounds.a <= bounds.b or bounds.a > start:
result.add(str.substr(lastIdx, bounds.a - 1))
splits += 1
lastIdx = bounds.b + 1
for cap in match.captures:
# if there are captures, include them in the result
result.add(cap)
if splits == maxSplit - 1:
break
# "12".split("\b") would be @["1", "2", ""], but
# if we skip an empty last match, it's the correct
# @["1", "2"]
if bounds.a <= bounds.b or bounds.b < str.high:
# last match: Each match takes the previous substring,
# but "1 2".split(/ /) needs to return @["1", "2"].
# This handles "2"
result.add(str.substr(bounds.b + 1, str.high))
template replaceImpl(str: string, pattern: Regex,
replacement: expr): stmt {.immediate, dirty.} =
# XXX seems very similar to split, maybe I can reduce code duplication
# somehow?
result = ""
var lastIdx = 0
for match {.inject.} in str.findIter(pattern):
let bounds = match.matchBounds
result.add(str.substr(lastIdx, bounds.a - 1))
let nextVal = replacement
assert(nextVal != nil)
result.add(nextVal)
lastIdx = bounds.b + 1
result.add(str.substr(lastIdx, str.len - 1))
return result
proc replace*(str: string, pattern: Regex,
subproc: proc (match: RegexMatch): string): string =
## Replaces each match of Regex in the string with ``sub``, which should
## never be or return ``nil``.
##
## If ``sub`` is a ``proc (RegexMatch): string``, then it is executed with
## each match and the return value is the replacement value.
##
## If ``sub`` is a ``proc (string): string``, then it is executed with the
## full text of the match and and the return value is the replacement
## value.
##
## If ``sub`` is a string, the syntax is as follows:
##
## - ``$$`` - literal ``$``
## - ``$123`` - capture number ``123``
## - ``$foo`` - named capture ``foo``
## - ``${foo}`` - same as above
## - ``$1$#`` - first and second captures
## - ``$#`` - first capture
## - ``$0`` - full match
##
## If a given capture is missing, a ``ValueError`` exception is thrown.
replaceImpl(str, pattern, subproc(match))
proc replace*(str: string, pattern: Regex,
subproc: proc (match: string): string): string =
replaceImpl(str, pattern, subproc(match.match))
proc replace*(str: string, pattern: Regex, sub: string): string =
# - 1 because the string numbers are 0-indexed
replaceImpl(str, pattern,
formatStr(sub, match.captures[name], match.captures[id - 1]))
# }}}
let SpecialCharMatcher = re"([\\+*?[^\]$(){}=!<>|:-])"
proc escapeRe*(str: string): string =
## Escapes the string so it doesn’t match any special characters.
## Incompatible with the Extra flag (``X``).
str.replace(SpecialCharMatcher, "\\$1")

9
lib/impure/nre/.gitignore vendored Normal file
View file

@ -0,0 +1,9 @@
# all executables
*
!*/
!*.*
*.exe
# Wildcard patterns.
*.swp
nimcache

View file

@ -0,0 +1,63 @@
## 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'}
proc checkNil(arg: string): string =
if arg == nil:
raise newException(ValueError, "Cannot use nil capture")
else:
return arg
template formatStr*(howExpr, namegetter, idgetter: expr): expr =
let how = howExpr
var val = newStringOfCap(how.len)
var i = 0
var lastNum = 1
while i < how.len:
if how[i] != '$':
val.add(how[i])
i += 1
else:
if how[i + 1] == '$':
val.add('$')
i += 2
elif how[i + 1] == '#':
var id {.inject.} = lastNum
val.add(checkNil(idgetter))
lastNum += 1
i += 2
elif how[i + 1] in {'0'..'9'}:
i += 1
var id {.inject.} = 0
while i < how.len and how[i] in {'0'..'9'}:
id += (id * 10) + (ord(how[i]) - ord('0'))
i += 1
val.add(checkNil(idgetter))
lastNum = id + 1
elif how[i + 1] in StartIdent:
i += 1
var name {.inject.} = ""
while i < how.len and how[i] in Ident:
name.add(how[i])
i += 1
val.add(checkNil(namegetter))
elif how[i + 1] == '{':
i += 2
var name {.inject.} = ""
while i < how.len and how[i] != '}':
name.add(how[i])
i += 1
i += 1
val.add(checkNil(namegetter))
else:
raise newException(Exception, "Syntax error in format string at " & $i)
val

View file

@ -135,7 +135,7 @@ else:
var cur, old: Termios var cur, old: Termios
discard fd.tcgetattr(cur.addr) discard fd.tcgetattr(cur.addr)
old = cur old = cur
cur.c_lflag = cur.c_lflag and not Tcflag(ECHO) cur.c_lflag = cur.c_lflag and not Cflag(ECHO)
discard fd.tcsetattr(TCSADRAIN, cur.addr) discard fd.tcsetattr(TCSADRAIN, cur.addr)
stdout.write prompt stdout.write prompt
result = stdin.readLine(password) result = stdin.readLine(password)

View file

@ -7,11 +7,8 @@
# distribution, for details about the copyright. # distribution, for details about the copyright.
# #
## Regular expression support for Nim. Consider using the pegs module instead. ## Regular expression support for Nim. Deprecated. Consider using the ``nre``
## ## or ``pegs`` modules instead.
## There is an alternative regular expressions library with a more unified API:
## `nre <https://github.com/flaviut/nre>`_. It may be added to the standard
## library in the future, instead of `re`.
## ##
## **Note:** The 're' proc defaults to the **extended regular expression ## **Note:** The 're' proc defaults to the **extended regular expression
## syntax** which lets you use whitespace freely to make your regexes readable. ## syntax** which lets you use whitespace freely to make your regexes readable.
@ -49,7 +46,7 @@ type
h: ptr Pcre h: ptr Pcre
e: ptr ExtraData e: ptr ExtraData
Regex* = ref RegexDesc ## a compiled regular expression Regex* {.deprecated.} = ref RegexDesc ## a compiled regular expression
RegexError* = object of ValueError RegexError* = object of ValueError
## is raised if the pattern is no valid regular expression. ## is raised if the pattern is no valid regular expression.
@ -79,7 +76,7 @@ proc finalizeRegEx(x: Regex) =
if not isNil(x.e): if not isNil(x.e):
pcre.free_substring(cast[cstring](x.e)) pcre.free_substring(cast[cstring](x.e))
proc re*(s: string, flags = {reExtended, reStudy}): Regex = proc re*(s: string, flags = {reExtended, reStudy}): Regex {.deprecated.} =
## Constructor of regular expressions. Note that Nim's ## Constructor of regular expressions. Note that Nim's
## extended raw string literals support this syntax ``re"[abc]"`` as ## extended raw string literals support this syntax ``re"[abc]"`` as
## a short form for ``re(r"[abc]")``. ## a short form for ``re(r"[abc]")``.

View file

@ -15,11 +15,12 @@
import openssl, strutils, os import openssl, strutils, os
type type
TSecureSocket* = object SecureSocket* = object
ssl: SslPtr ssl: SslPtr
bio: BIO bio: BIO
{.deprecated: [TSecureSocket: SecureSocket].}
proc connect*(sock: var TSecureSocket, address: string, proc connect*(sock: var SecureSocket, address: string,
port: int): int = port: int): int =
## Connects to the specified `address` on the specified `port`. ## Connects to the specified `address` on the specified `port`.
## Returns the result of the certificate validation. ## Returns the result of the certificate validation.
@ -52,7 +53,7 @@ proc connect*(sock: var TSecureSocket, address: string,
result = SSL_get_verify_result(sock.ssl) result = SSL_get_verify_result(sock.ssl)
proc recvLine*(sock: TSecureSocket, line: var TaintedString): bool = proc recvLine*(sock: SecureSocket, line: var TaintedString): bool =
## Acts in a similar fashion to the `recvLine` in the sockets module. ## Acts in a similar fashion to the `recvLine` in the sockets module.
## Returns false when no data is available to be read. ## Returns false when no data is available to be read.
## `Line` must be initialized and not nil! ## `Line` must be initialized and not nil!
@ -71,19 +72,19 @@ proc recvLine*(sock: TSecureSocket, line: var TaintedString): bool =
add(line.string, c) add(line.string, c)
proc send*(sock: TSecureSocket, data: string) = proc send*(sock: SecureSocket, data: string) =
## Writes `data` to the socket. ## Writes `data` to the socket.
if BIO_write(sock.bio, data, data.len.cint) <= 0: if BIO_write(sock.bio, data, data.len.cint) <= 0:
raiseOSError(osLastError()) raiseOSError(osLastError())
proc close*(sock: TSecureSocket) = proc close*(sock: SecureSocket) =
## Closes the socket ## Closes the socket
if BIO_free(sock.bio) <= 0: if BIO_free(sock.bio) <= 0:
ERR_print_errors_fp(stderr) ERR_print_errors_fp(stderr)
raiseOSError(osLastError()) raiseOSError(osLastError())
when not defined(testing) and isMainModule: when not defined(testing) and isMainModule:
var s: TSecureSocket var s: SecureSocket
echo connect(s, "smtp.gmail.com", 465) echo connect(s, "smtp.gmail.com", 465)
#var buffer: array[0..255, char] #var buffer: array[0..255, char]

View file

@ -13,18 +13,18 @@ import
streams, libzip, times, os, strutils streams, libzip, times, os, strutils
type type
TZipArchive* = object of RootObj ## represents a zip archive ZipArchive* = object of RootObj ## represents a zip archive
mode: FileMode mode: FileMode
w: PZip w: PZip
{.deprecated: [TZipArchive: ZipArchive].}
proc zipError(z: var ZipArchive) =
proc zipError(z: var TZipArchive) =
var e: ref IOError var e: ref IOError
new(e) new(e)
e.msg = $zip_strerror(z.w) e.msg = $zip_strerror(z.w)
raise e raise e
proc open*(z: var TZipArchive, filename: string, mode: FileMode = fmRead): bool = proc open*(z: var ZipArchive, filename: string, mode: FileMode = fmRead): bool =
## Opens a zip file for reading, writing or appending. All file modes are ## Opens a zip file for reading, writing or appending. All file modes are
## supported. Returns true iff successful, false otherwise. ## supported. Returns true iff successful, false otherwise.
var err, flags: int32 var err, flags: int32
@ -38,11 +38,11 @@ proc open*(z: var TZipArchive, filename: string, mode: FileMode = fmRead): bool
z.mode = mode z.mode = mode
result = z.w != nil result = z.w != nil
proc close*(z: var TZipArchive) = proc close*(z: var ZipArchive) =
## Closes a zip file. ## Closes a zip file.
zip_close(z.w) zip_close(z.w)
proc createDir*(z: var TZipArchive, dir: string) = proc createDir*(z: var ZipArchive, dir: string) =
## Creates a directory within the `z` archive. This does not fail if the ## Creates a directory within the `z` archive. This does not fail if the
## directory already exists. Note that for adding a file like ## directory already exists. Note that for adding a file like
## ``"path1/path2/filename"`` it is not necessary ## ``"path1/path2/filename"`` it is not necessary
@ -52,7 +52,7 @@ proc createDir*(z: var TZipArchive, dir: string) =
discard zip_add_dir(z.w, dir) discard zip_add_dir(z.w, dir)
zip_error_clear(z.w) zip_error_clear(z.w)
proc addFile*(z: var TZipArchive, dest, src: string) = proc addFile*(z: var ZipArchive, dest, src: string) =
## Adds the file `src` to the archive `z` with the name `dest`. `dest` ## Adds the file `src` to the archive `z` with the name `dest`. `dest`
## may contain a path that will be created. ## may contain a path that will be created.
assert(z.mode != fmRead) assert(z.mode != fmRead)
@ -67,13 +67,13 @@ proc addFile*(z: var TZipArchive, dest, src: string) =
zip_source_free(zipsrc) zip_source_free(zipsrc)
zipError(z) zipError(z)
proc addFile*(z: var TZipArchive, file: string) = proc addFile*(z: var ZipArchive, file: string) =
## A shortcut for ``addFile(z, file, file)``, i.e. the name of the source is ## A shortcut for ``addFile(z, file, file)``, i.e. the name of the source is
## the name of the destination. ## the name of the destination.
addFile(z, file, file) addFile(z, file, file)
proc mySourceCallback(state, data: pointer, len: int, proc mySourceCallback(state, data: pointer, len: int,
cmd: TZipSourceCmd): int {.cdecl.} = cmd: ZipSourceCmd): int {.cdecl.} =
var src = cast[Stream](state) var src = cast[Stream](state)
case cmd case cmd
of ZIP_SOURCE_OPEN: of ZIP_SOURCE_OPEN:
@ -86,7 +86,7 @@ proc mySourceCallback(state, data: pointer, len: int,
zip_stat_init(stat) zip_stat_init(stat)
stat.size = high(int32)-1 # we don't know the size stat.size = high(int32)-1 # we don't know the size
stat.mtime = getTime() stat.mtime = getTime()
result = sizeof(TZipStat) result = sizeof(ZipStat)
of ZIP_SOURCE_ERROR: of ZIP_SOURCE_ERROR:
var err = cast[ptr array[0..1, cint]](data) var err = cast[ptr array[0..1, cint]](data)
err[0] = ZIP_ER_INTERNAL err[0] = ZIP_ER_INTERNAL
@ -95,7 +95,7 @@ proc mySourceCallback(state, data: pointer, len: int,
of constZIP_SOURCE_FREE: GC_unref(src) of constZIP_SOURCE_FREE: GC_unref(src)
else: assert(false) else: assert(false)
proc addFile*(z: var TZipArchive, dest: string, src: Stream) = proc addFile*(z: var ZipArchive, dest: string, src: Stream) =
## Adds a file named with `dest` to the archive `z`. `dest` ## Adds a file named with `dest` to the archive `z`. `dest`
## may contain a path. The file's content is read from the `src` stream. ## may contain a path. The file's content is read from the `src` stream.
assert(z.mode != fmRead) assert(z.mode != fmRead)
@ -134,7 +134,7 @@ proc newZipFileStream(f: PZipFile): PZipFileStream =
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
proc getStream*(z: var TZipArchive, filename: string): PZipFileStream = proc getStream*(z: var ZipArchive, filename: string): PZipFileStream =
## returns a stream that can be used to read the file named `filename` ## returns a stream that can be used to read the file named `filename`
## from the archive `z`. Returns nil in case of an error. ## from the archive `z`. Returns nil in case of an error.
## The returned stream does not support the `setPosition`, `getPosition`, ## The returned stream does not support the `setPosition`, `getPosition`,
@ -142,7 +142,7 @@ proc getStream*(z: var TZipArchive, filename: string): PZipFileStream =
var x = zip_fopen(z.w, filename, 0'i32) var x = zip_fopen(z.w, filename, 0'i32)
if x != nil: result = newZipFileStream(x) if x != nil: result = newZipFileStream(x)
iterator walkFiles*(z: var TZipArchive): string = iterator walkFiles*(z: var ZipArchive): string =
## walks over all files in the archive `z` and returns the filename ## walks over all files in the archive `z` and returns the filename
## (including the path). ## (including the path).
var i = 0'i32 var i = 0'i32
@ -152,7 +152,7 @@ iterator walkFiles*(z: var TZipArchive): string =
inc(i) inc(i)
proc extractFile*(z: var TZipArchive, srcFile: string, dest: Stream) = proc extractFile*(z: var ZipArchive, srcFile: string, dest: Stream) =
## extracts a file from the zip archive `z` to the destination stream. ## extracts a file from the zip archive `z` to the destination stream.
var strm = getStream(z, srcFile) var strm = getStream(z, srcFile)
while true: while true:
@ -162,13 +162,13 @@ proc extractFile*(z: var TZipArchive, srcFile: string, dest: Stream) =
dest.flush() dest.flush()
strm.close() strm.close()
proc extractFile*(z: var TZipArchive, srcFile: string, dest: string) = proc extractFile*(z: var ZipArchive, srcFile: string, dest: string) =
## extracts a file from the zip archive `z` to the destination filename. ## extracts a file from the zip archive `z` to the destination filename.
var file = newFileStream(dest, fmWrite) var file = newFileStream(dest, fmWrite)
extractFile(z, srcFile, file) extractFile(z, srcFile, file)
file.close() file.close()
proc extractAll*(z: var TZipArchive, dest: string) = proc extractAll*(z: var ZipArchive, dest: string) =
## extracts all files from archive `z` to the destination directory. ## extracts all files from archive `z` to the destination directory.
for file in walkFiles(z): for file in walkFiles(z):
if file.endsWith("/"): if file.endsWith("/"):
@ -177,7 +177,7 @@ proc extractAll*(z: var TZipArchive, dest: string) =
extractFile(z, file, dest / file) extractFile(z, file, dest / file)
when not defined(testing) and isMainModule: when not defined(testing) and isMainModule:
var zip: TZipArchive var zip: ZipArchive
if not zip.open("nim-0.11.0.zip"): if not zip.open("nim-0.11.0.zip"):
raise newException(IOError, "opening zip failed") raise newException(IOError, "opening zip failed")
zip.extractAll("test") zip.extractAll("test")

View file

@ -15,7 +15,7 @@ import
strutils strutils
type type
TTokenClass* = enum TokenClass* = enum
gtEof, gtNone, gtWhitespace, gtDecNumber, gtBinNumber, gtHexNumber, gtEof, gtNone, gtWhitespace, gtDecNumber, gtBinNumber, gtHexNumber,
gtOctNumber, gtFloatNumber, gtIdentifier, gtKeyword, gtStringLit, gtOctNumber, gtFloatNumber, gtIdentifier, gtKeyword, gtStringLit,
gtLongStringLit, gtCharLit, gtEscapeSequence, # escape sequence like \xff gtLongStringLit, gtCharLit, gtEscapeSequence, # escape sequence like \xff
@ -23,20 +23,22 @@ type
gtTagStart, gtTagEnd, gtKey, gtValue, gtRawData, gtAssembler, gtTagStart, gtTagEnd, gtKey, gtValue, gtRawData, gtAssembler,
gtPreprocessor, gtDirective, gtCommand, gtRule, gtHyperlink, gtLabel, gtPreprocessor, gtDirective, gtCommand, gtRule, gtHyperlink, gtLabel,
gtReference, gtOther gtReference, gtOther
TGeneralTokenizer* = object of RootObj GeneralTokenizer* = object of RootObj
kind*: TTokenClass kind*: TokenClass
start*, length*: int start*, length*: int
buf: cstring buf: cstring
pos: int pos: int
state: TTokenClass state: TokenClass
TSourceLanguage* = enum SourceLanguage* = enum
langNone, langNim, langNimrod, langCpp, langCsharp, langC, langJava langNone, langNim, langNimrod, langCpp, langCsharp, langC, langJava
{.deprecated: [TSourceLanguage: SourceLanguage, TTokenClass: TokenClass,
TGeneralTokenizer: GeneralTokenizer].}
const const
sourceLanguageToStr*: array[TSourceLanguage, string] = ["none", sourceLanguageToStr*: array[SourceLanguage, string] = ["none",
"Nim", "Nimrod", "C++", "C#", "C", "Java"] "Nim", "Nimrod", "C++", "C#", "C", "Java"]
tokenClassToStr*: array[TTokenClass, string] = ["Eof", "None", "Whitespace", tokenClassToStr*: array[TokenClass, string] = ["Eof", "None", "Whitespace",
"DecNumber", "BinNumber", "HexNumber", "OctNumber", "FloatNumber", "DecNumber", "BinNumber", "HexNumber", "OctNumber", "FloatNumber",
"Identifier", "Keyword", "StringLit", "LongStringLit", "CharLit", "Identifier", "Keyword", "StringLit", "LongStringLit", "CharLit",
"EscapeSequence", "Operator", "Punctuation", "Comment", "LongComment", "EscapeSequence", "Operator", "Punctuation", "Comment", "LongComment",
@ -58,29 +60,29 @@ const
"template", "try", "tuple", "type", "using", "var", "when", "while", "with", "template", "try", "tuple", "type", "using", "var", "when", "while", "with",
"without", "xor", "yield"] "without", "xor", "yield"]
proc getSourceLanguage*(name: string): TSourceLanguage = proc getSourceLanguage*(name: string): SourceLanguage =
for i in countup(succ(low(TSourceLanguage)), high(TSourceLanguage)): for i in countup(succ(low(SourceLanguage)), high(SourceLanguage)):
if cmpIgnoreStyle(name, sourceLanguageToStr[i]) == 0: if cmpIgnoreStyle(name, sourceLanguageToStr[i]) == 0:
return i return i
result = langNone result = langNone
proc initGeneralTokenizer*(g: var TGeneralTokenizer, buf: cstring) = proc initGeneralTokenizer*(g: var GeneralTokenizer, buf: cstring) =
g.buf = buf g.buf = buf
g.kind = low(TTokenClass) g.kind = low(TokenClass)
g.start = 0 g.start = 0
g.length = 0 g.length = 0
g.state = low(TTokenClass) g.state = low(TokenClass)
var pos = 0 # skip initial whitespace: var pos = 0 # skip initial whitespace:
while g.buf[pos] in {' ', '\x09'..'\x0D'}: inc(pos) while g.buf[pos] in {' ', '\x09'..'\x0D'}: inc(pos)
g.pos = pos g.pos = pos
proc initGeneralTokenizer*(g: var TGeneralTokenizer, buf: string) = proc initGeneralTokenizer*(g: var GeneralTokenizer, buf: string) =
initGeneralTokenizer(g, cstring(buf)) initGeneralTokenizer(g, cstring(buf))
proc deinitGeneralTokenizer*(g: var TGeneralTokenizer) = proc deinitGeneralTokenizer*(g: var GeneralTokenizer) =
discard discard
proc nimGetKeyword(id: string): TTokenClass = proc nimGetKeyword(id: string): TokenClass =
for k in nimKeywords: for k in nimKeywords:
if cmpIgnoreStyle(id, k) == 0: return gtKeyword if cmpIgnoreStyle(id, k) == 0: return gtKeyword
result = gtIdentifier result = gtIdentifier
@ -92,7 +94,7 @@ proc nimGetKeyword(id: string): TTokenClass =
else: else:
result = gtIdentifier result = gtIdentifier
proc nimNumberPostfix(g: var TGeneralTokenizer, position: int): int = proc nimNumberPostfix(g: var GeneralTokenizer, position: int): int =
var pos = position var pos = position
if g.buf[pos] == '\'': if g.buf[pos] == '\'':
inc(pos) inc(pos)
@ -110,7 +112,7 @@ proc nimNumberPostfix(g: var TGeneralTokenizer, position: int): int =
discard discard
result = pos result = pos
proc nimNumber(g: var TGeneralTokenizer, position: int): int = proc nimNumber(g: var GeneralTokenizer, position: int): int =
const decChars = {'0'..'9', '_'} const decChars = {'0'..'9', '_'}
var pos = position var pos = position
g.kind = gtDecNumber g.kind = gtDecNumber
@ -130,7 +132,7 @@ const
OpChars = {'+', '-', '*', '/', '\\', '<', '>', '!', '?', '^', '.', OpChars = {'+', '-', '*', '/', '\\', '<', '>', '!', '?', '^', '.',
'|', '=', '%', '&', '$', '@', '~', ':', '\x80'..'\xFF'} '|', '=', '%', '&', '$', '@', '~', ':', '\x80'..'\xFF'}
proc nimNextToken(g: var TGeneralTokenizer) = proc nimNextToken(g: var GeneralTokenizer) =
const const
hexChars = {'0'..'9', 'A'..'F', 'a'..'f', '_'} hexChars = {'0'..'9', 'A'..'F', 'a'..'f', '_'}
octChars = {'0'..'7', '_'} octChars = {'0'..'7', '_'}
@ -278,7 +280,7 @@ proc nimNextToken(g: var TGeneralTokenizer) =
assert false, "nimNextToken: produced an empty token" assert false, "nimNextToken: produced an empty token"
g.pos = pos g.pos = pos
proc generalNumber(g: var TGeneralTokenizer, position: int): int = proc generalNumber(g: var GeneralTokenizer, position: int): int =
const decChars = {'0'..'9'} const decChars = {'0'..'9'}
var pos = position var pos = position
g.kind = gtDecNumber g.kind = gtDecNumber
@ -294,7 +296,7 @@ proc generalNumber(g: var TGeneralTokenizer, position: int): int =
while g.buf[pos] in decChars: inc(pos) while g.buf[pos] in decChars: inc(pos)
result = pos result = pos
proc generalStrLit(g: var TGeneralTokenizer, position: int): int = proc generalStrLit(g: var GeneralTokenizer, position: int): int =
const const
decChars = {'0'..'9'} decChars = {'0'..'9'}
hexChars = {'0'..'9', 'A'..'F', 'a'..'f'} hexChars = {'0'..'9', 'A'..'F', 'a'..'f'}
@ -355,12 +357,13 @@ proc isKeywordIgnoreCase(x: openArray[string], y: string): int =
result = - 1 result = - 1
type type
TTokenizerFlag = enum TokenizerFlag = enum
hasPreprocessor, hasNestedComments hasPreprocessor, hasNestedComments
TTokenizerFlags = set[TTokenizerFlag] TokenizerFlags = set[TokenizerFlag]
{.deprecated: [TTokenizerFlag: TokenizerFlag, TTokenizerFlags: TokenizerFlags].}
proc clikeNextToken(g: var TGeneralTokenizer, keywords: openArray[string], proc clikeNextToken(g: var GeneralTokenizer, keywords: openArray[string],
flags: TTokenizerFlags) = flags: TokenizerFlags) =
const const
hexChars = {'0'..'9', 'A'..'F', 'a'..'f'} hexChars = {'0'..'9', 'A'..'F', 'a'..'f'}
octChars = {'0'..'7'} octChars = {'0'..'7'}
@ -493,7 +496,7 @@ proc clikeNextToken(g: var TGeneralTokenizer, keywords: openArray[string],
assert false, "clikeNextToken: produced an empty token" assert false, "clikeNextToken: produced an empty token"
g.pos = pos g.pos = pos
proc cNextToken(g: var TGeneralTokenizer) = proc cNextToken(g: var GeneralTokenizer) =
const const
keywords: array[0..36, string] = ["_Bool", "_Complex", "_Imaginary", "auto", keywords: array[0..36, string] = ["_Bool", "_Complex", "_Imaginary", "auto",
"break", "case", "char", "const", "continue", "default", "do", "double", "break", "case", "char", "const", "continue", "default", "do", "double",
@ -503,7 +506,7 @@ proc cNextToken(g: var TGeneralTokenizer) =
"volatile", "while"] "volatile", "while"]
clikeNextToken(g, keywords, {hasPreprocessor}) clikeNextToken(g, keywords, {hasPreprocessor})
proc cppNextToken(g: var TGeneralTokenizer) = proc cppNextToken(g: var GeneralTokenizer) =
const const
keywords: array[0..47, string] = ["asm", "auto", "break", "case", "catch", keywords: array[0..47, string] = ["asm", "auto", "break", "case", "catch",
"char", "class", "const", "continue", "default", "delete", "do", "double", "char", "class", "const", "continue", "default", "delete", "do", "double",
@ -514,7 +517,7 @@ proc cppNextToken(g: var TGeneralTokenizer) =
"union", "unsigned", "virtual", "void", "volatile", "while"] "union", "unsigned", "virtual", "void", "volatile", "while"]
clikeNextToken(g, keywords, {hasPreprocessor}) clikeNextToken(g, keywords, {hasPreprocessor})
proc csharpNextToken(g: var TGeneralTokenizer) = proc csharpNextToken(g: var GeneralTokenizer) =
const const
keywords: array[0..76, string] = ["abstract", "as", "base", "bool", "break", keywords: array[0..76, string] = ["abstract", "as", "base", "bool", "break",
"byte", "case", "catch", "char", "checked", "class", "const", "continue", "byte", "case", "catch", "char", "checked", "class", "const", "continue",
@ -529,7 +532,7 @@ proc csharpNextToken(g: var TGeneralTokenizer) =
"virtual", "void", "volatile", "while"] "virtual", "void", "volatile", "while"]
clikeNextToken(g, keywords, {hasPreprocessor}) clikeNextToken(g, keywords, {hasPreprocessor})
proc javaNextToken(g: var TGeneralTokenizer) = proc javaNextToken(g: var GeneralTokenizer) =
const const
keywords: array[0..52, string] = ["abstract", "assert", "boolean", "break", keywords: array[0..52, string] = ["abstract", "assert", "boolean", "break",
"byte", "case", "catch", "char", "class", "const", "continue", "default", "byte", "case", "catch", "char", "class", "const", "continue", "default",
@ -541,7 +544,7 @@ proc javaNextToken(g: var TGeneralTokenizer) =
"try", "void", "volatile", "while"] "try", "void", "volatile", "while"]
clikeNextToken(g, keywords, {}) clikeNextToken(g, keywords, {})
proc getNextToken*(g: var TGeneralTokenizer, lang: TSourceLanguage) = proc getNextToken*(g: var GeneralTokenizer, lang: SourceLanguage) =
case lang case lang
of langNone: assert false of langNone: assert false
of langNim, langNimrod: nimNextToken(g) of langNim, langNimrod: nimNextToken(g)

View file

@ -15,7 +15,7 @@ import
os, strutils, rstast os, strutils, rstast
type type
TRstParseOption* = enum ## options for the RST parser RstParseOption* = enum ## options for the RST parser
roSkipPounds, ## skip ``#`` at line beginning (documentation roSkipPounds, ## skip ``#`` at line beginning (documentation
## embedded in Nim comments) ## embedded in Nim comments)
roSupportSmilies, ## make the RST parser support smilies like ``:)`` roSupportSmilies, ## make the RST parser support smilies like ``:)``
@ -23,14 +23,14 @@ type
## it for sandboxing) ## it for sandboxing)
roSupportMarkdown ## support additional features of markdown roSupportMarkdown ## support additional features of markdown
TRstParseOptions* = set[TRstParseOption] RstParseOptions* = set[RstParseOption]
TMsgClass* = enum MsgClass* = enum
mcHint = "Hint", mcHint = "Hint",
mcWarning = "Warning", mcWarning = "Warning",
mcError = "Error" mcError = "Error"
TMsgKind* = enum ## the possible messages MsgKind* = enum ## the possible messages
meCannotOpenFile, meCannotOpenFile,
meExpected, meExpected,
meGridTableNotImplemented, meGridTableNotImplemented,
@ -42,12 +42,14 @@ type
mwUnsupportedLanguage, mwUnsupportedLanguage,
mwUnsupportedField mwUnsupportedField
TMsgHandler* = proc (filename: string, line, col: int, msgKind: TMsgKind, MsgHandler* = proc (filename: string, line, col: int, msgKind: MsgKind,
arg: string) {.nimcall.} ## what to do in case of an error arg: string) {.nimcall.} ## what to do in case of an error
TFindFileHandler* = proc (filename: string): string {.nimcall.} FindFileHandler* = proc (filename: string): string {.nimcall.}
{.deprecated: [TRstParseOptions: RstParseOptions, TRstParseOption: RstParseOption,
TMsgKind: MsgKind].}
const const
messages: array [TMsgKind, string] = [ messages: array [MsgKind, string] = [
meCannotOpenFile: "cannot open '$1'", meCannotOpenFile: "cannot open '$1'",
meExpected: "'$1' expected", meExpected: "'$1' expected",
meGridTableNotImplemented: "grid table is not implemented", meGridTableNotImplemented: "grid table is not implemented",
@ -111,23 +113,24 @@ const
} }
type type
TTokType = enum TokType = enum
tkEof, tkIndent, tkWhite, tkWord, tkAdornment, tkPunct, tkOther tkEof, tkIndent, tkWhite, tkWord, tkAdornment, tkPunct, tkOther
TToken = object # a RST token Token = object # a RST token
kind*: TTokType # the type of the token kind*: TokType # the type of the token
ival*: int # the indentation or parsed integer value ival*: int # the indentation or parsed integer value
symbol*: string # the parsed symbol as string symbol*: string # the parsed symbol as string
line*, col*: int # line and column of the token line*, col*: int # line and column of the token
TTokenSeq = seq[TToken] TokenSeq = seq[Token]
TLexer = object of RootObj Lexer = object of RootObj
buf*: cstring buf*: cstring
bufpos*: int bufpos*: int
line*, col*, baseIndent*: int line*, col*, baseIndent*: int
skipPounds*: bool skipPounds*: bool
{.deprecated: [TTokType: TokType, TToken: Token, TTokenSeq: TokenSeq,
TLexer: Lexer].}
proc getThing(L: var Lexer, tok: var Token, s: set[char]) =
proc getThing(L: var TLexer, tok: var TToken, s: set[char]) =
tok.kind = tkWord tok.kind = tkWord
tok.line = L.line tok.line = L.line
tok.col = L.col tok.col = L.col
@ -139,7 +142,7 @@ proc getThing(L: var TLexer, tok: var TToken, s: set[char]) =
inc(L.col, pos - L.bufpos) inc(L.col, pos - L.bufpos)
L.bufpos = pos L.bufpos = pos
proc getAdornment(L: var TLexer, tok: var TToken) = proc getAdornment(L: var Lexer, tok: var Token) =
tok.kind = tkAdornment tok.kind = tkAdornment
tok.line = L.line tok.line = L.line
tok.col = L.col tok.col = L.col
@ -152,7 +155,7 @@ proc getAdornment(L: var TLexer, tok: var TToken) =
inc(L.col, pos - L.bufpos) inc(L.col, pos - L.bufpos)
L.bufpos = pos L.bufpos = pos
proc getIndentAux(L: var TLexer, start: int): int = proc getIndentAux(L: var Lexer, start: int): int =
var pos = start var pos = start
var buf = L.buf var buf = L.buf
# skip the newline (but include it in the token!) # skip the newline (but include it in the token!)
@ -181,7 +184,7 @@ proc getIndentAux(L: var TLexer, start: int): int =
result = getIndentAux(L, pos) result = getIndentAux(L, pos)
L.bufpos = pos # no need to set back buf L.bufpos = pos # no need to set back buf
proc getIndent(L: var TLexer, tok: var TToken) = proc getIndent(L: var Lexer, tok: var Token) =
tok.col = 0 tok.col = 0
tok.kind = tkIndent # skip the newline (but include it in the token!) tok.kind = tkIndent # skip the newline (but include it in the token!)
tok.ival = getIndentAux(L, L.bufpos) tok.ival = getIndentAux(L, L.bufpos)
@ -191,7 +194,7 @@ proc getIndent(L: var TLexer, tok: var TToken) =
tok.ival = max(tok.ival - L.baseIndent, 0) tok.ival = max(tok.ival - L.baseIndent, 0)
tok.symbol = "\n" & spaces(tok.ival) tok.symbol = "\n" & spaces(tok.ival)
proc rawGetTok(L: var TLexer, tok: var TToken) = proc rawGetTok(L: var Lexer, tok: var Token) =
tok.symbol = "" tok.symbol = ""
tok.ival = 0 tok.ival = 0
var c = L.buf[L.bufpos] var c = L.buf[L.bufpos]
@ -222,8 +225,8 @@ proc rawGetTok(L: var TLexer, tok: var TToken) =
inc(L.col) inc(L.col)
tok.col = max(tok.col - L.baseIndent, 0) tok.col = max(tok.col - L.baseIndent, 0)
proc getTokens(buffer: string, skipPounds: bool, tokens: var TTokenSeq): int = proc getTokens(buffer: string, skipPounds: bool, tokens: var TokenSeq): int =
var L: TLexer var L: Lexer
var length = len(tokens) var length = len(tokens)
L.buf = cstring(buffer) L.buf = cstring(buffer)
L.line = 0 # skip UTF-8 BOM L.line = 0 # skip UTF-8 BOM
@ -253,31 +256,31 @@ proc getTokens(buffer: string, skipPounds: bool, tokens: var TTokenSeq): int =
tokens[0].kind = tkIndent tokens[0].kind = tkIndent
type type
TLevelMap = array[char, int] LevelMap = array[char, int]
TSubstitution = object Substitution = object
key*: string key*: string
value*: PRstNode value*: PRstNode
TSharedState = object SharedState = object
options: TRstParseOptions # parsing options options: RstParseOptions # parsing options
uLevel, oLevel: int # counters for the section levels uLevel, oLevel: int # counters for the section levels
subs: seq[TSubstitution] # substitutions subs: seq[Substitution] # substitutions
refs: seq[TSubstitution] # references refs: seq[Substitution] # references
underlineToLevel: TLevelMap # Saves for each possible title adornment underlineToLevel: LevelMap # Saves for each possible title adornment
# character its level in the # character its level in the
# current document. # current document.
# This is for single underline adornments. # This is for single underline adornments.
overlineToLevel: TLevelMap # Saves for each possible title adornment overlineToLevel: LevelMap # Saves for each possible title adornment
# character its level in the current # character its level in the current
# document. # document.
# This is for over-underline adornments. # This is for over-underline adornments.
msgHandler: TMsgHandler # How to handle errors. msgHandler: MsgHandler # How to handle errors.
findFile: TFindFileHandler # How to find files. findFile: FindFileHandler # How to find files.
PSharedState = ref TSharedState PSharedState = ref SharedState
TRstParser = object of RootObj RstParser = object of RootObj
idx*: int idx*: int
tok*: TTokenSeq tok*: TokenSeq
s*: PSharedState s*: PSharedState
indentStack*: seq[int] indentStack*: seq[int]
filename*: string filename*: string
@ -285,8 +288,12 @@ type
hasToc*: bool hasToc*: bool
EParseError* = object of ValueError EParseError* = object of ValueError
{.deprecated: [TLevelMap: LevelMap, TSubstitution: Substitution,
TSharedState: SharedState, TRstParser: RstParser,
TMsgHandler: MsgHandler, TFindFileHandler: FindFileHandler,
TMsgClass: MsgClass].}
proc whichMsgClass*(k: TMsgKind): TMsgClass = proc whichMsgClass*(k: MsgKind): MsgClass =
## returns which message class `k` belongs to. ## returns which message class `k` belongs to.
case ($k)[1] case ($k)[1]
of 'e', 'E': result = mcError of 'e', 'E': result = mcError
@ -294,7 +301,7 @@ proc whichMsgClass*(k: TMsgKind): TMsgClass =
of 'h', 'H': result = mcHint of 'h', 'H': result = mcHint
else: assert false, "msgkind does not fit naming scheme" else: assert false, "msgkind does not fit naming scheme"
proc defaultMsgHandler*(filename: string, line, col: int, msgkind: TMsgKind, proc defaultMsgHandler*(filename: string, line, col: int, msgkind: MsgKind,
arg: string) {.procvar.} = arg: string) {.procvar.} =
let mc = msgkind.whichMsgClass let mc = msgkind.whichMsgClass
let a = messages[msgkind] % arg let a = messages[msgkind] % arg
@ -306,9 +313,9 @@ proc defaultFindFile*(filename: string): string {.procvar.} =
if existsFile(filename): result = filename if existsFile(filename): result = filename
else: result = "" else: result = ""
proc newSharedState(options: TRstParseOptions, proc newSharedState(options: RstParseOptions,
findFile: TFindFileHandler, findFile: FindFileHandler,
msgHandler: TMsgHandler): PSharedState = msgHandler: MsgHandler): PSharedState =
new(result) new(result)
result.subs = @[] result.subs = @[]
result.refs = @[] result.refs = @[]
@ -316,34 +323,34 @@ proc newSharedState(options: TRstParseOptions,
result.msgHandler = if not isNil(msgHandler): msgHandler else: defaultMsgHandler result.msgHandler = if not isNil(msgHandler): msgHandler else: defaultMsgHandler
result.findFile = if not isNil(findFile): findFile else: defaultFindFile result.findFile = if not isNil(findFile): findFile else: defaultFindFile
proc rstMessage(p: TRstParser, msgKind: TMsgKind, arg: string) = proc rstMessage(p: RstParser, msgKind: MsgKind, arg: string) =
p.s.msgHandler(p.filename, p.line + p.tok[p.idx].line, p.s.msgHandler(p.filename, p.line + p.tok[p.idx].line,
p.col + p.tok[p.idx].col, msgKind, arg) p.col + p.tok[p.idx].col, msgKind, arg)
proc rstMessage(p: TRstParser, msgKind: TMsgKind, arg: string, line, col: int) = proc rstMessage(p: RstParser, msgKind: MsgKind, arg: string, line, col: int) =
p.s.msgHandler(p.filename, p.line + line, p.s.msgHandler(p.filename, p.line + line,
p.col + col, msgKind, arg) p.col + col, msgKind, arg)
proc rstMessage(p: TRstParser, msgKind: TMsgKind) = proc rstMessage(p: RstParser, msgKind: MsgKind) =
p.s.msgHandler(p.filename, p.line + p.tok[p.idx].line, p.s.msgHandler(p.filename, p.line + p.tok[p.idx].line,
p.col + p.tok[p.idx].col, msgKind, p.col + p.tok[p.idx].col, msgKind,
p.tok[p.idx].symbol) p.tok[p.idx].symbol)
when false: when false:
proc corrupt(p: TRstParser) = proc corrupt(p: RstParser) =
assert p.indentStack[0] == 0 assert p.indentStack[0] == 0
for i in 1 .. high(p.indentStack): assert p.indentStack[i] < 1_000 for i in 1 .. high(p.indentStack): assert p.indentStack[i] < 1_000
proc currInd(p: TRstParser): int = proc currInd(p: RstParser): int =
result = p.indentStack[high(p.indentStack)] result = p.indentStack[high(p.indentStack)]
proc pushInd(p: var TRstParser, ind: int) = proc pushInd(p: var RstParser, ind: int) =
add(p.indentStack, ind) add(p.indentStack, ind)
proc popInd(p: var TRstParser) = proc popInd(p: var RstParser) =
if len(p.indentStack) > 1: setLen(p.indentStack, len(p.indentStack) - 1) if len(p.indentStack) > 1: setLen(p.indentStack, len(p.indentStack) - 1)
proc initParser(p: var TRstParser, sharedState: PSharedState) = proc initParser(p: var RstParser, sharedState: PSharedState) =
p.indentStack = @[0] p.indentStack = @[0]
p.tok = @[] p.tok = @[]
p.idx = 0 p.idx = 0
@ -393,7 +400,7 @@ proc rstnodeToRefname(n: PRstNode): string =
var b = false var b = false
rstnodeToRefnameAux(n, result, b) rstnodeToRefnameAux(n, result, b)
proc findSub(p: var TRstParser, n: PRstNode): int = proc findSub(p: var RstParser, n: PRstNode): int =
var key = addNodes(n) var key = addNodes(n)
# the spec says: if no exact match, try one without case distinction: # the spec says: if no exact match, try one without case distinction:
for i in countup(0, high(p.s.subs)): for i in countup(0, high(p.s.subs)):
@ -404,7 +411,7 @@ proc findSub(p: var TRstParser, n: PRstNode): int =
return i return i
result = -1 result = -1
proc setSub(p: var TRstParser, key: string, value: PRstNode) = proc setSub(p: var RstParser, key: string, value: PRstNode) =
var length = len(p.s.subs) var length = len(p.s.subs)
for i in countup(0, length - 1): for i in countup(0, length - 1):
if key == p.s.subs[i].key: if key == p.s.subs[i].key:
@ -414,7 +421,7 @@ proc setSub(p: var TRstParser, key: string, value: PRstNode) =
p.s.subs[length].key = key p.s.subs[length].key = key
p.s.subs[length].value = value p.s.subs[length].value = value
proc setRef(p: var TRstParser, key: string, value: PRstNode) = proc setRef(p: var RstParser, key: string, value: PRstNode) =
var length = len(p.s.refs) var length = len(p.s.refs)
for i in countup(0, length - 1): for i in countup(0, length - 1):
if key == p.s.refs[i].key: if key == p.s.refs[i].key:
@ -427,15 +434,15 @@ proc setRef(p: var TRstParser, key: string, value: PRstNode) =
p.s.refs[length].key = key p.s.refs[length].key = key
p.s.refs[length].value = value p.s.refs[length].value = value
proc findRef(p: var TRstParser, key: string): PRstNode = proc findRef(p: var RstParser, key: string): PRstNode =
for i in countup(0, high(p.s.refs)): for i in countup(0, high(p.s.refs)):
if key == p.s.refs[i].key: if key == p.s.refs[i].key:
return p.s.refs[i].value return p.s.refs[i].value
proc newLeaf(p: var TRstParser): PRstNode = proc newLeaf(p: var RstParser): PRstNode =
result = newRstNode(rnLeaf, p.tok[p.idx].symbol) result = newRstNode(rnLeaf, p.tok[p.idx].symbol)
proc getReferenceName(p: var TRstParser, endStr: string): PRstNode = proc getReferenceName(p: var RstParser, endStr: string): PRstNode =
var res = newRstNode(rnInner) var res = newRstNode(rnInner)
while true: while true:
case p.tok[p.idx].kind case p.tok[p.idx].kind
@ -453,17 +460,17 @@ proc getReferenceName(p: var TRstParser, endStr: string): PRstNode =
inc(p.idx) inc(p.idx)
result = res result = res
proc untilEol(p: var TRstParser): PRstNode = proc untilEol(p: var RstParser): PRstNode =
result = newRstNode(rnInner) result = newRstNode(rnInner)
while not (p.tok[p.idx].kind in {tkIndent, tkEof}): while not (p.tok[p.idx].kind in {tkIndent, tkEof}):
add(result, newLeaf(p)) add(result, newLeaf(p))
inc(p.idx) inc(p.idx)
proc expect(p: var TRstParser, tok: string) = proc expect(p: var RstParser, tok: string) =
if p.tok[p.idx].symbol == tok: inc(p.idx) if p.tok[p.idx].symbol == tok: inc(p.idx)
else: rstMessage(p, meExpected, tok) else: rstMessage(p, meExpected, tok)
proc isInlineMarkupEnd(p: TRstParser, markup: string): bool = proc isInlineMarkupEnd(p: RstParser, markup: string): bool =
result = p.tok[p.idx].symbol == markup result = p.tok[p.idx].symbol == markup
if not result: if not result:
return # Rule 3: return # Rule 3:
@ -480,7 +487,7 @@ proc isInlineMarkupEnd(p: TRstParser, markup: string): bool =
if (markup != "``") and (p.tok[p.idx - 1].symbol == "\\"): if (markup != "``") and (p.tok[p.idx - 1].symbol == "\\"):
result = false result = false
proc isInlineMarkupStart(p: TRstParser, markup: string): bool = proc isInlineMarkupStart(p: RstParser, markup: string): bool =
var d: char var d: char
result = p.tok[p.idx].symbol == markup result = p.tok[p.idx].symbol == markup
if not result: if not result:
@ -507,7 +514,7 @@ proc isInlineMarkupStart(p: TRstParser, markup: string): bool =
else: d = '\0' else: d = '\0'
if d != '\0': result = p.tok[p.idx + 1].symbol[0] != d if d != '\0': result = p.tok[p.idx + 1].symbol[0] != d
proc match(p: TRstParser, start: int, expr: string): bool = proc match(p: RstParser, start: int, expr: string): bool =
# regular expressions are: # regular expressions are:
# special char exact match # special char exact match
# 'w' tkWord # 'w' tkWord
@ -562,7 +569,7 @@ proc fixupEmbeddedRef(n, a, b: PRstNode) =
for i in countup(0, sep - incr): add(a, n.sons[i]) for i in countup(0, sep - incr): add(a, n.sons[i])
for i in countup(sep + 1, len(n) - 2): add(b, n.sons[i]) for i in countup(sep + 1, len(n) - 2): add(b, n.sons[i])
proc parsePostfix(p: var TRstParser, n: PRstNode): PRstNode = proc parsePostfix(p: var RstParser, n: PRstNode): PRstNode =
result = n result = n
if isInlineMarkupEnd(p, "_") or isInlineMarkupEnd(p, "__"): if isInlineMarkupEnd(p, "_") or isInlineMarkupEnd(p, "__"):
inc(p.idx) inc(p.idx)
@ -606,7 +613,7 @@ proc parsePostfix(p: var TRstParser, n: PRstNode): PRstNode =
add(result, newRstNode(rnLeaf, p.tok[p.idx + 1].symbol)) add(result, newRstNode(rnLeaf, p.tok[p.idx + 1].symbol))
inc(p.idx, 3) inc(p.idx, 3)
proc matchVerbatim(p: TRstParser, start: int, expr: string): int = proc matchVerbatim(p: RstParser, start: int, expr: string): int =
result = start result = start
var j = 0 var j = 0
while j < expr.len and result < p.tok.len and while j < expr.len and result < p.tok.len and
@ -615,7 +622,7 @@ proc matchVerbatim(p: TRstParser, start: int, expr: string): int =
inc result inc result
if j < expr.len: result = 0 if j < expr.len: result = 0
proc parseSmiley(p: var TRstParser): PRstNode = proc parseSmiley(p: var RstParser): PRstNode =
if p.tok[p.idx].symbol[0] notin SmileyStartChars: return if p.tok[p.idx].symbol[0] notin SmileyStartChars: return
for key, val in items(Smilies): for key, val in items(Smilies):
let m = matchVerbatim(p, p.idx, key) let m = matchVerbatim(p, p.idx, key)
@ -631,12 +638,12 @@ when false:
'$', '(', ')', '~', '_', '?', '+', '-', '=', '\\', '.', '&', '$', '(', ')', '~', '_', '?', '+', '-', '=', '\\', '.', '&',
'\128'..'\255'} '\128'..'\255'}
proc isUrl(p: TRstParser, i: int): bool = proc isUrl(p: RstParser, i: int): bool =
result = (p.tok[i+1].symbol == ":") and (p.tok[i+2].symbol == "//") and result = (p.tok[i+1].symbol == ":") and (p.tok[i+2].symbol == "//") and
(p.tok[i+3].kind == tkWord) and (p.tok[i+3].kind == tkWord) and
(p.tok[i].symbol in ["http", "https", "ftp", "telnet", "file"]) (p.tok[i].symbol in ["http", "https", "ftp", "telnet", "file"])
proc parseUrl(p: var TRstParser, father: PRstNode) = proc parseUrl(p: var RstParser, father: PRstNode) =
#if p.tok[p.idx].symbol[strStart] == '<': #if p.tok[p.idx].symbol[strStart] == '<':
if isUrl(p, p.idx): if isUrl(p, p.idx):
var n = newRstNode(rnStandaloneHyperlink) var n = newRstNode(rnStandaloneHyperlink)
@ -656,7 +663,7 @@ proc parseUrl(p: var TRstParser, father: PRstNode) =
if p.tok[p.idx].symbol == "_": n = parsePostfix(p, n) if p.tok[p.idx].symbol == "_": n = parsePostfix(p, n)
add(father, n) add(father, n)
proc parseBackslash(p: var TRstParser, father: PRstNode) = proc parseBackslash(p: var RstParser, father: PRstNode) =
assert(p.tok[p.idx].kind == tkPunct) assert(p.tok[p.idx].kind == tkPunct)
if p.tok[p.idx].symbol == "\\\\": if p.tok[p.idx].symbol == "\\\\":
add(father, newRstNode(rnLeaf, "\\")) add(father, newRstNode(rnLeaf, "\\"))
@ -671,7 +678,7 @@ proc parseBackslash(p: var TRstParser, father: PRstNode) =
inc(p.idx) inc(p.idx)
when false: when false:
proc parseAdhoc(p: var TRstParser, father: PRstNode, verbatim: bool) = proc parseAdhoc(p: var RstParser, father: PRstNode, verbatim: bool) =
if not verbatim and isURL(p, p.idx): if not verbatim and isURL(p, p.idx):
var n = newRstNode(rnStandaloneHyperlink) var n = newRstNode(rnStandaloneHyperlink)
while true: while true:
@ -694,7 +701,7 @@ when false:
if p.tok[p.idx].symbol == "_": n = parsePostfix(p, n) if p.tok[p.idx].symbol == "_": n = parsePostfix(p, n)
add(father, n) add(father, n)
proc parseUntil(p: var TRstParser, father: PRstNode, postfix: string, proc parseUntil(p: var RstParser, father: PRstNode, postfix: string,
interpretBackslash: bool) = interpretBackslash: bool) =
let let
line = p.tok[p.idx].line line = p.tok[p.idx].line
@ -725,7 +732,7 @@ proc parseUntil(p: var TRstParser, father: PRstNode, postfix: string,
inc(p.idx) inc(p.idx)
else: rstMessage(p, meExpected, postfix, line, col) else: rstMessage(p, meExpected, postfix, line, col)
proc parseMarkdownCodeblock(p: var TRstParser): PRstNode = proc parseMarkdownCodeblock(p: var RstParser): PRstNode =
var args = newRstNode(rnDirArg) var args = newRstNode(rnDirArg)
if p.tok[p.idx].kind == tkWord: if p.tok[p.idx].kind == tkWord:
add(args, newLeaf(p)) add(args, newLeaf(p))
@ -755,7 +762,7 @@ proc parseMarkdownCodeblock(p: var TRstParser): PRstNode =
add(result, nil) add(result, nil)
add(result, lb) add(result, lb)
proc parseInline(p: var TRstParser, father: PRstNode) = proc parseInline(p: var RstParser, father: PRstNode) =
case p.tok[p.idx].kind case p.tok[p.idx].kind
of tkPunct: of tkPunct:
if isInlineMarkupStart(p, "***"): if isInlineMarkupStart(p, "***"):
@ -810,7 +817,7 @@ proc parseInline(p: var TRstParser, father: PRstNode) =
inc(p.idx) inc(p.idx)
else: discard else: discard
proc getDirective(p: var TRstParser): string = proc getDirective(p: var RstParser): string =
if p.tok[p.idx].kind == tkWhite and p.tok[p.idx+1].kind == tkWord: if p.tok[p.idx].kind == tkWhite and p.tok[p.idx+1].kind == tkWord:
var j = p.idx var j = p.idx
inc(p.idx) inc(p.idx)
@ -830,7 +837,7 @@ proc getDirective(p: var TRstParser): string =
else: else:
result = "" result = ""
proc parseComment(p: var TRstParser): PRstNode = proc parseComment(p: var RstParser): PRstNode =
case p.tok[p.idx].kind case p.tok[p.idx].kind
of tkIndent, tkEof: of tkIndent, tkEof:
if p.tok[p.idx].kind != tkEof and p.tok[p.idx + 1].kind == tkIndent: if p.tok[p.idx].kind != tkEof and p.tok[p.idx + 1].kind == tkIndent:
@ -851,34 +858,35 @@ proc parseComment(p: var TRstParser): PRstNode =
result = nil result = nil
type type
TDirKind = enum # must be ordered alphabetically! DirKind = enum # must be ordered alphabetically!
dkNone, dkAuthor, dkAuthors, dkCode, dkCodeBlock, dkContainer, dkContents, dkNone, dkAuthor, dkAuthors, dkCode, dkCodeBlock, dkContainer, dkContents,
dkFigure, dkImage, dkInclude, dkIndex, dkRaw, dkTitle dkFigure, dkImage, dkInclude, dkIndex, dkRaw, dkTitle
{.deprecated: [TDirKind: DirKind].}
const const
DirIds: array[0..12, string] = ["", "author", "authors", "code", DirIds: array[0..12, string] = ["", "author", "authors", "code",
"code-block", "container", "contents", "figure", "image", "include", "code-block", "container", "contents", "figure", "image", "include",
"index", "raw", "title"] "index", "raw", "title"]
proc getDirKind(s: string): TDirKind = proc getDirKind(s: string): DirKind =
let i = find(DirIds, s) let i = find(DirIds, s)
if i >= 0: result = TDirKind(i) if i >= 0: result = DirKind(i)
else: result = dkNone else: result = dkNone
proc parseLine(p: var TRstParser, father: PRstNode) = proc parseLine(p: var RstParser, father: PRstNode) =
while true: while true:
case p.tok[p.idx].kind case p.tok[p.idx].kind
of tkWhite, tkWord, tkOther, tkPunct: parseInline(p, father) of tkWhite, tkWord, tkOther, tkPunct: parseInline(p, father)
else: break else: break
proc parseUntilNewline(p: var TRstParser, father: PRstNode) = proc parseUntilNewline(p: var RstParser, father: PRstNode) =
while true: while true:
case p.tok[p.idx].kind case p.tok[p.idx].kind
of tkWhite, tkWord, tkAdornment, tkOther, tkPunct: parseInline(p, father) of tkWhite, tkWord, tkAdornment, tkOther, tkPunct: parseInline(p, father)
of tkEof, tkIndent: break of tkEof, tkIndent: break
proc parseSection(p: var TRstParser, result: PRstNode) {.gcsafe.} proc parseSection(p: var RstParser, result: PRstNode) {.gcsafe.}
proc parseField(p: var TRstParser): PRstNode = proc parseField(p: var RstParser): PRstNode =
## Returns a parsed rnField node. ## Returns a parsed rnField node.
## ##
## rnField nodes have two children nodes, a rnFieldName and a rnFieldBody. ## rnField nodes have two children nodes, a rnFieldName and a rnFieldBody.
@ -897,7 +905,7 @@ proc parseField(p: var TRstParser): PRstNode =
add(result, fieldname) add(result, fieldname)
add(result, fieldbody) add(result, fieldbody)
proc parseFields(p: var TRstParser): PRstNode = proc parseFields(p: var RstParser): PRstNode =
## Parses fields for a section or directive block. ## Parses fields for a section or directive block.
## ##
## This proc may return nil if the parsing doesn't find anything of value, ## This proc may return nil if the parsing doesn't find anything of value,
@ -947,8 +955,8 @@ proc getArgument(n: PRstNode): string =
if n.sons[0] == nil: result = "" if n.sons[0] == nil: result = ""
else: result = addNodes(n.sons[0]) else: result = addNodes(n.sons[0])
proc parseDotDot(p: var TRstParser): PRstNode {.gcsafe.} proc parseDotDot(p: var RstParser): PRstNode {.gcsafe.}
proc parseLiteralBlock(p: var TRstParser): PRstNode = proc parseLiteralBlock(p: var RstParser): PRstNode =
result = newRstNode(rnLiteralBlock) result = newRstNode(rnLiteralBlock)
var n = newRstNode(rnLeaf, "") var n = newRstNode(rnLeaf, "")
if p.tok[p.idx].kind == tkIndent: if p.tok[p.idx].kind == tkIndent:
@ -974,13 +982,13 @@ proc parseLiteralBlock(p: var TRstParser): PRstNode =
inc(p.idx) inc(p.idx)
add(result, n) add(result, n)
proc getLevel(map: var TLevelMap, lvl: var int, c: char): int = proc getLevel(map: var LevelMap, lvl: var int, c: char): int =
if map[c] == 0: if map[c] == 0:
inc(lvl) inc(lvl)
map[c] = lvl map[c] = lvl
result = map[c] result = map[c]
proc tokenAfterNewline(p: TRstParser): int = proc tokenAfterNewline(p: RstParser): int =
result = p.idx result = p.idx
while true: while true:
case p.tok[result].kind case p.tok[result].kind
@ -991,28 +999,28 @@ proc tokenAfterNewline(p: TRstParser): int =
break break
else: inc(result) else: inc(result)
proc isLineBlock(p: TRstParser): bool = proc isLineBlock(p: RstParser): bool =
var j = tokenAfterNewline(p) var j = tokenAfterNewline(p)
result = (p.tok[p.idx].col == p.tok[j].col) and (p.tok[j].symbol == "|") or result = (p.tok[p.idx].col == p.tok[j].col) and (p.tok[j].symbol == "|") or
(p.tok[j].col > p.tok[p.idx].col) (p.tok[j].col > p.tok[p.idx].col)
proc predNL(p: TRstParser): bool = proc predNL(p: RstParser): bool =
result = true result = true
if p.idx > 0: if p.idx > 0:
result = p.tok[p.idx-1].kind == tkIndent and result = p.tok[p.idx-1].kind == tkIndent and
p.tok[p.idx-1].ival == currInd(p) p.tok[p.idx-1].ival == currInd(p)
proc isDefList(p: TRstParser): bool = proc isDefList(p: RstParser): bool =
var j = tokenAfterNewline(p) var j = tokenAfterNewline(p)
result = (p.tok[p.idx].col < p.tok[j].col) and result = (p.tok[p.idx].col < p.tok[j].col) and
(p.tok[j].kind in {tkWord, tkOther, tkPunct}) and (p.tok[j].kind in {tkWord, tkOther, tkPunct}) and
(p.tok[j - 2].symbol != "::") (p.tok[j - 2].symbol != "::")
proc isOptionList(p: TRstParser): bool = proc isOptionList(p: RstParser): bool =
result = match(p, p.idx, "-w") or match(p, p.idx, "--w") or result = match(p, p.idx, "-w") or match(p, p.idx, "--w") or
match(p, p.idx, "/w") or match(p, p.idx, "//w") match(p, p.idx, "/w") or match(p, p.idx, "//w")
proc whichSection(p: TRstParser): TRstNodeKind = proc whichSection(p: RstParser): RstNodeKind =
case p.tok[p.idx].kind case p.tok[p.idx].kind
of tkAdornment: of tkAdornment:
if match(p, p.idx + 1, "ii"): result = rnTransition if match(p, p.idx + 1, "ii"): result = rnTransition
@ -1053,7 +1061,7 @@ proc whichSection(p: TRstParser): TRstNodeKind =
else: result = rnParagraph else: result = rnParagraph
else: result = rnLeaf else: result = rnLeaf
proc parseLineBlock(p: var TRstParser): PRstNode = proc parseLineBlock(p: var RstParser): PRstNode =
result = nil result = nil
if p.tok[p.idx + 1].kind == tkWhite: if p.tok[p.idx + 1].kind == tkWhite:
var col = p.tok[p.idx].col var col = p.tok[p.idx].col
@ -1072,7 +1080,7 @@ proc parseLineBlock(p: var TRstParser): PRstNode =
break break
popInd(p) popInd(p)
proc parseParagraph(p: var TRstParser, result: PRstNode) = proc parseParagraph(p: var RstParser, result: PRstNode) =
while true: while true:
case p.tok[p.idx].kind case p.tok[p.idx].kind
of tkIndent: of tkIndent:
@ -1103,7 +1111,7 @@ proc parseParagraph(p: var TRstParser, result: PRstNode) =
parseInline(p, result) parseInline(p, result)
else: break else: break
proc parseHeadline(p: var TRstParser): PRstNode = proc parseHeadline(p: var RstParser): PRstNode =
result = newRstNode(rnHeadline) result = newRstNode(rnHeadline)
parseUntilNewline(p, result) parseUntilNewline(p, result)
assert(p.tok[p.idx].kind == tkIndent) assert(p.tok[p.idx].kind == tkIndent)
@ -1113,12 +1121,13 @@ proc parseHeadline(p: var TRstParser): PRstNode =
result.level = getLevel(p.s.underlineToLevel, p.s.uLevel, c) result.level = getLevel(p.s.underlineToLevel, p.s.uLevel, c)
type type
TIntSeq = seq[int] IntSeq = seq[int]
{.deprecated: [TIntSeq: IntSeq].}
proc tokEnd(p: TRstParser): int = proc tokEnd(p: RstParser): int =
result = p.tok[p.idx].col + len(p.tok[p.idx].symbol) - 1 result = p.tok[p.idx].col + len(p.tok[p.idx].symbol) - 1
proc getColumns(p: var TRstParser, cols: var TIntSeq) = proc getColumns(p: var RstParser, cols: var IntSeq) =
var L = 0 var L = 0
while true: while true:
inc(L) inc(L)
@ -1133,15 +1142,15 @@ proc getColumns(p: var TRstParser, cols: var TIntSeq) =
# last column has no limit: # last column has no limit:
cols[L - 1] = 32000 cols[L - 1] = 32000
proc parseDoc(p: var TRstParser): PRstNode {.gcsafe.} proc parseDoc(p: var RstParser): PRstNode {.gcsafe.}
proc parseSimpleTable(p: var TRstParser): PRstNode = proc parseSimpleTable(p: var RstParser): PRstNode =
var var
cols: TIntSeq cols: IntSeq
row: seq[string] row: seq[string]
i, last, line: int i, last, line: int
c: char c: char
q: TRstParser q: RstParser
a, b: PRstNode a, b: PRstNode
result = newRstNode(rnTable) result = newRstNode(rnTable)
cols = @[] cols = @[]
@ -1188,13 +1197,13 @@ proc parseSimpleTable(p: var TRstParser): PRstNode =
add(a, b) add(a, b)
add(result, a) add(result, a)
proc parseTransition(p: var TRstParser): PRstNode = proc parseTransition(p: var RstParser): PRstNode =
result = newRstNode(rnTransition) result = newRstNode(rnTransition)
inc(p.idx) inc(p.idx)
if p.tok[p.idx].kind == tkIndent: inc(p.idx) if p.tok[p.idx].kind == tkIndent: inc(p.idx)
if p.tok[p.idx].kind == tkIndent: inc(p.idx) if p.tok[p.idx].kind == tkIndent: inc(p.idx)
proc parseOverline(p: var TRstParser): PRstNode = proc parseOverline(p: var RstParser): PRstNode =
var c = p.tok[p.idx].symbol[0] var c = p.tok[p.idx].symbol[0]
inc(p.idx, 2) inc(p.idx, 2)
result = newRstNode(rnOverline) result = newRstNode(rnOverline)
@ -1213,7 +1222,7 @@ proc parseOverline(p: var TRstParser): PRstNode =
inc(p.idx) # XXX: check? inc(p.idx) # XXX: check?
if p.tok[p.idx].kind == tkIndent: inc(p.idx) if p.tok[p.idx].kind == tkIndent: inc(p.idx)
proc parseBulletList(p: var TRstParser): PRstNode = proc parseBulletList(p: var RstParser): PRstNode =
result = nil result = nil
if p.tok[p.idx + 1].kind == tkWhite: if p.tok[p.idx + 1].kind == tkWhite:
var bullet = p.tok[p.idx].symbol var bullet = p.tok[p.idx].symbol
@ -1233,7 +1242,7 @@ proc parseBulletList(p: var TRstParser): PRstNode =
break break
popInd(p) popInd(p)
proc parseOptionList(p: var TRstParser): PRstNode = proc parseOptionList(p: var RstParser): PRstNode =
result = newRstNode(rnOptionList) result = newRstNode(rnOptionList)
while true: while true:
if isOptionList(p): if isOptionList(p):
@ -1262,7 +1271,7 @@ proc parseOptionList(p: var TRstParser): PRstNode =
else: else:
break break
proc parseDefinitionList(p: var TRstParser): PRstNode = proc parseDefinitionList(p: var RstParser): PRstNode =
result = nil result = nil
var j = tokenAfterNewline(p) - 1 var j = tokenAfterNewline(p) - 1
if (j >= 1) and (p.tok[j].kind == tkIndent) and if (j >= 1) and (p.tok[j].kind == tkIndent) and
@ -1298,7 +1307,7 @@ proc parseDefinitionList(p: var TRstParser): PRstNode =
break break
if len(result) == 0: result = nil if len(result) == 0: result = nil
proc parseEnumList(p: var TRstParser): PRstNode = proc parseEnumList(p: var RstParser): PRstNode =
const const
wildcards: array[0..2, string] = ["(e) ", "e) ", "e. "] wildcards: array[0..2, string] = ["(e) ", "e) ", "e. "]
wildpos: array[0..2, int] = [1, 0, 0] wildpos: array[0..2, int] = [1, 0, 0]
@ -1328,11 +1337,11 @@ proc parseEnumList(p: var TRstParser): PRstNode =
dec(p.idx, wildpos[w] + 3) dec(p.idx, wildpos[w] + 3)
result = nil result = nil
proc sonKind(father: PRstNode, i: int): TRstNodeKind = proc sonKind(father: PRstNode, i: int): RstNodeKind =
result = rnLeaf result = rnLeaf
if i < len(father): result = father.sons[i].kind if i < len(father): result = father.sons[i].kind
proc parseSection(p: var TRstParser, result: PRstNode) = proc parseSection(p: var RstParser, result: PRstNode) =
while true: while true:
var leave = false var leave = false
assert(p.idx >= 0) assert(p.idx >= 0)
@ -1380,16 +1389,16 @@ proc parseSection(p: var TRstParser, result: PRstNode) =
if sonKind(result, 0) == rnParagraph and sonKind(result, 1) != rnParagraph: if sonKind(result, 0) == rnParagraph and sonKind(result, 1) != rnParagraph:
result.sons[0].kind = rnInner result.sons[0].kind = rnInner
proc parseSectionWrapper(p: var TRstParser): PRstNode = proc parseSectionWrapper(p: var RstParser): PRstNode =
result = newRstNode(rnInner) result = newRstNode(rnInner)
parseSection(p, result) parseSection(p, result)
while (result.kind == rnInner) and (len(result) == 1): while (result.kind == rnInner) and (len(result) == 1):
result = result.sons[0] result = result.sons[0]
proc `$`(t: TToken): string = proc `$`(t: Token): string =
result = $t.kind & ' ' & (if isNil(t.symbol): "NIL" else: t.symbol) result = $t.kind & ' ' & (if isNil(t.symbol): "NIL" else: t.symbol)
proc parseDoc(p: var TRstParser): PRstNode = proc parseDoc(p: var RstParser): PRstNode =
result = parseSectionWrapper(p) result = parseSectionWrapper(p)
if p.tok[p.idx].kind != tkEof: if p.tok[p.idx].kind != tkEof:
when false: when false:
@ -1403,12 +1412,14 @@ proc parseDoc(p: var TRstParser): PRstNode =
rstMessage(p, meGeneralParseError) rstMessage(p, meGeneralParseError)
type type
TDirFlag = enum DirFlag = enum
hasArg, hasOptions, argIsFile, argIsWord hasArg, hasOptions, argIsFile, argIsWord
TDirFlags = set[TDirFlag] DirFlags = set[DirFlag]
TSectionParser = proc (p: var TRstParser): PRstNode {.nimcall.} SectionParser = proc (p: var RstParser): PRstNode {.nimcall.}
{.deprecated: [TDirFlag: DirFlag, TDirFlags: DirFlags,
TSectionParser: SectionParser].}
proc parseDirective(p: var TRstParser, flags: TDirFlags): PRstNode = proc parseDirective(p: var RstParser, flags: DirFlags): PRstNode =
## Parses arguments and options for a directive block. ## Parses arguments and options for a directive block.
## ##
## A directive block will always have three sons: the arguments for the ## A directive block will always have three sons: the arguments for the
@ -1446,11 +1457,11 @@ proc parseDirective(p: var TRstParser, flags: TDirFlags): PRstNode =
options = parseFields(p) options = parseFields(p)
add(result, options) add(result, options)
proc indFollows(p: TRstParser): bool = proc indFollows(p: RstParser): bool =
result = p.tok[p.idx].kind == tkIndent and p.tok[p.idx].ival > currInd(p) result = p.tok[p.idx].kind == tkIndent and p.tok[p.idx].ival > currInd(p)
proc parseDirective(p: var TRstParser, flags: TDirFlags, proc parseDirective(p: var RstParser, flags: DirFlags,
contentParser: TSectionParser): PRstNode = contentParser: SectionParser): PRstNode =
## Returns a generic rnDirective tree. ## Returns a generic rnDirective tree.
## ##
## The children are rnDirArg, rnFieldList and rnLineBlock. Any might be nil. ## The children are rnDirArg, rnFieldList and rnLineBlock. Any might be nil.
@ -1463,13 +1474,13 @@ proc parseDirective(p: var TRstParser, flags: TDirFlags,
else: else:
add(result, nil) add(result, nil)
proc parseDirBody(p: var TRstParser, contentParser: TSectionParser): PRstNode = proc parseDirBody(p: var RstParser, contentParser: SectionParser): PRstNode =
if indFollows(p): if indFollows(p):
pushInd(p, p.tok[p.idx].ival) pushInd(p, p.tok[p.idx].ival)
result = contentParser(p) result = contentParser(p)
popInd(p) popInd(p)
proc dirInclude(p: var TRstParser): PRstNode = proc dirInclude(p: var RstParser): PRstNode =
# #
#The following options are recognized: #The following options are recognized:
# #
@ -1498,7 +1509,7 @@ proc dirInclude(p: var TRstParser): PRstNode =
result = newRstNode(rnLiteralBlock) result = newRstNode(rnLiteralBlock)
add(result, newRstNode(rnLeaf, readFile(path))) add(result, newRstNode(rnLeaf, readFile(path)))
else: else:
var q: TRstParser var q: RstParser
initParser(q, p.s) initParser(q, p.s)
q.filename = filename q.filename = filename
q.col += getTokens(readFile(path), false, q.tok) q.col += getTokens(readFile(path), false, q.tok)
@ -1507,7 +1518,7 @@ proc dirInclude(p: var TRstParser): PRstNode =
# InternalError("Too many binary zeros in include file") # InternalError("Too many binary zeros in include file")
result = parseDoc(q) result = parseDoc(q)
proc dirCodeBlock(p: var TRstParser, nimrodExtension = false): PRstNode = proc dirCodeBlock(p: var RstParser, nimrodExtension = false): PRstNode =
## Parses a code block. ## Parses a code block.
## ##
## Code blocks are rnDirective trees with a `kind` of rnCodeBlock. See the ## Code blocks are rnDirective trees with a `kind` of rnCodeBlock. See the
@ -1548,35 +1559,35 @@ proc dirCodeBlock(p: var TRstParser, nimrodExtension = false): PRstNode =
result.kind = rnCodeBlock result.kind = rnCodeBlock
proc dirContainer(p: var TRstParser): PRstNode = proc dirContainer(p: var RstParser): PRstNode =
result = parseDirective(p, {hasArg}, parseSectionWrapper) result = parseDirective(p, {hasArg}, parseSectionWrapper)
assert(result.kind == rnDirective) assert(result.kind == rnDirective)
assert(len(result) == 3) assert(len(result) == 3)
result.kind = rnContainer result.kind = rnContainer
proc dirImage(p: var TRstParser): PRstNode = proc dirImage(p: var RstParser): PRstNode =
result = parseDirective(p, {hasOptions, hasArg, argIsFile}, nil) result = parseDirective(p, {hasOptions, hasArg, argIsFile}, nil)
result.kind = rnImage result.kind = rnImage
proc dirFigure(p: var TRstParser): PRstNode = proc dirFigure(p: var RstParser): PRstNode =
result = parseDirective(p, {hasOptions, hasArg, argIsFile}, result = parseDirective(p, {hasOptions, hasArg, argIsFile},
parseSectionWrapper) parseSectionWrapper)
result.kind = rnFigure result.kind = rnFigure
proc dirTitle(p: var TRstParser): PRstNode = proc dirTitle(p: var RstParser): PRstNode =
result = parseDirective(p, {hasArg}, nil) result = parseDirective(p, {hasArg}, nil)
result.kind = rnTitle result.kind = rnTitle
proc dirContents(p: var TRstParser): PRstNode = proc dirContents(p: var RstParser): PRstNode =
result = parseDirective(p, {hasArg}, nil) result = parseDirective(p, {hasArg}, nil)
result.kind = rnContents result.kind = rnContents
proc dirIndex(p: var TRstParser): PRstNode = proc dirIndex(p: var RstParser): PRstNode =
result = parseDirective(p, {}, parseSectionWrapper) result = parseDirective(p, {}, parseSectionWrapper)
result.kind = rnIndex result.kind = rnIndex
proc dirRawAux(p: var TRstParser, result: var PRstNode, kind: TRstNodeKind, proc dirRawAux(p: var RstParser, result: var PRstNode, kind: RstNodeKind,
contentParser: TSectionParser) = contentParser: SectionParser) =
var filename = getFieldValue(result, "file") var filename = getFieldValue(result, "file")
if filename.len > 0: if filename.len > 0:
var path = p.s.findFile(filename) var path = p.s.findFile(filename)
@ -1590,7 +1601,7 @@ proc dirRawAux(p: var TRstParser, result: var PRstNode, kind: TRstNodeKind,
result.kind = kind result.kind = kind
add(result, parseDirBody(p, contentParser)) add(result, parseDirBody(p, contentParser))
proc dirRaw(p: var TRstParser): PRstNode = proc dirRaw(p: var RstParser): PRstNode =
# #
#The following options are recognized: #The following options are recognized:
# #
@ -1610,7 +1621,7 @@ proc dirRaw(p: var TRstParser): PRstNode =
else: else:
dirRawAux(p, result, rnRaw, parseSectionWrapper) dirRawAux(p, result, rnRaw, parseSectionWrapper)
proc parseDotDot(p: var TRstParser): PRstNode = proc parseDotDot(p: var RstParser): PRstNode =
result = nil result = nil
var col = p.tok[p.idx].col var col = p.tok[p.idx].col
inc(p.idx) inc(p.idx)
@ -1667,7 +1678,7 @@ proc parseDotDot(p: var TRstParser): PRstNode =
else: else:
result = parseComment(p) result = parseComment(p)
proc resolveSubs(p: var TRstParser, n: PRstNode): PRstNode = proc resolveSubs(p: var RstParser, n: PRstNode): PRstNode =
result = n result = n
if n == nil: return if n == nil: return
case n.kind case n.kind
@ -1696,10 +1707,10 @@ proc resolveSubs(p: var TRstParser, n: PRstNode): PRstNode =
proc rstParse*(text, filename: string, proc rstParse*(text, filename: string,
line, column: int, hasToc: var bool, line, column: int, hasToc: var bool,
options: TRstParseOptions, options: RstParseOptions,
findFile: TFindFileHandler = nil, findFile: FindFileHandler = nil,
msgHandler: TMsgHandler = nil): PRstNode = msgHandler: MsgHandler = nil): PRstNode =
var p: TRstParser var p: RstParser
initParser(p, newSharedState(options, findFile, msgHandler)) initParser(p, newSharedState(options, findFile, msgHandler))
p.filename = filename p.filename = filename
p.line = line p.line = line

View file

@ -12,7 +12,7 @@
import strutils, json import strutils, json
type type
TRstNodeKind* = enum ## the possible node kinds of an PRstNode RstNodeKind* = enum ## the possible node kinds of an PRstNode
rnInner, # an inner node or a root rnInner, # an inner node or a root
rnHeadline, # a headline rnHeadline, # a headline
rnOverline, # an over- and underlined headline rnOverline, # an over- and underlined headline
@ -62,24 +62,26 @@ type
# leaf val # leaf val
PRstNode* = ref TRstNode ## an RST node PRstNode* = ref RstNode ## an RST node
TRstNodeSeq* = seq[PRstNode] RstNodeSeq* = seq[PRstNode]
TRstNode* {.acyclic, final.} = object ## an RST node's description RstNode* {.acyclic, final.} = object ## an RST node's description
kind*: TRstNodeKind ## the node's kind kind*: RstNodeKind ## the node's kind
text*: string ## valid for leafs in the AST; and the title of text*: string ## valid for leafs in the AST; and the title of
## the document or the section ## the document or the section
level*: int ## valid for some node kinds level*: int ## valid for some node kinds
sons*: TRstNodeSeq ## the node's sons sons*: RstNodeSeq ## the node's sons
{.deprecated: [TRstNodeKind: RstNodeKind, TRstNodeSeq: RstNodeSeq,
TRstNode: RstNode].}
proc len*(n: PRstNode): int = proc len*(n: PRstNode): int =
result = len(n.sons) result = len(n.sons)
proc newRstNode*(kind: TRstNodeKind): PRstNode = proc newRstNode*(kind: RstNodeKind): PRstNode =
new(result) new(result)
result.sons = @[] result.sons = @[]
result.kind = kind result.kind = kind
proc newRstNode*(kind: TRstNodeKind, s: string): PRstNode = proc newRstNode*(kind: RstNodeKind, s: string): PRstNode =
result = newRstNode(kind) result = newRstNode(kind)
result.text = s result.text = s
@ -94,18 +96,19 @@ proc addIfNotNil*(father, son: PRstNode) =
type type
TRenderContext {.pure.} = object RenderContext {.pure.} = object
indent: int indent: int
verbatim: int verbatim: int
{.deprecated: [TRenderContext: RenderContext].}
proc renderRstToRst(d: var TRenderContext, n: PRstNode, proc renderRstToRst(d: var RenderContext, n: PRstNode,
result: var string) {.gcsafe.} result: var string) {.gcsafe.}
proc renderRstSons(d: var TRenderContext, n: PRstNode, result: var string) = proc renderRstSons(d: var RenderContext, n: PRstNode, result: var string) =
for i in countup(0, len(n) - 1): for i in countup(0, len(n) - 1):
renderRstToRst(d, n.sons[i], result) renderRstToRst(d, n.sons[i], result)
proc renderRstToRst(d: var TRenderContext, n: PRstNode, result: var string) = proc renderRstToRst(d: var RenderContext, n: PRstNode, result: var string) =
# this is needed for the index generation; it may also be useful for # this is needed for the index generation; it may also be useful for
# debugging, but most code is already debugged... # debugging, but most code is already debugged...
const const
@ -284,7 +287,7 @@ proc renderRstToRst(d: var TRenderContext, n: PRstNode, result: var string) =
proc renderRstToRst*(n: PRstNode, result: var string) = proc renderRstToRst*(n: PRstNode, result: var string) =
## renders `n` into its string representation and appends to `result`. ## renders `n` into its string representation and appends to `result`.
var d: TRenderContext var d: RenderContext
renderRstToRst(d, n, result) renderRstToRst(d, n, result)
proc renderRstToJsonNode(node: PRstNode): JsonNode = proc renderRstToJsonNode(node: PRstNode): JsonNode =

View file

@ -18,7 +18,7 @@
## document you provide yourself, so it won't contain the usual ``<header>`` or ## document you provide yourself, so it won't contain the usual ``<header>`` or
## ``<body>`` parts. ## ``<body>`` parts.
## ##
## You can also create a ``TRstGenerator`` structure and populate it with the ## You can also create a ``RstGenerator`` structure and populate it with the
## other lower level methods to finally build complete documents. This requires ## other lower level methods to finally build complete documents. This requires
## many options and tweaking, but you are not limited to snippets and can ## many options and tweaking, but you are not limited to snippets and can
## generate `LaTeX documents <https://en.wikipedia.org/wiki/LaTeX>`_ too. ## generate `LaTeX documents <https://en.wikipedia.org/wiki/LaTeX>`_ too.
@ -31,29 +31,29 @@ const
IndexExt* = ".idx" IndexExt* = ".idx"
type type
TOutputTarget* = enum ## which document type to generate OutputTarget* = enum ## which document type to generate
outHtml, # output is HTML outHtml, # output is HTML
outLatex # output is Latex outLatex # output is Latex
TTocEntry = object TocEntry = object
n*: PRstNode n*: PRstNode
refname*, header*: string refname*, header*: string
TMetaEnum* = enum MetaEnum* = enum
metaNone, metaTitle, metaSubtitle, metaAuthor, metaVersion metaNone, metaTitle, metaSubtitle, metaAuthor, metaVersion
TRstGenerator* = object of RootObj RstGenerator* = object of RootObj
target*: TOutputTarget target*: OutputTarget
config*: StringTableRef config*: StringTableRef
splitAfter*: int # split too long entries in the TOC splitAfter*: int # split too long entries in the TOC
tocPart*: seq[TTocEntry] tocPart*: seq[TocEntry]
hasToc*: bool hasToc*: bool
theIndex: string # Contents of the index file to be dumped at the end. theIndex: string # Contents of the index file to be dumped at the end.
options*: TRstParseOptions options*: RstParseOptions
findFile*: TFindFileHandler findFile*: FindFileHandler
msgHandler*: TMsgHandler msgHandler*: MsgHandler
filename*: string filename*: string
meta*: array[TMetaEnum, string] meta*: array[MetaEnum, string]
currentSection: string ## \ currentSection: string ## \
## Stores the empty string or the last headline/overline found in the rst ## Stores the empty string or the last headline/overline found in the rst
## document, so it can be used as a prettier name for term index generation. ## document, so it can be used as a prettier name for term index generation.
@ -61,14 +61,15 @@ type
## Keeps count of same text index terms to generate different identifiers ## Keeps count of same text index terms to generate different identifiers
## for hyperlinks. See renderIndexTerm proc for details. ## for hyperlinks. See renderIndexTerm proc for details.
PDoc = var TRstGenerator ## Alias to type less. PDoc = var RstGenerator ## Alias to type less.
CodeBlockParams = object ## Stores code block params. CodeBlockParams = object ## Stores code block params.
numberLines: bool ## True if the renderer has to show line numbers. numberLines: bool ## True if the renderer has to show line numbers.
startLine: int ## The starting line of the code block, by default 1. startLine: int ## The starting line of the code block, by default 1.
langStr: string ## Input string used to specify the language. langStr: string ## Input string used to specify the language.
lang: TSourceLanguage ## Type of highlighting, by default none. lang: SourceLanguage ## Type of highlighting, by default none.
{.deprecated: [TRstGenerator: RstGenerator, TTocEntry: TocEntry,
TOutputTarget: OutputTarget, TMetaEnum: MetaEnum].}
proc init(p: var CodeBlockParams) = proc init(p: var CodeBlockParams) =
## Default initialisation of CodeBlockParams to sane values. ## Default initialisation of CodeBlockParams to sane values.
@ -76,14 +77,14 @@ proc init(p: var CodeBlockParams) =
p.lang = langNone p.lang = langNone
p.langStr = "" p.langStr = ""
proc initRstGenerator*(g: var TRstGenerator, target: TOutputTarget, proc initRstGenerator*(g: var RstGenerator, target: OutputTarget,
config: StringTableRef, filename: string, config: StringTableRef, filename: string,
options: TRstParseOptions, options: RstParseOptions,
findFile: TFindFileHandler=nil, findFile: FindFileHandler=nil,
msgHandler: TMsgHandler=nil) = msgHandler: MsgHandler=nil) =
## Initializes a ``TRstGenerator``. ## Initializes a ``RstGenerator``.
## ##
## You need to call this before using a ``TRstGenerator`` with any other ## You need to call this before using a ``RstGenerator`` with any other
## procs in this module. Pass a non ``nil`` ``StringTableRef`` value as ## procs in this module. Pass a non ``nil`` ``StringTableRef`` value as
## `config` with parameters used by the HTML output generator. If you don't ## `config` with parameters used by the HTML output generator. If you don't
## know what to use, pass the results of the `defaultConfig() ## know what to use, pass the results of the `defaultConfig()
@ -96,7 +97,7 @@ proc initRstGenerator*(g: var TRstGenerator, target: TOutputTarget,
## filename``. This default title can be overriden by the embedded rst, but ## filename``. This default title can be overriden by the embedded rst, but
## it helps to prettify the generated index if no title is found. ## it helps to prettify the generated index if no title is found.
## ##
## The ``TRstParseOptions``, ``TFindFileHandler`` and ``TMsgHandler`` types ## The ``RstParseOptions``, ``FindFileHandler`` and ``MsgHandler`` types
## are defined in the the `packages/docutils/rst module <rst.html>`_. ## are defined in the the `packages/docutils/rst module <rst.html>`_.
## ``options`` selects the behaviour of the rst parser. ## ``options`` selects the behaviour of the rst parser.
## ##
@ -120,7 +121,7 @@ proc initRstGenerator*(g: var TRstGenerator, target: TOutputTarget,
## ##
## import packages/docutils/rstgen ## import packages/docutils/rstgen
## ##
## var gen: TRstGenerator ## var gen: RstGenerator
## gen.initRstGenerator(outHtml, defaultConfig(), "filename", {}) ## gen.initRstGenerator(outHtml, defaultConfig(), "filename", {})
g.config = config g.config = config
g.target = target g.target = target
@ -141,7 +142,7 @@ proc initRstGenerator*(g: var TRstGenerator, target: TOutputTarget,
if s != "": g.splitAfter = parseInt(s) if s != "": g.splitAfter = parseInt(s)
for i in low(g.meta)..high(g.meta): g.meta[i] = "" for i in low(g.meta)..high(g.meta): g.meta[i] = ""
proc writeIndexFile*(g: var TRstGenerator, outfile: string) = proc writeIndexFile*(g: var RstGenerator, outfile: string) =
## Writes the current index buffer to the specified output file. ## Writes the current index buffer to the specified output file.
## ##
## You previously need to add entries to the index with the `setIndexTerm() ## You previously need to add entries to the index with the `setIndexTerm()
@ -183,7 +184,7 @@ proc addTexChar(dest: var string, c: char) =
var splitter*: string = "<wbr />" var splitter*: string = "<wbr />"
proc escChar*(target: TOutputTarget, dest: var string, c: char) {.inline.} = proc escChar*(target: OutputTarget, dest: var string, c: char) {.inline.} =
case target case target
of outHtml: addXmlChar(dest, c) of outHtml: addXmlChar(dest, c)
of outLatex: addTexChar(dest, c) of outLatex: addTexChar(dest, c)
@ -200,7 +201,7 @@ proc nextSplitPoint*(s: string, start: int): int =
inc(result) inc(result)
dec(result) # last valid index dec(result) # last valid index
proc esc*(target: TOutputTarget, s: string, splitAfter = -1): string = proc esc*(target: OutputTarget, s: string, splitAfter = -1): string =
result = "" result = ""
if splitAfter >= 0: if splitAfter >= 0:
var partLen = 0 var partLen = 0
@ -217,16 +218,16 @@ proc esc*(target: TOutputTarget, s: string, splitAfter = -1): string =
for i in countup(0, len(s) - 1): escChar(target, result, s[i]) for i in countup(0, len(s) - 1): escChar(target, result, s[i])
proc disp(target: TOutputTarget, xml, tex: string): string = proc disp(target: OutputTarget, xml, tex: string): string =
if target != outLatex: result = xml if target != outLatex: result = xml
else: result = tex else: result = tex
proc dispF(target: TOutputTarget, xml, tex: string, proc dispF(target: OutputTarget, xml, tex: string,
args: varargs[string]): string = args: varargs[string]): string =
if target != outLatex: result = xml % args if target != outLatex: result = xml % args
else: result = tex % args else: result = tex % args
proc dispA(target: TOutputTarget, dest: var string, proc dispA(target: OutputTarget, dest: var string,
xml, tex: string, args: varargs[string]) = xml, tex: string, args: varargs[string]) =
if target != outLatex: addf(dest, xml, args) if target != outLatex: addf(dest, xml, args)
else: addf(dest, tex, args) else: addf(dest, tex, args)
@ -234,10 +235,10 @@ proc dispA(target: TOutputTarget, dest: var string,
proc `or`(x, y: string): string {.inline.} = proc `or`(x, y: string): string {.inline.} =
result = if x.isNil: y else: x result = if x.isNil: y else: x
proc renderRstToOut*(d: var TRstGenerator, n: PRstNode, result: var string) proc renderRstToOut*(d: var RstGenerator, n: PRstNode, result: var string)
## Writes into ``result`` the rst ast ``n`` using the ``d`` configuration. ## Writes into ``result`` the rst ast ``n`` using the ``d`` configuration.
## ##
## Before using this proc you need to initialise a ``TRstGenerator`` with ## Before using this proc you need to initialise a ``RstGenerator`` with
## ``initRstGenerator`` and parse a rst file with ``rstParse`` from the ## ``initRstGenerator`` and parse a rst file with ``rstParse`` from the
## `packages/docutils/rst module <rst.html>`_. Example: ## `packages/docutils/rst module <rst.html>`_. Example:
## ##
@ -277,7 +278,7 @@ proc unquoteIndexColumn(text: string): string =
## Returns the unquoted version generated by ``quoteIndexColumn``. ## Returns the unquoted version generated by ``quoteIndexColumn``.
result = text.replace("\\t", "\t").replace("\\n", "\n").replace("\\\\", "\\") result = text.replace("\\t", "\t").replace("\\n", "\n").replace("\\\\", "\\")
proc setIndexTerm*(d: var TRstGenerator, id, term: string, proc setIndexTerm*(d: var RstGenerator, id, term: string,
linkTitle, linkDesc = "") = linkTitle, linkDesc = "") =
## Adds a `term` to the index using the specified hyperlink identifier. ## Adds a `term` to the index using the specified hyperlink identifier.
## ##
@ -351,30 +352,30 @@ proc renderIndexTerm*(d: PDoc, n: PRstNode, result: var string) =
[id, term]) [id, term])
type type
TIndexEntry = object IndexEntry = object
keyword: string keyword: string
link: string link: string
linkTitle: string ## If not nil, contains a prettier text for the href linkTitle: string ## If not nil, contains a prettier text for the href
linkDesc: string ## If not nil, the title attribute of the final href linkDesc: string ## If not nil, the title attribute of the final href
TIndexedDocs = Table[TIndexEntry, seq[TIndexEntry]] ## \ IndexedDocs = Table[IndexEntry, seq[IndexEntry]] ## \
## Contains the index sequences for doc types. ## Contains the index sequences for doc types.
## ##
## The key is a *fake* TIndexEntry which will contain the title of the ## The key is a *fake* IndexEntry which will contain the title of the
## document in the `keyword` field and `link` will contain the html ## document in the `keyword` field and `link` will contain the html
## filename for the document. `linkTitle` and `linkDesc` will be nil. ## filename for the document. `linkTitle` and `linkDesc` will be nil.
## ##
## The value indexed by this TIndexEntry is a sequence with the real index ## The value indexed by this IndexEntry is a sequence with the real index
## entries found in the ``.idx`` file. ## entries found in the ``.idx`` file.
{.deprecated: [TIndexEntry: IndexEntry, TIndexedDocs: IndexedDocs].}
proc cmp(a, b: IndexEntry): int =
proc cmp(a, b: TIndexEntry): int = ## Sorts two ``IndexEntry`` first by `keyword` field, then by `link`.
## Sorts two ``TIndexEntry`` first by `keyword` field, then by `link`.
result = cmpIgnoreStyle(a.keyword, b.keyword) result = cmpIgnoreStyle(a.keyword, b.keyword)
if result == 0: if result == 0:
result = cmpIgnoreStyle(a.link, b.link) result = cmpIgnoreStyle(a.link, b.link)
proc hash(x: TIndexEntry): THash = proc hash(x: IndexEntry): Hash =
## Returns the hash for the combined fields of the type. ## Returns the hash for the combined fields of the type.
## ##
## The hash is computed as the chained hash of the individual string hashes. ## The hash is computed as the chained hash of the individual string hashes.
@ -385,7 +386,7 @@ proc hash(x: TIndexEntry): THash =
result = result !& (x.linkDesc or "").hash result = result !& (x.linkDesc or "").hash
result = !$result result = !$result
proc `<-`(a: var TIndexEntry, b: TIndexEntry) = proc `<-`(a: var IndexEntry, b: IndexEntry) =
shallowCopy a.keyword, b.keyword shallowCopy a.keyword, b.keyword
shallowCopy a.link, b.link shallowCopy a.link, b.link
if b.linkTitle.isNil: a.linkTitle = nil if b.linkTitle.isNil: a.linkTitle = nil
@ -393,7 +394,7 @@ proc `<-`(a: var TIndexEntry, b: TIndexEntry) =
if b.linkDesc.isNil: a.linkDesc = nil if b.linkDesc.isNil: a.linkDesc = nil
else: shallowCopy a.linkDesc, b.linkDesc else: shallowCopy a.linkDesc, b.linkDesc
proc sortIndex(a: var openArray[TIndexEntry]) = proc sortIndex(a: var openArray[IndexEntry]) =
# we use shellsort here; fast and simple # we use shellsort here; fast and simple
let n = len(a) let n = len(a)
var h = 1 var h = 1
@ -403,7 +404,7 @@ proc sortIndex(a: var openArray[TIndexEntry]) =
while true: while true:
h = h div 3 h = h div 3
for i in countup(h, n - 1): for i in countup(h, n - 1):
var v: TIndexEntry var v: IndexEntry
v <- a[i] v <- a[i]
var j = i var j = i
while cmp(a[j-h], v) >= 0: while cmp(a[j-h], v) >= 0:
@ -413,7 +414,7 @@ proc sortIndex(a: var openArray[TIndexEntry]) =
a[j] <- v a[j] <- v
if h == 1: break if h == 1: break
proc generateSymbolIndex(symbols: seq[TIndexEntry]): string = proc generateSymbolIndex(symbols: seq[IndexEntry]): string =
result = "" result = ""
var i = 0 var i = 0
while i < symbols.len: while i < symbols.len:
@ -466,7 +467,7 @@ proc indentToLevel(level: var int, newLevel: int): string =
result = repeat("</ul>", level - newLevel) result = repeat("</ul>", level - newLevel)
level = newLevel level = newLevel
proc generateDocumentationTOC(entries: seq[TIndexEntry]): string = proc generateDocumentationTOC(entries: seq[IndexEntry]): string =
## Returns the sequence of index entries in an HTML hierarchical list. ## Returns the sequence of index entries in an HTML hierarchical list.
result = "" result = ""
# Build a list of levels and extracted titles to make processing easier. # Build a list of levels and extracted titles to make processing easier.
@ -507,12 +508,12 @@ proc generateDocumentationTOC(entries: seq[TIndexEntry]): string =
assert(not titleRef.isNil, assert(not titleRef.isNil,
"Can't use this proc on an API index, docs always have a title entry") "Can't use this proc on an API index, docs always have a title entry")
proc generateDocumentationIndex(docs: TIndexedDocs): string = proc generateDocumentationIndex(docs: IndexedDocs): string =
## Returns all the documentation TOCs in an HTML hierarchical list. ## Returns all the documentation TOCs in an HTML hierarchical list.
result = "" result = ""
# Sort the titles to generate their toc in alphabetical order. # Sort the titles to generate their toc in alphabetical order.
var titles = toSeq(keys[TIndexEntry, seq[TIndexEntry]](docs)) var titles = toSeq(keys[IndexEntry, seq[IndexEntry]](docs))
sort(titles, cmp) sort(titles, cmp)
for title in titles: for title in titles:
@ -520,12 +521,12 @@ proc generateDocumentationIndex(docs: TIndexedDocs): string =
result.add("<ul><li><a href=\"" & result.add("<ul><li><a href=\"" &
title.link & "\">" & title.keyword & "</a>\n" & tocList & "</ul>\n") title.link & "\">" & title.keyword & "</a>\n" & tocList & "</ul>\n")
proc generateDocumentationJumps(docs: TIndexedDocs): string = proc generateDocumentationJumps(docs: IndexedDocs): string =
## Returns a plain list of hyperlinks to documentation TOCs in HTML. ## Returns a plain list of hyperlinks to documentation TOCs in HTML.
result = "Documents: " result = "Documents: "
# Sort the titles to generate their toc in alphabetical order. # Sort the titles to generate their toc in alphabetical order.
var titles = toSeq(keys[TIndexEntry, seq[TIndexEntry]](docs)) var titles = toSeq(keys[IndexEntry, seq[IndexEntry]](docs))
sort(titles, cmp) sort(titles, cmp)
var chunks: seq[string] = @[] var chunks: seq[string] = @[]
@ -545,14 +546,14 @@ proc generateModuleJumps(modules: seq[string]): string =
result.add(chunks.join(", ") & ".<br>") result.add(chunks.join(", ") & ".<br>")
proc readIndexDir(dir: string): proc readIndexDir(dir: string):
tuple[modules: seq[string], symbols: seq[TIndexEntry], docs: TIndexedDocs] = tuple[modules: seq[string], symbols: seq[IndexEntry], docs: IndexedDocs] =
## Walks `dir` reading ``.idx`` files converting them in TIndexEntry items. ## Walks `dir` reading ``.idx`` files converting them in IndexEntry items.
## ##
## Returns the list of found module names, the list of free symbol entries ## Returns the list of found module names, the list of free symbol entries
## and the different documentation indexes. The list of modules is sorted. ## and the different documentation indexes. The list of modules is sorted.
## See the documentation of ``mergeIndexes`` for details. ## See the documentation of ``mergeIndexes`` for details.
result.modules = @[] result.modules = @[]
result.docs = initTable[TIndexEntry, seq[TIndexEntry]](32) result.docs = initTable[IndexEntry, seq[IndexEntry]](32)
newSeq(result.symbols, 15_000) newSeq(result.symbols, 15_000)
setLen(result.symbols, 0) setLen(result.symbols, 0)
var L = 0 var L = 0
@ -560,8 +561,8 @@ proc readIndexDir(dir: string):
for kind, path in walkDir(dir): for kind, path in walkDir(dir):
if kind == pcFile and path.endsWith(IndexExt): if kind == pcFile and path.endsWith(IndexExt):
var var
fileEntries: seq[TIndexEntry] fileEntries: seq[IndexEntry]
title: TIndexEntry title: IndexEntry
F = 0 F = 0
newSeq(fileEntries, 500) newSeq(fileEntries, 500)
setLen(fileEntries, 0) setLen(fileEntries, 0)
@ -662,7 +663,7 @@ proc mergeIndexes*(dir: string): string =
proc stripTOCHTML(s: string): string = proc stripTOCHTML(s: string): string =
## Ugly quick hack to remove HTML tags from TOC titles. ## Ugly quick hack to remove HTML tags from TOC titles.
## ##
## A TTocEntry.header field already contains rendered HTML tags. Instead of ## A TocEntry.header field already contains rendered HTML tags. Instead of
## implementing a proper version of renderRstToOut() which recursively ## implementing a proper version of renderRstToOut() which recursively
## renders an rst tree to plain text, we simply remove text found between ## renders an rst tree to plain text, we simply remove text found between
## angled brackets. Given the limited possibilities of rst inside TOC titles ## angled brackets. Given the limited possibilities of rst inside TOC titles
@ -728,12 +729,12 @@ proc renderOverline(d: PDoc, n: PRstNode, result: var string) =
rstnodeToRefname(n), tmp, $chr(n.level - 1 + ord('A'))]) rstnodeToRefname(n), tmp, $chr(n.level - 1 + ord('A'))])
proc renderTocEntry(d: PDoc, e: TTocEntry, result: var string) = proc renderTocEntry(d: PDoc, e: TocEntry, result: var string) =
dispA(d.target, result, dispA(d.target, result,
"<li><a class=\"reference\" id=\"$1_toc\" href=\"#$1\">$2</a></li>\n", "<li><a class=\"reference\" id=\"$1_toc\" href=\"#$1\">$2</a></li>\n",
"\\item\\label{$1_toc} $2\\ref{$1}\n", [e.refname, e.header]) "\\item\\label{$1_toc} $2\\ref{$1}\n", [e.refname, e.header])
proc renderTocEntries*(d: var TRstGenerator, j: var int, lvl: int, proc renderTocEntries*(d: var RstGenerator, j: var int, lvl: int,
result: var string) = result: var string) =
var tmp = "" var tmp = ""
while j <= high(d.tocPart): while j <= high(d.tocPart):
@ -878,7 +879,7 @@ proc renderCodeBlock(d: PDoc, n: PRstNode, result: var string) =
d.msgHandler(d.filename, 1, 0, mwUnsupportedLanguage, params.langStr) d.msgHandler(d.filename, 1, 0, mwUnsupportedLanguage, params.langStr)
for letter in m.text: escChar(d.target, result, letter) for letter in m.text: escChar(d.target, result, letter)
else: else:
var g: TGeneralTokenizer var g: GeneralTokenizer
initGeneralTokenizer(g, m.text) initGeneralTokenizer(g, m.text)
while true: while true:
getNextToken(g, params.lang) getNextToken(g, params.lang)
@ -1214,7 +1215,7 @@ $content
# ---------- forum --------------------------------------------------------- # ---------- forum ---------------------------------------------------------
proc rstToHtml*(s: string, options: TRstParseOptions, proc rstToHtml*(s: string, options: RstParseOptions,
config: StringTableRef): string = config: StringTableRef): string =
## Converts an input rst string into embeddable HTML. ## Converts an input rst string into embeddable HTML.
## ##
@ -1233,7 +1234,7 @@ proc rstToHtml*(s: string, options: TRstParseOptions,
## # --> <em>Hello</em> <strong>world</strong>! ## # --> <em>Hello</em> <strong>world</strong>!
## ##
## If you need to allow the rst ``include`` directive or tweak the generated ## If you need to allow the rst ``include`` directive or tweak the generated
## output you have to create your own ``TRstGenerator`` with ## output you have to create your own ``RstGenerator`` with
## ``initRstGenerator`` and related procs. ## ``initRstGenerator`` and related procs.
proc myFindFile(filename: string): string = proc myFindFile(filename: string): string =
@ -1241,7 +1242,7 @@ proc rstToHtml*(s: string, options: TRstParseOptions,
result = "" result = ""
const filen = "input" const filen = "input"
var d: TRstGenerator var d: RstGenerator
initRstGenerator(d, outHtml, config, filen, options, myFindFile, initRstGenerator(d, outHtml, config, filen, options, myFindFile,
rst.defaultMsgHandler) rst.defaultMsgHandler)
var dummyHasToc = false var dummyHasToc = false

View file

@ -12,14 +12,15 @@
# Get the platform-dependent flags. # Get the platform-dependent flags.
# Structure describing an inotify event. # Structure describing an inotify event.
type type
Tinotify_event*{.pure, final, importc: "struct inotify_event", InotifyEvent*{.pure, final, importc: "struct inotify_event",
header: "<sys/inotify.h>".} = object header: "<sys/inotify.h>".} = object
wd*{.importc: "wd".}: cint # Watch descriptor. wd*{.importc: "wd".}: cint # Watch descriptor.
mask*{.importc: "mask".}: uint32 # Watch mask. mask*{.importc: "mask".}: uint32 # Watch mask.
cookie*{.importc: "cookie".}: uint32 # Cookie to synchronize two events. cookie*{.importc: "cookie".}: uint32 # Cookie to synchronize two events.
len*{.importc: "len".}: uint32 # Length (including NULs) of name. len*{.importc: "len".}: uint32 # Length (including NULs) of name.
name*{.importc: "name".}: char # Name. name*{.importc: "name".}: char # Name.
{.deprecated: [Tinotify_event: InotifyEvent].}
# Supported events suitable for MASK parameter of INOTIFY_ADD_WATCH. # Supported events suitable for MASK parameter of INOTIFY_ADD_WATCH.
const const
IN_ACCESS* = 0x00000001 # File was accessed. IN_ACCESS* = 0x00000001 # File was accessed.
@ -69,4 +70,4 @@ proc inotify_add_watch*(fd: cint; name: cstring; mask: uint32): cint{.
cdecl, importc: "inotify_add_watch", header: "<sys/inotify.h>".} cdecl, importc: "inotify_add_watch", header: "<sys/inotify.h>".}
# Remove the watch specified by WD from the inotify instance FD. # Remove the watch specified by WD from the inotify instance FD.
proc inotify_rm_watch*(fd: cint; wd: cint): cint{.cdecl, proc inotify_rm_watch*(fd: cint; wd: cint): cint{.cdecl,
importc: "inotify_rm_watch", header: "<sys/inotify.h>".} importc: "inotify_rm_watch", header: "<sys/inotify.h>".}

View file

@ -24,5 +24,5 @@ const
# fn should be of type proc (a2: pointer): void {.cdecl.} # fn should be of type proc (a2: pointer): void {.cdecl.}
proc clone*(fn: pointer; child_stack: pointer; flags: cint; proc clone*(fn: pointer; child_stack: pointer; flags: cint;
arg: pointer; ptid: ptr TPid; tls: pointer; arg: pointer; ptid: ptr Pid; tls: pointer;
ctid: ptr TPid): cint {.importc, header: "<sched.h>".} ctid: ptr Pid): cint {.importc, header: "<sched.h>".}

File diff suppressed because it is too large Load diff

View file

@ -12,17 +12,18 @@ import posix
type type
Speed* = cuint Speed* = cuint
Tcflag* = cuint Cflag* = cuint
{.deprecated: [Tcflag: Cflag].}
const const
NCCS* = 32 NCCS* = 32
type type
Termios* {.importc: "struct termios", header: "<termios.h>".} = object Termios* {.importc: "struct termios", header: "<termios.h>".} = object
c_iflag*: Tcflag # input mode flags c_iflag*: Cflag # input mode flags
c_oflag*: Tcflag # output mode flags c_oflag*: Cflag # output mode flags
c_cflag*: Tcflag # control mode flags c_cflag*: Cflag # control mode flags
c_lflag*: Tcflag # local mode flags c_lflag*: Cflag # local mode flags
c_line*: cuchar # line discipline c_line*: cuchar # line discipline
c_cc*: array[NCCS, cuchar] # control characters c_cc*: array[NCCS, cuchar] # control characters
@ -258,4 +259,4 @@ proc tcFlow*(fd: cint; action: cint): cint {.importc: "tcflow",
header: "<termios.h>".} header: "<termios.h>".}
# Get process group ID for session leader for controlling terminal FD. # Get process group ID for session leader for controlling terminal FD.
proc tcGetSid*(fd: cint): TPid {.importc: "tcgetsid", header: "<termios.h>".} proc tcGetSid*(fd: cint): Pid {.importc: "tcgetsid", header: "<termios.h>".}

View file

@ -16,7 +16,7 @@
## .. code-block:: nim ## .. code-block:: nim
## ##
## var ## var
## a: TActorPool[int, void] ## a: ActorPool[int, void]
## createActorPool(a) ## createActorPool(a)
## for i in 0 .. < 300: ## for i in 0 .. < 300:
## a.spawn(i, proc (x: int) {.thread.} = echo x) ## a.spawn(i, proc (x: int) {.thread.} = echo x)
@ -30,75 +30,76 @@
from os import sleep from os import sleep
type type
TTask*[TIn, TOut] = object{.pure, final.} ## a task Task*[In, Out] = object{.pure, final.} ## a task
when TOut isnot void: when Out isnot void:
receiver*: ptr TChannel[TOut] ## the receiver channel of the response receiver*: ptr Channel[Out] ## the receiver channel of the response
action*: proc (x: TIn): TOut {.thread.} ## action to execute; action*: proc (x: In): Out {.thread.} ## action to execute;
## sometimes useful ## sometimes useful
shutDown*: bool ## set to tell an actor to shut-down shutDown*: bool ## set to tell an actor to shut-down
data*: TIn ## the data to process data*: In ## the data to process
TActor[TIn, TOut] = object{.pure, final.} Actor[In, Out] = object{.pure, final.}
i: TChannel[TTask[TIn, TOut]] i: Channel[Task[In, Out]]
t: TThread[ptr TActor[TIn, TOut]] t: TThread[ptr Actor[In, Out]]
PActor*[TIn, TOut] = ptr TActor[TIn, TOut] ## an actor PActor*[In, Out] = ptr Actor[In, Out] ## an actor
{.deprecated: [TTask: Task, TActor: Actor].}
proc spawn*[TIn, TOut](action: proc(
self: PActor[TIn, TOut]){.thread.}): PActor[TIn, TOut] = proc spawn*[In, Out](action: proc(
self: PActor[In, Out]){.thread.}): PActor[In, Out] =
## creates an actor; that is a thread with an inbox. The caller MUST call ## creates an actor; that is a thread with an inbox. The caller MUST call
## ``join`` because that also frees the actor's associated resources. ## ``join`` because that also frees the actor's associated resources.
result = cast[PActor[TIn, TOut]](allocShared0(sizeof(result[]))) result = cast[PActor[In, Out]](allocShared0(sizeof(result[])))
open(result.i) open(result.i)
createThread(result.t, action, result) createThread(result.t, action, result)
proc inbox*[TIn, TOut](self: PActor[TIn, TOut]): ptr TChannel[TIn] = proc inbox*[In, Out](self: PActor[In, Out]): ptr Channel[In] =
## gets a pointer to the associated inbox of the actor `self`. ## gets a pointer to the associated inbox of the actor `self`.
result = addr(self.i) result = addr(self.i)
proc running*[TIn, TOut](a: PActor[TIn, TOut]): bool = proc running*[In, Out](a: PActor[In, Out]): bool =
## returns true if the actor `a` is running. ## returns true if the actor `a` is running.
result = running(a.t) result = running(a.t)
proc ready*[TIn, TOut](a: PActor[TIn, TOut]): bool = proc ready*[In, Out](a: PActor[In, Out]): bool =
## returns true if the actor `a` is ready to process new messages. ## returns true if the actor `a` is ready to process new messages.
result = ready(a.i) result = ready(a.i)
proc join*[TIn, TOut](a: PActor[TIn, TOut]) = proc join*[In, Out](a: PActor[In, Out]) =
## joins an actor. ## joins an actor.
joinThread(a.t) joinThread(a.t)
close(a.i) close(a.i)
deallocShared(a) deallocShared(a)
proc recv*[TIn, TOut](a: PActor[TIn, TOut]): TTask[TIn, TOut] = proc recv*[In, Out](a: PActor[In, Out]): Task[In, Out] =
## receives a task from `a`'s inbox. ## receives a task from `a`'s inbox.
result = recv(a.i) result = recv(a.i)
proc send*[TIn, TOut, X, Y](receiver: PActor[TIn, TOut], msg: TIn, proc send*[In, Out, X, Y](receiver: PActor[In, Out], msg: In,
sender: PActor[X, Y]) = sender: PActor[X, Y]) =
## sends a message to `a`'s inbox. ## sends a message to `a`'s inbox.
var t: TTask[TIn, TOut] var t: Task[In, Out]
t.receiver = addr(sender.i) t.receiver = addr(sender.i)
shallowCopy(t.data, msg) shallowCopy(t.data, msg)
send(receiver.i, t) send(receiver.i, t)
proc send*[TIn, TOut](receiver: PActor[TIn, TOut], msg: TIn, proc send*[In, Out](receiver: PActor[In, Out], msg: In,
sender: ptr TChannel[TOut] = nil) = sender: ptr Channel[Out] = nil) =
## sends a message to `receiver`'s inbox. ## sends a message to `receiver`'s inbox.
var t: TTask[TIn, TOut] var t: Task[In, Out]
t.receiver = sender t.receiver = sender
shallowCopy(t.data, msg) shallowCopy(t.data, msg)
send(receiver.i, t) send(receiver.i, t)
proc sendShutdown*[TIn, TOut](receiver: PActor[TIn, TOut]) = proc sendShutdown*[In, Out](receiver: PActor[In, Out]) =
## send a shutdown message to `receiver`. ## send a shutdown message to `receiver`.
var t: TTask[TIn, TOut] var t: Task[In, Out]
t.shutdown = true t.shutdown = true
send(receiver.i, t) send(receiver.i, t)
proc reply*[TIn, TOut](t: TTask[TIn, TOut], m: TOut) = proc reply*[In, Out](t: Task[In, Out], m: Out) =
## sends a message to io's output message box. ## sends a message to io's output message box.
when TOut is void: when Out is void:
{.error: "you cannot reply to a void outbox".} {.error: "you cannot reply to a void outbox".}
assert t.receiver != nil assert t.receiver != nil
send(t.receiver[], m) send(t.receiver[], m)
@ -107,34 +108,35 @@ proc reply*[TIn, TOut](t: TTask[TIn, TOut], m: TOut) =
# ----------------- actor pools ---------------------------------------------- # ----------------- actor pools ----------------------------------------------
type type
TActorPool*[TIn, TOut] = object{.pure, final.} ## an actor pool ActorPool*[In, Out] = object{.pure, final.} ## an actor pool
actors: seq[PActor[TIn, TOut]] actors: seq[PActor[In, Out]]
when TOut isnot void: when Out isnot void:
outputs: TChannel[TOut] outputs: Channel[Out]
{.deprecated: [TActorPool: ActorPool].}
proc `^`*[T](f: ptr TChannel[T]): T = proc `^`*[T](f: ptr Channel[T]): T =
## alias for 'recv'. ## alias for 'recv'.
result = recv(f[]) result = recv(f[])
proc poolWorker[TIn, TOut](self: PActor[TIn, TOut]) {.thread.} = proc poolWorker[In, Out](self: PActor[In, Out]) {.thread.} =
while true: while true:
var m = self.recv var m = self.recv
if m.shutDown: break if m.shutDown: break
when TOut is void: when Out is void:
m.action(m.data) m.action(m.data)
else: else:
send(m.receiver[], m.action(m.data)) send(m.receiver[], m.action(m.data))
#self.reply() #self.reply()
proc createActorPool*[TIn, TOut](a: var TActorPool[TIn, TOut], poolSize = 4) = proc createActorPool*[In, Out](a: var ActorPool[In, Out], poolSize = 4) =
## creates an actor pool. ## creates an actor pool.
newSeq(a.actors, poolSize) newSeq(a.actors, poolSize)
when TOut isnot void: when Out isnot void:
open(a.outputs) open(a.outputs)
for i in 0 .. < a.actors.len: for i in 0 .. < a.actors.len:
a.actors[i] = spawn(poolWorker[TIn, TOut]) a.actors[i] = spawn(poolWorker[In, Out])
proc sync*[TIn, TOut](a: var TActorPool[TIn, TOut], polling=50) = proc sync*[In, Out](a: var ActorPool[In, Out], polling=50) =
## waits for every actor of `a` to finish with its work. Currently this is ## waits for every actor of `a` to finish with its work. Currently this is
## implemented as polling every `polling` ms and has a slight chance ## implemented as polling every `polling` ms and has a slight chance
## of failing since we check for every actor to be in `ready` state and not ## of failing since we check for every actor to be in `ready` state and not
@ -157,18 +159,18 @@ proc sync*[TIn, TOut](a: var TActorPool[TIn, TOut], polling=50) =
if allReadyCount > 1: break if allReadyCount > 1: break
sleep(polling) sleep(polling)
proc terminate*[TIn, TOut](a: var TActorPool[TIn, TOut]) = proc terminate*[In, Out](a: var ActorPool[In, Out]) =
## terminates each actor in the actor pool `a` and frees the ## terminates each actor in the actor pool `a` and frees the
## resources attached to `a`. ## resources attached to `a`.
var t: TTask[TIn, TOut] var t: Task[In, Out]
t.shutdown = true t.shutdown = true
for i in 0.. <a.actors.len: send(a.actors[i].i, t) for i in 0.. <a.actors.len: send(a.actors[i].i, t)
for i in 0.. <a.actors.len: join(a.actors[i]) for i in 0.. <a.actors.len: join(a.actors[i])
when TOut isnot void: when Out isnot void:
close(a.outputs) close(a.outputs)
a.actors = nil a.actors = nil
proc join*[TIn, TOut](a: var TActorPool[TIn, TOut]) = proc join*[In, Out](a: var ActorPool[In, Out]) =
## short-cut for `sync` and then `terminate`. ## short-cut for `sync` and then `terminate`.
sync(a) sync(a)
terminate(a) terminate(a)
@ -202,28 +204,28 @@ template schedule =
else: else:
raise newException(DeadThreadError, "cannot send message; thread died") raise newException(DeadThreadError, "cannot send message; thread died")
proc spawn*[TIn, TOut](p: var TActorPool[TIn, TOut], input: TIn, proc spawn*[In, Out](p: var ActorPool[In, Out], input: In,
action: proc (input: TIn): TOut {.thread.} action: proc (input: In): Out {.thread.}
): ptr TChannel[TOut] = ): ptr Channel[Out] =
## uses the actor pool to run ``action(input)`` concurrently. ## uses the actor pool to run ``action(input)`` concurrently.
## `spawn` is guaranteed to not block. ## `spawn` is guaranteed to not block.
var t: TTask[TIn, TOut] var t: Task[In, Out]
setupTask() setupTask()
result = addr(p.outputs) result = addr(p.outputs)
t.receiver = result t.receiver = result
schedule() schedule()
proc spawn*[TIn](p: var TActorPool[TIn, void], input: TIn, proc spawn*[In](p: var ActorPool[In, void], input: In,
action: proc (input: TIn) {.thread.}) = action: proc (input: In) {.thread.}) =
## uses the actor pool to run ``action(input)`` concurrently. ## uses the actor pool to run ``action(input)`` concurrently.
## `spawn` is guaranteed to not block. ## `spawn` is guaranteed to not block.
var t: TTask[TIn, void] var t: Task[In, void]
setupTask() setupTask()
schedule() schedule()
when not defined(testing) and isMainModule: when not defined(testing) and isMainModule:
var var
a: TActorPool[int, void] a: ActorPool[int, void]
createActorPool(a) createActorPool(a)
for i in 0 .. < 300: for i in 0 .. < 300:
a.spawn(i, proc (x: int) {.thread.} = echo x) a.spawn(i, proc (x: int) {.thread.} = echo x)

View file

@ -99,16 +99,13 @@ proc lowerBound*[T](a: openArray[T], key: T, cmp: proc(x,y: T): int {.closure.})
## arr.insert(4, arr.lowerBound(4)) ## arr.insert(4, arr.lowerBound(4))
## `after running the above arr is `[1,2,3,4,5,6,7,8,9]` ## `after running the above arr is `[1,2,3,4,5,6,7,8,9]`
result = a.low result = a.low
var pos = result var count = a.high - a.low + 1
var count, step: int var step, pos: int
count = a.high - a.low + 1
while count != 0: while count != 0:
pos = result
step = count div 2 step = count div 2
pos += step pos = result + step
if cmp(a[pos], key) < 0: if cmp(a[pos], key) < 0:
pos.inc result = pos + 1
result = pos
count -= step + 1 count -= step + 1
else: else:
count = step count = step
@ -331,3 +328,16 @@ proc prevPermutation*[T](x: var openarray[T]): bool {.discardable.} =
swap x[i-1], x[j] swap x[i-1], x[j]
result = true result = true
when isMainModule:
# Tests for lowerBound
var arr = @[1,2,3,5,6,7,8,9]
assert arr.lowerBound(0) == 0
assert arr.lowerBound(4) == 3
assert arr.lowerBound(5) == 3
assert arr.lowerBound(10) == 8
arr = @[1,5,10]
assert arr.lowerBound(4) == 1
assert arr.lowerBound(5) == 1
assert arr.lowerBound(6) == 2

View file

@ -323,32 +323,34 @@ proc processTimers(p: PDispatcherBase) =
when defined(windows) or defined(nimdoc): when defined(windows) or defined(nimdoc):
import winlean, sets, hashes import winlean, sets, hashes
type type
TCompletionKey = Dword CompletionKey = Dword
TCompletionData* = object CompletionData* = object
fd*: TAsyncFD # TODO: Rename this. fd*: AsyncFD # TODO: Rename this.
cb*: proc (fd: TAsyncFD, bytesTransferred: Dword, cb*: proc (fd: AsyncFD, bytesTransferred: Dword,
errcode: OSErrorCode) {.closure,gcsafe.} errcode: OSErrorCode) {.closure,gcsafe.}
PDispatcher* = ref object of PDispatcherBase PDispatcher* = ref object of PDispatcherBase
ioPort: THandle ioPort: Handle
handles: HashSet[TAsyncFD] handles: HashSet[AsyncFD]
TCustomOverlapped = object of TOVERLAPPED CustomOverlapped = object of TOVERLAPPED
data*: TCompletionData data*: CompletionData
PCustomOverlapped* = ref TCustomOverlapped PCustomOverlapped* = ref CustomOverlapped
TAsyncFD* = distinct int AsyncFD* = distinct int
{.deprecated: [TCompletionKey: CompletionKey, TAsyncFD: AsyncFD,
TCustomOverlapped: CustomOverlapped, TCompletionData: CompletionData].}
proc hash(x: TAsyncFD): THash {.borrow.} proc hash(x: AsyncFD): Hash {.borrow.}
proc `==`*(x: TAsyncFD, y: TAsyncFD): bool {.borrow.} proc `==`*(x: AsyncFD, y: AsyncFD): bool {.borrow.}
proc newDispatcher*(): PDispatcher = proc newDispatcher*(): PDispatcher =
## Creates a new Dispatcher instance. ## Creates a new Dispatcher instance.
new result new result
result.ioPort = createIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 1) result.ioPort = createIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 1)
result.handles = initSet[TAsyncFD]() result.handles = initSet[AsyncFD]()
result.timers = @[] result.timers = @[]
var gDisp{.threadvar.}: PDispatcher ## Global dispatcher var gDisp{.threadvar.}: PDispatcher ## Global dispatcher
@ -357,15 +359,15 @@ when defined(windows) or defined(nimdoc):
if gDisp.isNil: gDisp = newDispatcher() if gDisp.isNil: gDisp = newDispatcher()
result = gDisp result = gDisp
proc register*(fd: TAsyncFD) = proc register*(fd: AsyncFD) =
## Registers ``fd`` with the dispatcher. ## Registers ``fd`` with the dispatcher.
let p = getGlobalDispatcher() let p = getGlobalDispatcher()
if createIoCompletionPort(fd.THandle, p.ioPort, if createIoCompletionPort(fd.Handle, p.ioPort,
cast[TCompletionKey](fd), 1) == 0: cast[CompletionKey](fd), 1) == 0:
raiseOSError(osLastError()) raiseOSError(osLastError())
p.handles.incl(fd) p.handles.incl(fd)
proc verifyPresence(fd: TAsyncFD) = proc verifyPresence(fd: AsyncFD) =
## Ensures that file descriptor has been registered with the dispatcher. ## Ensures that file descriptor has been registered with the dispatcher.
let p = getGlobalDispatcher() let p = getGlobalDispatcher()
if fd notin p.handles: if fd notin p.handles:
@ -394,7 +396,7 @@ when defined(windows) or defined(nimdoc):
# TODO: http://www.serverframework.com/handling-multiple-pending-socket-read-and-write-operations.html # TODO: http://www.serverframework.com/handling-multiple-pending-socket-read-and-write-operations.html
if res: if res:
# This is useful for ensuring the reliability of the overlapped struct. # This is useful for ensuring the reliability of the overlapped struct.
assert customOverlapped.data.fd == lpCompletionKey.TAsyncFD assert customOverlapped.data.fd == lpCompletionKey.AsyncFD
customOverlapped.data.cb(customOverlapped.data.fd, customOverlapped.data.cb(customOverlapped.data.fd,
lpNumberOfBytesTransferred, OSErrorCode(-1)) lpNumberOfBytesTransferred, OSErrorCode(-1))
@ -402,7 +404,7 @@ when defined(windows) or defined(nimdoc):
else: else:
let errCode = osLastError() let errCode = osLastError()
if customOverlapped != nil: if customOverlapped != nil:
assert customOverlapped.data.fd == lpCompletionKey.TAsyncFD assert customOverlapped.data.fd == lpCompletionKey.AsyncFD
customOverlapped.data.cb(customOverlapped.data.fd, customOverlapped.data.cb(customOverlapped.data.fd,
lpNumberOfBytesTransferred, errCode) lpNumberOfBytesTransferred, errCode)
GC_unref(customOverlapped) GC_unref(customOverlapped)
@ -480,7 +482,7 @@ when defined(windows) or defined(nimdoc):
dwRemoteAddressLength, LocalSockaddr, LocalSockaddrLength, dwRemoteAddressLength, LocalSockaddr, LocalSockaddrLength,
RemoteSockaddr, RemoteSockaddrLength) RemoteSockaddr, RemoteSockaddrLength)
proc connect*(socket: TAsyncFD, address: string, port: Port, proc connect*(socket: AsyncFD, address: string, port: Port,
af = AF_INET): Future[void] = af = AF_INET): Future[void] =
## Connects ``socket`` to server at ``address:port``. ## Connects ``socket`` to server at ``address:port``.
## ##
@ -506,8 +508,8 @@ when defined(windows) or defined(nimdoc):
# http://blogs.msdn.com/b/oldnewthing/archive/2011/02/02/10123392.aspx # http://blogs.msdn.com/b/oldnewthing/archive/2011/02/02/10123392.aspx
var ol = PCustomOverlapped() var ol = PCustomOverlapped()
GC_ref(ol) GC_ref(ol)
ol.data = TCompletionData(fd: socket, cb: ol.data = CompletionData(fd: socket, cb:
proc (fd: TAsyncFD, bytesCount: Dword, errcode: OSErrorCode) = proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
if not retFuture.finished: if not retFuture.finished:
if errcode == OSErrorCode(-1): if errcode == OSErrorCode(-1):
retFuture.complete() retFuture.complete()
@ -542,7 +544,7 @@ when defined(windows) or defined(nimdoc):
retFuture.fail(newException(OSError, osErrorMsg(lastError))) retFuture.fail(newException(OSError, osErrorMsg(lastError)))
return retFuture return retFuture
proc recv*(socket: TAsyncFD, size: int, proc recv*(socket: AsyncFD, size: int,
flags = {SocketFlag.SafeDisconn}): Future[string] = flags = {SocketFlag.SafeDisconn}): Future[string] =
## Reads **up to** ``size`` bytes from ``socket``. Returned future will ## Reads **up to** ``size`` bytes from ``socket``. Returned future will
## complete once all the data requested is read, a part of the data has been ## complete once all the data requested is read, a part of the data has been
@ -570,8 +572,8 @@ when defined(windows) or defined(nimdoc):
var flagsio = flags.toOSFlags().Dword var flagsio = flags.toOSFlags().Dword
var ol = PCustomOverlapped() var ol = PCustomOverlapped()
GC_ref(ol) GC_ref(ol)
ol.data = TCompletionData(fd: socket, cb: ol.data = CompletionData(fd: socket, cb:
proc (fd: TAsyncFD, bytesCount: Dword, errcode: OSErrorCode) = proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
if not retFuture.finished: if not retFuture.finished:
if errcode == OSErrorCode(-1): if errcode == OSErrorCode(-1):
if bytesCount == 0 and dataBuf.buf[0] == '\0': if bytesCount == 0 and dataBuf.buf[0] == '\0':
@ -634,7 +636,7 @@ when defined(windows) or defined(nimdoc):
# free ``ol``. # free ``ol``.
return retFuture return retFuture
proc recvInto*(socket: TAsyncFD, buf: cstring, size: int, proc recvInto*(socket: AsyncFD, buf: cstring, size: int,
flags = {SocketFlag.SafeDisconn}): Future[int] = flags = {SocketFlag.SafeDisconn}): Future[int] =
## Reads **up to** ``size`` bytes from ``socket`` into ``buf``, which must ## Reads **up to** ``size`` bytes from ``socket`` into ``buf``, which must
## at least be of that size. Returned future will complete once all the ## at least be of that size. Returned future will complete once all the
@ -665,8 +667,8 @@ when defined(windows) or defined(nimdoc):
var flagsio = flags.toOSFlags().Dword var flagsio = flags.toOSFlags().Dword
var ol = PCustomOverlapped() var ol = PCustomOverlapped()
GC_ref(ol) GC_ref(ol)
ol.data = TCompletionData(fd: socket, cb: ol.data = CompletionData(fd: socket, cb:
proc (fd: TAsyncFD, bytesCount: Dword, errcode: OSErrorCode) = proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
if not retFuture.finished: if not retFuture.finished:
if errcode == OSErrorCode(-1): if errcode == OSErrorCode(-1):
if bytesCount == 0 and dataBuf.buf[0] == '\0': if bytesCount == 0 and dataBuf.buf[0] == '\0':
@ -721,7 +723,7 @@ when defined(windows) or defined(nimdoc):
# free ``ol``. # free ``ol``.
return retFuture return retFuture
proc send*(socket: TAsyncFD, data: string, proc send*(socket: AsyncFD, data: string,
flags = {SocketFlag.SafeDisconn}): Future[void] = flags = {SocketFlag.SafeDisconn}): Future[void] =
## Sends ``data`` to ``socket``. The returned future will complete once all ## Sends ``data`` to ``socket``. The returned future will complete once all
## data has been sent. ## data has been sent.
@ -735,8 +737,8 @@ when defined(windows) or defined(nimdoc):
var bytesReceived, lowFlags: Dword var bytesReceived, lowFlags: Dword
var ol = PCustomOverlapped() var ol = PCustomOverlapped()
GC_ref(ol) GC_ref(ol)
ol.data = TCompletionData(fd: socket, cb: ol.data = CompletionData(fd: socket, cb:
proc (fd: TAsyncFD, bytesCount: Dword, errcode: OSErrorCode) = proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
if not retFuture.finished: if not retFuture.finished:
if errcode == OSErrorCode(-1): if errcode == OSErrorCode(-1):
retFuture.complete() retFuture.complete()
@ -764,8 +766,8 @@ when defined(windows) or defined(nimdoc):
# free ``ol``. # free ``ol``.
return retFuture return retFuture
proc acceptAddr*(socket: TAsyncFD, flags = {SocketFlag.SafeDisconn}): proc acceptAddr*(socket: AsyncFD, flags = {SocketFlag.SafeDisconn}):
Future[tuple[address: string, client: TAsyncFD]] = Future[tuple[address: string, client: AsyncFD]] =
## Accepts a new connection. Returns a future containing the client socket ## Accepts a new connection. Returns a future containing the client socket
## corresponding to that connection and the remote address of the client. ## corresponding to that connection and the remote address of the client.
## The future will complete when the connection is successfully accepted. ## The future will complete when the connection is successfully accepted.
@ -778,7 +780,7 @@ when defined(windows) or defined(nimdoc):
## flag is specified then this error will not be raised and instead ## flag is specified then this error will not be raised and instead
## accept will be called again. ## accept will be called again.
verifyPresence(socket) verifyPresence(socket)
var retFuture = newFuture[tuple[address: string, client: TAsyncFD]]("acceptAddr") var retFuture = newFuture[tuple[address: string, client: AsyncFD]]("acceptAddr")
var clientSock = newRawSocket() var clientSock = newRawSocket()
if clientSock == osInvalidSocket: raiseOSError(osLastError()) if clientSock == osInvalidSocket: raiseOSError(osLastError())
@ -803,11 +805,11 @@ when defined(windows) or defined(nimdoc):
dwLocalAddressLength, dwRemoteAddressLength, dwLocalAddressLength, dwRemoteAddressLength,
addr localSockaddr, addr localLen, addr localSockaddr, addr localLen,
addr remoteSockaddr, addr remoteLen) addr remoteSockaddr, addr remoteLen)
register(clientSock.TAsyncFD) register(clientSock.AsyncFD)
# TODO: IPv6. Check ``sa_family``. http://stackoverflow.com/a/9212542/492186 # TODO: IPv6. Check ``sa_family``. http://stackoverflow.com/a/9212542/492186
retFuture.complete( retFuture.complete(
(address: $inet_ntoa(cast[ptr Sockaddr_in](remoteSockAddr).sin_addr), (address: $inet_ntoa(cast[ptr Sockaddr_in](remoteSockAddr).sin_addr),
client: clientSock.TAsyncFD) client: clientSock.AsyncFD)
) )
template failAccept(errcode): stmt = template failAccept(errcode): stmt =
@ -824,8 +826,8 @@ when defined(windows) or defined(nimdoc):
var ol = PCustomOverlapped() var ol = PCustomOverlapped()
GC_ref(ol) GC_ref(ol)
ol.data = TCompletionData(fd: socket, cb: ol.data = CompletionData(fd: socket, cb:
proc (fd: TAsyncFD, bytesCount: Dword, errcode: OSErrorCode) = proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
if not retFuture.finished: if not retFuture.finished:
if errcode == OSErrorCode(-1): if errcode == OSErrorCode(-1):
completeAccept() completeAccept()
@ -853,26 +855,26 @@ when defined(windows) or defined(nimdoc):
return retFuture return retFuture
proc newAsyncRawSocket*(domain, typ, protocol: cint): TAsyncFD = proc newAsyncRawSocket*(domain, typ, protocol: cint): AsyncFD =
## Creates a new socket and registers it with the dispatcher implicitly. ## Creates a new socket and registers it with the dispatcher implicitly.
result = newRawSocket(domain, typ, protocol).TAsyncFD result = newRawSocket(domain, typ, protocol).AsyncFD
result.SocketHandle.setBlocking(false) result.SocketHandle.setBlocking(false)
register(result) register(result)
proc newAsyncRawSocket*(domain: Domain = AF_INET, proc newAsyncRawSocket*(domain: Domain = AF_INET,
typ: SockType = SOCK_STREAM, typ: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP): TAsyncFD = protocol: Protocol = IPPROTO_TCP): AsyncFD =
## Creates a new socket and registers it with the dispatcher implicitly. ## Creates a new socket and registers it with the dispatcher implicitly.
result = newRawSocket(domain, typ, protocol).TAsyncFD result = newRawSocket(domain, typ, protocol).AsyncFD
result.SocketHandle.setBlocking(false) result.SocketHandle.setBlocking(false)
register(result) register(result)
proc closeSocket*(socket: TAsyncFD) = proc closeSocket*(socket: AsyncFD) =
## Closes a socket and ensures that it is unregistered. ## Closes a socket and ensures that it is unregistered.
socket.SocketHandle.close() socket.SocketHandle.close()
getGlobalDispatcher().handles.excl(socket) getGlobalDispatcher().handles.excl(socket)
proc unregister*(fd: TAsyncFD) = proc unregister*(fd: AsyncFD) =
## Unregisters ``fd``. ## Unregisters ``fd``.
getGlobalDispatcher().handles.excl(fd) getGlobalDispatcher().handles.excl(fd)
@ -892,18 +894,19 @@ else:
MSG_NOSIGNAL MSG_NOSIGNAL
type type
TAsyncFD* = distinct cint AsyncFD* = distinct cint
TCallback = proc (fd: TAsyncFD): bool {.closure,gcsafe.} Callback = proc (fd: AsyncFD): bool {.closure,gcsafe.}
PData* = ref object of RootRef PData* = ref object of RootRef
fd: TAsyncFD fd: AsyncFD
readCBs: seq[TCallback] readCBs: seq[Callback]
writeCBs: seq[TCallback] writeCBs: seq[Callback]
PDispatcher* = ref object of PDispatcherBase PDispatcher* = ref object of PDispatcherBase
selector: Selector selector: Selector
{.deprecated: [TAsyncFD: AsyncFD, TCallback: Callback].}
proc `==`*(x, y: TAsyncFD): bool {.borrow.} proc `==`*(x, y: AsyncFD): bool {.borrow.}
proc newDispatcher*(): PDispatcher = proc newDispatcher*(): PDispatcher =
new result new result
@ -915,18 +918,18 @@ else:
if gDisp.isNil: gDisp = newDispatcher() if gDisp.isNil: gDisp = newDispatcher()
result = gDisp result = gDisp
proc update(fd: TAsyncFD, events: set[Event]) = proc update(fd: AsyncFD, events: set[Event]) =
let p = getGlobalDispatcher() let p = getGlobalDispatcher()
assert fd.SocketHandle in p.selector assert fd.SocketHandle in p.selector
discard p.selector.update(fd.SocketHandle, events) discard p.selector.update(fd.SocketHandle, events)
proc register*(fd: TAsyncFD) = proc register*(fd: AsyncFD) =
let p = getGlobalDispatcher() let p = getGlobalDispatcher()
var data = PData(fd: fd, readCBs: @[], writeCBs: @[]) var data = PData(fd: fd, readCBs: @[], writeCBs: @[])
p.selector.register(fd.SocketHandle, {}, data.RootRef) p.selector.register(fd.SocketHandle, {}, data.RootRef)
proc newAsyncRawSocket*(domain: cint, typ: cint, protocol: cint): TAsyncFD = proc newAsyncRawSocket*(domain: cint, typ: cint, protocol: cint): AsyncFD =
result = newRawSocket(domain, typ, protocol).TAsyncFD result = newRawSocket(domain, typ, protocol).AsyncFD
result.SocketHandle.setBlocking(false) result.SocketHandle.setBlocking(false)
when defined(macosx): when defined(macosx):
result.SocketHandle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1) result.SocketHandle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1)
@ -934,29 +937,29 @@ else:
proc newAsyncRawSocket*(domain: Domain = AF_INET, proc newAsyncRawSocket*(domain: Domain = AF_INET,
typ: SockType = SOCK_STREAM, typ: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP): TAsyncFD = protocol: Protocol = IPPROTO_TCP): AsyncFD =
result = newRawSocket(domain, typ, protocol).TAsyncFD result = newRawSocket(domain, typ, protocol).AsyncFD
result.SocketHandle.setBlocking(false) result.SocketHandle.setBlocking(false)
when defined(macosx): when defined(macosx):
result.SocketHandle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1) result.SocketHandle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1)
register(result) register(result)
proc closeSocket*(sock: TAsyncFD) = proc closeSocket*(sock: AsyncFD) =
let disp = getGlobalDispatcher() let disp = getGlobalDispatcher()
sock.SocketHandle.close() sock.SocketHandle.close()
disp.selector.unregister(sock.SocketHandle) disp.selector.unregister(sock.SocketHandle)
proc unregister*(fd: TAsyncFD) = proc unregister*(fd: AsyncFD) =
getGlobalDispatcher().selector.unregister(fd.SocketHandle) getGlobalDispatcher().selector.unregister(fd.SocketHandle)
proc addRead*(fd: TAsyncFD, cb: TCallback) = proc addRead*(fd: AsyncFD, cb: Callback) =
let p = getGlobalDispatcher() let p = getGlobalDispatcher()
if fd.SocketHandle notin p.selector: if fd.SocketHandle notin p.selector:
raise newException(ValueError, "File descriptor not registered.") raise newException(ValueError, "File descriptor not registered.")
p.selector[fd.SocketHandle].data.PData.readCBs.add(cb) p.selector[fd.SocketHandle].data.PData.readCBs.add(cb)
update(fd, p.selector[fd.SocketHandle].events + {EvRead}) update(fd, p.selector[fd.SocketHandle].events + {EvRead})
proc addWrite*(fd: TAsyncFD, cb: TCallback) = proc addWrite*(fd: AsyncFD, cb: Callback) =
let p = getGlobalDispatcher() let p = getGlobalDispatcher()
if fd.SocketHandle notin p.selector: if fd.SocketHandle notin p.selector:
raise newException(ValueError, "File descriptor not registered.") raise newException(ValueError, "File descriptor not registered.")
@ -967,7 +970,7 @@ else:
let p = getGlobalDispatcher() let p = getGlobalDispatcher()
for info in p.selector.select(timeout): for info in p.selector.select(timeout):
let data = PData(info.key.data) let data = PData(info.key.data)
assert data.fd == info.key.fd.TAsyncFD assert data.fd == info.key.fd.AsyncFD
#echo("In poll ", data.fd.cint) #echo("In poll ", data.fd.cint)
if EvError in info.events: if EvError in info.events:
closeSocket(data.fd) closeSocket(data.fd)
@ -1005,11 +1008,11 @@ else:
processTimers(p) processTimers(p)
proc connect*(socket: TAsyncFD, address: string, port: Port, proc connect*(socket: AsyncFD, address: string, port: Port,
af = AF_INET): Future[void] = af = AF_INET): Future[void] =
var retFuture = newFuture[void]("connect") var retFuture = newFuture[void]("connect")
proc cb(fd: TAsyncFD): bool = proc cb(fd: AsyncFD): bool =
# We have connected. # We have connected.
retFuture.complete() retFuture.complete()
return true return true
@ -1040,13 +1043,13 @@ else:
retFuture.fail(newException(OSError, osErrorMsg(lastError))) retFuture.fail(newException(OSError, osErrorMsg(lastError)))
return retFuture return retFuture
proc recv*(socket: TAsyncFD, size: int, proc recv*(socket: AsyncFD, size: int,
flags = {SocketFlag.SafeDisconn}): Future[string] = flags = {SocketFlag.SafeDisconn}): Future[string] =
var retFuture = newFuture[string]("recv") var retFuture = newFuture[string]("recv")
var readBuffer = newString(size) var readBuffer = newString(size)
proc cb(sock: TAsyncFD): bool = proc cb(sock: AsyncFD): bool =
result = true result = true
let res = recv(sock.SocketHandle, addr readBuffer[0], size.cint, let res = recv(sock.SocketHandle, addr readBuffer[0], size.cint,
flags.toOSFlags()) flags.toOSFlags())
@ -1070,11 +1073,11 @@ else:
addRead(socket, cb) addRead(socket, cb)
return retFuture return retFuture
proc recvInto*(socket: TAsyncFD, buf: cstring, size: int, proc recvInto*(socket: AsyncFD, buf: cstring, size: int,
flags = {SocketFlag.SafeDisconn}): Future[int] = flags = {SocketFlag.SafeDisconn}): Future[int] =
var retFuture = newFuture[int]("recvInto") var retFuture = newFuture[int]("recvInto")
proc cb(sock: TAsyncFD): bool = proc cb(sock: AsyncFD): bool =
result = true result = true
let res = recv(sock.SocketHandle, buf, size.cint, let res = recv(sock.SocketHandle, buf, size.cint,
flags.toOSFlags()) flags.toOSFlags())
@ -1094,13 +1097,13 @@ else:
addRead(socket, cb) addRead(socket, cb)
return retFuture return retFuture
proc send*(socket: TAsyncFD, data: string, proc send*(socket: AsyncFD, data: string,
flags = {SocketFlag.SafeDisconn}): Future[void] = flags = {SocketFlag.SafeDisconn}): Future[void] =
var retFuture = newFuture[void]("send") var retFuture = newFuture[void]("send")
var written = 0 var written = 0
proc cb(sock: TAsyncFD): bool = proc cb(sock: AsyncFD): bool =
result = true result = true
let netSize = data.len-written let netSize = data.len-written
var d = data.cstring var d = data.cstring
@ -1126,11 +1129,11 @@ else:
addWrite(socket, cb) addWrite(socket, cb)
return retFuture return retFuture
proc acceptAddr*(socket: TAsyncFD, flags = {SocketFlag.SafeDisconn}): proc acceptAddr*(socket: AsyncFD, flags = {SocketFlag.SafeDisconn}):
Future[tuple[address: string, client: TAsyncFD]] = Future[tuple[address: string, client: AsyncFD]] =
var retFuture = newFuture[tuple[address: string, var retFuture = newFuture[tuple[address: string,
client: TAsyncFD]]("acceptAddr") client: AsyncFD]]("acceptAddr")
proc cb(sock: TAsyncFD): bool = proc cb(sock: AsyncFD): bool =
result = true result = true
var sockAddress: SockAddr_in var sockAddress: SockAddr_in
var addrLen = sizeof(sockAddress).Socklen var addrLen = sizeof(sockAddress).Socklen
@ -1147,8 +1150,8 @@ else:
else: else:
retFuture.fail(newException(OSError, osErrorMsg(lastError))) retFuture.fail(newException(OSError, osErrorMsg(lastError)))
else: else:
register(client.TAsyncFD) register(client.AsyncFD)
retFuture.complete(($inet_ntoa(sockAddress.sin_addr), client.TAsyncFD)) retFuture.complete(($inet_ntoa(sockAddress.sin_addr), client.AsyncFD))
addRead(socket, cb) addRead(socket, cb)
return retFuture return retFuture
@ -1160,15 +1163,15 @@ proc sleepAsync*(ms: int): Future[void] =
p.timers.add((epochTime() + (ms / 1000), retFuture)) p.timers.add((epochTime() + (ms / 1000), retFuture))
return retFuture return retFuture
proc accept*(socket: TAsyncFD, proc accept*(socket: AsyncFD,
flags = {SocketFlag.SafeDisconn}): Future[TAsyncFD] = flags = {SocketFlag.SafeDisconn}): Future[AsyncFD] =
## Accepts a new connection. Returns a future containing the client socket ## Accepts a new connection. Returns a future containing the client socket
## corresponding to that connection. ## corresponding to that connection.
## The future will complete when the connection is successfully accepted. ## The future will complete when the connection is successfully accepted.
var retFut = newFuture[TAsyncFD]("accept") var retFut = newFuture[AsyncFD]("accept")
var fut = acceptAddr(socket, flags) var fut = acceptAddr(socket, flags)
fut.callback = fut.callback =
proc (future: Future[tuple[address: string, client: TAsyncFD]]) = proc (future: Future[tuple[address: string, client: AsyncFD]]) =
assert future.finished assert future.finished
if future.failed: if future.failed:
retFut.fail(future.error) retFut.fail(future.error)
@ -1495,7 +1498,7 @@ macro async*(prc: stmt): stmt {.immediate.} =
#if prc[0].getName == "test": #if prc[0].getName == "test":
# echo(toStrLit(result)) # echo(toStrLit(result))
proc recvLine*(socket: TAsyncFD): Future[string] {.async.} = proc recvLine*(socket: AsyncFD): Future[string] {.async.} =
## Reads a line of data from ``socket``. Returned future will complete once ## Reads a line of data from ``socket``. Returned future will complete once
## a full line is read or an error occurs. ## a full line is read or an error occurs.
## ##

View file

@ -31,7 +31,7 @@ else:
type type
AsyncFile* = ref object AsyncFile* = ref object
fd: TAsyncFd fd: AsyncFd
offset: int64 offset: int64
when defined(windows) or defined(nimdoc): when defined(windows) or defined(nimdoc):
@ -72,7 +72,7 @@ proc getFileSize(f: AsyncFile): int64 =
## Retrieves the specified file's size. ## Retrieves the specified file's size.
when defined(windows) or defined(nimdoc): when defined(windows) or defined(nimdoc):
var high: DWord var high: DWord
let low = getFileSize(f.fd.THandle, addr high) let low = getFileSize(f.fd.Handle, addr high)
if low == INVALID_FILE_SIZE: if low == INVALID_FILE_SIZE:
raiseOSError(osLastError()) raiseOSError(osLastError())
return (high shl 32) or low return (high shl 32) or low
@ -88,13 +88,13 @@ proc openAsync*(filename: string, mode = fmRead): AsyncFile =
when useWinUnicode: when useWinUnicode:
result.fd = createFileW(newWideCString(filename), desiredAccess, result.fd = createFileW(newWideCString(filename), desiredAccess,
FILE_SHARE_READ, FILE_SHARE_READ,
nil, creationDisposition, flags, 0).TAsyncFd nil, creationDisposition, flags, 0).AsyncFd
else: else:
result.fd = createFileA(filename, desiredAccess, result.fd = createFileA(filename, desiredAccess,
FILE_SHARE_READ, FILE_SHARE_READ,
nil, creationDisposition, flags, 0).TAsyncFd nil, creationDisposition, flags, 0).AsyncFd
if result.fd.THandle == INVALID_HANDLE_VALUE: if result.fd.Handle == INVALID_HANDLE_VALUE:
raiseOSError(osLastError()) raiseOSError(osLastError())
register(result.fd) register(result.fd)
@ -106,7 +106,7 @@ proc openAsync*(filename: string, mode = fmRead): AsyncFile =
let flags = getPosixFlags(mode) let flags = getPosixFlags(mode)
# RW (Owner), RW (Group), R (Other) # RW (Owner), RW (Group), R (Other)
let perm = S_IRUSR or S_IWUSR or S_IRGRP or S_IWGRP or S_IROTH let perm = S_IRUSR or S_IWUSR or S_IRGRP or S_IWGRP or S_IROTH
result.fd = open(filename, flags, perm).TAsyncFD result.fd = open(filename, flags, perm).AsyncFD
if result.fd.cint == -1: if result.fd.cint == -1:
raiseOSError(osLastError()) raiseOSError(osLastError())
@ -125,8 +125,8 @@ proc read*(f: AsyncFile, size: int): Future[string] =
var ol = PCustomOverlapped() var ol = PCustomOverlapped()
GC_ref(ol) GC_ref(ol)
ol.data = TCompletionData(fd: f.fd, cb: ol.data = CompletionData(fd: f.fd, cb:
proc (fd: TAsyncFD, bytesCount: Dword, errcode: OSErrorCode) = proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
if not retFuture.finished: if not retFuture.finished:
if errcode == OSErrorCode(-1): if errcode == OSErrorCode(-1):
assert bytesCount > 0 assert bytesCount > 0
@ -148,7 +148,7 @@ proc read*(f: AsyncFile, size: int): Future[string] =
ol.offsetHigh = DWord(f.offset shr 32) ol.offsetHigh = DWord(f.offset shr 32)
# According to MSDN we're supposed to pass nil to lpNumberOfBytesRead. # According to MSDN we're supposed to pass nil to lpNumberOfBytesRead.
let ret = readFile(f.fd.THandle, buffer, size.int32, nil, let ret = readFile(f.fd.Handle, buffer, size.int32, nil,
cast[POVERLAPPED](ol)) cast[POVERLAPPED](ol))
if not ret.bool: if not ret.bool:
let err = osLastError() let err = osLastError()
@ -161,7 +161,7 @@ proc read*(f: AsyncFile, size: int): Future[string] =
else: else:
# Request completed immediately. # Request completed immediately.
var bytesRead: DWord var bytesRead: DWord
let overlappedRes = getOverlappedResult(f.fd.THandle, let overlappedRes = getOverlappedResult(f.fd.Handle,
cast[POverlapped](ol)[], bytesRead, false.WinBool) cast[POverlapped](ol)[], bytesRead, false.WinBool)
if not overlappedRes.bool: if not overlappedRes.bool:
let err = osLastError() let err = osLastError()
@ -179,7 +179,7 @@ proc read*(f: AsyncFile, size: int): Future[string] =
else: else:
var readBuffer = newString(size) var readBuffer = newString(size)
proc cb(fd: TAsyncFD): bool = proc cb(fd: AsyncFD): bool =
result = true result = true
let res = read(fd.cint, addr readBuffer[0], size.cint) let res = read(fd.cint, addr readBuffer[0], size.cint)
if res < 0: if res < 0:
@ -251,8 +251,8 @@ proc write*(f: AsyncFile, data: string): Future[void] =
var ol = PCustomOverlapped() var ol = PCustomOverlapped()
GC_ref(ol) GC_ref(ol)
ol.data = TCompletionData(fd: f.fd, cb: ol.data = CompletionData(fd: f.fd, cb:
proc (fd: TAsyncFD, bytesCount: DWord, errcode: OSErrorCode) = proc (fd: AsyncFD, bytesCount: DWord, errcode: OSErrorCode) =
if not retFuture.finished: if not retFuture.finished:
if errcode == OSErrorCode(-1): if errcode == OSErrorCode(-1):
assert bytesCount == data.len.int32 assert bytesCount == data.len.int32
@ -268,7 +268,7 @@ proc write*(f: AsyncFile, data: string): Future[void] =
ol.offsetHigh = DWord(f.offset shr 32) ol.offsetHigh = DWord(f.offset shr 32)
# According to MSDN we're supposed to pass nil to lpNumberOfBytesWritten. # According to MSDN we're supposed to pass nil to lpNumberOfBytesWritten.
let ret = writeFile(f.fd.THandle, buffer, data.len.int32, nil, let ret = writeFile(f.fd.Handle, buffer, data.len.int32, nil,
cast[POVERLAPPED](ol)) cast[POVERLAPPED](ol))
if not ret.bool: if not ret.bool:
let err = osLastError() let err = osLastError()
@ -281,7 +281,7 @@ proc write*(f: AsyncFile, data: string): Future[void] =
else: else:
# Request completed immediately. # Request completed immediately.
var bytesWritten: DWord var bytesWritten: DWord
let overlappedRes = getOverlappedResult(f.fd.THandle, let overlappedRes = getOverlappedResult(f.fd.Handle,
cast[POverlapped](ol)[], bytesWritten, false.WinBool) cast[POverlapped](ol)[], bytesWritten, false.WinBool)
if not overlappedRes.bool: if not overlappedRes.bool:
retFuture.fail(newException(OSError, osErrorMsg(osLastError()))) retFuture.fail(newException(OSError, osErrorMsg(osLastError())))
@ -292,7 +292,7 @@ proc write*(f: AsyncFile, data: string): Future[void] =
else: else:
var written = 0 var written = 0
proc cb(fd: TAsyncFD): bool = proc cb(fd: AsyncFD): bool =
result = true result = true
let remainderSize = data.len-written let remainderSize = data.len-written
let res = write(fd.cint, addr copy[written], remainderSize.cint) let res = write(fd.cint, addr copy[written], remainderSize.cint)
@ -317,7 +317,7 @@ proc write*(f: AsyncFile, data: string): Future[void] =
proc close*(f: AsyncFile) = proc close*(f: AsyncFile) =
## Closes the file specified. ## Closes the file specified.
when defined(windows) or defined(nimdoc): when defined(windows) or defined(nimdoc):
if not closeHandle(f.fd.THandle).bool: if not closeHandle(f.fd.Handle).bool:
raiseOSError(osLastError()) raiseOSError(osLastError())
else: else:
if close(f.fd.cint) == -1: if close(f.fd.cint) == -1:

View file

@ -210,6 +210,7 @@ proc processClient(client: AsyncSocket, address: string,
var contentLength = 0 var contentLength = 0
if parseInt(request.headers["Content-Length"], contentLength) == 0: if parseInt(request.headers["Content-Length"], contentLength) == 0:
await request.respond(Http400, "Bad Request. Invalid Content-Length.") await request.respond(Http400, "Bad Request. Invalid Content-Length.")
continue
else: else:
request.body = await client.recv(contentLength) request.body = await client.recv(contentLength)
assert request.body.len == contentLength assert request.body.len == contentLength

View file

@ -188,8 +188,8 @@ proc asyncSocket*(domain: Domain = AF_INET, typ: SockType = SOCK_STREAM,
result.socket.setBlocking(false) result.socket.setBlocking(false)
proc toAsyncSocket*(sock: Socket, state: SocketStatus = SockConnected): AsyncSocket = proc toAsyncSocket*(sock: Socket, state: SocketStatus = SockConnected): AsyncSocket =
## Wraps an already initialized ``TSocket`` into a AsyncSocket. ## Wraps an already initialized ``Socket`` into a AsyncSocket.
## This is useful if you want to use an already connected TSocket as an ## This is useful if you want to use an already connected Socket as an
## asynchronous AsyncSocket in asyncio's event loop. ## asynchronous AsyncSocket in asyncio's event loop.
## ##
## ``state`` may be overriden, i.e. if ``sock`` is not connected it should be ## ``state`` may be overriden, i.e. if ``sock`` is not connected it should be

View file

@ -91,13 +91,13 @@ type
# TODO: Save AF, domain etc info and reuse it in procs which need it like connect. # TODO: Save AF, domain etc info and reuse it in procs which need it like connect.
proc newAsyncSocket*(fd: TAsyncFD, isBuff: bool): AsyncSocket = proc newAsyncSocket*(fd: AsyncFD, buffered = true): AsyncSocket =
## Creates a new ``AsyncSocket`` based on the supplied params. ## Creates a new ``AsyncSocket`` based on the supplied params.
assert fd != osInvalidSocket.TAsyncFD assert fd != osInvalidSocket.AsyncFD
new(result) new(result)
result.fd = fd.SocketHandle result.fd = fd.SocketHandle
result.isBuffered = isBuff result.isBuffered = buffered
if isBuff: if buffered:
result.currPos = 0 result.currPos = 0
proc newAsyncSocket*(domain: Domain = AF_INET, typ: SockType = SOCK_STREAM, proc newAsyncSocket*(domain: Domain = AF_INET, typ: SockType = SOCK_STREAM,
@ -142,7 +142,7 @@ when defined(ssl):
if read < 0: if read < 0:
raiseSslError() raiseSslError()
data.setLen(read) data.setLen(read)
await socket.fd.TAsyncFd.send(data, flags) await socket.fd.AsyncFd.send(data, flags)
proc appeaseSsl(socket: AsyncSocket, flags: set[SocketFlag], proc appeaseSsl(socket: AsyncSocket, flags: set[SocketFlag],
sslError: cint) {.async.} = sslError: cint) {.async.} =
@ -150,7 +150,7 @@ when defined(ssl):
of SSL_ERROR_WANT_WRITE: of SSL_ERROR_WANT_WRITE:
await sendPendingSslData(socket, flags) await sendPendingSslData(socket, flags)
of SSL_ERROR_WANT_READ: of SSL_ERROR_WANT_READ:
var data = await recv(socket.fd.TAsyncFD, BufferSize, flags) var data = await recv(socket.fd.AsyncFD, BufferSize, flags)
let ret = bioWrite(socket.bioIn, addr data[0], data.len.cint) let ret = bioWrite(socket.bioIn, addr data[0], data.len.cint)
if ret < 0: if ret < 0:
raiseSSLError() raiseSSLError()
@ -175,7 +175,7 @@ proc connect*(socket: AsyncSocket, address: string, port: Port,
## ##
## Returns a ``Future`` which will complete when the connection succeeds ## Returns a ``Future`` which will complete when the connection succeeds
## or an error occurs. ## or an error occurs.
await connect(socket.fd.TAsyncFD, address, port, af) await connect(socket.fd.AsyncFD, address, port, af)
if socket.isSsl: if socket.isSsl:
when defined(ssl): when defined(ssl):
let flags = {SocketFlag.SafeDisconn} let flags = {SocketFlag.SafeDisconn}
@ -194,7 +194,7 @@ template readInto(buf: cstring, size: int, socket: AsyncSocket,
sslRead(socket.sslHandle, buf, size.cint)) sslRead(socket.sslHandle, buf, size.cint))
res = opResult res = opResult
else: else:
var recvIntoFut = recvInto(socket.fd.TAsyncFD, buf, size, flags) var recvIntoFut = recvInto(socket.fd.AsyncFD, buf, size, flags)
yield recvIntoFut yield recvIntoFut
# Not in SSL mode. # Not in SSL mode.
res = recvIntoFut.read() res = recvIntoFut.read()
@ -271,7 +271,7 @@ proc send*(socket: AsyncSocket, data: string,
sslWrite(socket.sslHandle, addr copy[0], copy.len.cint)) sslWrite(socket.sslHandle, addr copy[0], copy.len.cint))
await sendPendingSslData(socket, flags) await sendPendingSslData(socket, flags)
else: else:
await send(socket.fd.TAsyncFD, data, flags) await send(socket.fd.AsyncFD, data, flags)
proc acceptAddr*(socket: AsyncSocket, flags = {SocketFlag.SafeDisconn}): proc acceptAddr*(socket: AsyncSocket, flags = {SocketFlag.SafeDisconn}):
Future[tuple[address: string, client: AsyncSocket]] = Future[tuple[address: string, client: AsyncSocket]] =
@ -279,9 +279,9 @@ proc acceptAddr*(socket: AsyncSocket, flags = {SocketFlag.SafeDisconn}):
## corresponding to that connection and the remote address of the client. ## corresponding to that connection and the remote address of the client.
## The future will complete when the connection is successfully accepted. ## The future will complete when the connection is successfully accepted.
var retFuture = newFuture[tuple[address: string, client: AsyncSocket]]("asyncnet.acceptAddr") var retFuture = newFuture[tuple[address: string, client: AsyncSocket]]("asyncnet.acceptAddr")
var fut = acceptAddr(socket.fd.TAsyncFD, flags) var fut = acceptAddr(socket.fd.AsyncFD, flags)
fut.callback = fut.callback =
proc (future: Future[tuple[address: string, client: TAsyncFD]]) = proc (future: Future[tuple[address: string, client: AsyncFD]]) =
assert future.finished assert future.finished
if future.failed: if future.failed:
retFuture.fail(future.readError) retFuture.fail(future.readError)
@ -445,7 +445,7 @@ proc bindAddr*(socket: AsyncSocket, port = Port(0), address = "") {.
proc close*(socket: AsyncSocket) = proc close*(socket: AsyncSocket) =
## Closes the socket. ## Closes the socket.
defer: defer:
socket.fd.TAsyncFD.closeSocket() socket.fd.AsyncFD.closeSocket()
when defined(ssl): when defined(ssl):
if socket.isSSL: if socket.isSSL:
let res = SslShutdown(socket.sslHandle) let res = SslShutdown(socket.sslHandle)

View file

@ -20,20 +20,20 @@ import strutils
## ##
## # Create a matrix which first rotates, then scales and at last translates ## # Create a matrix which first rotates, then scales and at last translates
## ##
## var m:TMatrix2d=rotate(DEG90) & scale(2.0) & move(100.0,200.0) ## var m:Matrix2d=rotate(DEG90) & scale(2.0) & move(100.0,200.0)
## ##
## # Create a 2d point at (100,0) and a vector (5,2) ## # Create a 2d point at (100,0) and a vector (5,2)
## ##
## var pt:TPoint2d=point2d(100.0,0.0) ## var pt:Point2d=point2d(100.0,0.0)
## ##
## var vec:TVector2d=vector2d(5.0,2.0) ## var vec:Vector2d=vector2d(5.0,2.0)
## ##
## ##
## pt &= m # transforms pt in place ## pt &= m # transforms pt in place
## ##
## var pt2:TPoint2d=pt & m #concatenates pt with m and returns a new point ## var pt2:Point2d=pt & m #concatenates pt with m and returns a new point
## ##
## var vec2:TVector2d=vec & m #concatenates vec with m and returns a new vector ## var vec2:Vector2d=vec & m #concatenates vec with m and returns a new vector
const const
@ -57,46 +57,46 @@ const
## used internally by DegToRad and RadToDeg ## used internally by DegToRad and RadToDeg
type type
TMatrix2d* = object Matrix2d* = object
## Implements a row major 2d matrix, which means ## Implements a row major 2d matrix, which means
## transformations are applied the order they are concatenated. ## transformations are applied the order they are concatenated.
## The rightmost column of the 3x3 matrix is left out since normally ## The rightmost column of the 3x3 matrix is left out since normally
## not used for geometric transformations in 2d. ## not used for geometric transformations in 2d.
ax*,ay*,bx*,by*,tx*,ty*:float ax*,ay*,bx*,by*,tx*,ty*:float
TPoint2d* = object Point2d* = object
## Implements a non-homegeneous 2d point stored as ## Implements a non-homogeneous 2d point stored as
## an `x` coordinate and an `y` coordinate. ## an `x` coordinate and an `y` coordinate.
x*,y*:float x*,y*:float
TVector2d* = object Vector2d* = object
## Implements a 2d **direction vector** stored as ## Implements a 2d **direction vector** stored as
## an `x` coordinate and an `y` coordinate. Direction vector means, ## an `x` coordinate and an `y` coordinate. Direction vector means,
## that when transforming a vector with a matrix, the translational ## that when transforming a vector with a matrix, the translational
## part of the matrix is ignored. ## part of the matrix is ignored.
x*,y*:float x*,y*:float
{.deprecated: [TMatrix2d: Matrix2d, TPoint2d: Point2d, TVector2d: Vector2d].}
# Some forward declarations... # Some forward declarations...
proc matrix2d*(ax,ay,bx,by,tx,ty:float):TMatrix2d {.noInit.} proc matrix2d*(ax,ay,bx,by,tx,ty:float):Matrix2d {.noInit.}
## Creates a new matrix. ## Creates a new matrix.
## `ax`,`ay` is the local x axis ## `ax`,`ay` is the local x axis
## `bx`,`by` is the local y axis ## `bx`,`by` is the local y axis
## `tx`,`ty` is the translation ## `tx`,`ty` is the translation
proc vector2d*(x,y:float):TVector2d {.noInit,inline.} proc vector2d*(x,y:float):Vector2d {.noInit,inline.}
## Returns a new vector (`x`,`y`) ## Returns a new vector (`x`,`y`)
proc point2d*(x,y:float):TPoint2d {.noInit,inline.} proc point2d*(x,y:float):Point2d {.noInit,inline.}
## Returns a new point (`x`,`y`) ## Returns a new point (`x`,`y`)
let let
IDMATRIX*:TMatrix2d=matrix2d(1.0,0.0,0.0,1.0,0.0,0.0) IDMATRIX*:Matrix2d=matrix2d(1.0,0.0,0.0,1.0,0.0,0.0)
## Quick access to an identity matrix ## Quick access to an identity matrix
ORIGO*:TPoint2d=point2d(0.0,0.0) ORIGO*:Point2d=point2d(0.0,0.0)
## Quick acces to point (0,0) ## Quick acces to point (0,0)
XAXIS*:TVector2d=vector2d(1.0,0.0) XAXIS*:Vector2d=vector2d(1.0,0.0)
## Quick acces to an 2d x-axis unit vector ## Quick acces to an 2d x-axis unit vector
YAXIS*:TVector2d=vector2d(0.0,1.0) YAXIS*:Vector2d=vector2d(0.0,1.0)
## Quick acces to an 2d y-axis unit vector ## Quick acces to an 2d y-axis unit vector
@ -116,21 +116,21 @@ proc safeArccos(v:float):float=
template makeBinOpVector(s:expr)= template makeBinOpVector(s:expr)=
## implements binary operators + , - , * and / for vectors ## implements binary operators + , - , * and / for vectors
proc s*(a,b:TVector2d):TVector2d {.inline,noInit.} = vector2d(s(a.x,b.x),s(a.y,b.y)) proc s*(a,b:Vector2d):Vector2d {.inline,noInit.} = vector2d(s(a.x,b.x),s(a.y,b.y))
proc s*(a:TVector2d,b:float):TVector2d {.inline,noInit.} = vector2d(s(a.x,b),s(a.y,b)) proc s*(a:Vector2d,b:float):Vector2d {.inline,noInit.} = vector2d(s(a.x,b),s(a.y,b))
proc s*(a:float,b:TVector2d):TVector2d {.inline,noInit.} = vector2d(s(a,b.x),s(a,b.y)) proc s*(a:float,b:Vector2d):Vector2d {.inline,noInit.} = vector2d(s(a,b.x),s(a,b.y))
template makeBinOpAssignVector(s:expr)= template makeBinOpAssignVector(s:expr)=
## implements inplace binary operators += , -= , /= and *= for vectors ## implements inplace binary operators += , -= , /= and *= for vectors
proc s*(a:var TVector2d,b:TVector2d) {.inline.} = s(a.x,b.x) ; s(a.y,b.y) proc s*(a:var Vector2d,b:Vector2d) {.inline.} = s(a.x,b.x) ; s(a.y,b.y)
proc s*(a:var TVector2d,b:float) {.inline.} = s(a.x,b) ; s(a.y,b) proc s*(a:var Vector2d,b:float) {.inline.} = s(a.x,b) ; s(a.y,b)
# *************************************** # ***************************************
# TMatrix2d implementation # Matrix2d implementation
# *************************************** # ***************************************
proc setElements*(t:var TMatrix2d,ax,ay,bx,by,tx,ty:float) {.inline.}= proc setElements*(t:var Matrix2d,ax,ay,bx,by,tx,ty:float) {.inline.}=
## Sets arbitrary elements in an existing matrix. ## Sets arbitrary elements in an existing matrix.
t.ax=ax t.ax=ax
t.ay=ay t.ay=ay
@ -139,10 +139,10 @@ proc setElements*(t:var TMatrix2d,ax,ay,bx,by,tx,ty:float) {.inline.}=
t.tx=tx t.tx=tx
t.ty=ty t.ty=ty
proc matrix2d*(ax,ay,bx,by,tx,ty:float):TMatrix2d = proc matrix2d*(ax,ay,bx,by,tx,ty:float):Matrix2d =
result.setElements(ax,ay,bx,by,tx,ty) result.setElements(ax,ay,bx,by,tx,ty)
proc `&`*(a,b:TMatrix2d):TMatrix2d {.noInit.} = #concatenate matrices proc `&`*(a,b:Matrix2d):Matrix2d {.noInit.} = #concatenate matrices
## Concatenates matrices returning a new matrix. ## Concatenates matrices returning a new matrix.
# | a.AX a.AY 0 | | b.AX b.AY 0 | # | a.AX a.AY 0 | | b.AX b.AY 0 |
@ -157,34 +157,34 @@ proc `&`*(a,b:TMatrix2d):TMatrix2d {.noInit.} = #concatenate matrices
a.tx * b.ay + a.ty * b.by + b.ty) a.tx * b.ay + a.ty * b.by + b.ty)
proc scale*(s:float):TMatrix2d {.noInit.} = proc scale*(s:float):Matrix2d {.noInit.} =
## Returns a new scale matrix. ## Returns a new scale matrix.
result.setElements(s,0,0,s,0,0) result.setElements(s,0,0,s,0,0)
proc scale*(s:float,org:TPoint2d):TMatrix2d {.noInit.} = proc scale*(s:float,org:Point2d):Matrix2d {.noInit.} =
## Returns a new scale matrix using, `org` as scale origin. ## Returns a new scale matrix using, `org` as scale origin.
result.setElements(s,0,0,s,org.x-s*org.x,org.y-s*org.y) result.setElements(s,0,0,s,org.x-s*org.x,org.y-s*org.y)
proc stretch*(sx,sy:float):TMatrix2d {.noInit.} = proc stretch*(sx,sy:float):Matrix2d {.noInit.} =
## Returns new a stretch matrix, which is a ## Returns new a stretch matrix, which is a
## scale matrix with non uniform scale in x and y. ## scale matrix with non uniform scale in x and y.
result.setElements(sx,0,0,sy,0,0) result.setElements(sx,0,0,sy,0,0)
proc stretch*(sx,sy:float,org:TPoint2d):TMatrix2d {.noInit.} = proc stretch*(sx,sy:float,org:Point2d):Matrix2d {.noInit.} =
## Returns a new stretch matrix, which is a ## Returns a new stretch matrix, which is a
## scale matrix with non uniform scale in x and y. ## scale matrix with non uniform scale in x and y.
## `org` is used as stretch origin. ## `org` is used as stretch origin.
result.setElements(sx,0,0,sy,org.x-sx*org.x,org.y-sy*org.y) result.setElements(sx,0,0,sy,org.x-sx*org.x,org.y-sy*org.y)
proc move*(dx,dy:float):TMatrix2d {.noInit.} = proc move*(dx,dy:float):Matrix2d {.noInit.} =
## Returns a new translation matrix. ## Returns a new translation matrix.
result.setElements(1,0,0,1,dx,dy) result.setElements(1,0,0,1,dx,dy)
proc move*(v:TVector2d):TMatrix2d {.noInit.} = proc move*(v:Vector2d):Matrix2d {.noInit.} =
## Returns a new translation matrix from a vector. ## Returns a new translation matrix from a vector.
result.setElements(1,0,0,1,v.x,v.y) result.setElements(1,0,0,1,v.x,v.y)
proc rotate*(rad:float):TMatrix2d {.noInit.} = proc rotate*(rad:float):Matrix2d {.noInit.} =
## Returns a new rotation matrix, which ## Returns a new rotation matrix, which
## represents a rotation by `rad` radians ## represents a rotation by `rad` radians
let let
@ -192,7 +192,7 @@ proc rotate*(rad:float):TMatrix2d {.noInit.} =
c=cos(rad) c=cos(rad)
result.setElements(c,s,-s,c,0,0) result.setElements(c,s,-s,c,0,0)
proc rotate*(rad:float,org:TPoint2d):TMatrix2d {.noInit.} = proc rotate*(rad:float,org:Point2d):Matrix2d {.noInit.} =
## Returns a new rotation matrix, which ## Returns a new rotation matrix, which
## represents a rotation by `rad` radians around ## represents a rotation by `rad` radians around
## the origin `org` ## the origin `org`
@ -201,7 +201,7 @@ proc rotate*(rad:float,org:TPoint2d):TMatrix2d {.noInit.} =
c=cos(rad) c=cos(rad)
result.setElements(c,s,-s,c,org.x+s*org.y-c*org.x,org.y-c*org.y-s*org.x) result.setElements(c,s,-s,c,org.x+s*org.y-c*org.x,org.y-c*org.y-s*org.x)
proc mirror*(v:TVector2d):TMatrix2d {.noInit.} = proc mirror*(v:Vector2d):Matrix2d {.noInit.} =
## Returns a new mirror matrix, mirroring ## Returns a new mirror matrix, mirroring
## around the line that passes through origo and ## around the line that passes through origo and
## has the direction of `v` ## has the direction of `v`
@ -220,7 +220,7 @@ proc mirror*(v:TVector2d):TMatrix2d {.noInit.} =
xy2,-sqd, xy2,-sqd,
0.0,0.0) 0.0,0.0)
proc mirror*(org:TPoint2d,v:TVector2d):TMatrix2d {.noInit.} = proc mirror*(org:Point2d,v:Vector2d):Matrix2d {.noInit.} =
## Returns a new mirror matrix, mirroring ## Returns a new mirror matrix, mirroring
## around the line that passes through `org` and ## around the line that passes through `org` and
## has the direction of `v` ## has the direction of `v`
@ -241,20 +241,20 @@ proc mirror*(org:TPoint2d,v:TVector2d):TMatrix2d {.noInit.} =
proc skew*(xskew,yskew:float):TMatrix2d {.noInit.} = proc skew*(xskew,yskew:float):Matrix2d {.noInit.} =
## Returns a new skew matrix, which has its ## Returns a new skew matrix, which has its
## x axis rotated `xskew` radians from the local x axis, and ## x axis rotated `xskew` radians from the local x axis, and
## y axis rotated `yskew` radians from the local y axis ## y axis rotated `yskew` radians from the local y axis
result.setElements(cos(yskew),sin(yskew),-sin(xskew),cos(xskew),0,0) result.setElements(cos(yskew),sin(yskew),-sin(xskew),cos(xskew),0,0)
proc `$`* (t:TMatrix2d):string {.noInit.} = proc `$`* (t:Matrix2d):string {.noInit.} =
## Returns a string representation of the matrix ## Returns a string representation of the matrix
return rtos(t.ax) & "," & rtos(t.ay) & return rtos(t.ax) & "," & rtos(t.ay) &
"," & rtos(t.bx) & "," & rtos(t.by) & "," & rtos(t.bx) & "," & rtos(t.by) &
"," & rtos(t.tx) & "," & rtos(t.ty) "," & rtos(t.tx) & "," & rtos(t.ty)
proc isUniform*(t:TMatrix2d,tol=1.0e-6):bool= proc isUniform*(t:Matrix2d,tol=1.0e-6):bool=
## Checks if the transform is uniform, that is ## Checks if the transform is uniform, that is
## perpendicular axes of equal length, which means (for example) ## perpendicular axes of equal length, which means (for example)
## it cannot transform a circle into an ellipse. ## it cannot transform a circle into an ellipse.
@ -268,18 +268,18 @@ proc isUniform*(t:TMatrix2d,tol=1.0e-6):bool=
return true return true
return false return false
proc determinant*(t:TMatrix2d):float= proc determinant*(t:Matrix2d):float=
## Computes the determinant of the matrix. ## Computes the determinant of the matrix.
#NOTE: equivalent with perp.dot product for two 2d vectors #NOTE: equivalent with perp.dot product for two 2d vectors
return t.ax*t.by-t.bx*t.ay return t.ax*t.by-t.bx*t.ay
proc isMirroring* (m:TMatrix2d):bool= proc isMirroring* (m:Matrix2d):bool=
## Checks if the `m` is a mirroring matrix, ## Checks if the `m` is a mirroring matrix,
## which means it will reverse direction of a curve transformed with it ## which means it will reverse direction of a curve transformed with it
return m.determinant<0.0 return m.determinant<0.0
proc inverse*(m:TMatrix2d):TMatrix2d {.noInit.} = proc inverse*(m:Matrix2d):Matrix2d {.noInit.} =
## Returns a new matrix, which is the inverse of the matrix ## Returns a new matrix, which is the inverse of the matrix
## If the matrix is not invertible (determinant=0), an EDivByZero ## If the matrix is not invertible (determinant=0), an EDivByZero
## will be raised. ## will be raised.
@ -293,7 +293,7 @@ proc inverse*(m:TMatrix2d):TMatrix2d {.noInit.} =
(m.bx*m.ty-m.by*m.tx)/d, (m.bx*m.ty-m.by*m.tx)/d,
(m.ay*m.tx-m.ax*m.ty)/d) (m.ay*m.tx-m.ax*m.ty)/d)
proc equals*(m1:TMatrix2d,m2:TMatrix2d,tol=1.0e-6):bool= proc equals*(m1:Matrix2d,m2:Matrix2d,tol=1.0e-6):bool=
## Checks if all elements of `m1`and `m2` is equal within ## Checks if all elements of `m1`and `m2` is equal within
## a given tolerance `tol`. ## a given tolerance `tol`.
return return
@ -304,17 +304,17 @@ proc equals*(m1:TMatrix2d,m2:TMatrix2d,tol=1.0e-6):bool=
abs(m1.tx-m2.tx)<=tol and abs(m1.tx-m2.tx)<=tol and
abs(m1.ty-m2.ty)<=tol abs(m1.ty-m2.ty)<=tol
proc `=~`*(m1,m2:TMatrix2d):bool= proc `=~`*(m1,m2:Matrix2d):bool=
## Checks if `m1`and `m2` is approximately equal, using a ## Checks if `m1`and `m2` is approximately equal, using a
## tolerance of 1e-6. ## tolerance of 1e-6.
equals(m1,m2) equals(m1,m2)
proc isIdentity*(m:TMatrix2d,tol=1.0e-6):bool= proc isIdentity*(m:Matrix2d,tol=1.0e-6):bool=
## Checks is a matrix is approximately an identity matrix, ## Checks is a matrix is approximately an identity matrix,
## using `tol` as tolerance for each element. ## using `tol` as tolerance for each element.
return equals(m,IDMATRIX,tol) return equals(m,IDMATRIX,tol)
proc apply*(m:TMatrix2d,x,y:var float,translate=false)= proc apply*(m:Matrix2d,x,y:var float,translate=false)=
## Applies transformation `m` onto `x`,`y`, optionally ## Applies transformation `m` onto `x`,`y`, optionally
## using the translation part of the matrix. ## using the translation part of the matrix.
if translate: # positional style transform if translate: # positional style transform
@ -329,29 +329,29 @@ proc apply*(m:TMatrix2d,x,y:var float,translate=false)=
# *************************************** # ***************************************
# TVector2d implementation # Vector2d implementation
# *************************************** # ***************************************
proc vector2d*(x,y:float):TVector2d = #forward decl. proc vector2d*(x,y:float):Vector2d = #forward decl.
result.x=x result.x=x
result.y=y result.y=y
proc polarVector2d*(ang:float,len:float):TVector2d {.noInit.} = proc polarVector2d*(ang:float,len:float):Vector2d {.noInit.} =
## Returns a new vector with angle `ang` and magnitude `len` ## Returns a new vector with angle `ang` and magnitude `len`
result.x=cos(ang)*len result.x=cos(ang)*len
result.y=sin(ang)*len result.y=sin(ang)*len
proc slopeVector2d*(slope:float,len:float):TVector2d {.noInit.} = proc slopeVector2d*(slope:float,len:float):Vector2d {.noInit.} =
## Returns a new vector having slope (dy/dx) given by ## Returns a new vector having slope (dy/dx) given by
## `slope`, and a magnitude of `len` ## `slope`, and a magnitude of `len`
let ang=arctan(slope) let ang=arctan(slope)
result.x=cos(ang)*len result.x=cos(ang)*len
result.y=sin(ang)*len result.y=sin(ang)*len
proc len*(v:TVector2d):float {.inline.}= proc len*(v:Vector2d):float {.inline.}=
## Returns the length of the vector. ## Returns the length of the vector.
sqrt(v.x*v.x+v.y*v.y) sqrt(v.x*v.x+v.y*v.y)
proc `len=`*(v:var TVector2d,newlen:float) {.noInit.} = proc `len=`*(v:var Vector2d,newlen:float) {.noInit.} =
## Sets the length of the vector, keeping its angle. ## Sets the length of the vector, keeping its angle.
let fac=newlen/v.len let fac=newlen/v.len
@ -369,25 +369,25 @@ proc `len=`*(v:var TVector2d,newlen:float) {.noInit.} =
v.x*=fac v.x*=fac
v.y*=fac v.y*=fac
proc sqrLen*(v:TVector2d):float {.inline.}= proc sqrLen*(v:Vector2d):float {.inline.}=
## Computes the squared length of the vector, which is ## Computes the squared length of the vector, which is
## faster than computing the absolute length. ## faster than computing the absolute length.
v.x*v.x+v.y*v.y v.x*v.x+v.y*v.y
proc angle*(v:TVector2d):float= proc angle*(v:Vector2d):float=
## Returns the angle of the vector. ## Returns the angle of the vector.
## (The counter clockwise plane angle between posetive x axis and `v`) ## (The counter clockwise plane angle between posetive x axis and `v`)
result=arctan2(v.y,v.x) result=arctan2(v.y,v.x)
if result<0.0: result+=DEG360 if result<0.0: result+=DEG360
proc `$` *(v:TVector2d):string= proc `$` *(v:Vector2d):string=
## String representation of `v` ## String representation of `v`
result=rtos(v.x) result=rtos(v.x)
result.add(",") result.add(",")
result.add(rtos(v.y)) result.add(rtos(v.y))
proc `&` *(v:TVector2d,m:TMatrix2d):TVector2d {.noInit.} = proc `&` *(v:Vector2d,m:Matrix2d):Vector2d {.noInit.} =
## Concatenate vector `v` with a transformation matrix. ## Concatenate vector `v` with a transformation matrix.
## Transforming a vector ignores the translational part ## Transforming a vector ignores the translational part
## of the matrix. ## of the matrix.
@ -399,7 +399,7 @@ proc `&` *(v:TVector2d,m:TMatrix2d):TVector2d {.noInit.} =
result.y=v.x*m.ay+v.y*m.by result.y=v.x*m.ay+v.y*m.by
proc `&=`*(v:var TVector2d,m:TMatrix2d) {.inline.}= proc `&=`*(v:var Vector2d,m:Matrix2d) {.inline.}=
## Applies transformation `m` onto `v` in place. ## Applies transformation `m` onto `v` in place.
## Transforming a vector ignores the translational part ## Transforming a vector ignores the translational part
## of the matrix. ## of the matrix.
@ -412,7 +412,7 @@ proc `&=`*(v:var TVector2d,m:TMatrix2d) {.inline.}=
v.x=newx v.x=newx
proc tryNormalize*(v:var TVector2d):bool= proc tryNormalize*(v:var Vector2d):bool=
## Modifies `v` to have a length of 1.0, keeping its angle. ## Modifies `v` to have a length of 1.0, keeping its angle.
## If `v` has zero length (and thus no angle), it is left unmodified and ## If `v` has zero length (and thus no angle), it is left unmodified and
## false is returned, otherwise true is returned. ## false is returned, otherwise true is returned.
@ -427,13 +427,13 @@ proc tryNormalize*(v:var TVector2d):bool=
return true return true
proc normalize*(v:var TVector2d) {.inline.}= proc normalize*(v:var Vector2d) {.inline.}=
## Modifies `v` to have a length of 1.0, keeping its angle. ## Modifies `v` to have a length of 1.0, keeping its angle.
## If `v` has zero length, an EDivByZero will be raised. ## If `v` has zero length, an EDivByZero will be raised.
if not tryNormalize(v): if not tryNormalize(v):
raise newException(DivByZeroError,"Cannot normalize zero length vector") raise newException(DivByZeroError,"Cannot normalize zero length vector")
proc transformNorm*(v:var TVector2d,t:TMatrix2d)= proc transformNorm*(v:var Vector2d,t:Matrix2d)=
## Applies a normal direction transformation `t` onto `v` in place. ## Applies a normal direction transformation `t` onto `v` in place.
## The resulting vector is *not* normalized. Transforming a vector ignores the ## The resulting vector is *not* normalized. Transforming a vector ignores the
## translational part of the matrix. If the matrix is not invertible ## translational part of the matrix. If the matrix is not invertible
@ -452,7 +452,7 @@ proc transformNorm*(v:var TVector2d,t:TMatrix2d)=
v.y = (t.ax*v.y-t.bx*v.x)/d v.y = (t.ax*v.y-t.bx*v.x)/d
v.x = newx v.x = newx
proc transformInv*(v:var TVector2d,t:TMatrix2d)= proc transformInv*(v:var Vector2d,t:Matrix2d)=
## Applies inverse of a transformation `t` to `v` in place. ## Applies inverse of a transformation `t` to `v` in place.
## This is faster than creating an inverse matrix and apply() it. ## This is faster than creating an inverse matrix and apply() it.
## Transforming a vector ignores the translational part ## Transforming a vector ignores the translational part
@ -467,7 +467,7 @@ proc transformInv*(v:var TVector2d,t:TMatrix2d)=
v.y = (t.ax*v.y-t.ay*v.x)/d v.y = (t.ax*v.y-t.ay*v.x)/d
v.x = newx v.x = newx
proc transformNormInv*(v:var TVector2d,t:TMatrix2d)= proc transformNormInv*(v:var Vector2d,t:Matrix2d)=
## Applies an inverse normal direction transformation `t` onto `v` in place. ## Applies an inverse normal direction transformation `t` onto `v` in place.
## This is faster than creating an inverse ## This is faster than creating an inverse
## matrix and transformNorm(...) it. Transforming a vector ignores the ## matrix and transformNorm(...) it. Transforming a vector ignores the
@ -484,25 +484,25 @@ proc transformNormInv*(v:var TVector2d,t:TMatrix2d)=
v.y=t.by*v.y+t.bx*v.x v.y=t.by*v.y+t.bx*v.x
v.x=newx v.x=newx
proc rotate90*(v:var TVector2d) {.inline.}= proc rotate90*(v:var Vector2d) {.inline.}=
## Quickly rotates vector `v` 90 degrees counter clockwise, ## Quickly rotates vector `v` 90 degrees counter clockwise,
## without using any trigonometrics. ## without using any trigonometrics.
swap(v.x,v.y) swap(v.x,v.y)
v.x= -v.x v.x= -v.x
proc rotate180*(v:var TVector2d){.inline.}= proc rotate180*(v:var Vector2d){.inline.}=
## Quickly rotates vector `v` 180 degrees counter clockwise, ## Quickly rotates vector `v` 180 degrees counter clockwise,
## without using any trigonometrics. ## without using any trigonometrics.
v.x= -v.x v.x= -v.x
v.y= -v.y v.y= -v.y
proc rotate270*(v:var TVector2d) {.inline.}= proc rotate270*(v:var Vector2d) {.inline.}=
## Quickly rotates vector `v` 270 degrees counter clockwise, ## Quickly rotates vector `v` 270 degrees counter clockwise,
## without using any trigonometrics. ## without using any trigonometrics.
swap(v.x,v.y) swap(v.x,v.y)
v.y= -v.y v.y= -v.y
proc rotate*(v:var TVector2d,rad:float) = proc rotate*(v:var Vector2d,rad:float) =
## Rotates vector `v` `rad` radians in place. ## Rotates vector `v` `rad` radians in place.
let let
s=sin(rad) s=sin(rad)
@ -511,18 +511,18 @@ proc rotate*(v:var TVector2d,rad:float) =
v.y=c*v.y+s*v.x v.y=c*v.y+s*v.x
v.x=newx v.x=newx
proc scale*(v:var TVector2d,fac:float){.inline.}= proc scale*(v:var Vector2d,fac:float){.inline.}=
## Scales vector `v` `rad` radians in place. ## Scales vector `v` `rad` radians in place.
v.x*=fac v.x*=fac
v.y*=fac v.y*=fac
proc stretch*(v:var TVector2d,facx,facy:float){.inline.}= proc stretch*(v:var Vector2d,facx,facy:float){.inline.}=
## Stretches vector `v` `facx` times horizontally, ## Stretches vector `v` `facx` times horizontally,
## and `facy` times vertically. ## and `facy` times vertically.
v.x*=facx v.x*=facx
v.y*=facy v.y*=facy
proc mirror*(v:var TVector2d,mirrvec:TVector2d)= proc mirror*(v:var Vector2d,mirrvec:Vector2d)=
## Mirrors vector `v` using `mirrvec` as mirror direction. ## Mirrors vector `v` using `mirrvec` as mirror direction.
let let
sqx=mirrvec.x*mirrvec.x sqx=mirrvec.x*mirrvec.x
@ -539,7 +539,7 @@ proc mirror*(v:var TVector2d,mirrvec:TVector2d)=
v.x=newx v.x=newx
proc `-` *(v:TVector2d):TVector2d= proc `-` *(v:Vector2d):Vector2d=
## Negates a vector ## Negates a vector
result.x= -v.x result.x= -v.x
result.y= -v.y result.y= -v.y
@ -555,27 +555,27 @@ makeBinOpAssignVector(`*=`)
makeBinOpAssignVector(`/=`) makeBinOpAssignVector(`/=`)
proc dot*(v1,v2:TVector2d):float= proc dot*(v1,v2:Vector2d):float=
## Computes the dot product of two vectors. ## Computes the dot product of two vectors.
## Returns 0.0 if the vectors are perpendicular. ## Returns 0.0 if the vectors are perpendicular.
return v1.x*v2.x+v1.y*v2.y return v1.x*v2.x+v1.y*v2.y
proc cross*(v1,v2:TVector2d):float= proc cross*(v1,v2:Vector2d):float=
## Computes the cross product of two vectors, also called ## Computes the cross product of two vectors, also called
## the 'perpendicular dot product' in 2d. Returns 0.0 if the vectors ## the 'perpendicular dot product' in 2d. Returns 0.0 if the vectors
## are parallel. ## are parallel.
return v1.x*v2.y-v1.y*v2.x return v1.x*v2.y-v1.y*v2.x
proc equals*(v1,v2:TVector2d,tol=1.0e-6):bool= proc equals*(v1,v2:Vector2d,tol=1.0e-6):bool=
## Checks if two vectors approximately equals with a tolerance. ## Checks if two vectors approximately equals with a tolerance.
return abs(v2.x-v1.x)<=tol and abs(v2.y-v1.y)<=tol return abs(v2.x-v1.x)<=tol and abs(v2.y-v1.y)<=tol
proc `=~` *(v1,v2:TVector2d):bool= proc `=~` *(v1,v2:Vector2d):bool=
## Checks if two vectors approximately equals with a ## Checks if two vectors approximately equals with a
## hardcoded tolerance 1e-6 ## hardcoded tolerance 1e-6
equals(v1,v2) equals(v1,v2)
proc angleTo*(v1,v2:TVector2d):float= proc angleTo*(v1,v2:Vector2d):float=
## Returns the smallest of the two possible angles ## Returns the smallest of the two possible angles
## between `v1` and `v2` in radians. ## between `v1` and `v2` in radians.
var var
@ -585,7 +585,7 @@ proc angleTo*(v1,v2:TVector2d):float=
return 0.0 # zero length vector has zero angle to any other vector return 0.0 # zero length vector has zero angle to any other vector
return safeArccos(dot(nv1,nv2)) return safeArccos(dot(nv1,nv2))
proc angleCCW*(v1,v2:TVector2d):float= proc angleCCW*(v1,v2:Vector2d):float=
## Returns the counter clockwise plane angle from `v1` to `v2`, ## Returns the counter clockwise plane angle from `v1` to `v2`,
## in range 0 - 2*PI ## in range 0 - 2*PI
let a=v1.angleTo(v2) let a=v1.angleTo(v2)
@ -593,7 +593,7 @@ proc angleCCW*(v1,v2:TVector2d):float=
return a return a
return DEG360-a return DEG360-a
proc angleCW*(v1,v2:TVector2d):float= proc angleCW*(v1,v2:Vector2d):float=
## Returns the clockwise plane angle from `v1` to `v2`, ## Returns the clockwise plane angle from `v1` to `v2`,
## in range 0 - 2*PI ## in range 0 - 2*PI
let a=v1.angleTo(v2) let a=v1.angleTo(v2)
@ -601,7 +601,7 @@ proc angleCW*(v1,v2:TVector2d):float=
return a return a
return DEG360-a return DEG360-a
proc turnAngle*(v1,v2:TVector2d):float= proc turnAngle*(v1,v2:Vector2d):float=
## Returns the amount v1 should be rotated (in radians) to equal v2, ## Returns the amount v1 should be rotated (in radians) to equal v2,
## in range -PI to PI ## in range -PI to PI
let a=v1.angleTo(v2) let a=v1.angleTo(v2)
@ -609,7 +609,7 @@ proc turnAngle*(v1,v2:TVector2d):float=
return -a return -a
return a return a
proc bisect*(v1,v2:TVector2d):TVector2d {.noInit.}= proc bisect*(v1,v2:Vector2d):Vector2d {.noInit.}=
## Computes the bisector between v1 and v2 as a normalized vector. ## Computes the bisector between v1 and v2 as a normalized vector.
## If one of the input vectors has zero length, a normalized version ## If one of the input vectors has zero length, a normalized version
## of the other is returned. If both input vectors has zero length, ## of the other is returned. If both input vectors has zero length,
@ -645,24 +645,24 @@ proc bisect*(v1,v2:TVector2d):TVector2d {.noInit.}=
# *************************************** # ***************************************
# TPoint2d implementation # Point2d implementation
# *************************************** # ***************************************
proc point2d*(x,y:float):TPoint2d = proc point2d*(x,y:float):Point2d =
result.x=x result.x=x
result.y=y result.y=y
proc sqrDist*(a,b:TPoint2d):float= proc sqrDist*(a,b:Point2d):float=
## Computes the squared distance between `a` and `b` ## Computes the squared distance between `a` and `b`
let dx=b.x-a.x let dx=b.x-a.x
let dy=b.y-a.y let dy=b.y-a.y
result=dx*dx+dy*dy result=dx*dx+dy*dy
proc dist*(a,b:TPoint2d):float {.inline.}= proc dist*(a,b:Point2d):float {.inline.}=
## Computes the absolute distance between `a` and `b` ## Computes the absolute distance between `a` and `b`
result=sqrt(sqrDist(a,b)) result=sqrt(sqrDist(a,b))
proc angle*(a,b:TPoint2d):float= proc angle*(a,b:Point2d):float=
## Computes the angle of the vector `b`-`a` ## Computes the angle of the vector `b`-`a`
let dx=b.x-a.x let dx=b.x-a.x
let dy=b.y-a.y let dy=b.y-a.y
@ -670,13 +670,13 @@ proc angle*(a,b:TPoint2d):float=
if result<0: if result<0:
result += DEG360 result += DEG360
proc `$` *(p:TPoint2d):string= proc `$` *(p:Point2d):string=
## String representation of `p` ## String representation of `p`
result=rtos(p.x) result=rtos(p.x)
result.add(",") result.add(",")
result.add(rtos(p.y)) result.add(rtos(p.y))
proc `&`*(p:TPoint2d,t:TMatrix2d):TPoint2d {.noInit,inline.} = proc `&`*(p:Point2d,t:Matrix2d):Point2d {.noInit,inline.} =
## Concatenates a point `p` with a transform `t`, ## Concatenates a point `p` with a transform `t`,
## resulting in a new, transformed point. ## resulting in a new, transformed point.
@ -686,14 +686,14 @@ proc `&`*(p:TPoint2d,t:TMatrix2d):TPoint2d {.noInit,inline.} =
result.x=p.x*t.ax+p.y*t.bx+t.tx result.x=p.x*t.ax+p.y*t.bx+t.tx
result.y=p.x*t.ay+p.y*t.by+t.ty result.y=p.x*t.ay+p.y*t.by+t.ty
proc `&=` *(p:var TPoint2d,t:TMatrix2d) {.inline.}= proc `&=` *(p:var Point2d,t:Matrix2d) {.inline.}=
## Applies transformation `t` onto `p` in place. ## Applies transformation `t` onto `p` in place.
let newx=p.x*t.ax+p.y*t.bx+t.tx let newx=p.x*t.ax+p.y*t.bx+t.tx
p.y=p.x*t.ay+p.y*t.by+t.ty p.y=p.x*t.ay+p.y*t.by+t.ty
p.x=newx p.x=newx
proc transformInv*(p:var TPoint2d,t:TMatrix2d){.inline.}= proc transformInv*(p:var Point2d,t:Matrix2d){.inline.}=
## Applies the inverse of transformation `t` onto `p` in place. ## Applies the inverse of transformation `t` onto `p` in place.
## If the matrix is not invertable (determinant=0) , EDivByZero will ## If the matrix is not invertable (determinant=0) , EDivByZero will
## be raised. ## be raised.
@ -710,48 +710,48 @@ proc transformInv*(p:var TPoint2d,t:TMatrix2d){.inline.}=
p.x=newx p.x=newx
proc `+`*(p:TPoint2d,v:TVector2d):TPoint2d {.noInit,inline.} = proc `+`*(p:Point2d,v:Vector2d):Point2d {.noInit,inline.} =
## Adds a vector `v` to a point `p`, resulting ## Adds a vector `v` to a point `p`, resulting
## in a new point. ## in a new point.
result.x=p.x+v.x result.x=p.x+v.x
result.y=p.y+v.y result.y=p.y+v.y
proc `+=`*(p:var TPoint2d,v:TVector2d) {.noInit,inline.} = proc `+=`*(p:var Point2d,v:Vector2d) {.noInit,inline.} =
## Adds a vector `v` to a point `p` in place. ## Adds a vector `v` to a point `p` in place.
p.x+=v.x p.x+=v.x
p.y+=v.y p.y+=v.y
proc `-`*(p:TPoint2d,v:TVector2d):TPoint2d {.noInit,inline.} = proc `-`*(p:Point2d,v:Vector2d):Point2d {.noInit,inline.} =
## Subtracts a vector `v` from a point `p`, resulting ## Subtracts a vector `v` from a point `p`, resulting
## in a new point. ## in a new point.
result.x=p.x-v.x result.x=p.x-v.x
result.y=p.y-v.y result.y=p.y-v.y
proc `-`*(p1,p2:TPoint2d):TVector2d {.noInit,inline.} = proc `-`*(p1,p2:Point2d):Vector2d {.noInit,inline.} =
## Subtracts `p2`from `p1` resulting in a difference vector. ## Subtracts `p2`from `p1` resulting in a difference vector.
result.x=p1.x-p2.x result.x=p1.x-p2.x
result.y=p1.y-p2.y result.y=p1.y-p2.y
proc `-=`*(p:var TPoint2d,v:TVector2d) {.noInit,inline.} = proc `-=`*(p:var Point2d,v:Vector2d) {.noInit,inline.} =
## Subtracts a vector `v` from a point `p` in place. ## Subtracts a vector `v` from a point `p` in place.
p.x-=v.x p.x-=v.x
p.y-=v.y p.y-=v.y
proc equals(p1,p2:TPoint2d,tol=1.0e-6):bool {.inline.}= proc equals(p1,p2:Point2d,tol=1.0e-6):bool {.inline.}=
## Checks if two points approximately equals with a tolerance. ## Checks if two points approximately equals with a tolerance.
return abs(p2.x-p1.x)<=tol and abs(p2.y-p1.y)<=tol return abs(p2.x-p1.x)<=tol and abs(p2.y-p1.y)<=tol
proc `=~`*(p1,p2:TPoint2d):bool {.inline.}= proc `=~`*(p1,p2:Point2d):bool {.inline.}=
## Checks if two vectors approximately equals with a ## Checks if two vectors approximately equals with a
## hardcoded tolerance 1e-6 ## hardcoded tolerance 1e-6
equals(p1,p2) equals(p1,p2)
proc polar*(p:TPoint2d,ang,dist:float):TPoint2d {.noInit.} = proc polar*(p:Point2d,ang,dist:float):Point2d {.noInit.} =
## Returns a point with a given angle and distance away from `p` ## Returns a point with a given angle and distance away from `p`
result.x=p.x+cos(ang)*dist result.x=p.x+cos(ang)*dist
result.y=p.y+sin(ang)*dist result.y=p.y+sin(ang)*dist
proc rotate*(p:var TPoint2d,rad:float)= proc rotate*(p:var Point2d,rad:float)=
## Rotates a point in place `rad` radians around origo. ## Rotates a point in place `rad` radians around origo.
let let
c=cos(rad) c=cos(rad)
@ -760,7 +760,7 @@ proc rotate*(p:var TPoint2d,rad:float)=
p.y=p.y*c+p.x*s p.y=p.y*c+p.x*s
p.x=newx p.x=newx
proc rotate*(p:var TPoint2d,rad:float,org:TPoint2d)= proc rotate*(p:var Point2d,rad:float,org:Point2d)=
## Rotates a point in place `rad` radians using `org` as ## Rotates a point in place `rad` radians using `org` as
## center of rotation. ## center of rotation.
let let
@ -770,50 +770,50 @@ proc rotate*(p:var TPoint2d,rad:float,org:TPoint2d)=
p.y=(p.y - org.y) * c + (p.x - org.x) * s + org.y p.y=(p.y - org.y) * c + (p.x - org.x) * s + org.y
p.x=newx p.x=newx
proc scale*(p:var TPoint2d,fac:float) {.inline.}= proc scale*(p:var Point2d,fac:float) {.inline.}=
## Scales a point in place `fac` times with world origo as origin. ## Scales a point in place `fac` times with world origo as origin.
p.x*=fac p.x*=fac
p.y*=fac p.y*=fac
proc scale*(p:var TPoint2d,fac:float,org:TPoint2d){.inline.}= proc scale*(p:var Point2d,fac:float,org:Point2d){.inline.}=
## Scales the point in place `fac` times with `org` as origin. ## Scales the point in place `fac` times with `org` as origin.
p.x=(p.x - org.x) * fac + org.x p.x=(p.x - org.x) * fac + org.x
p.y=(p.y - org.y) * fac + org.y p.y=(p.y - org.y) * fac + org.y
proc stretch*(p:var TPoint2d,facx,facy:float){.inline.}= proc stretch*(p:var Point2d,facx,facy:float){.inline.}=
## Scales a point in place non uniformly `facx` and `facy` times with ## Scales a point in place non uniformly `facx` and `facy` times with
## world origo as origin. ## world origo as origin.
p.x*=facx p.x*=facx
p.y*=facy p.y*=facy
proc stretch*(p:var TPoint2d,facx,facy:float,org:TPoint2d){.inline.}= proc stretch*(p:var Point2d,facx,facy:float,org:Point2d){.inline.}=
## Scales the point in place non uniformly `facx` and `facy` times with ## Scales the point in place non uniformly `facx` and `facy` times with
## `org` as origin. ## `org` as origin.
p.x=(p.x - org.x) * facx + org.x p.x=(p.x - org.x) * facx + org.x
p.y=(p.y - org.y) * facy + org.y p.y=(p.y - org.y) * facy + org.y
proc move*(p:var TPoint2d,dx,dy:float){.inline.}= proc move*(p:var Point2d,dx,dy:float){.inline.}=
## Translates a point `dx`, `dy` in place. ## Translates a point `dx`, `dy` in place.
p.x+=dx p.x+=dx
p.y+=dy p.y+=dy
proc move*(p:var TPoint2d,v:TVector2d){.inline.}= proc move*(p:var Point2d,v:Vector2d){.inline.}=
## Translates a point with vector `v` in place. ## Translates a point with vector `v` in place.
p.x+=v.x p.x+=v.x
p.y+=v.y p.y+=v.y
proc sgnArea*(a,b,c:TPoint2d):float= proc sgnArea*(a,b,c:Point2d):float=
## Computes the signed area of the triangle thru points `a`,`b` and `c` ## Computes the signed area of the triangle thru points `a`,`b` and `c`
## result>0.0 for counter clockwise triangle ## result>0.0 for counter clockwise triangle
## result<0.0 for clockwise triangle ## result<0.0 for clockwise triangle
## This is commonly used to determinate side of a point with respect to a line. ## This is commonly used to determinate side of a point with respect to a line.
return ((b.x - c.x) * (b.y - a.y)-(b.y - c.y) * (b.x - a.x))*0.5 return ((b.x - c.x) * (b.y - a.y)-(b.y - c.y) * (b.x - a.x))*0.5
proc area*(a,b,c:TPoint2d):float= proc area*(a,b,c:Point2d):float=
## Computes the area of the triangle thru points `a`,`b` and `c` ## Computes the area of the triangle thru points `a`,`b` and `c`
return abs(sgnArea(a,b,c)) return abs(sgnArea(a,b,c))
proc closestPoint*(p:TPoint2d,pts:varargs[TPoint2d]):TPoint2d= proc closestPoint*(p:Point2d,pts:varargs[Point2d]):Point2d=
## Returns a point selected from `pts`, that has the closest ## Returns a point selected from `pts`, that has the closest
## euclidean distance to `p` ## euclidean distance to `p`
assert(pts.len>0) # must have at least one point assert(pts.len>0) # must have at least one point

View file

@ -25,25 +25,25 @@ import times
## ##
## # Create a matrix which first rotates, then scales and at last translates ## # Create a matrix which first rotates, then scales and at last translates
## ##
## var m:TMatrix3d=rotate(PI,vector3d(1,1,2.5)) & scale(2.0) & move(100.0,200.0,300.0) ## var m:Matrix3d=rotate(PI,vector3d(1,1,2.5)) & scale(2.0) & move(100.0,200.0,300.0)
## ##
## # Create a 3d point at (100,150,200) and a vector (5,2,3) ## # Create a 3d point at (100,150,200) and a vector (5,2,3)
## ##
## var pt:TPoint3d=point3d(100.0,150.0,200.0) ## var pt:Point3d=point3d(100.0,150.0,200.0)
## ##
## var vec:TVector3d=vector3d(5.0,2.0,3.0) ## var vec:Vector3d=vector3d(5.0,2.0,3.0)
## ##
## ##
## pt &= m # transforms pt in place ## pt &= m # transforms pt in place
## ##
## var pt2:TPoint3d=pt & m #concatenates pt with m and returns a new point ## var pt2:Point3d=pt & m #concatenates pt with m and returns a new point
## ##
## var vec2:TVector3d=vec & m #concatenates vec with m and returns a new vector ## var vec2:Vector3d=vec & m #concatenates vec with m and returns a new vector
type type
TMatrix3d* =object Matrix3d* =object
## Implements a row major 3d matrix, which means ## Implements a row major 3d matrix, which means
## transformations are applied the order they are concatenated. ## transformations are applied the order they are concatenated.
## This matrix is stored as an 4x4 matrix: ## This matrix is stored as an 4x4 matrix:
@ -52,31 +52,31 @@ type
## [ cx cy cz cw ] ## [ cx cy cz cw ]
## [ tx ty tz tw ] ## [ tx ty tz tw ]
ax*,ay*,az*,aw*, bx*,by*,bz*,bw*, cx*,cy*,cz*,cw*, tx*,ty*,tz*,tw*:float ax*,ay*,az*,aw*, bx*,by*,bz*,bw*, cx*,cy*,cz*,cw*, tx*,ty*,tz*,tw*:float
TPoint3d* = object Point3d* = object
## Implements a non-homegeneous 2d point stored as ## Implements a non-homogeneous 3d point stored as
## an `x` , `y` and `z` coordinate. ## an `x` , `y` and `z` coordinate.
x*,y*,z*:float x*,y*,z*:float
TVector3d* = object Vector3d* = object
## Implements a 3d **direction vector** stored as ## Implements a 3d **direction vector** stored as
## an `x` , `y` and `z` coordinate. Direction vector means, ## an `x` , `y` and `z` coordinate. Direction vector means,
## that when transforming a vector with a matrix, the translational ## that when transforming a vector with a matrix, the translational
## part of the matrix is ignored. ## part of the matrix is ignored.
x*,y*,z*:float x*,y*,z*:float
{.deprecated: [TMatrix3d: Matrix3d, TPoint3d: Point3d, TVector3d: Vector3d].}
# Some forward declarations # Some forward declarations
proc matrix3d*(ax,ay,az,aw,bx,by,bz,bw,cx,cy,cz,cw,tx,ty,tz,tw:float):TMatrix3d {.noInit.} proc matrix3d*(ax,ay,az,aw,bx,by,bz,bw,cx,cy,cz,cw,tx,ty,tz,tw:float):Matrix3d {.noInit.}
## Creates a new 4x4 3d transformation matrix. ## Creates a new 4x4 3d transformation matrix.
## `ax` , `ay` , `az` is the local x axis. ## `ax` , `ay` , `az` is the local x axis.
## `bx` , `by` , `bz` is the local y axis. ## `bx` , `by` , `bz` is the local y axis.
## `cx` , `cy` , `cz` is the local z axis. ## `cx` , `cy` , `cz` is the local z axis.
## `tx` , `ty` , `tz` is the translation. ## `tx` , `ty` , `tz` is the translation.
proc vector3d*(x,y,z:float):TVector3d {.noInit,inline.} proc vector3d*(x,y,z:float):Vector3d {.noInit,inline.}
## Returns a new 3d vector (`x`,`y`,`z`) ## Returns a new 3d vector (`x`,`y`,`z`)
proc point3d*(x,y,z:float):TPoint3d {.noInit,inline.} proc point3d*(x,y,z:float):Point3d {.noInit,inline.}
## Returns a new 4d point (`x`,`y`,`z`) ## Returns a new 4d point (`x`,`y`,`z`)
proc tryNormalize*(v:var TVector3d):bool proc tryNormalize*(v:var Vector3d):bool
## Modifies `v` to have a length of 1.0, keeping its angle. ## Modifies `v` to have a length of 1.0, keeping its angle.
## If `v` has zero length (and thus no angle), it is left unmodified and false is ## If `v` has zero length (and thus no angle), it is left unmodified and false is
## returned, otherwise true is returned. ## returned, otherwise true is returned.
@ -84,19 +84,19 @@ proc tryNormalize*(v:var TVector3d):bool
let let
IDMATRIX*:TMatrix3d=matrix3d( IDMATRIX*:Matrix3d=matrix3d(
1.0,0.0,0.0,0.0, 1.0,0.0,0.0,0.0,
0.0,1.0,0.0,0.0, 0.0,1.0,0.0,0.0,
0.0,0.0,1.0,0.0, 0.0,0.0,1.0,0.0,
0.0,0.0,0.0,1.0) 0.0,0.0,0.0,1.0)
## Quick access to a 3d identity matrix ## Quick access to a 3d identity matrix
ORIGO*:TPoint3d=point3d(0.0,0.0,0.0) ORIGO*:Point3d=point3d(0.0,0.0,0.0)
## Quick access to point (0,0) ## Quick access to point (0,0)
XAXIS*:TVector3d=vector3d(1.0,0.0,0.0) XAXIS*:Vector3d=vector3d(1.0,0.0,0.0)
## Quick access to an 3d x-axis unit vector ## Quick access to an 3d x-axis unit vector
YAXIS*:TVector3d=vector3d(0.0,1.0,0.0) YAXIS*:Vector3d=vector3d(0.0,1.0,0.0)
## Quick access to an 3d y-axis unit vector ## Quick access to an 3d y-axis unit vector
ZAXIS*:TVector3d=vector3d(0.0,0.0,1.0) ZAXIS*:Vector3d=vector3d(0.0,0.0,1.0)
## Quick access to an 3d z-axis unit vector ## Quick access to an 3d z-axis unit vector
@ -116,27 +116,27 @@ proc safeArccos(v:float):float=
template makeBinOpVector(s:expr)= template makeBinOpVector(s:expr)=
## implements binary operators + , - , * and / for vectors ## implements binary operators + , - , * and / for vectors
proc s*(a,b:TVector3d):TVector3d {.inline,noInit.} = proc s*(a,b:Vector3d):Vector3d {.inline,noInit.} =
vector3d(s(a.x,b.x),s(a.y,b.y),s(a.z,b.z)) vector3d(s(a.x,b.x),s(a.y,b.y),s(a.z,b.z))
proc s*(a:TVector3d,b:float):TVector3d {.inline,noInit.} = proc s*(a:Vector3d,b:float):Vector3d {.inline,noInit.} =
vector3d(s(a.x,b),s(a.y,b),s(a.z,b)) vector3d(s(a.x,b),s(a.y,b),s(a.z,b))
proc s*(a:float,b:TVector3d):TVector3d {.inline,noInit.} = proc s*(a:float,b:Vector3d):Vector3d {.inline,noInit.} =
vector3d(s(a,b.x),s(a,b.y),s(a,b.z)) vector3d(s(a,b.x),s(a,b.y),s(a,b.z))
template makeBinOpAssignVector(s:expr)= template makeBinOpAssignVector(s:expr)=
## implements inplace binary operators += , -= , /= and *= for vectors ## implements inplace binary operators += , -= , /= and *= for vectors
proc s*(a:var TVector3d,b:TVector3d) {.inline.} = proc s*(a:var Vector3d,b:Vector3d) {.inline.} =
s(a.x,b.x) ; s(a.y,b.y) ; s(a.z,b.z) s(a.x,b.x) ; s(a.y,b.y) ; s(a.z,b.z)
proc s*(a:var TVector3d,b:float) {.inline.} = proc s*(a:var Vector3d,b:float) {.inline.} =
s(a.x,b) ; s(a.y,b) ; s(a.z,b) s(a.x,b) ; s(a.y,b) ; s(a.z,b)
# *************************************** # ***************************************
# TMatrix3d implementation # Matrix3d implementation
# *************************************** # ***************************************
proc setElements*(t:var TMatrix3d,ax,ay,az,aw,bx,by,bz,bw,cx,cy,cz,cw,tx,ty,tz,tw:float) {.inline.}= proc setElements*(t:var Matrix3d,ax,ay,az,aw,bx,by,bz,bw,cx,cy,cz,cw,tx,ty,tz,tw:float) {.inline.}=
## Sets arbitrary elements in an exisitng matrix. ## Sets arbitrary elements in an exisitng matrix.
t.ax=ax t.ax=ax
t.ay=ay t.ay=ay
@ -155,10 +155,10 @@ proc setElements*(t:var TMatrix3d,ax,ay,az,aw,bx,by,bz,bw,cx,cy,cz,cw,tx,ty,tz,t
t.tz=tz t.tz=tz
t.tw=tw t.tw=tw
proc matrix3d*(ax,ay,az,aw,bx,by,bz,bw,cx,cy,cz,cw,tx,ty,tz,tw:float):TMatrix3d = proc matrix3d*(ax,ay,az,aw,bx,by,bz,bw,cx,cy,cz,cw,tx,ty,tz,tw:float):Matrix3d =
result.setElements(ax,ay,az,aw,bx,by,bz,bw,cx,cy,cz,cw,tx,ty,tz,tw) result.setElements(ax,ay,az,aw,bx,by,bz,bw,cx,cy,cz,cw,tx,ty,tz,tw)
proc `&`*(a,b:TMatrix3d):TMatrix3d {.noinit.} = proc `&`*(a,b:Matrix3d):Matrix3d {.noinit.} =
## Concatenates matrices returning a new matrix. ## Concatenates matrices returning a new matrix.
result.setElements( result.setElements(
a.aw*b.tx+a.az*b.cx+a.ay*b.bx+a.ax*b.ax, a.aw*b.tx+a.az*b.cx+a.ay*b.bx+a.ax*b.ax,
@ -182,36 +182,36 @@ proc `&`*(a,b:TMatrix3d):TMatrix3d {.noinit.} =
a.tw*b.tw+a.tz*b.cw+a.ty*b.bw+a.tx*b.aw) a.tw*b.tw+a.tz*b.cw+a.ty*b.bw+a.tx*b.aw)
proc scale*(s:float):TMatrix3d {.noInit.} = proc scale*(s:float):Matrix3d {.noInit.} =
## Returns a new scaling matrix. ## Returns a new scaling matrix.
result.setElements(s,0,0,0, 0,s,0,0, 0,0,s,0, 0,0,0,1) result.setElements(s,0,0,0, 0,s,0,0, 0,0,s,0, 0,0,0,1)
proc scale*(s:float,org:TPoint3d):TMatrix3d {.noInit.} = proc scale*(s:float,org:Point3d):Matrix3d {.noInit.} =
## Returns a new scaling matrix using, `org` as scale origin. ## Returns a new scaling matrix using, `org` as scale origin.
result.setElements(s,0,0,0, 0,s,0,0, 0,0,s,0, result.setElements(s,0,0,0, 0,s,0,0, 0,0,s,0,
org.x-s*org.x,org.y-s*org.y,org.z-s*org.z,1.0) org.x-s*org.x,org.y-s*org.y,org.z-s*org.z,1.0)
proc stretch*(sx,sy,sz:float):TMatrix3d {.noInit.} = proc stretch*(sx,sy,sz:float):Matrix3d {.noInit.} =
## Returns new a stretch matrix, which is a ## Returns new a stretch matrix, which is a
## scale matrix with non uniform scale in x,y and z. ## scale matrix with non uniform scale in x,y and z.
result.setElements(sx,0,0,0, 0,sy,0,0, 0,0,sz,0, 0,0,0,1) result.setElements(sx,0,0,0, 0,sy,0,0, 0,0,sz,0, 0,0,0,1)
proc stretch*(sx,sy,sz:float,org:TPoint3d):TMatrix3d {.noInit.} = proc stretch*(sx,sy,sz:float,org:Point3d):Matrix3d {.noInit.} =
## Returns a new stretch matrix, which is a ## Returns a new stretch matrix, which is a
## scale matrix with non uniform scale in x,y and z. ## scale matrix with non uniform scale in x,y and z.
## `org` is used as stretch origin. ## `org` is used as stretch origin.
result.setElements(sx,0,0,0, 0,sy,0,0, 0,0,sz,0, org.x-sx*org.x,org.y-sy*org.y,org.z-sz*org.z,1) result.setElements(sx,0,0,0, 0,sy,0,0, 0,0,sz,0, org.x-sx*org.x,org.y-sy*org.y,org.z-sz*org.z,1)
proc move*(dx,dy,dz:float):TMatrix3d {.noInit.} = proc move*(dx,dy,dz:float):Matrix3d {.noInit.} =
## Returns a new translation matrix. ## Returns a new translation matrix.
result.setElements(1,0,0,0, 0,1,0,0, 0,0,1,0, dx,dy,dz,1) result.setElements(1,0,0,0, 0,1,0,0, 0,0,1,0, dx,dy,dz,1)
proc move*(v:TVector3d):TMatrix3d {.noInit.} = proc move*(v:Vector3d):Matrix3d {.noInit.} =
## Returns a new translation matrix from a vector. ## Returns a new translation matrix from a vector.
result.setElements(1,0,0,0, 0,1,0,0, 0,0,1,0, v.x,v.y,v.z,1) result.setElements(1,0,0,0, 0,1,0,0, 0,0,1,0, v.x,v.y,v.z,1)
proc rotate*(angle:float,axis:TVector3d):TMatrix3d {.noInit.}= proc rotate*(angle:float,axis:Vector3d):Matrix3d {.noInit.}=
## Creates a rotation matrix that rotates `angle` radians over ## Creates a rotation matrix that rotates `angle` radians over
## `axis`, which passes through origo. ## `axis`, which passes through origo.
@ -242,7 +242,7 @@ proc rotate*(angle:float,axis:TVector3d):TMatrix3d {.noInit.}=
uwomc+vsi, vwomc-usi, w2+(1.0-w2)*cs, 0.0, uwomc+vsi, vwomc-usi, w2+(1.0-w2)*cs, 0.0,
0.0,0.0,0.0,1.0) 0.0,0.0,0.0,1.0)
proc rotate*(angle:float,org:TPoint3d,axis:TVector3d):TMatrix3d {.noInit.}= proc rotate*(angle:float,org:Point3d,axis:Vector3d):Matrix3d {.noInit.}=
## Creates a rotation matrix that rotates `angle` radians over ## Creates a rotation matrix that rotates `angle` radians over
## `axis`, which passes through `org`. ## `axis`, which passes through `org`.
@ -282,7 +282,7 @@ proc rotate*(angle:float,org:TPoint3d,axis:TVector3d):TMatrix3d {.noInit.}=
(c*(u2+v2)-w*(a*u+b*v))*omc+(a*v-b*u)*si,1.0) (c*(u2+v2)-w*(a*u+b*v))*omc+(a*v-b*u)*si,1.0)
proc rotateX*(angle:float):TMatrix3d {.noInit.}= proc rotateX*(angle:float):Matrix3d {.noInit.}=
## Creates a matrix that rotates around the x-axis with `angle` radians, ## Creates a matrix that rotates around the x-axis with `angle` radians,
## which is also called a 'roll' matrix. ## which is also called a 'roll' matrix.
let let
@ -294,7 +294,7 @@ proc rotateX*(angle:float):TMatrix3d {.noInit.}=
0,-s,c,0, 0,-s,c,0,
0,0,0,1) 0,0,0,1)
proc rotateY*(angle:float):TMatrix3d {.noInit.}= proc rotateY*(angle:float):Matrix3d {.noInit.}=
## Creates a matrix that rotates around the y-axis with `angle` radians, ## Creates a matrix that rotates around the y-axis with `angle` radians,
## which is also called a 'pitch' matrix. ## which is also called a 'pitch' matrix.
let let
@ -306,7 +306,7 @@ proc rotateY*(angle:float):TMatrix3d {.noInit.}=
s,0,c,0, s,0,c,0,
0,0,0,1) 0,0,0,1)
proc rotateZ*(angle:float):TMatrix3d {.noInit.}= proc rotateZ*(angle:float):Matrix3d {.noInit.}=
## Creates a matrix that rotates around the z-axis with `angle` radians, ## Creates a matrix that rotates around the z-axis with `angle` radians,
## which is also called a 'yaw' matrix. ## which is also called a 'yaw' matrix.
let let
@ -318,7 +318,7 @@ proc rotateZ*(angle:float):TMatrix3d {.noInit.}=
0,0,1,0, 0,0,1,0,
0,0,0,1) 0,0,0,1)
proc isUniform*(m:TMatrix3d,tol=1.0e-6):bool= proc isUniform*(m:Matrix3d,tol=1.0e-6):bool=
## Checks if the transform is uniform, that is ## Checks if the transform is uniform, that is
## perpendicular axes of equal length, which means (for example) ## perpendicular axes of equal length, which means (for example)
## it cannot transform a sphere into an ellipsoid. ## it cannot transform a sphere into an ellipsoid.
@ -341,7 +341,7 @@ proc isUniform*(m:TMatrix3d,tol=1.0e-6):bool=
proc mirror*(planeperp:TVector3d):TMatrix3d {.noInit.}= proc mirror*(planeperp:Vector3d):Matrix3d {.noInit.}=
## Creates a matrix that mirrors over the plane that has `planeperp` as normal, ## Creates a matrix that mirrors over the plane that has `planeperp` as normal,
## and passes through origo. `planeperp` does not need to be normalized. ## and passes through origo. `planeperp` does not need to be normalized.
@ -365,7 +365,7 @@ proc mirror*(planeperp:TVector3d):TMatrix3d {.noInit.}=
0,0,0,1) 0,0,0,1)
proc mirror*(org:TPoint3d,planeperp:TVector3d):TMatrix3d {.noInit.}= proc mirror*(org:Point3d,planeperp:Vector3d):Matrix3d {.noInit.}=
## Creates a matrix that mirrors over the plane that has `planeperp` as normal, ## Creates a matrix that mirrors over the plane that has `planeperp` as normal,
## and passes through `org`. `planeperp` does not need to be normalized. ## and passes through `org`. `planeperp` does not need to be normalized.
@ -400,7 +400,7 @@ proc mirror*(org:TPoint3d,planeperp:TVector3d):TMatrix3d {.noInit.}=
2*(cc*tz+bc*ty+ac*tx) ,1) 2*(cc*tz+bc*ty+ac*tx) ,1)
proc determinant*(m:TMatrix3d):float= proc determinant*(m:Matrix3d):float=
## Computes the determinant of matrix `m`. ## Computes the determinant of matrix `m`.
# This computation is gotten from ratsimp(optimize(determinant(m))) # This computation is gotten from ratsimp(optimize(determinant(m)))
@ -419,7 +419,7 @@ proc determinant*(m:TMatrix3d):float=
(O3*m.az-O5*m.ay+O6*m.ax)*m.bw (O3*m.az-O5*m.ay+O6*m.ax)*m.bw
proc inverse*(m:TMatrix3d):TMatrix3d {.noInit.}= proc inverse*(m:Matrix3d):Matrix3d {.noInit.}=
## Computes the inverse of matrix `m`. If the matrix ## Computes the inverse of matrix `m`. If the matrix
## determinant is zero, thus not invertible, a EDivByZero ## determinant is zero, thus not invertible, a EDivByZero
## will be raised. ## will be raised.
@ -461,7 +461,7 @@ proc inverse*(m:TMatrix3d):TMatrix3d {.noInit.}=
(-m.ax*O7+m.ay*O14-m.az*O18)/det , (m.ax*O10-m.ay*O16+m.az*O19)/det) (-m.ax*O7+m.ay*O14-m.az*O18)/det , (m.ax*O10-m.ay*O16+m.az*O19)/det)
proc equals*(m1:TMatrix3d,m2:TMatrix3d,tol=1.0e-6):bool= proc equals*(m1:Matrix3d,m2:Matrix3d,tol=1.0e-6):bool=
## Checks if all elements of `m1`and `m2` is equal within ## Checks if all elements of `m1`and `m2` is equal within
## a given tolerance `tol`. ## a given tolerance `tol`.
return return
@ -482,42 +482,42 @@ proc equals*(m1:TMatrix3d,m2:TMatrix3d,tol=1.0e-6):bool=
abs(m1.tz-m2.tz)<=tol and abs(m1.tz-m2.tz)<=tol and
abs(m1.tw-m2.tw)<=tol abs(m1.tw-m2.tw)<=tol
proc `=~`*(m1,m2:TMatrix3d):bool= proc `=~`*(m1,m2:Matrix3d):bool=
## Checks if `m1` and `m2` is approximately equal, using a ## Checks if `m1` and `m2` is approximately equal, using a
## tolerance of 1e-6. ## tolerance of 1e-6.
equals(m1,m2) equals(m1,m2)
proc transpose*(m:TMatrix3d):TMatrix3d {.noInit.}= proc transpose*(m:Matrix3d):Matrix3d {.noInit.}=
## Returns the transpose of `m` ## Returns the transpose of `m`
result.setElements(m.ax,m.bx,m.cx,m.tx,m.ay,m.by,m.cy,m.ty,m.az,m.bz,m.cz,m.tz,m.aw,m.bw,m.cw,m.tw) result.setElements(m.ax,m.bx,m.cx,m.tx,m.ay,m.by,m.cy,m.ty,m.az,m.bz,m.cz,m.tz,m.aw,m.bw,m.cw,m.tw)
proc getXAxis*(m:TMatrix3d):TVector3d {.noInit.}= proc getXAxis*(m:Matrix3d):Vector3d {.noInit.}=
## Gets the local x axis of `m` ## Gets the local x axis of `m`
result.x=m.ax result.x=m.ax
result.y=m.ay result.y=m.ay
result.z=m.az result.z=m.az
proc getYAxis*(m:TMatrix3d):TVector3d {.noInit.}= proc getYAxis*(m:Matrix3d):Vector3d {.noInit.}=
## Gets the local y axis of `m` ## Gets the local y axis of `m`
result.x=m.bx result.x=m.bx
result.y=m.by result.y=m.by
result.z=m.bz result.z=m.bz
proc getZAxis*(m:TMatrix3d):TVector3d {.noInit.}= proc getZAxis*(m:Matrix3d):Vector3d {.noInit.}=
## Gets the local y axis of `m` ## Gets the local y axis of `m`
result.x=m.cx result.x=m.cx
result.y=m.cy result.y=m.cy
result.z=m.cz result.z=m.cz
proc `$`*(m:TMatrix3d):string= proc `$`*(m:Matrix3d):string=
## String representation of `m` ## String representation of `m`
return rtos(m.ax) & "," & rtos(m.ay) & "," & rtos(m.az) & "," & rtos(m.aw) & return rtos(m.ax) & "," & rtos(m.ay) & "," & rtos(m.az) & "," & rtos(m.aw) &
"\n" & rtos(m.bx) & "," & rtos(m.by) & "," & rtos(m.bz) & "," & rtos(m.bw) & "\n" & rtos(m.bx) & "," & rtos(m.by) & "," & rtos(m.bz) & "," & rtos(m.bw) &
"\n" & rtos(m.cx) & "," & rtos(m.cy) & "," & rtos(m.cz) & "," & rtos(m.cw) & "\n" & rtos(m.cx) & "," & rtos(m.cy) & "," & rtos(m.cz) & "," & rtos(m.cw) &
"\n" & rtos(m.tx) & "," & rtos(m.ty) & "," & rtos(m.tz) & "," & rtos(m.tw) "\n" & rtos(m.tx) & "," & rtos(m.ty) & "," & rtos(m.tz) & "," & rtos(m.tw)
proc apply*(m:TMatrix3d, x,y,z:var float, translate=false)= proc apply*(m:Matrix3d, x,y,z:var float, translate=false)=
## Applies transformation `m` onto `x` , `y` , `z` , optionally ## Applies transformation `m` onto `x` , `y` , `z` , optionally
## using the translation part of the matrix. ## using the translation part of the matrix.
let let
@ -535,18 +535,18 @@ proc apply*(m:TMatrix3d, x,y,z:var float, translate=false)=
z+=m.tz z+=m.tz
# *************************************** # ***************************************
# TVector3d implementation # Vector3d implementation
# *************************************** # ***************************************
proc vector3d*(x,y,z:float):TVector3d= proc vector3d*(x,y,z:float):Vector3d=
result.x=x result.x=x
result.y=y result.y=y
result.z=z result.z=z
proc len*(v:TVector3d):float= proc len*(v:Vector3d):float=
## Returns the length of the vector `v`. ## Returns the length of the vector `v`.
sqrt(v.x*v.x+v.y*v.y+v.z*v.z) sqrt(v.x*v.x+v.y*v.y+v.z*v.z)
proc `len=`*(v:var TVector3d,newlen:float) {.noInit.} = proc `len=`*(v:var Vector3d,newlen:float) {.noInit.} =
## Sets the length of the vector, keeping its direction. ## Sets the length of the vector, keeping its direction.
## If the vector has zero length before changing it's length, ## If the vector has zero length before changing it's length,
## an arbitrary vector of the requested length is returned. ## an arbitrary vector of the requested length is returned.
@ -571,12 +571,12 @@ proc `len=`*(v:var TVector3d,newlen:float) {.noInit.} =
v.z*=fac v.z*=fac
proc sqrLen*(v:TVector3d):float {.inline.}= proc sqrLen*(v:Vector3d):float {.inline.}=
## Computes the squared length of the vector, which is ## Computes the squared length of the vector, which is
## faster than computing the absolute length. ## faster than computing the absolute length.
return v.x*v.x+v.y*v.y+v.z*v.z return v.x*v.x+v.y*v.y+v.z*v.z
proc `$` *(v:TVector3d):string= proc `$` *(v:Vector3d):string=
## String representation of `v` ## String representation of `v`
result=rtos(v.x) result=rtos(v.x)
result.add(",") result.add(",")
@ -584,7 +584,7 @@ proc `$` *(v:TVector3d):string=
result.add(",") result.add(",")
result.add(rtos(v.z)) result.add(rtos(v.z))
proc `&` *(v:TVector3d,m:TMatrix3d):TVector3d {.noInit.} = proc `&` *(v:Vector3d,m:Matrix3d):Vector3d {.noInit.} =
## Concatenate vector `v` with a transformation matrix. ## Concatenate vector `v` with a transformation matrix.
## Transforming a vector ignores the translational part ## Transforming a vector ignores the translational part
## of the matrix. ## of the matrix.
@ -601,7 +601,7 @@ proc `&` *(v:TVector3d,m:TMatrix3d):TVector3d {.noInit.} =
result.x=newx result.x=newx
proc `&=` *(v:var TVector3d,m:TMatrix3d) {.noInit.} = proc `&=` *(v:var Vector3d,m:Matrix3d) {.noInit.} =
## Applies transformation `m` onto `v` in place. ## Applies transformation `m` onto `v` in place.
## Transforming a vector ignores the translational part ## Transforming a vector ignores the translational part
## of the matrix. ## of the matrix.
@ -618,7 +618,7 @@ proc `&=` *(v:var TVector3d,m:TMatrix3d) {.noInit.} =
v.y=newy v.y=newy
v.x=newx v.x=newx
proc transformNorm*(v:var TVector3d,m:TMatrix3d)= proc transformNorm*(v:var Vector3d,m:Matrix3d)=
## Applies a normal direction transformation `m` onto `v` in place. ## Applies a normal direction transformation `m` onto `v` in place.
## The resulting vector is *not* normalized. Transforming a vector ignores the ## The resulting vector is *not* normalized. Transforming a vector ignores the
## translational part of the matrix. If the matrix is not invertible ## translational part of the matrix. If the matrix is not invertible
@ -631,7 +631,7 @@ proc transformNorm*(v:var TVector3d,m:TMatrix3d)=
# (possibly by hardware) as well as having a consistent API with the 2d version. # (possibly by hardware) as well as having a consistent API with the 2d version.
v&=transpose(inverse(m)) v&=transpose(inverse(m))
proc transformInv*(v:var TVector3d,m:TMatrix3d)= proc transformInv*(v:var Vector3d,m:Matrix3d)=
## Applies the inverse of `m` on vector `v`. Transforming a vector ignores ## Applies the inverse of `m` on vector `v`. Transforming a vector ignores
## the translational part of the matrix. Transforming a vector ignores the ## the translational part of the matrix. Transforming a vector ignores the
## translational part of the matrix. ## translational part of the matrix.
@ -642,7 +642,7 @@ proc transformInv*(v:var TVector3d,m:TMatrix3d)=
# (possibly by hardware) as well as having a consistent API with the 2d version. # (possibly by hardware) as well as having a consistent API with the 2d version.
v&=m.inverse v&=m.inverse
proc transformNormInv*(vec:var TVector3d,m:TMatrix3d)= proc transformNormInv*(vec:var Vector3d,m:Matrix3d)=
## Applies an inverse normal direction transformation `m` onto `v` in place. ## Applies an inverse normal direction transformation `m` onto `v` in place.
## This is faster than creating an inverse ## This is faster than creating an inverse
## matrix and transformNorm(...) it. Transforming a vector ignores the ## matrix and transformNorm(...) it. Transforming a vector ignores the
@ -651,7 +651,7 @@ proc transformNormInv*(vec:var TVector3d,m:TMatrix3d)=
# see vector2d:s equivalent for a deeper look how/why this works # see vector2d:s equivalent for a deeper look how/why this works
vec&=m.transpose vec&=m.transpose
proc tryNormalize*(v:var TVector3d):bool= proc tryNormalize*(v:var Vector3d):bool=
## Modifies `v` to have a length of 1.0, keeping its angle. ## Modifies `v` to have a length of 1.0, keeping its angle.
## If `v` has zero length (and thus no angle), it is left unmodified and false is ## If `v` has zero length (and thus no angle), it is left unmodified and false is
## returned, otherwise true is returned. ## returned, otherwise true is returned.
@ -666,13 +666,13 @@ proc tryNormalize*(v:var TVector3d):bool=
return true return true
proc normalize*(v:var TVector3d) {.inline.}= proc normalize*(v:var Vector3d) {.inline.}=
## Modifies `v` to have a length of 1.0, keeping its angle. ## Modifies `v` to have a length of 1.0, keeping its angle.
## If `v` has zero length, an EDivByZero will be raised. ## If `v` has zero length, an EDivByZero will be raised.
if not tryNormalize(v): if not tryNormalize(v):
raise newException(DivByZeroError,"Cannot normalize zero length vector") raise newException(DivByZeroError,"Cannot normalize zero length vector")
proc rotate*(vec:var TVector3d,angle:float,axis:TVector3d)= proc rotate*(vec:var Vector3d,angle:float,axis:Vector3d)=
## Rotates `vec` in place, with `angle` radians over `axis`, which passes ## Rotates `vec` in place, with `angle` radians over `axis`, which passes
## through origo. ## through origo.
@ -699,19 +699,19 @@ proc rotate*(vec:var TVector3d,angle:float,axis:TVector3d)=
vec.y=v*uxyzomc+y*cs+(w*x-u*z)*si vec.y=v*uxyzomc+y*cs+(w*x-u*z)*si
vec.z=w*uxyzomc+z*cs+(u*y-v*x)*si vec.z=w*uxyzomc+z*cs+(u*y-v*x)*si
proc scale*(v:var TVector3d,s:float)= proc scale*(v:var Vector3d,s:float)=
## Scales the vector in place with factor `s` ## Scales the vector in place with factor `s`
v.x*=s v.x*=s
v.y*=s v.y*=s
v.z*=s v.z*=s
proc stretch*(v:var TVector3d,sx,sy,sz:float)= proc stretch*(v:var Vector3d,sx,sy,sz:float)=
## Scales the vector non uniformly with factors `sx` , `sy` , `sz` ## Scales the vector non uniformly with factors `sx` , `sy` , `sz`
v.x*=sx v.x*=sx
v.y*=sy v.y*=sy
v.z*=sz v.z*=sz
proc mirror*(v:var TVector3d,planeperp:TVector3d)= proc mirror*(v:var Vector3d,planeperp:Vector3d)=
## Computes the mirrored vector of `v` over the plane ## Computes the mirrored vector of `v` over the plane
## that has `planeperp` as normal direction. ## that has `planeperp` as normal direction.
## `planeperp` does not need to be normalized. ## `planeperp` does not need to be normalized.
@ -735,7 +735,7 @@ proc mirror*(v:var TVector3d,planeperp:TVector3d)=
v.z= -2*(c*c*z+bc*y+ac*x)+z v.z= -2*(c*c*z+bc*y+ac*x)+z
proc `-` *(v:TVector3d):TVector3d= proc `-` *(v:Vector3d):Vector3d=
## Negates a vector ## Negates a vector
result.x= -v.x result.x= -v.x
result.y= -v.y result.y= -v.y
@ -751,12 +751,12 @@ makeBinOpAssignVector(`-=`)
makeBinOpAssignVector(`*=`) makeBinOpAssignVector(`*=`)
makeBinOpAssignVector(`/=`) makeBinOpAssignVector(`/=`)
proc dot*(v1,v2:TVector3d):float {.inline.}= proc dot*(v1,v2:Vector3d):float {.inline.}=
## Computes the dot product of two vectors. ## Computes the dot product of two vectors.
## Returns 0.0 if the vectors are perpendicular. ## Returns 0.0 if the vectors are perpendicular.
return v1.x*v2.x+v1.y*v2.y+v1.z*v2.z return v1.x*v2.x+v1.y*v2.y+v1.z*v2.z
proc cross*(v1,v2:TVector3d):TVector3d {.inline.}= proc cross*(v1,v2:Vector3d):Vector3d {.inline.}=
## Computes the cross product of two vectors. ## Computes the cross product of two vectors.
## The result is a vector which is perpendicular ## The result is a vector which is perpendicular
## to the plane of `v1` and `v2`, which means ## to the plane of `v1` and `v2`, which means
@ -766,16 +766,16 @@ proc cross*(v1,v2:TVector3d):TVector3d {.inline.}=
result.y = (v1.z * v2.x) - (v2.z * v1.x) result.y = (v1.z * v2.x) - (v2.z * v1.x)
result.z = (v1.x * v2.y) - (v2.x * v1.y) result.z = (v1.x * v2.y) - (v2.x * v1.y)
proc equals*(v1,v2:TVector3d,tol=1.0e-6):bool= proc equals*(v1,v2:Vector3d,tol=1.0e-6):bool=
## Checks if two vectors approximately equals with a tolerance. ## Checks if two vectors approximately equals with a tolerance.
return abs(v2.x-v1.x)<=tol and abs(v2.y-v1.y)<=tol and abs(v2.z-v1.z)<=tol return abs(v2.x-v1.x)<=tol and abs(v2.y-v1.y)<=tol and abs(v2.z-v1.z)<=tol
proc `=~` *(v1,v2:TVector3d):bool= proc `=~` *(v1,v2:Vector3d):bool=
## Checks if two vectors approximately equals with a ## Checks if two vectors approximately equals with a
## hardcoded tolerance 1e-6 ## hardcoded tolerance 1e-6
equals(v1,v2) equals(v1,v2)
proc angleTo*(v1,v2:TVector3d):float= proc angleTo*(v1,v2:Vector3d):float=
## Returns the smallest angle between v1 and v2, ## Returns the smallest angle between v1 and v2,
## which is in range 0-PI ## which is in range 0-PI
var var
@ -785,13 +785,13 @@ proc angleTo*(v1,v2:TVector3d):float=
return 0.0 # zero length vector has zero angle to any other vector return 0.0 # zero length vector has zero angle to any other vector
return safeArccos(dot(nv1,nv2)) return safeArccos(dot(nv1,nv2))
proc arbitraryAxis*(norm:TVector3d):TMatrix3d {.noInit.}= proc arbitraryAxis*(norm:Vector3d):Matrix3d {.noInit.}=
## Computes the rotation matrix that would transform ## Computes the rotation matrix that would transform
## world z vector into `norm`. The inverse of this matrix ## world z vector into `norm`. The inverse of this matrix
## is useful to transform a planar 3d object to 2d space. ## is useful to transform a planar 3d object to 2d space.
## This is the same algorithm used to interpret DXF and DWG files. ## This is the same algorithm used to interpret DXF and DWG files.
const lim=1.0/64.0 const lim=1.0/64.0
var ax,ay,az:TVector3d var ax,ay,az:Vector3d
if abs(norm.x)<lim and abs(norm.y)<lim: if abs(norm.x)<lim and abs(norm.y)<lim:
ax=cross(YAXIS,norm) ax=cross(YAXIS,norm)
else: else:
@ -808,7 +808,7 @@ proc arbitraryAxis*(norm:TVector3d):TMatrix3d {.noInit.}=
az.x,az.y,az.z,0.0, az.x,az.y,az.z,0.0,
0.0,0.0,0.0,1.0) 0.0,0.0,0.0,1.0)
proc bisect*(v1,v2:TVector3d):TVector3d {.noInit.}= proc bisect*(v1,v2:Vector3d):Vector3d {.noInit.}=
## Computes the bisector between v1 and v2 as a normalized vector. ## Computes the bisector between v1 and v2 as a normalized vector.
## If one of the input vectors has zero length, a normalized version ## If one of the input vectors has zero length, a normalized version
## of the other is returned. If both input vectors has zero length, ## of the other is returned. If both input vectors has zero length,
@ -851,25 +851,25 @@ proc bisect*(v1,v2:TVector3d):TVector3d {.noInit.}=
# *************************************** # ***************************************
# TPoint3d implementation # Point3d implementation
# *************************************** # ***************************************
proc point3d*(x,y,z:float):TPoint3d= proc point3d*(x,y,z:float):Point3d=
result.x=x result.x=x
result.y=y result.y=y
result.z=z result.z=z
proc sqrDist*(a,b:TPoint3d):float= proc sqrDist*(a,b:Point3d):float=
## Computes the squared distance between `a`and `b` ## Computes the squared distance between `a`and `b`
let dx=b.x-a.x let dx=b.x-a.x
let dy=b.y-a.y let dy=b.y-a.y
let dz=b.z-a.z let dz=b.z-a.z
result=dx*dx+dy*dy+dz*dz result=dx*dx+dy*dy+dz*dz
proc dist*(a,b:TPoint3d):float {.inline.}= proc dist*(a,b:Point3d):float {.inline.}=
## Computes the absolute distance between `a`and `b` ## Computes the absolute distance between `a`and `b`
result=sqrt(sqrDist(a,b)) result=sqrt(sqrDist(a,b))
proc `$` *(p:TPoint3d):string= proc `$` *(p:Point3d):string=
## String representation of `p` ## String representation of `p`
result=rtos(p.x) result=rtos(p.x)
result.add(",") result.add(",")
@ -877,14 +877,14 @@ proc `$` *(p:TPoint3d):string=
result.add(",") result.add(",")
result.add(rtos(p.z)) result.add(rtos(p.z))
proc `&`*(p:TPoint3d,m:TMatrix3d):TPoint3d= proc `&`*(p:Point3d,m:Matrix3d):Point3d=
## Concatenates a point `p` with a transform `m`, ## Concatenates a point `p` with a transform `m`,
## resulting in a new, transformed point. ## resulting in a new, transformed point.
result.z=m.cz*p.z+m.bz*p.y+m.az*p.x+m.tz result.z=m.cz*p.z+m.bz*p.y+m.az*p.x+m.tz
result.y=m.cy*p.z+m.by*p.y+m.ay*p.x+m.ty result.y=m.cy*p.z+m.by*p.y+m.ay*p.x+m.ty
result.x=m.cx*p.z+m.bx*p.y+m.ax*p.x+m.tx result.x=m.cx*p.z+m.bx*p.y+m.ax*p.x+m.tx
proc `&=` *(p:var TPoint3d,m:TMatrix3d)= proc `&=` *(p:var Point3d,m:Matrix3d)=
## Applies transformation `m` onto `p` in place. ## Applies transformation `m` onto `p` in place.
let let
x=p.x x=p.x
@ -894,7 +894,7 @@ proc `&=` *(p:var TPoint3d,m:TMatrix3d)=
p.y=m.cy*z+m.by*y+m.ay*x+m.ty p.y=m.cy*z+m.by*y+m.ay*x+m.ty
p.z=m.cz*z+m.bz*y+m.az*x+m.tz p.z=m.cz*z+m.bz*y+m.az*x+m.tz
proc transformInv*(p:var TPoint3d,m:TMatrix3d)= proc transformInv*(p:var Point3d,m:Matrix3d)=
## Applies the inverse of transformation `m` onto `p` in place. ## Applies the inverse of transformation `m` onto `p` in place.
## If the matrix is not invertable (determinant=0) , EDivByZero will ## If the matrix is not invertable (determinant=0) , EDivByZero will
## be raised. ## be raised.
@ -903,48 +903,48 @@ proc transformInv*(p:var TPoint3d,m:TMatrix3d)=
p&=inverse(m) p&=inverse(m)
proc `+`*(p:TPoint3d,v:TVector3d):TPoint3d {.noInit,inline.} = proc `+`*(p:Point3d,v:Vector3d):Point3d {.noInit,inline.} =
## Adds a vector `v` to a point `p`, resulting ## Adds a vector `v` to a point `p`, resulting
## in a new point. ## in a new point.
result.x=p.x+v.x result.x=p.x+v.x
result.y=p.y+v.y result.y=p.y+v.y
result.z=p.z+v.z result.z=p.z+v.z
proc `+=`*(p:var TPoint3d,v:TVector3d) {.noInit,inline.} = proc `+=`*(p:var Point3d,v:Vector3d) {.noInit,inline.} =
## Adds a vector `v` to a point `p` in place. ## Adds a vector `v` to a point `p` in place.
p.x+=v.x p.x+=v.x
p.y+=v.y p.y+=v.y
p.z+=v.z p.z+=v.z
proc `-`*(p:TPoint3d,v:TVector3d):TPoint3d {.noInit,inline.} = proc `-`*(p:Point3d,v:Vector3d):Point3d {.noInit,inline.} =
## Subtracts a vector `v` from a point `p`, resulting ## Subtracts a vector `v` from a point `p`, resulting
## in a new point. ## in a new point.
result.x=p.x-v.x result.x=p.x-v.x
result.y=p.y-v.y result.y=p.y-v.y
result.z=p.z-v.z result.z=p.z-v.z
proc `-`*(p1,p2:TPoint3d):TVector3d {.noInit,inline.} = proc `-`*(p1,p2:Point3d):Vector3d {.noInit,inline.} =
## Subtracts `p2`from `p1` resulting in a difference vector. ## Subtracts `p2`from `p1` resulting in a difference vector.
result.x=p1.x-p2.x result.x=p1.x-p2.x
result.y=p1.y-p2.y result.y=p1.y-p2.y
result.z=p1.z-p2.z result.z=p1.z-p2.z
proc `-=`*(p:var TPoint3d,v:TVector3d) {.noInit,inline.} = proc `-=`*(p:var Point3d,v:Vector3d) {.noInit,inline.} =
## Subtracts a vector `v` from a point `p` in place. ## Subtracts a vector `v` from a point `p` in place.
p.x-=v.x p.x-=v.x
p.y-=v.y p.y-=v.y
p.z-=v.z p.z-=v.z
proc equals(p1,p2:TPoint3d,tol=1.0e-6):bool {.inline.}= proc equals(p1,p2:Point3d,tol=1.0e-6):bool {.inline.}=
## Checks if two points approximately equals with a tolerance. ## Checks if two points approximately equals with a tolerance.
return abs(p2.x-p1.x)<=tol and abs(p2.y-p1.y)<=tol and abs(p2.z-p1.z)<=tol return abs(p2.x-p1.x)<=tol and abs(p2.y-p1.y)<=tol and abs(p2.z-p1.z)<=tol
proc `=~`*(p1,p2:TPoint3d):bool {.inline.}= proc `=~`*(p1,p2:Point3d):bool {.inline.}=
## Checks if two vectors approximately equals with a ## Checks if two vectors approximately equals with a
## hardcoded tolerance 1e-6 ## hardcoded tolerance 1e-6
equals(p1,p2) equals(p1,p2)
proc rotate*(p:var TPoint3d,rad:float,axis:TVector3d)= proc rotate*(p:var Point3d,rad:float,axis:Vector3d)=
## Rotates point `p` in place `rad` radians about an axis ## Rotates point `p` in place `rad` radians about an axis
## passing through origo. ## passing through origo.
@ -954,7 +954,7 @@ proc rotate*(p:var TPoint3d,rad:float,axis:TVector3d)=
p.y=v.y p.y=v.y
p.z=v.z p.z=v.z
proc rotate*(p:var TPoint3d,angle:float,org:TPoint3d,axis:TVector3d)= proc rotate*(p:var Point3d,angle:float,org:Point3d,axis:Vector3d)=
## Rotates point `p` in place `rad` radians about an axis ## Rotates point `p` in place `rad` radians about an axis
## passing through `org` ## passing through `org`
@ -992,26 +992,26 @@ proc rotate*(p:var TPoint3d,angle:float,org:TPoint3d,axis:TVector3d)=
p.y=(b*(uu+ww)-v*(au+cw-uxmvymwz))*omc + y*cs + (c*u-a*w+w*x-u*z)*si p.y=(b*(uu+ww)-v*(au+cw-uxmvymwz))*omc + y*cs + (c*u-a*w+w*x-u*z)*si
p.z=(c*(uu+vv)-w*(au+bv-uxmvymwz))*omc + z*cs + (a*v+u*y-b*u-v*x)*si p.z=(c*(uu+vv)-w*(au+bv-uxmvymwz))*omc + z*cs + (a*v+u*y-b*u-v*x)*si
proc scale*(p:var TPoint3d,fac:float) {.inline.}= proc scale*(p:var Point3d,fac:float) {.inline.}=
## Scales a point in place `fac` times with world origo as origin. ## Scales a point in place `fac` times with world origo as origin.
p.x*=fac p.x*=fac
p.y*=fac p.y*=fac
p.z*=fac p.z*=fac
proc scale*(p:var TPoint3d,fac:float,org:TPoint3d){.inline.}= proc scale*(p:var Point3d,fac:float,org:Point3d){.inline.}=
## Scales the point in place `fac` times with `org` as origin. ## Scales the point in place `fac` times with `org` as origin.
p.x=(p.x - org.x) * fac + org.x p.x=(p.x - org.x) * fac + org.x
p.y=(p.y - org.y) * fac + org.y p.y=(p.y - org.y) * fac + org.y
p.z=(p.z - org.z) * fac + org.z p.z=(p.z - org.z) * fac + org.z
proc stretch*(p:var TPoint3d,facx,facy,facz:float){.inline.}= proc stretch*(p:var Point3d,facx,facy,facz:float){.inline.}=
## Scales a point in place non uniformly `facx` , `facy` , `facz` times ## Scales a point in place non uniformly `facx` , `facy` , `facz` times
## with world origo as origin. ## with world origo as origin.
p.x*=facx p.x*=facx
p.y*=facy p.y*=facy
p.z*=facz p.z*=facz
proc stretch*(p:var TPoint3d,facx,facy,facz:float,org:TPoint3d){.inline.}= proc stretch*(p:var Point3d,facx,facy,facz:float,org:Point3d){.inline.}=
## Scales the point in place non uniformly `facx` , `facy` , `facz` times ## Scales the point in place non uniformly `facx` , `facy` , `facz` times
## with `org` as origin. ## with `org` as origin.
p.x=(p.x - org.x) * facx + org.x p.x=(p.x - org.x) * facx + org.x
@ -1019,19 +1019,19 @@ proc stretch*(p:var TPoint3d,facx,facy,facz:float,org:TPoint3d){.inline.}=
p.z=(p.z - org.z) * facz + org.z p.z=(p.z - org.z) * facz + org.z
proc move*(p:var TPoint3d,dx,dy,dz:float){.inline.}= proc move*(p:var Point3d,dx,dy,dz:float){.inline.}=
## Translates a point `dx` , `dy` , `dz` in place. ## Translates a point `dx` , `dy` , `dz` in place.
p.x+=dx p.x+=dx
p.y+=dy p.y+=dy
p.z+=dz p.z+=dz
proc move*(p:var TPoint3d,v:TVector3d){.inline.}= proc move*(p:var Point3d,v:Vector3d){.inline.}=
## Translates a point with vector `v` in place. ## Translates a point with vector `v` in place.
p.x+=v.x p.x+=v.x
p.y+=v.y p.y+=v.y
p.z+=v.z p.z+=v.z
proc area*(a,b,c:TPoint3d):float {.inline.}= proc area*(a,b,c:Point3d):float {.inline.}=
## Computes the area of the triangle thru points `a` , `b` and `c` ## Computes the area of the triangle thru points `a` , `b` and `c`
# The area of a planar 3d quadliteral is the magnitude of the cross # The area of a planar 3d quadliteral is the magnitude of the cross

View file

@ -46,24 +46,25 @@ const
when sizeof(int) == 4: # 32bit when sizeof(int) == 4: # 32bit
type type
TRaw = range[0..1073741823] Raw = range[0..1073741823]
## The range of uint values that can be stored directly in a value slot ## The range of uint values that can be stored directly in a value slot
## when on a 32 bit platform ## when on a 32 bit platform
{.deprecated: [TRaw: Raw].}
elif sizeof(int) == 8: # 64bit elif sizeof(int) == 8: # 64bit
type type
TRaw = range[0..4611686018427387903] Raw = range[0..4611686018427387903]
## The range of uint values that can be stored directly in a value slot ## The range of uint values that can be stored directly in a value slot
## when on a 64 bit platform ## when on a 64 bit platform
{.deprecated: [TRaw: Raw].}
else: else:
{.error: "unsupported platform".} {.error: "unsupported platform".}
type type
TEntry = tuple Entry = tuple
key: int key: int
value: int value: int
TEntryArr = ptr array[0..10_000_000, TEntry] EntryArr = ptr array[0..10_000_000, Entry]
PConcTable[K,V] = ptr object {.pure.} PConcTable[K,V] = ptr object {.pure.}
len: int len: int
@ -72,8 +73,8 @@ type
copyIdx: int copyIdx: int
copyDone: int copyDone: int
next: PConcTable[K,V] next: PConcTable[K,V]
data: TEntryArr data: EntryArr
{.deprecated: [TEntry: Entry, TEntryArr: EntryArr.}
proc setVal[K,V](table: var PConcTable[K,V], key: int, val: int, proc setVal[K,V](table: var PConcTable[K,V], key: int, val: int,
expVal: int, match: bool): int expVal: int, match: bool): int
@ -84,7 +85,7 @@ proc setVal[K,V](table: var PConcTable[K,V], key: int, val: int,
proc newLFTable*[K,V](size: int = minTableSize): PConcTable[K,V] = proc newLFTable*[K,V](size: int = minTableSize): PConcTable[K,V] =
let let
dataLen = max(nextPowerOfTwo(size), minTableSize) dataLen = max(nextPowerOfTwo(size), minTableSize)
dataSize = dataLen*sizeof(TEntry) dataSize = dataLen*sizeof(Entry)
dataMem = allocShared0(dataSize) dataMem = allocShared0(dataSize)
tableSize = 7 * intSize tableSize = 7 * intSize
tableMem = allocShared0(tableSize) tableMem = allocShared0(tableSize)
@ -95,7 +96,7 @@ proc newLFTable*[K,V](size: int = minTableSize): PConcTable[K,V] =
table.copyIdx = 0 table.copyIdx = 0
table.copyDone = 0 table.copyDone = 0
table.next = nil table.next = nil
table.data = cast[TEntryArr](dataMem) table.data = cast[EntryArr](dataMem)
result = table result = table
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
@ -107,7 +108,7 @@ proc deleteConcTable[K,V](tbl: PConcTable[K,V]) =
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
proc `[]`[K,V](table: var PConcTable[K,V], i: int): var TEntry {.inline.} = proc `[]`[K,V](table: var PConcTable[K,V], i: int): var Entry {.inline.} =
table.data[i] table.data[i]
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
@ -191,7 +192,7 @@ proc resize[K,V](self: PConcTable[K,V]): PConcTable[K,V] =
#proc keyEQ[K](key1: ptr K, key2: ptr K): bool {.inline.} = #proc keyEQ[K](key1: ptr K, key2: ptr K): bool {.inline.} =
proc keyEQ[K](key1: int, key2: int): bool {.inline.} = proc keyEQ[K](key1: int, key2: int): bool {.inline.} =
result = false result = false
when K is TRaw: when K is Raw:
if key1 == key2: if key1 == key2:
result = true result = true
else: else:
@ -236,7 +237,7 @@ proc copySlot[K,V](idx: int, oldTbl: var PConcTable[K,V], newTbl: var PConcTable
break break
#echo("oldVal was = ", oldVal, " set it to prime ", box) #echo("oldVal was = ", oldVal, " set it to prime ", box)
if isPrime(oldVal) and isTomb(oldVal): if isPrime(oldVal) and isTomb(oldVal):
#when not (K is TRaw): #when not (K is Raw):
# deallocShared(popPtr[K](oldKey)) # deallocShared(popPtr[K](oldKey))
return false return false
if isTomb(oldVal): if isTomb(oldVal):
@ -343,7 +344,7 @@ proc helpCopy[K,V](table: var PConcTable[K,V]): PConcTable[K,V] =
proc setVal[K,V](table: var PConcTable[K,V], key: int, val: int, proc setVal[K,V](table: var PConcTable[K,V], key: int, val: int,
expVal: int, match: bool): int = expVal: int, match: bool): int =
#echo("-try set- in table ", " key = ", (popPtr[K](key)[]), " val = ", val) #echo("-try set- in table ", " key = ", (popPtr[K](key)[]), " val = ", val)
when K is TRaw: when K is Raw:
var idx = hashInt(key) var idx = hashInt(key)
else: else:
var idx = popPtr[K](key)[].hash var idx = popPtr[K](key)[].hash
@ -428,7 +429,7 @@ proc setVal[K,V](table: var PConcTable[K,V], key: int, val: int,
proc getVal[K,V](table: var PConcTable[K,V], key: int): int = proc getVal[K,V](table: var PConcTable[K,V], key: int): int =
#echo("-try get- key = " & $key) #echo("-try get- key = " & $key)
when K is TRaw: when K is Raw:
var idx = hashInt(key) var idx = hashInt(key)
else: else:
var idx = popPtr[K](key)[].hash var idx = popPtr[K](key)[].hash
@ -468,37 +469,37 @@ proc getVal[K,V](table: var PConcTable[K,V], key: int): int =
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
#proc set*(table: var PConcTable[TRaw,TRaw], key: TRaw, val: TRaw) = #proc set*(table: var PConcTable[Raw,Raw], key: Raw, val: Raw) =
# discard setVal(table, pack(key), pack(key), 0, false) # discard setVal(table, pack(key), pack(key), 0, false)
#proc set*[V](table: var PConcTable[TRaw,V], key: TRaw, val: ptr V) = #proc set*[V](table: var PConcTable[Raw,V], key: Raw, val: ptr V) =
# discard setVal(table, pack(key), cast[int](val), 0, false) # discard setVal(table, pack(key), cast[int](val), 0, false)
proc set*[K,V](table: var PConcTable[K,V], key: var K, val: var V) = proc set*[K,V](table: var PConcTable[K,V], key: var K, val: var V) =
when not (K is TRaw): when not (K is Raw):
var newKey = cast[int](copyShared(key)) var newKey = cast[int](copyShared(key))
else: else:
var newKey = pack(key) var newKey = pack(key)
when not (V is TRaw): when not (V is Raw):
var newVal = cast[int](copyShared(val)) var newVal = cast[int](copyShared(val))
else: else:
var newVal = pack(val) var newVal = pack(val)
var oldPtr = pop(setVal(table, newKey, newVal, 0, false)) var oldPtr = pop(setVal(table, newKey, newVal, 0, false))
#echo("oldPtr = ", cast[int](oldPtr), " newPtr = ", cast[int](newPtr)) #echo("oldPtr = ", cast[int](oldPtr), " newPtr = ", cast[int](newPtr))
when not (V is TRaw): when not (V is Raw):
if newVal != oldPtr and oldPtr != 0: if newVal != oldPtr and oldPtr != 0:
deallocShared(cast[ptr V](oldPtr)) deallocShared(cast[ptr V](oldPtr))
proc get*[K,V](table: var PConcTable[K,V], key: var K): V = proc get*[K,V](table: var PConcTable[K,V], key: var K): V =
when not (V is TRaw): when not (V is Raw):
when not (K is TRaw): when not (K is Raw):
return popPtr[V](getVal(table, cast[int](key.addr)))[] return popPtr[V](getVal(table, cast[int](key.addr)))[]
else: else:
return popPtr[V](getVal(table, pack(key)))[] return popPtr[V](getVal(table, pack(key)))[]
else: else:
when not (K is TRaw): when not (K is Raw):
return popRaw(getVal(table, cast[int](key.addr))) return popRaw(getVal(table, cast[int](key.addr)))
else: else:
return popRaw(getVal(table, pack(key))) return popRaw(getVal(table, pack(key)))
@ -535,23 +536,24 @@ when not defined(testing) and isMainModule:
type type
TTestObj = tuple TestObj = tuple
thr: int thr: int
f0: int f0: int
f1: int f1: int
TData = tuple[k: string,v: TTestObj] Data = tuple[k: string,v: TestObj]
PDataArr = array[0..numTests-1, TData] PDataArr = array[0..numTests-1, Data]
Dict = PConcTable[string,TTestObj] Dict = PConcTable[string,TestObj]
{.deprecated: [TTestObj: TestObj, TData: Data].}
var var
thr: array[0..numThreads-1, TThread[Dict]] thr: array[0..numThreads-1, Thread[Dict]]
table = newLFTable[string,TTestObj](8) table = newLFTable[string,TestObj](8)
rand = newMersenneTwister(2525) rand = newMersenneTwister(2525)
proc createSampleData(len: int): PDataArr = proc createSampleData(len: int): PDataArr =
#result = cast[PDataArr](allocShared0(sizeof(TData)*numTests)) #result = cast[PDataArr](allocShared0(sizeof(Data)*numTests))
for i in 0..len-1: for i in 0..len-1:
result[i].k = "mark" & $(i+1) result[i].k = "mark" & $(i+1)
#echo("mark" & $(i+1), " ", hash("mark" & $(i+1))) #echo("mark" & $(i+1), " ", hash("mark" & $(i+1)))

View file

@ -30,25 +30,25 @@ const
IntMask = 1 shl IntShift - 1 IntMask = 1 shl IntShift - 1
type type
PTrunk = ref TTrunk PTrunk = ref Trunk
TTrunk {.final.} = object Trunk {.final.} = object
next: PTrunk # all nodes are connected with this pointer next: PTrunk # all nodes are connected with this pointer
key: int # start address at bit 0 key: int # start address at bit 0
bits: array[0..IntsPerTrunk - 1, BitScalar] # a bit vector bits: array[0..IntsPerTrunk - 1, BitScalar] # a bit vector
TTrunkSeq = seq[PTrunk] TrunkSeq = seq[PTrunk]
IntSet* = object ## an efficient set of 'int' implemented as a sparse bit set IntSet* = object ## an efficient set of 'int' implemented as a sparse bit set
counter, max: int counter, max: int
head: PTrunk head: PTrunk
data: TTrunkSeq data: TrunkSeq
{.deprecated: [TIntSet: IntSet].} {.deprecated: [TIntSet: IntSet, TTrunk: Trunk, TTrunkSeq: TrunkSeq].}
proc mustRehash(length, counter: int): bool {.inline.} = proc mustRehash(length, counter: int): bool {.inline.} =
assert(length > counter) assert(length > counter)
result = (length * 2 < counter * 3) or (length - counter < 4) result = (length * 2 < counter * 3) or (length - counter < 4)
proc nextTry(h, maxHash: THash): THash {.inline.} = proc nextTry(h, maxHash: Hash): Hash {.inline.} =
result = ((5 * h) + 1) and maxHash result = ((5 * h) + 1) and maxHash
proc intSetGet(t: IntSet, key: int): PTrunk = proc intSetGet(t: IntSet, key: int): PTrunk =
@ -59,7 +59,7 @@ proc intSetGet(t: IntSet, key: int): PTrunk =
h = nextTry(h, t.max) h = nextTry(h, t.max)
result = nil result = nil
proc intSetRawInsert(t: IntSet, data: var TTrunkSeq, desc: PTrunk) = proc intSetRawInsert(t: IntSet, data: var TrunkSeq, desc: PTrunk) =
var h = desc.key and t.max var h = desc.key and t.max
while data[h] != nil: while data[h] != nil:
assert(data[h] != desc) assert(data[h] != desc)
@ -68,7 +68,7 @@ proc intSetRawInsert(t: IntSet, data: var TTrunkSeq, desc: PTrunk) =
data[h] = desc data[h] = desc
proc intSetEnlarge(t: var IntSet) = proc intSetEnlarge(t: var IntSet) =
var n: TTrunkSeq var n: TrunkSeq
var oldMax = t.max var oldMax = t.max
t.max = ((t.max + 1) * 2) - 1 t.max = ((t.max + 1) * 2) - 1
newSeq(n, t.max + 1) newSeq(n, t.max + 1)

View file

@ -29,7 +29,7 @@ when not defined(nimhygiene):
# codes should never be needed, and this can pack more entries per cache-line. # codes should never be needed, and this can pack more entries per cache-line.
# Losing hcode entirely is also possible - if some element value is forbidden. # Losing hcode entirely is also possible - if some element value is forbidden.
type type
KeyValuePair[A] = tuple[hcode: THash, key: A] KeyValuePair[A] = tuple[hcode: Hash, key: A]
KeyValuePairSeq[A] = seq[KeyValuePair[A]] KeyValuePairSeq[A] = seq[KeyValuePair[A]]
HashSet* {.myShallow.}[A] = object ## \ HashSet* {.myShallow.}[A] = object ## \
## A generic hash set. ## A generic hash set.
@ -43,10 +43,10 @@ type
# hcode for real keys cannot be zero. hcode==0 signifies an empty slot. These # hcode for real keys cannot be zero. hcode==0 signifies an empty slot. These
# two procs retain clarity of that encoding without the space cost of an enum. # two procs retain clarity of that encoding without the space cost of an enum.
proc isEmpty(hcode: THash): bool {.inline.} = proc isEmpty(hcode: Hash): bool {.inline.} =
result = hcode == 0 result = hcode == 0
proc isFilled(hcode: THash): bool {.inline.} = proc isFilled(hcode: Hash): bool {.inline.} =
result = hcode != 0 result = hcode != 0
proc isValid*[A](s: HashSet[A]): bool = proc isValid*[A](s: HashSet[A]): bool =
@ -58,7 +58,7 @@ proc isValid*[A](s: HashSet[A]): bool =
## initialized. Example: ## initialized. Example:
## ##
## .. code-block :: ## .. code-block ::
## proc savePreferences(options: TSet[string]) = ## proc savePreferences(options: Set[string]) =
## assert options.isValid, "Pass an initialized set!" ## assert options.isValid, "Pass an initialized set!"
## # Do stuff here, may crash in release builds! ## # Do stuff here, may crash in release builds!
result = not s.data.isNil result = not s.data.isNil
@ -72,7 +72,7 @@ proc len*[A](s: HashSet[A]): int =
## ##
## .. code-block:: ## .. code-block::
## ##
## var values: TSet[int] ## var values: Set[int]
## assert(not values.isValid) ## assert(not values.isValid)
## assert values.len == 0 ## assert values.len == 0
result = s.counter result = s.counter
@ -123,15 +123,15 @@ proc rightSize*(count: Natural): int {.inline.} =
## Internally, we want mustRehash(rightSize(x), x) == false. ## Internally, we want mustRehash(rightSize(x), x) == false.
result = nextPowerOfTwo(count * 3 div 2 + 4) result = nextPowerOfTwo(count * 3 div 2 + 4)
proc nextTry(h, maxHash: THash): THash {.inline.} = proc nextTry(h, maxHash: Hash): Hash {.inline.} =
result = (h + 1) and maxHash result = (h + 1) and maxHash
template rawGetKnownHCImpl() {.dirty.} = template rawGetKnownHCImpl() {.dirty.} =
var h: THash = hc and high(s.data) # start with real hash value var h: Hash = hc and high(s.data) # start with real hash value
while isFilled(s.data[h].hcode): while isFilled(s.data[h].hcode):
# Compare hc THEN key with boolean short circuit. This makes the common case # Compare hc THEN key with boolean short circuit. This makes the common case
# zero ==key's for missing (e.g.inserts) and exactly one ==key for present. # zero ==key's for missing (e.g.inserts) and exactly one ==key for present.
# It does slow down succeeding lookups by one extra THash cmp&and..usually # It does slow down succeeding lookups by one extra Hash cmp&and..usually
# just a few clock cycles, generally worth it for any non-integer-like A. # just a few clock cycles, generally worth it for any non-integer-like A.
if s.data[h].hcode == hc and s.data[h].key == key: # compare hc THEN key if s.data[h].hcode == hc and s.data[h].key == key: # compare hc THEN key
return h return h
@ -148,10 +148,10 @@ template rawInsertImpl() {.dirty.} =
data[h].key = key data[h].key = key
data[h].hcode = hc data[h].hcode = hc
proc rawGetKnownHC[A](s: HashSet[A], key: A, hc: THash): int {.inline.} = proc rawGetKnownHC[A](s: HashSet[A], key: A, hc: Hash): int {.inline.} =
rawGetKnownHCImpl() rawGetKnownHCImpl()
proc rawGet[A](s: HashSet[A], key: A, hc: var THash): int {.inline.} = proc rawGet[A](s: HashSet[A], key: A, hc: var Hash): int {.inline.} =
rawGetImpl() rawGetImpl()
proc mget*[A](s: var HashSet[A], key: A): var A = proc mget*[A](s: var HashSet[A], key: A): var A =
@ -160,7 +160,7 @@ proc mget*[A](s: var HashSet[A], key: A): var A =
## when one overloaded 'hash' and '==' but still needs reference semantics ## when one overloaded 'hash' and '==' but still needs reference semantics
## for sharing. ## for sharing.
assert s.isValid, "The set needs to be initialized." assert s.isValid, "The set needs to be initialized."
var hc: THash var hc: Hash
var index = rawGet(s, key, hc) var index = rawGet(s, key, hc)
if index >= 0: result = s.data[index].key if index >= 0: result = s.data[index].key
else: raise newException(KeyError, "key not found: " & $key) else: raise newException(KeyError, "key not found: " & $key)
@ -178,12 +178,12 @@ proc contains*[A](s: HashSet[A], key: A): bool =
## values.excl(2) ## values.excl(2)
## assert(not values.contains(2)) ## assert(not values.contains(2))
assert s.isValid, "The set needs to be initialized." assert s.isValid, "The set needs to be initialized."
var hc: THash var hc: Hash
var index = rawGet(s, key, hc) var index = rawGet(s, key, hc)
result = index >= 0 result = index >= 0
proc rawInsert[A](s: var HashSet[A], data: var KeyValuePairSeq[A], key: A, proc rawInsert[A](s: var HashSet[A], data: var KeyValuePairSeq[A], key: A,
hc: THash, h: THash) = hc: Hash, h: Hash) =
rawInsertImpl() rawInsertImpl()
proc enlarge[A](s: var HashSet[A]) = proc enlarge[A](s: var HashSet[A]) =
@ -196,7 +196,7 @@ proc enlarge[A](s: var HashSet[A]) =
rawInsert(s, s.data, n[i].key, n[i].hcode, j) rawInsert(s, s.data, n[i].key, n[i].hcode, j)
template inclImpl() {.dirty.} = template inclImpl() {.dirty.} =
var hc: THash var hc: Hash
var index = rawGet(s, key, hc) var index = rawGet(s, key, hc)
if index < 0: if index < 0:
if mustRehash(len(s.data), s.counter): if mustRehash(len(s.data), s.counter):
@ -206,7 +206,7 @@ template inclImpl() {.dirty.} =
inc(s.counter) inc(s.counter)
template containsOrInclImpl() {.dirty.} = template containsOrInclImpl() {.dirty.} =
var hc: THash var hc: Hash
var index = rawGet(s, key, hc) var index = rawGet(s, key, hc)
if index >= 0: if index >= 0:
result = true result = true
@ -261,7 +261,7 @@ proc excl*[A](s: var HashSet[A], key: A) =
## s.excl(2) ## s.excl(2)
## assert s.len == 3 ## assert s.len == 3
assert s.isValid, "The set needs to be initialized." assert s.isValid, "The set needs to be initialized."
var hc: THash var hc: Hash
var i = rawGet(s, key, hc) var i = rawGet(s, key, hc)
var msk = high(s.data) var msk = high(s.data)
if i >= 0: if i >= 0:
@ -323,7 +323,7 @@ proc init*[A](s: var HashSet[A], initialSize=64) =
## existing values and calling `excl() <#excl,TSet[A],A>`_ on them. Example: ## existing values and calling `excl() <#excl,TSet[A],A>`_ on them. Example:
## ##
## .. code-block :: ## .. code-block ::
## var a: TSet[int] ## var a: Set[int]
## a.init(4) ## a.init(4)
## a.incl(2) ## a.incl(2)
## a.init ## a.init
@ -552,7 +552,7 @@ proc map*[A, B](data: HashSet[A], op: proc (x: A): B {.closure.}): HashSet[B] =
type type
OrderedKeyValuePair[A] = tuple[ OrderedKeyValuePair[A] = tuple[
hcode: THash, next: int, key: A] hcode: Hash, next: int, key: A]
OrderedKeyValuePairSeq[A] = seq[OrderedKeyValuePair[A]] OrderedKeyValuePairSeq[A] = seq[OrderedKeyValuePair[A]]
OrderedSet* {.myShallow.}[A] = object ## \ OrderedSet* {.myShallow.}[A] = object ## \
## A generic hash set that remembers insertion order. ## A generic hash set that remembers insertion order.
@ -574,7 +574,7 @@ proc isValid*[A](s: OrderedSet[A]): bool =
## correctly initialized. Example: ## correctly initialized. Example:
## ##
## .. code-block:: ## .. code-block::
## proc saveTarotCards(cards: TOrderedSet[int]) = ## proc saveTarotCards(cards: OrderedSet[int]) =
## assert cards.isValid, "Pass an initialized set!" ## assert cards.isValid, "Pass an initialized set!"
## # Do stuff here, may crash in release builds! ## # Do stuff here, may crash in release builds!
result = not s.data.isNil result = not s.data.isNil
@ -588,7 +588,7 @@ proc len*[A](s: OrderedSet[A]): int {.inline.} =
## ##
## .. code-block:: ## .. code-block::
## ##
## var values: TOrderedSet[int] ## var values: OrderedSet[int]
## assert(not values.isValid) ## assert(not values.isValid)
## assert values.len == 0 ## assert values.len == 0
result = s.counter result = s.counter
@ -629,10 +629,10 @@ iterator items*[A](s: OrderedSet[A]): A =
forAllOrderedPairs: forAllOrderedPairs:
yield s.data[h].key yield s.data[h].key
proc rawGetKnownHC[A](s: OrderedSet[A], key: A, hc: THash): int {.inline.} = proc rawGetKnownHC[A](s: OrderedSet[A], key: A, hc: Hash): int {.inline.} =
rawGetKnownHCImpl() rawGetKnownHCImpl()
proc rawGet[A](s: OrderedSet[A], key: A, hc: var THash): int {.inline.} = proc rawGet[A](s: OrderedSet[A], key: A, hc: var Hash): int {.inline.} =
rawGetImpl() rawGetImpl()
proc contains*[A](s: OrderedSet[A], key: A): bool = proc contains*[A](s: OrderedSet[A], key: A): bool =
@ -646,12 +646,12 @@ proc contains*[A](s: OrderedSet[A], key: A): bool =
## values.incl(2) ## values.incl(2)
## assert values.contains(2) ## assert values.contains(2)
assert s.isValid, "The set needs to be initialized." assert s.isValid, "The set needs to be initialized."
var hc: THash var hc: Hash
var index = rawGet(s, key, hc) var index = rawGet(s, key, hc)
result = index >= 0 result = index >= 0
proc rawInsert[A](s: var OrderedSet[A], data: var OrderedKeyValuePairSeq[A], proc rawInsert[A](s: var OrderedSet[A], data: var OrderedKeyValuePairSeq[A],
key: A, hc: THash, h: THash) = key: A, hc: Hash, h: Hash) =
rawInsertImpl() rawInsertImpl()
data[h].next = -1 data[h].next = -1
if s.first < 0: s.first = h if s.first < 0: s.first = h
@ -729,7 +729,7 @@ proc init*[A](s: var OrderedSet[A], initialSize=64) =
## from an ordered hash set. Example: ## from an ordered hash set. Example:
## ##
## .. code-block :: ## .. code-block ::
## var a: TOrderedSet[int] ## var a: OrderedSet[int]
## a.init(4) ## a.init(4)
## a.incl(2) ## a.incl(2)
## a.init ## a.init

View file

@ -24,13 +24,13 @@
## ##
## Error: type mismatch: got (Person) ## Error: type mismatch: got (Person)
## but expected one of: ## but expected one of:
## hashes.hash(x: openarray[A]): THash ## hashes.hash(x: openarray[A]): Hash
## hashes.hash(x: int): THash ## hashes.hash(x: int): Hash
## hashes.hash(x: float): THash ## hashes.hash(x: float): Hash
## … ## …
## ##
## What is happening here is that the types used for table keys require to have ## What is happening here is that the types used for table keys require to have
## a ``hash()`` proc which will convert them to a `THash <hashes.html#THash>`_ ## a ``hash()`` proc which will convert them to a `Hash <hashes.html#Hash>`_
## value, and the compiler is listing all the hash functions it knows. ## value, and the compiler is listing all the hash functions it knows.
## Additionally there has to be a ``==`` operator that provides the same ## Additionally there has to be a ``==`` operator that provides the same
## semantics as its corresponding ``hash`` proc. ## semantics as its corresponding ``hash`` proc.
@ -46,7 +46,7 @@
## Person = object ## Person = object
## firstName, lastName: string ## firstName, lastName: string
## ##
## proc hash(x: Person): THash = ## proc hash(x: Person): Hash =
## ## Piggyback on the already available string hash proc. ## ## Piggyback on the already available string hash proc.
## ## ## ##
## ## Without this proc nothing works! ## ## Without this proc nothing works!
@ -71,7 +71,7 @@ import
{.pragma: myShallow.} {.pragma: myShallow.}
type type
KeyValuePair[A, B] = tuple[hcode: THash, key: A, val: B] KeyValuePair[A, B] = tuple[hcode: Hash, key: A, val: B]
KeyValuePairSeq[A, B] = seq[KeyValuePair[A, B]] KeyValuePairSeq[A, B] = seq[KeyValuePair[A, B]]
Table* {.myShallow.}[A, B] = object ## generic hash table Table* {.myShallow.}[A, B] = object ## generic hash table
data: KeyValuePairSeq[A, B] data: KeyValuePairSeq[A, B]
@ -85,10 +85,10 @@ when not defined(nimhygiene):
# hcode for real keys cannot be zero. hcode==0 signifies an empty slot. These # hcode for real keys cannot be zero. hcode==0 signifies an empty slot. These
# two procs retain clarity of that encoding without the space cost of an enum. # two procs retain clarity of that encoding without the space cost of an enum.
proc isEmpty(hcode: THash): bool {.inline.} = proc isEmpty(hcode: Hash): bool {.inline.} =
result = hcode == 0 result = hcode == 0
proc isFilled(hcode: THash): bool {.inline.} = proc isFilled(hcode: Hash): bool {.inline.} =
result = hcode != 0 result = hcode != 0
proc len*[A, B](t: Table[A, B]): int = proc len*[A, B](t: Table[A, B]): int =
@ -137,15 +137,15 @@ proc rightSize*(count: Natural): int {.inline.} =
## Internally, we want mustRehash(rightSize(x), x) == false. ## Internally, we want mustRehash(rightSize(x), x) == false.
result = nextPowerOfTwo(count * 3 div 2 + 4) result = nextPowerOfTwo(count * 3 div 2 + 4)
proc nextTry(h, maxHash: THash): THash {.inline.} = proc nextTry(h, maxHash: Hash): Hash {.inline.} =
result = (h + 1) and maxHash result = (h + 1) and maxHash
template rawGetKnownHCImpl() {.dirty.} = template rawGetKnownHCImpl() {.dirty.} =
var h: THash = hc and high(t.data) # start with real hash value var h: Hash = hc and high(t.data) # start with real hash value
while isFilled(t.data[h].hcode): while isFilled(t.data[h].hcode):
# Compare hc THEN key with boolean short circuit. This makes the common case # Compare hc THEN key with boolean short circuit. This makes the common case
# zero ==key's for missing (e.g.inserts) and exactly one ==key for present. # zero ==key's for missing (e.g.inserts) and exactly one ==key for present.
# It does slow down succeeding lookups by one extra THash cmp&and..usually # It does slow down succeeding lookups by one extra Hash cmp&and..usually
# just a few clock cycles, generally worth it for any non-integer-like A. # just a few clock cycles, generally worth it for any non-integer-like A.
if t.data[h].hcode == hc and t.data[h].key == key: if t.data[h].hcode == hc and t.data[h].key == key:
return h return h
@ -162,7 +162,7 @@ template rawGetDeepImpl() {.dirty.} = # Search algo for unconditional add
hc = hash(key) hc = hash(key)
if hc == 0: if hc == 0:
hc = 314159265 hc = 314159265
var h: THash = hc and high(t.data) var h: Hash = hc and high(t.data)
while isFilled(t.data[h].hcode): while isFilled(t.data[h].hcode):
h = nextTry(h, high(t.data)) h = nextTry(h, high(t.data))
result = h result = h
@ -172,13 +172,13 @@ template rawInsertImpl() {.dirty.} =
data[h].val = val data[h].val = val
data[h].hcode = hc data[h].hcode = hc
proc rawGetKnownHC[A, B](t: Table[A, B], key: A, hc: THash): int {.inline.} = proc rawGetKnownHC[A, B](t: Table[A, B], key: A, hc: Hash): int {.inline.} =
rawGetKnownHCImpl() rawGetKnownHCImpl()
proc rawGetDeep[A, B](t: Table[A, B], key: A, hc: var THash): int {.inline.} = proc rawGetDeep[A, B](t: Table[A, B], key: A, hc: var Hash): int {.inline.} =
rawGetDeepImpl() rawGetDeepImpl()
proc rawGet[A, B](t: Table[A, B], key: A, hc: var THash): int {.inline.} = proc rawGet[A, B](t: Table[A, B], key: A, hc: var Hash): int {.inline.} =
rawGetImpl() rawGetImpl()
proc `[]`*[A, B](t: Table[A, B], key: A): B = proc `[]`*[A, B](t: Table[A, B], key: A): B =
@ -186,14 +186,14 @@ proc `[]`*[A, B](t: Table[A, B], key: A): B =
## default empty value for the type `B` is returned ## default empty value for the type `B` is returned
## and no exception is raised. One can check with ``hasKey`` whether the key ## and no exception is raised. One can check with ``hasKey`` whether the key
## exists. ## exists.
var hc: THash var hc: Hash
var index = rawGet(t, key, hc) var index = rawGet(t, key, hc)
if index >= 0: result = t.data[index].val if index >= 0: result = t.data[index].val
proc mget*[A, B](t: var Table[A, B], key: A): var B = proc mget*[A, B](t: var Table[A, B], key: A): var B =
## retrieves the value at ``t[key]``. The value can be modified. ## retrieves the value at ``t[key]``. The value can be modified.
## If `key` is not in `t`, the ``KeyError`` exception is raised. ## If `key` is not in `t`, the ``KeyError`` exception is raised.
var hc: THash var hc: Hash
var index = rawGet(t, key, hc) var index = rawGet(t, key, hc)
if index >= 0: result = t.data[index].val if index >= 0: result = t.data[index].val
else: else:
@ -204,7 +204,7 @@ proc mget*[A, B](t: var Table[A, B], key: A): var B =
iterator allValues*[A, B](t: Table[A, B]; key: A): B = iterator allValues*[A, B](t: Table[A, B]; key: A): B =
## iterates over any value in the table `t` that belongs to the given `key`. ## iterates over any value in the table `t` that belongs to the given `key`.
var h: THash = hash(key) and high(t.data) var h: Hash = hash(key) and high(t.data)
while isFilled(t.data[h].hcode): while isFilled(t.data[h].hcode):
if t.data[h].key == key: if t.data[h].key == key:
yield t.data[h].val yield t.data[h].val
@ -212,7 +212,7 @@ iterator allValues*[A, B](t: Table[A, B]; key: A): B =
proc hasKey*[A, B](t: Table[A, B], key: A): bool = proc hasKey*[A, B](t: Table[A, B], key: A): bool =
## returns true iff `key` is in the table `t`. ## returns true iff `key` is in the table `t`.
var hc: THash var hc: Hash
result = rawGet(t, key, hc) >= 0 result = rawGet(t, key, hc) >= 0
proc contains*[A, B](t: Table[A, B], key: A): bool = proc contains*[A, B](t: Table[A, B], key: A): bool =
@ -220,7 +220,7 @@ proc contains*[A, B](t: Table[A, B], key: A): bool =
return hasKey[A, B](t, key) return hasKey[A, B](t, key)
proc rawInsert[A, B](t: var Table[A, B], data: var KeyValuePairSeq[A, B], proc rawInsert[A, B](t: var Table[A, B], data: var KeyValuePairSeq[A, B],
key: A, val: B, hc: THash, h: THash) = key: A, val: B, hc: Hash, h: Hash) =
rawInsertImpl() rawInsertImpl()
proc enlarge[A, B](t: var Table[A, B]) = proc enlarge[A, B](t: var Table[A, B]) =
@ -234,7 +234,7 @@ proc enlarge[A, B](t: var Table[A, B]) =
template addImpl() {.dirty.} = template addImpl() {.dirty.} =
if mustRehash(len(t.data), t.counter): enlarge(t) if mustRehash(len(t.data), t.counter): enlarge(t)
var hc: THash var hc: Hash
var j = rawGetDeep(t, key, hc) var j = rawGetDeep(t, key, hc)
rawInsert(t, t.data, key, val, hc, j) rawInsert(t, t.data, key, val, hc, j)
inc(t.counter) inc(t.counter)
@ -248,19 +248,19 @@ template maybeRehashPutImpl() {.dirty.} =
inc(t.counter) inc(t.counter)
template putImpl() {.dirty.} = template putImpl() {.dirty.} =
var hc: THash var hc: Hash
var index = rawGet(t, key, hc) var index = rawGet(t, key, hc)
if index >= 0: t.data[index].val = val if index >= 0: t.data[index].val = val
else: maybeRehashPutImpl() else: maybeRehashPutImpl()
template mgetOrPutImpl() {.dirty.} = template mgetOrPutImpl() {.dirty.} =
var hc: THash var hc: Hash
var index = rawGet(t, key, hc) var index = rawGet(t, key, hc)
if index < 0: maybeRehashPutImpl() # not present: insert (flipping index) if index < 0: maybeRehashPutImpl() # not present: insert (flipping index)
result = t.data[index].val # either way return modifiable val result = t.data[index].val # either way return modifiable val
template hasKeyOrPutImpl() {.dirty.} = template hasKeyOrPutImpl() {.dirty.} =
var hc: THash var hc: Hash
var index = rawGet(t, key, hc) var index = rawGet(t, key, hc)
if index < 0: if index < 0:
result = false result = false
@ -291,7 +291,7 @@ template doWhile(a: expr, b: stmt): stmt =
proc del*[A, B](t: var Table[A, B], key: A) = proc del*[A, B](t: var Table[A, B], key: A) =
## deletes `key` from hash table `t`. ## deletes `key` from hash table `t`.
var hc: THash var hc: Hash
var i = rawGet(t, key, hc) var i = rawGet(t, key, hc)
let msk = high(t.data) let msk = high(t.data)
if i >= 0: if i >= 0:
@ -460,7 +460,7 @@ proc newTableFrom*[A, B, C](collection: A, index: proc(x: B): C): TableRef[C, B]
type type
OrderedKeyValuePair[A, B] = tuple[ OrderedKeyValuePair[A, B] = tuple[
hcode: THash, next: int, key: A, val: B] hcode: Hash, next: int, key: A, val: B]
OrderedKeyValuePairSeq[A, B] = seq[OrderedKeyValuePair[A, B]] OrderedKeyValuePairSeq[A, B] = seq[OrderedKeyValuePair[A, B]]
OrderedTable* {. OrderedTable* {.
myShallow.}[A, B] = object ## table that remembers insertion order myShallow.}[A, B] = object ## table that remembers insertion order
@ -509,13 +509,13 @@ iterator mvalues*[A, B](t: var OrderedTable[A, B]): var B =
forAllOrderedPairs: forAllOrderedPairs:
yield t.data[h].val yield t.data[h].val
proc rawGetKnownHC[A, B](t: OrderedTable[A, B], key: A, hc: THash): int = proc rawGetKnownHC[A, B](t: OrderedTable[A, B], key: A, hc: Hash): int =
rawGetKnownHCImpl() rawGetKnownHCImpl()
proc rawGetDeep[A, B](t: OrderedTable[A, B], key: A, hc: var THash): int {.inline.} = proc rawGetDeep[A, B](t: OrderedTable[A, B], key: A, hc: var Hash): int {.inline.} =
rawGetDeepImpl() rawGetDeepImpl()
proc rawGet[A, B](t: OrderedTable[A, B], key: A, hc: var THash): int = proc rawGet[A, B](t: OrderedTable[A, B], key: A, hc: var Hash): int =
rawGetImpl() rawGetImpl()
proc `[]`*[A, B](t: OrderedTable[A, B], key: A): B = proc `[]`*[A, B](t: OrderedTable[A, B], key: A): B =
@ -523,21 +523,21 @@ proc `[]`*[A, B](t: OrderedTable[A, B], key: A): B =
## default empty value for the type `B` is returned ## default empty value for the type `B` is returned
## and no exception is raised. One can check with ``hasKey`` whether the key ## and no exception is raised. One can check with ``hasKey`` whether the key
## exists. ## exists.
var hc: THash var hc: Hash
var index = rawGet(t, key, hc) var index = rawGet(t, key, hc)
if index >= 0: result = t.data[index].val if index >= 0: result = t.data[index].val
proc mget*[A, B](t: var OrderedTable[A, B], key: A): var B = proc mget*[A, B](t: var OrderedTable[A, B], key: A): var B =
## retrieves the value at ``t[key]``. The value can be modified. ## retrieves the value at ``t[key]``. The value can be modified.
## If `key` is not in `t`, the ``EInvalidKey`` exception is raised. ## If `key` is not in `t`, the ``EInvalidKey`` exception is raised.
var hc: THash var hc: Hash
var index = rawGet(t, key, hc) var index = rawGet(t, key, hc)
if index >= 0: result = t.data[index].val if index >= 0: result = t.data[index].val
else: raise newException(KeyError, "key not found: " & $key) else: raise newException(KeyError, "key not found: " & $key)
proc hasKey*[A, B](t: OrderedTable[A, B], key: A): bool = proc hasKey*[A, B](t: OrderedTable[A, B], key: A): bool =
## returns true iff `key` is in the table `t`. ## returns true iff `key` is in the table `t`.
var hc: THash var hc: Hash
result = rawGet(t, key, hc) >= 0 result = rawGet(t, key, hc) >= 0
proc contains*[A, B](t: OrderedTable[A, B], key: A): bool = proc contains*[A, B](t: OrderedTable[A, B], key: A): bool =
@ -546,7 +546,7 @@ proc contains*[A, B](t: OrderedTable[A, B], key: A): bool =
proc rawInsert[A, B](t: var OrderedTable[A, B], proc rawInsert[A, B](t: var OrderedTable[A, B],
data: var OrderedKeyValuePairSeq[A, B], data: var OrderedKeyValuePairSeq[A, B],
key: A, val: B, hc: THash, h: THash) = key: A, val: B, hc: Hash, h: Hash) =
rawInsertImpl() rawInsertImpl()
data[h].next = -1 data[h].next = -1
if t.first < 0: t.first = h if t.first < 0: t.first = h
@ -796,7 +796,7 @@ iterator mvalues*[A](t: CountTable[A]): var int =
if t.data[h].val != 0: yield t.data[h].val if t.data[h].val != 0: yield t.data[h].val
proc rawGet[A](t: CountTable[A], key: A): int = proc rawGet[A](t: CountTable[A], key: A): int =
var h: THash = hash(key) and high(t.data) # start with real hash value var h: Hash = hash(key) and high(t.data) # start with real hash value
while t.data[h].val != 0: while t.data[h].val != 0:
if t.data[h].key == key: return h if t.data[h].key == key: return h
h = nextTry(h, high(t.data)) h = nextTry(h, high(t.data))
@ -826,7 +826,7 @@ proc contains*[A](t: CountTable[A], key: A): bool =
proc rawInsert[A](t: CountTable[A], data: var seq[tuple[key: A, val: int]], proc rawInsert[A](t: CountTable[A], data: var seq[tuple[key: A, val: int]],
key: A, val: int) = key: A, val: int) =
var h: THash = hash(key) and high(data) var h: Hash = hash(key) and high(data)
while data[h].val != 0: h = nextTry(h, high(data)) while data[h].val != 0: h = nextTry(h, high(data))
data[h].key = key data[h].key = key
data[h].val = val data[h].val = val
@ -1032,7 +1032,7 @@ when isMainModule:
Person = object Person = object
firstName, lastName: string firstName, lastName: string
proc hash(x: Person): THash = proc hash(x: Person): Hash =
## Piggyback on the already available string hash proc. ## Piggyback on the already available string hash proc.
## ##
## Without this proc nothing works! ## Without this proc nothing works!

View file

@ -13,7 +13,7 @@
when defined(windows): when defined(windows):
import winlean, os, strutils, math import winlean, os, strutils, math
proc `-`(a, b: TFILETIME): int64 = a.rdFileTime - b.rdFileTime proc `-`(a, b: FILETIME): int64 = a.rdFileTime - b.rdFileTime
elif defined(linux): elif defined(linux):
from cpuinfo import countProcessors from cpuinfo import countProcessors
@ -25,16 +25,16 @@ type
ThreadPoolState* = object ThreadPoolState* = object
when defined(windows): when defined(windows):
prevSysKernel, prevSysUser, prevProcKernel, prevProcUser: TFILETIME prevSysKernel, prevSysUser, prevProcKernel, prevProcUser: FILETIME
calls*: int calls*: int
proc advice*(s: var ThreadPoolState): ThreadPoolAdvice = proc advice*(s: var ThreadPoolState): ThreadPoolAdvice =
when defined(windows): when defined(windows):
var var
sysIdle, sysKernel, sysUser, sysIdle, sysKernel, sysUser,
procCreation, procExit, procKernel, procUser: TFILETIME procCreation, procExit, procKernel, procUser: FILETIME
if getSystemTimes(sysIdle, sysKernel, sysUser) == 0 or if getSystemTimes(sysIdle, sysKernel, sysUser) == 0 or
getProcessTimes(THandle(-1), procCreation, procExit, getProcessTimes(Handle(-1), procCreation, procExit,
procKernel, procUser) == 0: procKernel, procUser) == 0:
return doNothing return doNothing
if s.calls > 0: if s.calls > 0:
@ -57,7 +57,7 @@ proc advice*(s: var ThreadPoolState): ThreadPoolAdvice =
s.prevProcKernel = procKernel s.prevProcKernel = procKernel
s.prevProcUser = procUser s.prevProcUser = procUser
elif defined(linux): elif defined(linux):
proc fscanf(c: File, frmt: cstring) {.varargs, importc, proc fscanf(c: File, frmt: cstring) {.varargs, importc,
header: "<stdio.h>".} header: "<stdio.h>".}
var f = open("/proc/loadavg") var f = open("/proc/loadavg")

View file

@ -18,8 +18,8 @@ import cpuinfo, cpuload, locks
type type
Semaphore = object Semaphore = object
c: TCond c: Cond
L: TLock L: Lock
counter: int counter: int
proc createSemaphore(): Semaphore = proc createSemaphore(): Semaphore =
@ -113,7 +113,7 @@ type
ToFreeQueue = object ToFreeQueue = object
len: int len: int
lock: TLock lock: Lock
empty: Semaphore empty: Semaphore
data: array[128, pointer] data: array[128, pointer]
@ -221,11 +221,17 @@ proc awaitAndThen*[T](fv: FlowVar[T]; action: proc (x: T) {.closure.}) =
action(fv.blob) action(fv.blob)
finished(fv) finished(fv)
proc `^`*[T](fv: FlowVar[ref T]): foreign ptr T = proc unsafeRead*[T](fv: FlowVar[ref T]): foreign ptr T =
## blocks until the value is available and then returns this value. ## blocks until the value is available and then returns this value.
await(fv) await(fv)
result = cast[foreign ptr T](fv.data) result = cast[foreign ptr T](fv.data)
proc `^`*[T](fv: FlowVar[ref T]): ref T =
## blocks until the value is available and then returns this value.
await(fv)
let src = cast[ref T](fv.data)
deepCopy result, src
proc `^`*[T](fv: FlowVar[T]): T = proc `^`*[T](fv: FlowVar[T]): T =
## blocks until the value is available and then returns this value. ## blocks until the value is available and then returns this value.
await(fv) await(fv)
@ -284,7 +290,8 @@ proc slave(w: ptr Worker) {.thread.} =
readyWorker = w readyWorker = w
signal(gSomeReady) signal(gSomeReady)
await(w.taskArrived) await(w.taskArrived)
assert(not w.ready) # XXX Somebody needs to look into this (why does this assertion fail in Visual Studio?)
when not defined(vcc): assert(not w.ready)
w.f(w, w.data) w.f(w, w.data)
if w.q.len != 0: w.cleanFlowVars if w.q.len != 0: w.cleanFlowVars
if w.shutdown: if w.shutdown:
@ -349,7 +356,7 @@ proc parallel*(body: stmt) {.magic: "Parallel".}
var var
state: ThreadPoolState state: ThreadPoolState
stateLock: TLock stateLock: Lock
initLock stateLock initLock stateLock

View file

@ -211,12 +211,13 @@ when defined(windows):
when false: when false:
# not needed yet: # not needed yet:
type type
TCpInfo = object CpInfo = object
maxCharSize: int32 maxCharSize: int32
defaultChar: array[0..1, char] defaultChar: array[0..1, char]
leadByte: array[0..12-1, char] leadByte: array[0..12-1, char]
{.deprecated: [TCpInfo: CpInfo].}
proc getCPInfo(codePage: CodePage, lpCPInfo: var TCpInfo): int32 {. proc getCPInfo(codePage: CodePage, lpCPInfo: var CpInfo): int32 {.
stdcall, importc: "GetCPInfo", dynlib: "kernel32".} stdcall, importc: "GetCPInfo", dynlib: "kernel32".}
proc nameToCodePage(name: string): CodePage = proc nameToCodePage(name: string): CodePage =

23
lib/pure/etcpriv.nim Normal file
View file

@ -0,0 +1,23 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2015 Nim Authors
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module contains utils that are less then easy to categorize and
## don't really warrant a specific module. They are private to compiler
## and stdlib usage, and should not be used outside of that - they may
## change or disappear at any time.
# Used by pure/hashes.nim, and the compiler parsing
const magicIdentSeparatorRuneByteWidth* = 3
# Used by pure/hashes.nim, and the compiler parsing
proc isMagicIdentSeparatorRune*(cs: cstring, i: int): bool {. inline } =
result = cs[i] == '\226' and
cs[i + 1] == '\128' and
cs[i + 2] == '\147' # en-dash # 145 = nb-hyphen

View file

@ -108,7 +108,7 @@ proc del*(monitor: FSMonitor, wd: cint) =
proc getEvent(m: FSMonitor, fd: cint): seq[MonitorEvent] = proc getEvent(m: FSMonitor, fd: cint): seq[MonitorEvent] =
result = @[] result = @[]
let size = (sizeof(TINotifyEvent)+2000)*MaxEvents let size = (sizeof(INotifyEvent)+2000)*MaxEvents
var buffer = newString(size) var buffer = newString(size)
let le = read(fd, addr(buffer[0]), size) let le = read(fd, addr(buffer[0]), size)
@ -117,7 +117,7 @@ proc getEvent(m: FSMonitor, fd: cint): seq[MonitorEvent] =
var i = 0 var i = 0
while i < le: while i < le:
var event = cast[ptr TINotifyEvent](addr(buffer[i])) var event = cast[ptr INotifyEvent](addr(buffer[i]))
var mev: MonitorEvent var mev: MonitorEvent
mev.wd = event.wd mev.wd = event.wd
if event.len.int != 0: if event.len.int != 0:
@ -129,7 +129,7 @@ proc getEvent(m: FSMonitor, fd: cint): seq[MonitorEvent] =
if (event.mask.int and IN_MOVED_FROM) != 0: if (event.mask.int and IN_MOVED_FROM) != 0:
# Moved from event, add to m's collection # Moved from event, add to m's collection
movedFrom.add(event.cookie.cint, (mev.wd, mev.name)) movedFrom.add(event.cookie.cint, (mev.wd, mev.name))
inc(i, sizeof(TINotifyEvent) + event.len.int) inc(i, sizeof(INotifyEvent) + event.len.int)
continue continue
elif (event.mask.int and IN_MOVED_TO) != 0: elif (event.mask.int and IN_MOVED_TO) != 0:
mev.kind = MonitorMoved mev.kind = MonitorMoved
@ -159,7 +159,7 @@ proc getEvent(m: FSMonitor, fd: cint): seq[MonitorEvent] =
mev.fullname = "" mev.fullname = ""
result.add(mev) result.add(mev)
inc(i, sizeof(TINotifyEvent) + event.len.int) inc(i, sizeof(INotifyEvent) + event.len.int)
# If movedFrom events have not been matched with a moveTo. File has # If movedFrom events have not been matched with a moveTo. File has
# been moved to an unwatched location, emit a MonitorDelete. # been moved to an unwatched location, emit a MonitorDelete.

View file

@ -18,20 +18,22 @@ import
os, hashes, strutils os, hashes, strutils
type type
TGenTableMode* = enum ## describes the table's key matching mode GenTableMode* = enum ## describes the table's key matching mode
modeCaseSensitive, ## case sensitive matching of keys modeCaseSensitive, ## case sensitive matching of keys
modeCaseInsensitive, ## case insensitive matching of keys modeCaseInsensitive, ## case insensitive matching of keys
modeStyleInsensitive ## style sensitive matching of keys modeStyleInsensitive ## style sensitive matching of keys
TGenKeyValuePair[T] = tuple[key: string, val: T] GenKeyValuePair[T] = tuple[key: string, val: T]
TGenKeyValuePairSeq[T] = seq[TGenKeyValuePair[T]] GenKeyValuePairSeq[T] = seq[GenKeyValuePair[T]]
TGenTable*[T] = object of RootObj GenTable*[T] = object of RootObj
counter: int counter: int
data: TGenKeyValuePairSeq[T] data: GenKeyValuePairSeq[T]
mode: TGenTableMode mode: GenTableMode
PGenTable*[T] = ref TGenTable[T] ## use this type to declare hash tables PGenTable*[T] = ref GenTable[T] ## use this type to declare hash tables
{.deprecated: [TGenTableMode: GenTableMode, TGenKeyValuePair: GenKeyValuePair,
TGenKeyValuePairSeq: GenKeyValuePairSeq, TGenTable: GenTable].}
const const
growthFactor = 2 growthFactor = 2
@ -48,7 +50,7 @@ iterator pairs*[T](tbl: PGenTable[T]): tuple[key: string, value: T] =
if not isNil(tbl.data[h].key): if not isNil(tbl.data[h].key):
yield (tbl.data[h].key, tbl.data[h].val) yield (tbl.data[h].key, tbl.data[h].val)
proc myhash[T](tbl: PGenTable[T], key: string): THash = proc myhash[T](tbl: PGenTable[T], key: string): Hash =
case tbl.mode case tbl.mode
of modeCaseSensitive: result = hashes.hash(key) of modeCaseSensitive: result = hashes.hash(key)
of modeCaseInsensitive: result = hashes.hashIgnoreCase(key) of modeCaseInsensitive: result = hashes.hashIgnoreCase(key)
@ -64,18 +66,18 @@ proc mustRehash(length, counter: int): bool =
assert(length > counter) assert(length > counter)
result = (length * 2 < counter * 3) or (length - counter < 4) result = (length * 2 < counter * 3) or (length - counter < 4)
proc newGenTable*[T](mode: TGenTableMode): PGenTable[T] = proc newGenTable*[T](mode: GenTableMode): PGenTable[T] =
## creates a new generic hash table that is empty. ## creates a new generic hash table that is empty.
new(result) new(result)
result.mode = mode result.mode = mode
result.counter = 0 result.counter = 0
newSeq(result.data, startSize) newSeq(result.data, startSize)
proc nextTry(h, maxHash: THash): THash {.inline.} = proc nextTry(h, maxHash: Hash): Hash {.inline.} =
result = ((5 * h) + 1) and maxHash result = ((5 * h) + 1) and maxHash
proc rawGet[T](tbl: PGenTable[T], key: string): int = proc rawGet[T](tbl: PGenTable[T], key: string): int =
var h: THash var h: Hash
h = myhash(tbl, key) and high(tbl.data) # start with real hash value h = myhash(tbl, key) and high(tbl.data) # start with real hash value
while not isNil(tbl.data[h].key): while not isNil(tbl.data[h].key):
if myCmp(tbl, tbl.data[h].key, key): if myCmp(tbl, tbl.data[h].key, key):
@ -83,9 +85,9 @@ proc rawGet[T](tbl: PGenTable[T], key: string): int =
h = nextTry(h, high(tbl.data)) h = nextTry(h, high(tbl.data))
result = - 1 result = - 1
proc rawInsert[T](tbl: PGenTable[T], data: var TGenKeyValuePairSeq[T], proc rawInsert[T](tbl: PGenTable[T], data: var GenKeyValuePairSeq[T],
key: string, val: T) = key: string, val: T) =
var h: THash var h: Hash
h = myhash(tbl, key) and high(data) h = myhash(tbl, key) and high(data)
while not isNil(data[h].key): while not isNil(data[h].key):
h = nextTry(h, high(data)) h = nextTry(h, high(data))
@ -93,7 +95,7 @@ proc rawInsert[T](tbl: PGenTable[T], data: var TGenKeyValuePairSeq[T],
data[h].val = val data[h].val = val
proc enlarge[T](tbl: PGenTable[T]) = proc enlarge[T](tbl: PGenTable[T]) =
var n: TGenKeyValuePairSeq[T] var n: GenKeyValuePairSeq[T]
newSeq(n, len(tbl.data) * growthFactor) newSeq(n, len(tbl.data) * growthFactor)
for i in countup(0, high(tbl.data)): for i in countup(0, high(tbl.data)):
if not isNil(tbl.data[i].key): if not isNil(tbl.data[i].key):
@ -146,19 +148,20 @@ when isMainModule:
# Verify a table of user-defined types # Verify a table of user-defined types
# #
type type
TMyType = tuple[first, second: string] # a pair of strings MyType = tuple[first, second: string] # a pair of strings
{.deprecated: [TMyType: MyType].}
var y = newGenTable[TMyType](modeCaseInsensitive) # hash table where each var y = newGenTable[MyType](modeCaseInsensitive) # hash table where each
# value is TMyType tuple # value is MyType tuple
#var junk: TMyType = ("OK", "Here") #var junk: MyType = ("OK", "Here")
#echo junk.first, " ", junk.second #echo junk.first, " ", junk.second
y["Hello"] = ("Hello", "World") y["Hello"] = ("Hello", "World")
y["Goodbye"] = ("Goodbye", "Everyone") y["Goodbye"] = ("Goodbye", "Everyone")
#y["Hello"] = TMyType( ("Hello", "World") ) #y["Hello"] = MyType( ("Hello", "World") )
#y["Goodbye"] = TMyType( ("Goodbye", "Everyone") ) #y["Goodbye"] = MyType( ("Goodbye", "Everyone") )
assert( not isNil(y["Hello"].first) ) assert( not isNil(y["Hello"].first) )
assert( y["Hello"].first == "Hello" ) assert( y["Hello"].first == "Hello" )

View file

@ -15,9 +15,9 @@
## code: ## code:
## ##
## .. code-block:: Nim ## .. code-block:: Nim
## proc hash(x: Something): THash = ## proc hash(x: Something): Hash =
## ## Computes a THash from `x`. ## ## Computes a Hash from `x`.
## var h: THash = 0 ## var h: Hash = 0
## # Iterate over parts of `x`. ## # Iterate over parts of `x`.
## for xAtom in x: ## for xAtom in x:
## # Mix the atom with the partial hash. ## # Mix the atom with the partial hash.
@ -30,38 +30,39 @@
## together the hash value of the individual fields: ## together the hash value of the individual fields:
## ##
## .. code-block:: Nim ## .. code-block:: Nim
## proc hash(x: Something): THash = ## proc hash(x: Something): Hash =
## ## Computes a THash from `x`. ## ## Computes a Hash from `x`.
## var h: THash = 0 ## var h: Hash = 0
## h = h !& hash(x.foo) ## h = h !& hash(x.foo)
## h = h !& hash(x.bar) ## h = h !& hash(x.bar)
## result = !$h ## result = !$h
import import
strutils strutils, etcpriv
type type
THash* = int ## a hash value; hash tables using these values should Hash* = int ## a hash value; hash tables using these values should
## always have a size of a power of two and can use the ``and`` ## always have a size of a power of two and can use the ``and``
## operator instead of ``mod`` for truncation of the hash value. ## operator instead of ``mod`` for truncation of the hash value.
{.deprecated: [THash: Hash].}
proc `!&`*(h: THash, val: int): THash {.inline.} = proc `!&`*(h: Hash, val: int): Hash {.inline.} =
## mixes a hash value `h` with `val` to produce a new hash value. This is ## mixes a hash value `h` with `val` to produce a new hash value. This is
## only needed if you need to implement a hash proc for a new datatype. ## only needed if you need to implement a hash proc for a new datatype.
result = h +% val result = h +% val
result = result +% result shl 10 result = result +% result shl 10
result = result xor (result shr 6) result = result xor (result shr 6)
proc `!$`*(h: THash): THash {.inline.} = proc `!$`*(h: Hash): Hash {.inline.} =
## finishes the computation of the hash value. This is ## finishes the computation of the hash value. This is
## only needed if you need to implement a hash proc for a new datatype. ## only needed if you need to implement a hash proc for a new datatype.
result = h +% h shl 3 result = h +% h shl 3
result = result xor (result shr 11) result = result xor (result shr 11)
result = result +% result shl 15 result = result +% result shl 15
proc hashData*(data: pointer, size: int): THash = proc hashData*(data: pointer, size: int): Hash =
## hashes an array of bytes of size `size` ## hashes an array of bytes of size `size`
var h: THash = 0 var h: Hash = 0
when defined(js): when defined(js):
var p: cstring var p: cstring
asm """`p` = `Data`;""" asm """`p` = `Data`;"""
@ -78,7 +79,7 @@ proc hashData*(data: pointer, size: int): THash =
when defined(js): when defined(js):
var objectID = 0 var objectID = 0
proc hash*(x: pointer): THash {.inline.} = proc hash*(x: pointer): Hash {.inline.} =
## efficient hashing of pointers ## efficient hashing of pointers
when defined(js): when defined(js):
asm """ asm """
@ -92,50 +93,57 @@ proc hash*(x: pointer): THash {.inline.} =
} }
""" """
else: else:
result = (cast[THash](x)) shr 3 # skip the alignment result = (cast[Hash](x)) shr 3 # skip the alignment
when not defined(booting): when not defined(booting):
proc hash*[T: proc](x: T): THash {.inline.} = proc hash*[T: proc](x: T): Hash {.inline.} =
## efficient hashing of proc vars; closures are supported too. ## efficient hashing of proc vars; closures are supported too.
when T is "closure": when T is "closure":
result = hash(rawProc(x)) !& hash(rawEnv(x)) result = hash(rawProc(x)) !& hash(rawEnv(x))
else: else:
result = hash(pointer(x)) result = hash(pointer(x))
proc hash*(x: int): THash {.inline.} = proc hash*(x: int): Hash {.inline.} =
## efficient hashing of integers ## efficient hashing of integers
result = x result = x
proc hash*(x: int64): THash {.inline.} = proc hash*(x: int64): Hash {.inline.} =
## efficient hashing of integers ## efficient hashing of integers
result = toU32(x) result = toU32(x)
proc hash*(x: char): THash {.inline.} = proc hash*(x: char): Hash {.inline.} =
## efficient hashing of characters ## efficient hashing of characters
result = ord(x) result = ord(x)
proc hash*(x: string): THash = proc hash*(x: string): Hash =
## efficient hashing of strings ## efficient hashing of strings
var h: THash = 0 var h: Hash = 0
for i in 0..x.len-1: for i in 0..x.len-1:
h = h !& ord(x[i]) h = h !& ord(x[i])
result = !$h result = !$h
proc hashIgnoreStyle*(x: string): THash = proc hashIgnoreStyle*(x: string): Hash =
## efficient hashing of strings; style is ignored ## efficient hashing of strings; style is ignored
var h: THash = 0 var h: Hash = 0
for i in 0..x.len-1: var i = 0
let xLen = x.len
while i < xLen:
var c = x[i] var c = x[i]
if c == '_': if c == '_':
continue # skip _ inc(i)
if c in {'A'..'Z'}: elif isMagicIdentSeparatorRune(cstring(x), i):
c = chr(ord(c) + (ord('a') - ord('A'))) # toLower() inc(i, magicIdentSeparatorRuneByteWidth)
h = h !& ord(c) else:
if c in {'A'..'Z'}:
c = chr(ord(c) + (ord('a') - ord('A'))) # toLower()
h = h !& ord(c)
inc(i)
result = !$h result = !$h
proc hashIgnoreCase*(x: string): THash = proc hashIgnoreCase*(x: string): Hash =
## efficient hashing of strings; case is ignored ## efficient hashing of strings; case is ignored
var h: THash = 0 var h: Hash = 0
for i in 0..x.len-1: for i in 0..x.len-1:
var c = x[i] var c = x[i]
if c in {'A'..'Z'}: if c in {'A'..'Z'}:
@ -143,28 +151,28 @@ proc hashIgnoreCase*(x: string): THash =
h = h !& ord(c) h = h !& ord(c)
result = !$h result = !$h
proc hash*(x: float): THash {.inline.} = proc hash*(x: float): Hash {.inline.} =
var y = x + 1.0 var y = x + 1.0
result = cast[ptr THash](addr(y))[] result = cast[ptr Hash](addr(y))[]
# Forward declarations before methods that hash containers. This allows # Forward declarations before methods that hash containers. This allows
# containers to contain other containers # containers to contain other containers
proc hash*[A](x: openArray[A]): THash proc hash*[A](x: openArray[A]): Hash
proc hash*[A](x: set[A]): THash proc hash*[A](x: set[A]): Hash
proc hash*[T: tuple](x: T): THash = proc hash*[T: tuple](x: T): Hash =
## efficient hashing of tuples. ## efficient hashing of tuples.
for f in fields(x): for f in fields(x):
result = result !& hash(f) result = result !& hash(f)
result = !$result result = !$result
proc hash*[A](x: openArray[A]): THash = proc hash*[A](x: openArray[A]): Hash =
for it in items(x): result = result !& hash(it) for it in items(x): result = result !& hash(it)
result = !$result result = !$result
proc hash*[A](x: set[A]): THash = proc hash*[A](x: set[A]): Hash =
for it in items(x): result = result !& hash(it) for it in items(x): result = result !& hash(it)
result = !$result result = !$result

View file

@ -52,7 +52,7 @@
import strutils, streams, parsexml, xmltree, unicode, strtabs import strutils, streams, parsexml, xmltree, unicode, strtabs
type type
THtmlTag* = enum ## list of all supported HTML tags; order will always be HtmlTag* = enum ## list of all supported HTML tags; order will always be
## alphabetically ## alphabetically
tagUnknown, ## unknown HTML element tagUnknown, ## unknown HTML element
tagA, ## the HTML ``a`` element tagA, ## the HTML ``a`` element
@ -178,6 +178,7 @@ type
tagVar, ## the HTML ``var`` element tagVar, ## the HTML ``var`` element
tagVideo, ## the HTML ``video`` element tagVideo, ## the HTML ``video`` element
tagWbr ## the HTML ``wbr`` element tagWbr ## the HTML ``wbr`` element
{.deprecated: [THtmlTag: HtmlTag].}
const const
tagToStr* = [ tagToStr* = [
@ -295,7 +296,7 @@ proc allLower(s: string): bool =
if c < 'a' or c > 'z': return false if c < 'a' or c > 'z': return false
return true return true
proc toHtmlTag(s: string): THtmlTag = proc toHtmlTag(s: string): HtmlTag =
case s case s
of "a": tagA of "a": tagA
of "abbr": tagAbbr of "abbr": tagAbbr
@ -422,14 +423,14 @@ proc toHtmlTag(s: string): THtmlTag =
of "wbr": tagWbr of "wbr": tagWbr
else: tagUnknown else: tagUnknown
proc htmlTag*(n: XmlNode): THtmlTag = proc htmlTag*(n: XmlNode): HtmlTag =
## gets `n`'s tag as a ``THtmlTag``. ## gets `n`'s tag as a ``HtmlTag``.
if n.clientData == 0: if n.clientData == 0:
n.clientData = toHtmlTag(n.tag).ord n.clientData = toHtmlTag(n.tag).ord
result = THtmlTag(n.clientData) result = HtmlTag(n.clientData)
proc htmlTag*(s: string): THtmlTag = proc htmlTag*(s: string): HtmlTag =
## converts `s` to a ``THtmlTag``. If `s` is no HTML tag, ``tagUnknown`` is ## converts `s` to a ``HtmlTag``. If `s` is no HTML tag, ``tagUnknown`` is
## returned. ## returned.
let s = if allLower(s): s else: s.toLower let s = if allLower(s): s else: s.toLower
result = toHtmlTag(s) result = toHtmlTag(s)

View file

@ -569,7 +569,7 @@ proc downloadFile*(url: string, outputFilename: string,
fileError("Unable to open file") fileError("Unable to open file")
proc generateHeaders(r: Uri, httpMethod: string, proc generateHeaders(r: Uri, httpMethod: string,
headers: StringTableRef): string = headers: StringTableRef, body: string): string =
# TODO: Use this in the blocking HttpClient once it supports proxies. # TODO: Use this in the blocking HttpClient once it supports proxies.
result = substr(httpMethod, len("http")) result = substr(httpMethod, len("http"))
# TODO: Proxies # TODO: Proxies
@ -582,6 +582,8 @@ proc generateHeaders(r: Uri, httpMethod: string,
add(result, "Host: " & r.hostname & "\c\L") add(result, "Host: " & r.hostname & "\c\L")
add(result, "Connection: Keep-Alive\c\L") add(result, "Connection: Keep-Alive\c\L")
if body.len > 0 and not headers.hasKey("Content-Length"):
add(result, "Content-Length: " & $body.len & "\c\L")
for key, val in headers: for key, val in headers:
add(result, key & ": " & val & "\c\L") add(result, key & ": " & val & "\c\L")
@ -786,7 +788,7 @@ proc request*(client: AsyncHttpClient, url: string, httpMethod: string,
if not client.headers.hasKey("user-agent") and client.userAgent != "": if not client.headers.hasKey("user-agent") and client.userAgent != "":
client.headers["User-Agent"] = client.userAgent client.headers["User-Agent"] = client.userAgent
var headers = generateHeaders(r, $httpMethod, client.headers) var headers = generateHeaders(r, $httpMethod, client.headers, body)
await client.socket.send(headers) await client.socket.send(headers)
if body != "": if body != "":

View file

@ -106,9 +106,10 @@ proc serveFile*(client: Socket, filename: string) =
when false: when false:
# TODO: Fix this, or get rid of it. # TODO: Fix this, or get rid of it.
type type
TRequestMethod = enum reqGet, reqPost RequestMethod = enum reqGet, reqPost
{.deprecated: [TRequestMethod: RequestMethod].}
proc executeCgi(client: Socket, path, query: string, meth: TRequestMethod) = proc executeCgi(client: Socket, path, query: string, meth: RequestMethod) =
var env = newStringTable(modeCaseInsensitive) var env = newStringTable(modeCaseInsensitive)
var contentLength = -1 var contentLength = -1
case meth case meth
@ -208,7 +209,7 @@ when false:
executeCgi(client, path, query, meth) executeCgi(client, path, query, meth)
type type
TServer* = object of RootObj ## contains the current server state Server* = object of RootObj ## contains the current server state
socket: Socket socket: Socket
port: Port port: Port
client*: Socket ## the socket to write the file data to client*: Socket ## the socket to write the file data to
@ -218,11 +219,12 @@ type
body*: string ## only set with POST requests body*: string ## only set with POST requests
ip*: string ## ip address of the requesting client ip*: string ## ip address of the requesting client
PAsyncHTTPServer* = ref TAsyncHTTPServer PAsyncHTTPServer* = ref AsyncHTTPServer
TAsyncHTTPServer = object of TServer AsyncHTTPServer = object of Server
asyncSocket: AsyncSocket asyncSocket: AsyncSocket
{.deprecated: [TAsyncHTTPServer: AsyncHTTPServer, TServer: Server].}
proc open*(s: var TServer, port = Port(80), reuseAddr = false) = proc open*(s: var Server, port = Port(80), reuseAddr = false) =
## creates a new server at port `port`. If ``port == 0`` a free port is ## creates a new server at port `port`. If ``port == 0`` a free port is
## acquired that can be accessed later by the ``port`` proc. ## acquired that can be accessed later by the ``port`` proc.
s.socket = socket(AF_INET) s.socket = socket(AF_INET)
@ -243,11 +245,11 @@ proc open*(s: var TServer, port = Port(80), reuseAddr = false) =
s.query = "" s.query = ""
s.headers = {:}.newStringTable() s.headers = {:}.newStringTable()
proc port*(s: var TServer): Port = proc port*(s: var Server): Port =
## get the port number the server has acquired. ## get the port number the server has acquired.
result = s.port result = s.port
proc next*(s: var TServer) = proc next*(s: var Server) =
## proceed to the first/next request. ## proceed to the first/next request.
var client: Socket var client: Socket
new(client) new(client)
@ -354,7 +356,7 @@ proc next*(s: var TServer) =
s.query = "" s.query = ""
s.path = data.substr(i, last-1) s.path = data.substr(i, last-1)
proc close*(s: TServer) = proc close*(s: Server) =
## closes the server (and the socket the server uses). ## closes the server (and the socket the server uses).
close(s.socket) close(s.socket)
@ -362,7 +364,7 @@ proc run*(handleRequest: proc (client: Socket,
path, query: string): bool {.closure.}, path, query: string): bool {.closure.},
port = Port(80)) = port = Port(80)) =
## encapsulates the server object and main loop ## encapsulates the server object and main loop
var s: TServer var s: Server
open(s, port, reuseAddr = true) open(s, port, reuseAddr = true)
#echo("httpserver running on port ", s.port) #echo("httpserver running on port ", s.port)
while true: while true:
@ -517,7 +519,7 @@ proc close*(h: PAsyncHTTPServer) =
when not defined(testing) and isMainModule: when not defined(testing) and isMainModule:
var counter = 0 var counter = 0
var s: TServer var s: Server
open(s, Port(0)) open(s, Port(0))
echo("httpserver running on port ", s.port) echo("httpserver running on port ", s.port)
while true: while true:

View file

@ -68,7 +68,7 @@ type
jsonArrayStart, ## start of an array: the ``[`` token jsonArrayStart, ## start of an array: the ``[`` token
jsonArrayEnd ## start of an array: the ``]`` token jsonArrayEnd ## start of an array: the ``]`` token
TTokKind = enum # must be synchronized with TJsonEventKind! TokKind = enum # must be synchronized with TJsonEventKind!
tkError, tkError,
tkEof, tkEof,
tkString, tkString,
@ -103,14 +103,14 @@ type
JsonParser* = object of BaseLexer ## the parser object. JsonParser* = object of BaseLexer ## the parser object.
a: string a: string
tok: TTokKind tok: TokKind
kind: JsonEventKind kind: JsonEventKind
err: JsonError err: JsonError
state: seq[ParserState] state: seq[ParserState]
filename: string filename: string
{.deprecated: [TJsonEventKind: JsonEventKind, TJsonError: JsonError, {.deprecated: [TJsonEventKind: JsonEventKind, TJsonError: JsonError,
TJsonParser: JsonParser].} TJsonParser: JsonParser, TTokKind: TokKind].}
const const
errorMessages: array [JsonError, string] = [ errorMessages: array [JsonError, string] = [
@ -126,7 +126,7 @@ const
"EOF expected", "EOF expected",
"expression expected" "expression expected"
] ]
tokToStr: array [TTokKind, string] = [ tokToStr: array [TokKind, string] = [
"invalid token", "invalid token",
"EOF", "EOF",
"string literal", "string literal",
@ -203,7 +203,7 @@ proc handleHexChar(c: char, x: var int): bool =
of 'A'..'F': x = (x shl 4) or (ord(c) - ord('A') + 10) of 'A'..'F': x = (x shl 4) or (ord(c) - ord('A') + 10)
else: result = false # error else: result = false # error
proc parseString(my: var JsonParser): TTokKind = proc parseString(my: var JsonParser): TokKind =
result = tkString result = tkString
var pos = my.bufpos + 1 var pos = my.bufpos + 1
var buf = my.buf var buf = my.buf
@ -359,7 +359,7 @@ proc parseName(my: var JsonParser) =
inc(pos) inc(pos)
my.bufpos = pos my.bufpos = pos
proc getTok(my: var JsonParser): TTokKind = proc getTok(my: var JsonParser): TokKind =
setLen(my.a, 0) setLen(my.a, 0)
skip(my) # skip whitespace, comments skip(my) # skip whitespace, comments
case my.buf[my.bufpos] case my.buf[my.bufpos]
@ -734,7 +734,7 @@ proc `==`* (a,b: JsonNode): bool =
of JObject: of JObject:
a.fields == b.fields a.fields == b.fields
proc hash* (n:JsonNode): THash = proc hash* (n:JsonNode): Hash =
## Compute the hash for a JSON node ## Compute the hash for a JSON node
case n.kind case n.kind
of JArray: of JArray:
@ -1016,7 +1016,7 @@ iterator mpairs*(node: var JsonNode): var tuple[key: string, val: JsonNode] =
for keyVal in mitems(node.fields): for keyVal in mitems(node.fields):
yield keyVal yield keyVal
proc eat(p: var JsonParser, tok: TTokKind) = proc eat(p: var JsonParser, tok: TokKind) =
if p.tok == tok: discard getTok(p) if p.tok == tok: discard getTok(p)
else: raiseParseErr(p, tokToStr[tok]) else: raiseParseErr(p, tokToStr[tok])
@ -1091,8 +1091,10 @@ when not defined(js):
else: else:
from math import `mod` from math import `mod`
type type
TJSObject = object JSObject = object
proc parseNativeJson(x: cstring): TJSObject {.importc: "JSON.parse".} {.deprecated: [TJSObject: JSObject].}
proc parseNativeJson(x: cstring): JSObject {.importc: "JSON.parse".}
proc getVarType(x): JsonNodeKind = proc getVarType(x): JsonNodeKind =
result = JNull result = JNull
@ -1111,25 +1113,25 @@ else:
of "[object String]": return JString of "[object String]": return JString
else: assert false else: assert false
proc len(x: TJSObject): int = proc len(x: JSObject): int =
assert x.getVarType == JArray assert x.getVarType == JArray
asm """ asm """
return `x`.length; return `x`.length;
""" """
proc `[]`(x: TJSObject, y: string): TJSObject = proc `[]`(x: JSObject, y: string): JSObject =
assert x.getVarType == JObject assert x.getVarType == JObject
asm """ asm """
return `x`[`y`]; return `x`[`y`];
""" """
proc `[]`(x: TJSObject, y: int): TJSObject = proc `[]`(x: JSObject, y: int): JSObject =
assert x.getVarType == JArray assert x.getVarType == JArray
asm """ asm """
return `x`[`y`]; return `x`[`y`];
""" """
proc convertObject(x: TJSObject): JsonNode = proc convertObject(x: JSObject): JsonNode =
case getVarType(x) case getVarType(x)
of JArray: of JArray:
result = newJArray() result = newJArray()
@ -1141,7 +1143,7 @@ else:
if (`x`.hasOwnProperty(property)) { if (`x`.hasOwnProperty(property)) {
""" """
var nimProperty: cstring var nimProperty: cstring
var nimValue: TJSObject var nimValue: JSObject
asm "`nimProperty` = property; `nimValue` = `x`[property];" asm "`nimProperty` = property; `nimValue` = `x`[property];"
result[$nimProperty] = nimValue.convertObject() result[$nimProperty] = nimValue.convertObject()
asm "}}" asm "}}"

View file

@ -39,7 +39,7 @@ type
{.deprecated: [TBaseLexer: BaseLexer].} {.deprecated: [TBaseLexer: BaseLexer].}
proc open*(L: var BaseLexer, input: Stream, bufLen: int = 8192) proc open*(L: var BaseLexer, input: Stream, bufLen: int = 8192)
## inits the TBaseLexer with a stream to read from ## inits the BaseLexer with a stream to read from
proc close*(L: var BaseLexer) proc close*(L: var BaseLexer)
## closes the base lexer. This closes `L`'s associated stream too. ## closes the base lexer. This closes `L`'s associated stream too.

View file

@ -19,7 +19,12 @@
## ============ ======================= ## ============ =======================
## $date Current date ## $date Current date
## $time Current time ## $time Current time
## $datetime $dateT$time
## $app ``os.getAppFilename()`` ## $app ``os.getAppFilename()``
## $appname base name of $app
## $appdir directory name of $app
## $levelid first letter of log level
## $levelname log level name
## ============ ======================= ## ============ =======================
## ##
## ##
@ -59,8 +64,8 @@ const
"DEBUG", "DEBUG", "INFO", "WARN", "ERROR", "FATAL", "NONE" "DEBUG", "DEBUG", "INFO", "WARN", "ERROR", "FATAL", "NONE"
] ]
defaultFmtStr* = "" ## default string between log level and message per logger defaultFmtStr* = "$levelname " ## default format string
verboseFmtStr* = "$date $time " verboseFmtStr* = "$levelid, [$datetime] -- $appname: "
type type
Logger* = ref object of RootObj ## abstract logger; the base type of all loggers Logger* = ref object of RootObj ## abstract logger; the base type of all loggers
@ -82,16 +87,16 @@ type
baseName: string # initial filename baseName: string # initial filename
baseMode: FileMode # initial file mode baseMode: FileMode # initial file mode
logFiles: int # how many log files already created, e.g. basename.1, basename.2... logFiles: int # how many log files already created, e.g. basename.1, basename.2...
bufSize: int # size of output buffer (-1: use system defaults, 0: unbuffered, >0: fixed buffer size)
{.deprecated: [TLevel: Level, PLogger: Logger, PConsoleLogger: ConsoleLogger, {.deprecated: [TLevel: Level, PLogger: Logger, PConsoleLogger: ConsoleLogger,
PFileLogger: FileLogger, PRollingFileLogger: RollingFileLogger].} PFileLogger: FileLogger, PRollingFileLogger: RollingFileLogger].}
proc substituteLog(frmt: string): string = proc substituteLog(frmt: string, level: Level, args: varargs[string, `$`]): string =
## converts $date to the current date var msgLen = 0
## converts $time to the current time for arg in args:
## converts $app to getAppFilename() msgLen += arg.len
## converts result = newStringOfCap(frmt.len + msgLen + 20)
result = newStringOfCap(frmt.len + 20)
var i = 0 var i = 0
while i < frmt.len: while i < frmt.len:
if frmt[i] != '$': if frmt[i] != '$':
@ -107,32 +112,32 @@ proc substituteLog(frmt: string): string =
case v case v
of "date": result.add(getDateStr()) of "date": result.add(getDateStr())
of "time": result.add(getClockStr()) of "time": result.add(getClockStr())
of "datetime": result.add(getDateStr() & "T" & getClockStr())
of "app": result.add(app) of "app": result.add(app)
of "appdir": result.add(app.splitFile.dir) of "appdir": result.add(app.splitFile.dir)
of "appname": result.add(app.splitFile.name) of "appname": result.add(app.splitFile.name)
of "levelid": result.add(LevelNames[level][0])
of "levelname": result.add(LevelNames[level])
else: discard else: discard
for arg in args:
result.add(arg)
method log*(logger: Logger, level: Level, method log*(logger: Logger, level: Level, args: varargs[string, `$`]) {.
frmt: string, args: varargs[string, `$`]) {.
raises: [Exception], raises: [Exception],
tags: [TimeEffect, WriteIOEffect, ReadIOEffect].} = tags: [TimeEffect, WriteIOEffect, ReadIOEffect].} =
## Override this method in custom loggers. Default implementation does ## Override this method in custom loggers. Default implementation does
## nothing. ## nothing.
discard discard
method log*(logger: ConsoleLogger, level: Level, method log*(logger: ConsoleLogger, level: Level, args: varargs[string, `$`]) =
frmt: string, args: varargs[string, `$`]) =
## Logs to the console using ``logger`` only. ## Logs to the console using ``logger`` only.
if level >= logger.levelThreshold: if level >= logger.levelThreshold:
writeln(stdout, LevelNames[level], " ", substituteLog(logger.fmtStr), writeln(stdout, substituteLog(logger.fmtStr, level, args))
frmt % args)
method log*(logger: FileLogger, level: Level, method log*(logger: FileLogger, level: Level, args: varargs[string, `$`]) =
frmt: string, args: varargs[string, `$`]) =
## Logs to a file using ``logger`` only. ## Logs to a file using ``logger`` only.
if level >= logger.levelThreshold: if level >= logger.levelThreshold:
writeln(logger.f, LevelNames[level], " ", writeln(logger.f, substituteLog(logger.fmtStr, level, args))
substituteLog(logger.fmtStr), frmt % args)
proc defaultFilename*(): string = proc defaultFilename*(): string =
## Returns the default filename for a logger. ## Returns the default filename for a logger.
@ -148,11 +153,14 @@ proc newConsoleLogger*(levelThreshold = lvlAll, fmtStr = defaultFmtStr): Console
proc newFileLogger*(filename = defaultFilename(), proc newFileLogger*(filename = defaultFilename(),
mode: FileMode = fmAppend, mode: FileMode = fmAppend,
levelThreshold = lvlAll, levelThreshold = lvlAll,
fmtStr = defaultFmtStr): FileLogger = fmtStr = defaultFmtStr,
bufSize: int = -1): FileLogger =
## Creates a new file logger. This logger logs to a file. ## Creates a new file logger. This logger logs to a file.
## Use ``bufSize`` as size of the output buffer when writing the file
## (-1: use system defaults, 0: unbuffered, >0: fixed buffer size).
new(result) new(result)
result.levelThreshold = levelThreshold result.levelThreshold = levelThreshold
result.f = open(filename, mode) result.f = open(filename, mode, bufSize = bufSize)
result.fmtStr = fmtStr result.fmtStr = fmtStr
# ------ # ------
@ -181,14 +189,18 @@ proc newRollingFileLogger*(filename = defaultFilename(),
mode: FileMode = fmReadWrite, mode: FileMode = fmReadWrite,
levelThreshold = lvlAll, levelThreshold = lvlAll,
fmtStr = defaultFmtStr, fmtStr = defaultFmtStr,
maxLines = 1000): RollingFileLogger = maxLines = 1000,
bufSize: int = -1): RollingFileLogger =
## Creates a new rolling file logger. Once a file reaches ``maxLines`` lines ## Creates a new rolling file logger. Once a file reaches ``maxLines`` lines
## a new log file will be started and the old will be renamed. ## a new log file will be started and the old will be renamed.
## Use ``bufSize`` as size of the output buffer when writing the file
## (-1: use system defaults, 0: unbuffered, >0: fixed buffer size).
new(result) new(result)
result.levelThreshold = levelThreshold result.levelThreshold = levelThreshold
result.fmtStr = fmtStr result.fmtStr = fmtStr
result.maxLines = maxLines result.maxLines = maxLines
result.f = open(filename, mode) result.bufSize = bufSize
result.f = open(filename, mode, bufSize=result.bufSize)
result.curLine = 0 result.curLine = 0
result.baseName = filename result.baseName = filename
result.baseMode = mode result.baseMode = mode
@ -206,8 +218,7 @@ proc rotate(logger: RollingFileLogger) =
moveFile(dir / (name & ext & srcSuff), moveFile(dir / (name & ext & srcSuff),
dir / (name & ext & ExtSep & $(i+1))) dir / (name & ext & ExtSep & $(i+1)))
method log*(logger: RollingFileLogger, level: Level, method log*(logger: RollingFileLogger, level: Level, args: varargs[string, `$`]) =
frmt: string, args: varargs[string, `$`]) =
## Logs to a file using rolling ``logger`` only. ## Logs to a file using rolling ``logger`` only.
if level >= logger.levelThreshold: if level >= logger.levelThreshold:
if logger.curLine >= logger.maxLines: if logger.curLine >= logger.maxLines:
@ -215,9 +226,9 @@ method log*(logger: RollingFileLogger, level: Level,
rotate(logger) rotate(logger)
logger.logFiles.inc logger.logFiles.inc
logger.curLine = 0 logger.curLine = 0
logger.f = open(logger.baseName, logger.baseMode) logger.f = open(logger.baseName, logger.baseMode, bufSize = logger.bufSize)
writeln(logger.f, LevelNames[level], " ",substituteLog(logger.fmtStr), frmt % args) writeln(logger.f, substituteLog(logger.fmtStr, level, args))
logger.curLine.inc logger.curLine.inc
# -------- # --------
@ -225,39 +236,39 @@ method log*(logger: RollingFileLogger, level: Level,
var level {.threadvar.}: Level ## global log filter var level {.threadvar.}: Level ## global log filter
var handlers {.threadvar.}: seq[Logger] ## handlers with their own log levels var handlers {.threadvar.}: seq[Logger] ## handlers with their own log levels
proc logLoop(level: Level, frmt: string, args: varargs[string, `$`]) = proc logLoop(level: Level, args: varargs[string, `$`]) =
for logger in items(handlers): for logger in items(handlers):
if level >= logger.levelThreshold: if level >= logger.levelThreshold:
log(logger, level, frmt, args) log(logger, level, args)
template log*(level: Level, frmt: string, args: varargs[string, `$`]) = template log*(level: Level, args: varargs[string, `$`]) =
## Logs a message to all registered handlers at the given level. ## Logs a message to all registered handlers at the given level.
bind logLoop bind logLoop
bind `%` bind `%`
bind logging.level bind logging.level
if level >= logging.level: if level >= logging.level:
logLoop(level, frmt, args) logLoop(level, args)
template debug*(frmt: string, args: varargs[string, `$`]) = template debug*(args: varargs[string, `$`]) =
## Logs a debug message to all registered handlers. ## Logs a debug message to all registered handlers.
log(lvlDebug, frmt, args) log(lvlDebug, args)
template info*(frmt: string, args: varargs[string, `$`]) = template info*(args: varargs[string, `$`]) =
## Logs an info message to all registered handlers. ## Logs an info message to all registered handlers.
log(lvlInfo, frmt, args) log(lvlInfo, args)
template warn*(frmt: string, args: varargs[string, `$`]) = template warn*(args: varargs[string, `$`]) =
## Logs a warning message to all registered handlers. ## Logs a warning message to all registered handlers.
log(lvlWarn, frmt, args) log(lvlWarn, args)
template error*(frmt: string, args: varargs[string, `$`]) = template error*(args: varargs[string, `$`]) =
## Logs an error message to all registered handlers. ## Logs an error message to all registered handlers.
log(lvlError, frmt, args) log(lvlError, args)
template fatal*(frmt: string, args: varargs[string, `$`]) = template fatal*(args: varargs[string, `$`]) =
## Logs a fatal error message to all registered handlers. ## Logs a fatal error message to all registered handlers.
log(lvlFatal, frmt, args) log(lvlFatal, args)
proc addHandler*(handler: Logger) = proc addHandler*(handler: Logger) =
## Adds ``handler`` to the list of handlers. ## Adds ``handler`` to the list of handlers.
@ -286,6 +297,4 @@ when not defined(testing) and isMainModule:
addHandler(fL) addHandler(fL)
addHandler(rL) addHandler(rL)
for i in 0 .. 25: for i in 0 .. 25:
info("hello" & $i, []) info("hello", i)

View file

@ -17,13 +17,13 @@
## .. code-block:: nim ## .. code-block:: nim
## ##
## type ## type
## TA = object ## A = object
## TB = object of TA ## B = object of A
## f: int ## f: int
## ##
## var ## var
## a: ref TA ## a: ref A
## b: ref TB ## b: ref B
## ##
## new(b) ## new(b)
## a = b ## a = b
@ -36,7 +36,7 @@ import streams, typeinfo, json, intsets, tables
proc ptrToInt(x: pointer): int {.inline.} = proc ptrToInt(x: pointer): int {.inline.} =
result = cast[int](x) # don't skip alignment result = cast[int](x) # don't skip alignment
proc storeAny(s: Stream, a: TAny, stored: var IntSet) = proc storeAny(s: Stream, a: Any, stored: var IntSet) =
case a.kind case a.kind
of akNone: assert false of akNone: assert false
of akBool: s.write($getBool(a)) of akBool: s.write($getBool(a))
@ -96,7 +96,7 @@ proc storeAny(s: Stream, a: TAny, stored: var IntSet) =
of akInt..akInt64, akUInt..akUInt64: s.write($getBiggestInt(a)) of akInt..akInt64, akUInt..akUInt64: s.write($getBiggestInt(a))
of akFloat..akFloat128: s.write($getBiggestFloat(a)) of akFloat..akFloat128: s.write($getBiggestFloat(a))
proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) = proc loadAny(p: var JsonParser, a: Any, t: var Table[BiggestInt, pointer]) =
case a.kind case a.kind
of akNone: assert false of akNone: assert false
of akBool: of akBool:
@ -222,7 +222,7 @@ proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) =
raiseParseErr(p, "float expected") raiseParseErr(p, "float expected")
of akRange: loadAny(p, a.skipRange, t) of akRange: loadAny(p, a.skipRange, t)
proc loadAny(s: Stream, a: TAny, t: var Table[BiggestInt, pointer]) = proc loadAny(s: Stream, a: Any, t: var Table[BiggestInt, pointer]) =
var p: JsonParser var p: JsonParser
open(p, s, "unknown file") open(p, s, "unknown file")
next(p) next(p)
@ -278,10 +278,11 @@ when not defined(testing) and isMainModule:
else: else:
nil nil
PNode = ref TNode PNode = ref Node
TNode = object Node = object
next, prev: PNode next, prev: PNode
data: string data: string
{.deprecated: [TNode: Node].}
proc buildList(): PNode = proc buildList(): PNode =
new(result) new(result)
@ -317,14 +318,15 @@ when not defined(testing) and isMainModule:
testit(test7) testit(test7)
type type
TA {.inheritable.} = object A {.inheritable.} = object
TB = object of TA B = object of A
f: int f: int
var var
a: ref TA a: ref A
b: ref TB b: ref B
new(b) new(b)
a = b a = b
echo($$a[]) # produces "{}", not "{f: 0}" echo($$a[]) # produces "{}", not "{f: 0}"

View file

@ -196,7 +196,7 @@ proc open*(filename: string, mode: FileMode = fmRead,
if mappedSize != -1: if mappedSize != -1:
result.size = mappedSize result.size = mappedSize
else: else:
var stat: TStat var stat: Stat
if fstat(result.handle, stat) != -1: if fstat(result.handle, stat) != -1:
# XXX: Hmm, this could be unsafe # XXX: Hmm, this could be unsafe
# Why is mmap taking int anyway? # Why is mmap taking int anyway?

View file

@ -86,7 +86,7 @@ type
IPv6, ## IPv6 address IPv6, ## IPv6 address
IPv4 ## IPv4 address IPv4 ## IPv4 address
TIpAddress* = object ## stores an arbitrary IP address IpAddress* = object ## stores an arbitrary IP address
case family*: IpAddressFamily ## the type of the IP address (IPv4 or IPv6) case family*: IpAddressFamily ## the type of the IP address (IPv4 or IPv6)
of IpAddressFamily.IPv6: of IpAddressFamily.IPv6:
address_v6*: array[0..15, uint8] ## Contains the IP address in bytes in address_v6*: array[0..15, uint8] ## Contains the IP address in bytes in
@ -94,9 +94,10 @@ type
of IpAddressFamily.IPv4: of IpAddressFamily.IPv4:
address_v4*: array[0..3, uint8] ## Contains the IP address in bytes in address_v4*: array[0..3, uint8] ## Contains the IP address in bytes in
## case of IPv4 ## case of IPv4
{.deprecated: [TIpAddress: IpAddress].}
proc isIpAddress*(address_str: string): bool {.tags: [].} proc isIpAddress*(address_str: string): bool {.tags: [].}
proc parseIpAddress*(address_str: string): TIpAddress proc parseIpAddress*(address_str: string): IpAddress
proc isDisconnectionError*(flags: set[SocketFlag], proc isDisconnectionError*(flags: set[SocketFlag],
lastError: OSErrorCode): bool = lastError: OSErrorCode): bool =
@ -118,13 +119,13 @@ proc toOSFlags*(socketFlags: set[SocketFlag]): cint =
result = result or MSG_PEEK result = result or MSG_PEEK
of SocketFlag.SafeDisconn: continue of SocketFlag.SafeDisconn: continue
proc newSocket(fd: SocketHandle, isBuff: bool): Socket = proc newSocket*(fd: SocketHandle, buffered = true): Socket =
## Creates a new socket as specified by the params. ## Creates a new socket as specified by the params.
assert fd != osInvalidSocket assert fd != osInvalidSocket
new(result) new(result)
result.fd = fd result.fd = fd
result.isBuffered = isBuff result.isBuffered = buffered
if isBuff: if buffered:
result.currPos = 0 result.currPos = 0
proc newSocket*(domain, typ, protocol: cint, buffered = true): Socket = proc newSocket*(domain, typ, protocol: cint, buffered = true): Socket =
@ -395,7 +396,7 @@ proc acceptAddr*(server: Socket, client: var Socket, address: var string,
when false: #defined(ssl): when false: #defined(ssl):
proc acceptAddrSSL*(server: Socket, client: var Socket, proc acceptAddrSSL*(server: Socket, client: var Socket,
address: var string): TSSLAcceptResult {. address: var string): SSLAcceptResult {.
tags: [ReadIOEffect].} = tags: [ReadIOEffect].} =
## This procedure should only be used for non-blocking **SSL** sockets. ## This procedure should only be used for non-blocking **SSL** sockets.
## It will immediately return with one of the following values: ## It will immediately return with one of the following values:
@ -992,39 +993,39 @@ proc isSsl*(socket: Socket): bool =
proc getFd*(socket: Socket): SocketHandle = return socket.fd proc getFd*(socket: Socket): SocketHandle = return socket.fd
## Returns the socket's file descriptor ## Returns the socket's file descriptor
proc IPv4_any*(): TIpAddress = proc IPv4_any*(): IpAddress =
## Returns the IPv4 any address, which can be used to listen on all available ## Returns the IPv4 any address, which can be used to listen on all available
## network adapters ## network adapters
result = TIpAddress( result = IpAddress(
family: IpAddressFamily.IPv4, family: IpAddressFamily.IPv4,
address_v4: [0'u8, 0, 0, 0]) address_v4: [0'u8, 0, 0, 0])
proc IPv4_loopback*(): TIpAddress = proc IPv4_loopback*(): IpAddress =
## Returns the IPv4 loopback address (127.0.0.1) ## Returns the IPv4 loopback address (127.0.0.1)
result = TIpAddress( result = IpAddress(
family: IpAddressFamily.IPv4, family: IpAddressFamily.IPv4,
address_v4: [127'u8, 0, 0, 1]) address_v4: [127'u8, 0, 0, 1])
proc IPv4_broadcast*(): TIpAddress = proc IPv4_broadcast*(): IpAddress =
## Returns the IPv4 broadcast address (255.255.255.255) ## Returns the IPv4 broadcast address (255.255.255.255)
result = TIpAddress( result = IpAddress(
family: IpAddressFamily.IPv4, family: IpAddressFamily.IPv4,
address_v4: [255'u8, 255, 255, 255]) address_v4: [255'u8, 255, 255, 255])
proc IPv6_any*(): TIpAddress = proc IPv6_any*(): IpAddress =
## Returns the IPv6 any address (::0), which can be used ## Returns the IPv6 any address (::0), which can be used
## to listen on all available network adapters ## to listen on all available network adapters
result = TIpAddress( result = IpAddress(
family: IpAddressFamily.IPv6, family: IpAddressFamily.IPv6,
address_v6: [0'u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) address_v6: [0'u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
proc IPv6_loopback*(): TIpAddress = proc IPv6_loopback*(): IpAddress =
## Returns the IPv6 loopback address (::1) ## Returns the IPv6 loopback address (::1)
result = TIpAddress( result = IpAddress(
family: IpAddressFamily.IPv6, family: IpAddressFamily.IPv6,
address_v6: [0'u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]) address_v6: [0'u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1])
proc `==`*(lhs, rhs: TIpAddress): bool = proc `==`*(lhs, rhs: IpAddress): bool =
## Compares two IpAddresses for Equality. Returns two if the addresses are equal ## Compares two IpAddresses for Equality. Returns two if the addresses are equal
if lhs.family != rhs.family: return false if lhs.family != rhs.family: return false
if lhs.family == IpAddressFamily.IPv4: if lhs.family == IpAddressFamily.IPv4:
@ -1035,8 +1036,8 @@ proc `==`*(lhs, rhs: TIpAddress): bool =
if lhs.address_v6[i] != rhs.address_v6[i]: return false if lhs.address_v6[i] != rhs.address_v6[i]: return false
return true return true
proc `$`*(address: TIpAddress): string = proc `$`*(address: IpAddress): string =
## Converts an TIpAddress into the textual representation ## Converts an IpAddress into the textual representation
result = "" result = ""
case address.family case address.family
of IpAddressFamily.IPv4: of IpAddressFamily.IPv4:
@ -1095,7 +1096,7 @@ proc `$`*(address: TIpAddress): string =
mask = mask shr 4 mask = mask shr 4
printedLastGroup = true printedLastGroup = true
proc parseIPv4Address(address_str: string): TIpAddress = proc parseIPv4Address(address_str: string): IpAddress =
## Parses IPv4 adresses ## Parses IPv4 adresses
## Raises EInvalidValue on errors ## Raises EInvalidValue on errors
var var
@ -1129,7 +1130,7 @@ proc parseIPv4Address(address_str: string): TIpAddress =
raise newException(ValueError, "Invalid IP Address") raise newException(ValueError, "Invalid IP Address")
result.address_v4[byteCount] = cast[uint8](currentByte) result.address_v4[byteCount] = cast[uint8](currentByte)
proc parseIPv6Address(address_str: string): TIpAddress = proc parseIPv6Address(address_str: string): IpAddress =
## Parses IPv6 adresses ## Parses IPv6 adresses
## Raises EInvalidValue on errors ## Raises EInvalidValue on errors
result.family = IpAddressFamily.IPv6 result.family = IpAddressFamily.IPv6
@ -1250,7 +1251,7 @@ proc parseIPv6Address(address_str: string): TIpAddress =
raise newException(ValueError, raise newException(ValueError,
"Invalid IP Address. The address consists of too many groups") "Invalid IP Address. The address consists of too many groups")
proc parseIpAddress(address_str: string): TIpAddress = proc parseIpAddress(address_str: string): IpAddress =
## Parses an IP address ## Parses an IP address
## Raises EInvalidValue on error ## Raises EInvalidValue on error
if address_str == nil: if address_str == nil:

View file

@ -26,17 +26,19 @@ const
withThreads = compileOption("threads") withThreads = compileOption("threads")
tickCountCorrection = 50_000 tickCountCorrection = 50_000
when not declared(system.TStackTrace): when not declared(system.StackTrace):
type TStackTrace = array [0..20, cstring] type StackTrace = array [0..20, cstring]
{.deprecated: [TStackTrace: StackTrace].}
# We use a simple hash table of bounded size to keep track of the stack traces: # We use a simple hash table of bounded size to keep track of the stack traces:
type type
TProfileEntry = object ProfileEntry = object
total: int total: int
st: TStackTrace st: StackTrace
TProfileData = array [0..64*1024-1, ptr TProfileEntry] ProfileData = array [0..64*1024-1, ptr ProfileEntry]
{.deprecated: [TProfileEntry: ProfileEntry, TProfileData: ProfileData].}
proc `==`(a, b: TStackTrace): bool = proc `==`(a, b: StackTrace): bool =
for i in 0 .. high(a): for i in 0 .. high(a):
if a[i] != b[i]: return false if a[i] != b[i]: return false
result = true result = true
@ -44,13 +46,13 @@ proc `==`(a, b: TStackTrace): bool =
# XXX extract this data structure; it is generally useful ;-) # XXX extract this data structure; it is generally useful ;-)
# However a chain length of over 3000 is suspicious... # However a chain length of over 3000 is suspicious...
var var
profileData: TProfileData profileData: ProfileData
emptySlots = profileData.len * 3 div 2 emptySlots = profileData.len * 3 div 2
maxChainLen = 0 maxChainLen = 0
totalCalls = 0 totalCalls = 0
when not defined(memProfiler): when not defined(memProfiler):
var interval: TNanos = 5_000_000 - tickCountCorrection # 5ms var interval: Nanos = 5_000_000 - tickCountCorrection # 5ms
proc setSamplingFrequency*(intervalInUs: int) = proc setSamplingFrequency*(intervalInUs: int) =
## set this to change the sampling frequency. Default value is 5ms. ## set this to change the sampling frequency. Default value is 5ms.
@ -62,11 +64,11 @@ when not defined(memProfiler):
when withThreads: when withThreads:
import locks import locks
var var
profilingLock: TLock profilingLock: Lock
initLock profilingLock initLock profilingLock
proc hookAux(st: TStackTrace, costs: int) = proc hookAux(st: StackTrace, costs: int) =
# this is quite performance sensitive! # this is quite performance sensitive!
when withThreads: acquire profilingLock when withThreads: acquire profilingLock
inc totalCalls inc totalCalls
@ -94,8 +96,8 @@ proc hookAux(st: TStackTrace, costs: int) =
var chain = 0 var chain = 0
while true: while true:
if profileData[h] == nil: if profileData[h] == nil:
profileData[h] = cast[ptr TProfileEntry]( profileData[h] = cast[ptr ProfileEntry](
allocShared0(sizeof(TProfileEntry))) allocShared0(sizeof(ProfileEntry)))
profileData[h].total = costs profileData[h].total = costs
profileData[h].st = st profileData[h].st = st
dec emptySlots dec emptySlots
@ -115,7 +117,7 @@ when defined(memProfiler):
var var
gTicker {.threadvar.}: int gTicker {.threadvar.}: int
proc hook(st: TStackTrace, size: int) {.nimcall.} = proc hook(st: StackTrace, size: int) {.nimcall.} =
if gTicker == 0: if gTicker == 0:
gTicker = -1 gTicker = -1
when defined(ignoreAllocationSize): when defined(ignoreAllocationSize):
@ -127,26 +129,26 @@ when defined(memProfiler):
else: else:
var var
t0 {.threadvar.}: TTicks t0 {.threadvar.}: Ticks
proc hook(st: TStackTrace) {.nimcall.} = proc hook(st: StackTrace) {.nimcall.} =
if interval == 0: if interval == 0:
hookAux(st, 1) hookAux(st, 1)
elif int64(t0) == 0 or getTicks() - t0 > interval: elif int64(t0) == 0 or getTicks() - t0 > interval:
hookAux(st, 1) hookAux(st, 1)
t0 = getTicks() t0 = getTicks()
proc getTotal(x: ptr TProfileEntry): int = proc getTotal(x: ptr ProfileEntry): int =
result = if isNil(x): 0 else: x.total result = if isNil(x): 0 else: x.total
proc cmpEntries(a, b: ptr TProfileEntry): int = proc cmpEntries(a, b: ptr ProfileEntry): int =
result = b.getTotal - a.getTotal result = b.getTotal - a.getTotal
proc `//`(a, b: int): string = proc `//`(a, b: int): string =
result = format("$1/$2 = $3%", a, b, formatFloat(a / b * 100.0, ffDefault, 2)) result = format("$1/$2 = $3%", a, b, formatFloat(a / b * 100.0, ffDefault, 2))
proc writeProfile() {.noconv.} = proc writeProfile() {.noconv.} =
when declared(system.TStackTrace): when declared(system.StackTrace):
system.profilerHook = nil system.profilerHook = nil
const filename = "profile_results.txt" const filename = "profile_results.txt"
echo "writing " & filename & "..." echo "writing " & filename & "..."
@ -161,7 +163,7 @@ proc writeProfile() {.noconv.} =
var perProc = initCountTable[string]() var perProc = initCountTable[string]()
for i in 0..entries-1: for i in 0..entries-1:
var dups = initSet[string]() var dups = initSet[string]()
for ii in 0..high(TStackTrace): for ii in 0..high(StackTrace):
let procname = profileData[i].st[ii] let procname = profileData[i].st[ii]
if isNil(procname): break if isNil(procname): break
let p = $procname let p = $procname
@ -176,7 +178,7 @@ proc writeProfile() {.noconv.} =
writeln(f, "Entry: ", i+1, "/", entries, " Calls: ", writeln(f, "Entry: ", i+1, "/", entries, " Calls: ",
profileData[i].total // totalCalls, " [sum: ", sum, "; ", profileData[i].total // totalCalls, " [sum: ", sum, "; ",
sum // totalCalls, "]") sum // totalCalls, "]")
for ii in 0..high(TStackTrace): for ii in 0..high(StackTrace):
let procname = profileData[i].st[ii] let procname = profileData[i].st[ii]
if isNil(procname): break if isNil(procname): break
writeln(f, " ", procname, " ", perProc[$procname] // totalCalls) writeln(f, " ", procname, " ", perProc[$procname] // totalCalls)
@ -189,16 +191,16 @@ var
disabled: int disabled: int
proc disableProfiling*() = proc disableProfiling*() =
when declared(system.TStackTrace): when declared(system.StackTrace):
atomicDec disabled atomicDec disabled
system.profilerHook = nil system.profilerHook = nil
proc enableProfiling*() = proc enableProfiling*() =
when declared(system.TStackTrace): when declared(system.StackTrace):
if atomicInc(disabled) >= 0: if atomicInc(disabled) >= 0:
system.profilerHook = hook system.profilerHook = hook
when declared(system.TStackTrace): when declared(system.StackTrace):
system.profilerHook = hook system.profilerHook = hook
addQuitProc(writeProfile) addQuitProc(writeProfile)

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