Merge branch 'devel'

This commit is contained in:
Dominik Picheta
2015-04-30 16:09:27 +01:00
669 changed files with 28587 additions and 20697 deletions

View File

@@ -21,7 +21,7 @@ type
## is performed. Deprecated, do not use anymore!
AquireEffect* {.deprecated.} = object of LockEffect ## \
## effect that denotes that some lock is
## aquired. Deprecated, do not use anymore!
## acquired. Deprecated, do not use anymore!
ReleaseEffect* {.deprecated.} = object of LockEffect ## \
## effect that denotes that some lock is
## released. Deprecated, do not use anymore!

View File

@@ -1,7 +1,7 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2013 Andreas Rumpf
# (c) Copyright 2015 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@@ -15,7 +15,7 @@ include "system/inclrtl"
## .. include:: ../doc/astspec.txt
type
TNimrodNodeKind* = enum
NimNodeKind* = enum
nnkNone, nnkEmpty, nnkIdent, nnkSym,
nnkType, nnkCharLit, nnkIntLit, nnkInt8Lit,
nnkInt16Lit, nnkInt32Lit, nnkInt64Lit, nnkUIntLit, nnkUInt8Lit,
@@ -23,9 +23,9 @@ type
nnkFloat32Lit, nnkFloat64Lit, nnkFloat128Lit, nnkStrLit, nnkRStrLit,
nnkTripleStrLit, nnkNilLit, nnkMetaNode, nnkDotCall,
nnkCommand, nnkCall, nnkCallStrLit, nnkInfix,
nnkPrefix, nnkPostfix, nnkHiddenCallConv,
nnkPrefix, nnkPostfix, nnkHiddenCallConv,
nnkExprEqExpr,
nnkExprColonExpr, nnkIdentDefs, nnkVarTuple,
nnkExprColonExpr, nnkIdentDefs, nnkVarTuple,
nnkPar, nnkObjConstr, nnkCurly, nnkCurlyExpr,
nnkBracket, nnkBracketExpr, nnkPragmaExpr, nnkRange,
nnkDotExpr, nnkCheckedFieldExpr, nnkDerefExpr, nnkIfExpr,
@@ -48,7 +48,7 @@ type
nnkConstDef, nnkTypeDef,
nnkYieldStmt, nnkDefer, nnkTryStmt, nnkFinally, nnkRaiseStmt,
nnkReturnStmt, nnkBreakStmt, nnkContinueStmt, nnkBlockStmt, nnkStaticStmt,
nnkDiscardStmt, nnkStmtList,
nnkDiscardStmt, nnkStmtList,
nnkImportStmt,
nnkImportExceptStmt,
nnkExportStmt,
@@ -60,159 +60,184 @@ type
nnkStmtListType, nnkBlockType,
nnkWith, nnkWithout,
nnkTypeOfExpr, nnkObjectTy,
nnkTupleTy, nnkTypeClassTy, nnkStaticTy,
nnkTupleTy, nnkTupleClassTy, nnkTypeClassTy, nnkStaticTy,
nnkRecList, nnkRecCase, nnkRecWhen,
nnkRefTy, nnkPtrTy, nnkVarTy,
nnkConstTy, nnkMutableTy,
nnkDistinctTy,
nnkProcTy,
nnkProcTy,
nnkIteratorTy, # iterator type
nnkSharedTy, # 'shared T'
nnkEnumTy,
nnkEnumFieldDef,
nnkArglist, nnkPattern
nnkReturnToken
TNimNodeKinds* = set[TNimrodNodeKind]
TNimrodTypeKind* = enum
NimNodeKinds* = set[NimNodeKind]
NimTypeKind* = enum
ntyNone, ntyBool, ntyChar, ntyEmpty,
ntyArrayConstr, ntyNil, ntyExpr, ntyStmt,
ntyTypeDesc, ntyGenericInvokation, ntyGenericBody, ntyGenericInst,
ntyTypeDesc, ntyGenericInvocation, ntyGenericBody, ntyGenericInst,
ntyGenericParam, ntyDistinct, ntyEnum, ntyOrdinal,
ntyArray, ntyObject, ntyTuple, ntySet,
ntyRange, ntyPtr, ntyRef, ntyVar,
ntySequence, ntyProc, ntyPointer, ntyOpenArray,
ntyString, ntyCString, ntyForward, ntyInt,
ntyInt8, ntyInt16, ntyInt32, ntyInt64,
ntyFloat, ntyFloat32, ntyFloat64, ntyFloat128
TNimTypeKinds* = set[TNimrodTypeKind]
TNimrodSymKind* = enum
ntyFloat, ntyFloat32, ntyFloat64, ntyFloat128,
ntyUInt, ntyUInt8, ntyUInt16, ntyUInt32, ntyUInt64,
ntyBigNum,
ntyConst, ntyMutable, ntyVarargs,
ntyIter,
ntyError,
ntyBuiltinTypeClass, ntyConcept, ntyConceptInst, ntyComposite,
ntyAnd, ntyOr, ntyNot
TNimTypeKinds* {.deprecated.} = set[NimTypeKind]
NimSymKind* = enum
nskUnknown, nskConditional, nskDynLib, nskParam,
nskGenericParam, nskTemp, nskModule, nskType, nskVar, nskLet,
nskGenericParam, nskTemp, nskModule, nskType, nskVar, nskLet,
nskConst, nskResult,
nskProc, nskMethod, nskIterator, nskClosureIterator,
nskConverter, nskMacro, nskTemplate, nskField,
nskEnumField, nskForVar, nskLabel,
nskStub
TNimSymKinds* = set[TNimrodSymKind]
TNimSymKinds* {.deprecated.} = set[NimSymKind]
type
TNimrodIdent* = object of RootObj
## represents a Nimrod identifier in the AST
NimIdent* = object of RootObj
## represents a Nim identifier in the AST
TNimrodSymbol {.final.} = object # hidden
PNimrodSymbol* {.compilerproc.} = ref TNimrodSymbol
## represents a Nimrod *symbol* in the compiler; a *symbol* is a looked-up
NimSymObj = object # hidden
NimSym* = ref NimSymObj
## represents a Nim *symbol* in the compiler; a *symbol* is a looked-up
## *ident*.
{.deprecated: [TNimrodNodeKind: NimNodeKind, TNimNodeKinds: NimNodeKinds,
TNimrodTypeKind: NimTypeKind, TNimrodSymKind: NimSymKind,
TNimrodIdent: NimIdent, PNimrodSymbol: NimSym].}
const
nnkLiterals* = {nnkCharLit..nnkNilLit}
nnkCallKinds* = {nnkCall, nnkInfix, nnkPrefix, nnkPostfix, nnkCommand,
nnkCallStrLit}
proc `[]`*(n: PNimrodNode, i: int): PNimrodNode {.magic: "NChild", noSideEffect.}
proc `[]`*(n: NimNode, i: int): NimNode {.magic: "NChild", noSideEffect.}
## get `n`'s `i`'th child.
proc `[]=`*(n: PNimrodNode, i: int, child: PNimrodNode) {.magic: "NSetChild",
proc `[]=`*(n: NimNode, i: int, child: NimNode) {.magic: "NSetChild",
noSideEffect.}
## set `n`'s `i`'th child to `child`.
proc `!`*(s: string): TNimrodIdent {.magic: "StrToIdent", noSideEffect.}
proc `!`*(s: string): NimIdent {.magic: "StrToIdent", noSideEffect.}
## constructs an identifier from the string `s`
proc `$`*(i: TNimrodIdent): string {.magic: "IdentToStr", noSideEffect.}
## converts a Nimrod identifier to a string
proc `$`*(i: NimIdent): string {.magic: "IdentToStr", noSideEffect.}
## converts a Nim identifier to a string
proc `$`*(s: PNimrodSymbol): string {.magic: "IdentToStr", noSideEffect.}
## converts a Nimrod symbol to a string
proc `$`*(s: NimSym): string {.magic: "IdentToStr", noSideEffect.}
## converts a Nim symbol to a string
proc `==`*(a, b: TNimrodIdent): bool {.magic: "EqIdent", noSideEffect.}
## compares two Nimrod identifiers
proc `==`*(a, b: NimIdent): bool {.magic: "EqIdent", noSideEffect.}
## compares two Nim identifiers
proc `==`*(a, b: PNimrodNode): bool {.magic: "EqNimrodNode", noSideEffect.}
## compares two Nimrod nodes
proc `==`*(a, b: NimNode): bool {.magic: "EqNimrodNode", noSideEffect.}
## compares two Nim nodes
proc len*(n: PNimrodNode): int {.magic: "NLen", noSideEffect.}
proc len*(n: NimNode): int {.magic: "NLen", noSideEffect.}
## returns the number of children of `n`.
proc add*(father, child: PNimrodNode): PNimrodNode {.magic: "NAdd", discardable,
noSideEffect.}
proc add*(father, child: NimNode): NimNode {.magic: "NAdd", discardable,
noSideEffect, locks: 0.}
## Adds the `child` to the `father` node. Returns the
## father node so that calls can be nested.
proc add*(father: PNimrodNode, children: varargs[PNimrodNode]): PNimrodNode {.
magic: "NAddMultiple", discardable, noSideEffect.}
proc add*(father: NimNode, children: varargs[NimNode]): NimNode {.
magic: "NAddMultiple", discardable, noSideEffect, locks: 0.}
## Adds each child of `children` to the `father` node.
## Returns the `father` node so that calls can be nested.
proc del*(father: PNimrodNode, idx = 0, n = 1) {.magic: "NDel", noSideEffect.}
proc del*(father: NimNode, idx = 0, n = 1) {.magic: "NDel", noSideEffect.}
## deletes `n` children of `father` starting at index `idx`.
proc kind*(n: PNimrodNode): TNimrodNodeKind {.magic: "NKind", noSideEffect.}
proc kind*(n: NimNode): NimNodeKind {.magic: "NKind", noSideEffect.}
## returns the `kind` of the node `n`.
proc intVal*(n: PNimrodNode): BiggestInt {.magic: "NIntVal", noSideEffect.}
proc floatVal*(n: PNimrodNode): BiggestFloat {.magic: "NFloatVal", noSideEffect.}
proc symbol*(n: PNimrodNode): PNimrodSymbol {.magic: "NSymbol", noSideEffect.}
proc ident*(n: PNimrodNode): TNimrodIdent {.magic: "NIdent", noSideEffect.}
proc typ*(n: PNimrodNode): typedesc {.magic: "NGetType", noSideEffect.}
proc strVal*(n: PNimrodNode): string {.magic: "NStrVal", noSideEffect.}
proc intVal*(n: NimNode): BiggestInt {.magic: "NIntVal", noSideEffect.}
proc boolVal*(n: NimNode): bool {.compileTime, noSideEffect.} = n.intVal != 0
proc floatVal*(n: NimNode): BiggestFloat {.magic: "NFloatVal", noSideEffect.}
proc symbol*(n: NimNode): NimSym {.magic: "NSymbol", noSideEffect.}
proc ident*(n: NimNode): NimIdent {.magic: "NIdent", noSideEffect.}
proc `intVal=`*(n: PNimrodNode, val: BiggestInt) {.magic: "NSetIntVal", noSideEffect.}
proc `floatVal=`*(n: PNimrodNode, val: BiggestFloat) {.magic: "NSetFloatVal", noSideEffect.}
proc `symbol=`*(n: PNimrodNode, val: PNimrodSymbol) {.magic: "NSetSymbol", noSideEffect.}
proc `ident=`*(n: PNimrodNode, val: TNimrodIdent) {.magic: "NSetIdent", noSideEffect.}
#proc `typ=`*(n: PNimrodNode, typ: typedesc) {.magic: "NSetType".}
proc getType*(n: NimNode): NimNode {.magic: "NGetType", noSideEffect.}
## with 'getType' you can access the node's `type`:idx:. A Nim type is
## mapped to a Nim AST too, so it's slightly confusing but it means the same
## API can be used to traverse types. Recursive types are flattened for you
## so there is no danger of infinite recursions during traversal. To
## resolve recursive types, you have to call 'getType' again. To see what
## kind of type it is, call `typeKind` on getType's result.
proc typeKind*(n: NimNode): NimTypeKind {.magic: "NGetType", noSideEffect.}
## Returns the type kind of the node 'n' that should represent a type, that
## means the node should have been obtained via `getType`.
proc strVal*(n: NimNode): string {.magic: "NStrVal", noSideEffect.}
proc `intVal=`*(n: NimNode, val: BiggestInt) {.magic: "NSetIntVal", noSideEffect.}
proc `floatVal=`*(n: NimNode, val: BiggestFloat) {.magic: "NSetFloatVal", noSideEffect.}
proc `symbol=`*(n: NimNode, val: NimSym) {.magic: "NSetSymbol", noSideEffect.}
proc `ident=`*(n: NimNode, val: NimIdent) {.magic: "NSetIdent", noSideEffect.}
#proc `typ=`*(n: NimNode, typ: typedesc) {.magic: "NSetType".}
# this is not sound! Unfortunately forbidding 'typ=' is not enough, as you
# can easily do:
# let bracket = semCheck([1, 2])
# let fake = semCheck(2.0)
# bracket[0] = fake # constructs a mixed array with ints and floats!
proc `strVal=`*(n: PNimrodNode, val: string) {.magic: "NSetStrVal", noSideEffect.}
proc `strVal=`*(n: NimNode, val: string) {.magic: "NSetStrVal", noSideEffect.}
proc newNimNode*(kind: TNimrodNodeKind,
n: PNimrodNode=nil): PNimrodNode {.magic: "NNewNimNode", noSideEffect.}
proc newNimNode*(kind: NimNodeKind,
n: NimNode=nil): NimNode {.magic: "NNewNimNode", noSideEffect.}
proc copyNimNode*(n: PNimrodNode): PNimrodNode {.magic: "NCopyNimNode", noSideEffect.}
proc copyNimTree*(n: PNimrodNode): PNimrodNode {.magic: "NCopyNimTree", noSideEffect.}
proc copyNimNode*(n: NimNode): NimNode {.magic: "NCopyNimNode", noSideEffect.}
proc copyNimTree*(n: NimNode): NimNode {.magic: "NCopyNimTree", noSideEffect.}
proc error*(msg: string) {.magic: "NError", gcsafe.}
proc error*(msg: string) {.magic: "NError", benign.}
## writes an error message at compile time
proc warning*(msg: string) {.magic: "NWarning", gcsafe.}
proc warning*(msg: string) {.magic: "NWarning", benign.}
## writes a warning message at compile time
proc hint*(msg: string) {.magic: "NHint", gcsafe.}
proc hint*(msg: string) {.magic: "NHint", benign.}
## writes a hint message at compile time
proc newStrLitNode*(s: string): PNimrodNode {.compileTime, noSideEffect.} =
proc newStrLitNode*(s: string): NimNode {.compileTime, noSideEffect.} =
## creates a string literal node from `s`
result = newNimNode(nnkStrLit)
result.strVal = s
proc newIntLitNode*(i: BiggestInt): PNimrodNode {.compileTime.} =
proc newIntLitNode*(i: BiggestInt): NimNode {.compileTime.} =
## creates a int literal node from `i`
result = newNimNode(nnkIntLit)
result.intVal = i
proc newFloatLitNode*(f: BiggestFloat): PNimrodNode {.compileTime.} =
proc newFloatLitNode*(f: BiggestFloat): NimNode {.compileTime.} =
## creates a float literal node from `f`
result = newNimNode(nnkFloatLit)
result.floatVal = f
proc newIdentNode*(i: TNimrodIdent): PNimrodNode {.compileTime.} =
proc newIdentNode*(i: NimIdent): NimNode {.compileTime.} =
## creates an identifier node from `i`
result = newNimNode(nnkIdent)
result.ident = i
proc newIdentNode*(i: string): PNimrodNode {.compileTime.} =
proc newIdentNode*(i: string): NimNode {.compileTime.} =
## creates an identifier node from `i`
result = newNimNode(nnkIdent)
result.ident = !i
type
TBindSymRule* = enum ## specifies how ``bindSym`` behaves
BindSymRule* = enum ## specifies how ``bindSym`` behaves
brClosed, ## only the symbols in current scope are bound
brOpen, ## open wrt overloaded symbols, but may be a single
## symbol if not ambiguous (the rules match that of
@@ -221,7 +246,9 @@ type
## if not ambiguous (this cannot be achieved with
## any other means in the language currently)
proc bindSym*(ident: string, rule: TBindSymRule = brClosed): PNimrodNode {.
{.deprecated: [TBindSymRule: BindSymRule].}
proc bindSym*(ident: string, rule: BindSymRule = brClosed): NimNode {.
magic: "NBindSym", noSideEffect.}
## creates a node that binds `ident` to a symbol node. The bound symbol
## may be an overloaded symbol.
@@ -232,48 +259,48 @@ proc bindSym*(ident: string, rule: TBindSymRule = brClosed): PNimrodNode {.
## If ``rule == brForceOpen`` always an ``nkOpenSymChoice`` tree is
## returned even if the symbol is not ambiguous.
proc genSym*(kind: TNimrodSymKind = nskLet; ident = ""): PNimrodNode {.
proc genSym*(kind: NimSymKind = nskLet; ident = ""): NimNode {.
magic: "NGenSym", noSideEffect.}
## generates a fresh symbol that is guaranteed to be unique. The symbol
## needs to occur in a declaration context.
proc callsite*(): PNimrodNode {.magic: "NCallSite", gcsafe.}
## returns the AST if the invokation expression that invoked this macro.
proc callsite*(): NimNode {.magic: "NCallSite", benign.}
## returns the AST of the invocation expression that invoked this macro.
proc toStrLit*(n: PNimrodNode): PNimrodNode {.compileTime.} =
## converts the AST `n` to the concrete Nimrod code and wraps that
proc toStrLit*(n: NimNode): NimNode {.compileTime.} =
## converts the AST `n` to the concrete Nim code and wraps that
## in a string literal node
return newStrLitNode(repr(n))
proc lineinfo*(n: PNimrodNode): string {.magic: "NLineInfo", noSideEffect.}
proc lineinfo*(n: NimNode): string {.magic: "NLineInfo", noSideEffect.}
## returns the position the node appears in the original source file
## in the form filename(line, col)
proc internalParseExpr(s: string): PNimrodNode {.
proc internalParseExpr(s: string): NimNode {.
magic: "ParseExprToAst", noSideEffect.}
proc internalParseStmt(s: string): PNimrodNode {.
proc internalParseStmt(s: string): NimNode {.
magic: "ParseStmtToAst", noSideEffect.}
proc internalErrorFlag*(): string {.magic: "NError", noSideEffect.}
## Some builtins set an error flag. This is then turned into a proper
## exception. **Note**: Ordinary application code should not call this.
proc parseExpr*(s: string): PNimrodNode {.noSideEffect, compileTime.} =
proc parseExpr*(s: string): NimNode {.noSideEffect, compileTime.} =
## Compiles the passed string to its AST representation.
## Expects a single expression. Raises ``ValueError`` for parsing errors.
result = internalParseExpr(s)
let x = internalErrorFlag()
if x.len > 0: raise newException(ValueError, x)
proc parseStmt*(s: string): PNimrodNode {.noSideEffect, compileTime.} =
proc parseStmt*(s: string): NimNode {.noSideEffect, compileTime.} =
## Compiles the passed string to its AST representation.
## Expects one or more statements. Raises ``ValueError`` for parsing errors.
result = internalParseStmt(s)
let x = internalErrorFlag()
if x.len > 0: raise newException(ValueError, x)
proc getAst*(macroOrTemplate: expr): PNimrodNode {.magic: "ExpandToAst", noSideEffect.}
proc getAst*(macroOrTemplate: expr): NimNode {.magic: "ExpandToAst", noSideEffect.}
## Obtains the AST nodes returned from a macro or template invocation.
## Example:
##
@@ -282,10 +309,10 @@ proc getAst*(macroOrTemplate: expr): PNimrodNode {.magic: "ExpandToAst", noSideE
## macro FooMacro() =
## var ast = getAst(BarTemplate())
proc quote*(bl: stmt, op = "``"): PNimrodNode {.magic: "QuoteAst", noSideEffect.}
proc quote*(bl: stmt, op = "``"): NimNode {.magic: "QuoteAst", noSideEffect.}
## Quasi-quoting operator.
## Accepts an expression or a block and returns the AST that represents it.
## Within the quoted AST, you are able to interpolate PNimrodNode expressions
## Within the quoted AST, you are able to interpolate NimNode expressions
## from the surrounding scope. If no operator is given, quoting is done using
## backticks. Otherwise, the given operator must be used as a prefix operator
## for any interpolated expression. The original meaning of the interpolation
@@ -293,7 +320,7 @@ proc quote*(bl: stmt, op = "``"): PNimrodNode {.magic: "QuoteAst", noSideEffect.
## e.g. `@` is escaped as `@@`, `@@` is escaped as `@@@` and so on.
##
## Example:
##
##
## .. code-block:: nim
##
## macro check(ex: expr): stmt =
@@ -312,35 +339,41 @@ proc quote*(bl: stmt, op = "``"): PNimrodNode {.magic: "QuoteAst", noSideEffect.
## result = quote do:
## if not `ex`:
## echo `info` & ": Check failed: " & `expString`
proc expectKind*(n: PNimrodNode, k: TNimrodNodeKind) {.compileTime.} =
proc expectKind*(n: NimNode, k: NimNodeKind) {.compileTime.} =
## checks that `n` is of kind `k`. If this is not the case,
## compilation aborts with an error message. This is useful for writing
## macros that check the AST that is passed to them.
if n.kind != k: error("macro expects a node of kind: " & $k)
if n.kind != k: error("Expected a node of kind " & $k & ", got " & $n.kind)
proc expectMinLen*(n: PNimrodNode, min: int) {.compileTime.} =
proc expectMinLen*(n: NimNode, min: int) {.compileTime.} =
## checks that `n` has at least `min` children. If this is not the case,
## compilation aborts with an error message. This is useful for writing
## macros that check its number of arguments.
if n.len < min: error("macro expects a node with " & $min & " children")
proc expectLen*(n: PNimrodNode, len: int) {.compileTime.} =
proc expectLen*(n: NimNode, len: int) {.compileTime.} =
## checks that `n` has exactly `len` children. If this is not the case,
## compilation aborts with an error message. This is useful for writing
## macros that check its number of arguments.
if n.len != len: error("macro expects a node with " & $len & " children")
proc newCall*(theProc: PNimrodNode,
args: varargs[PNimrodNode]): PNimrodNode {.compileTime.} =
proc newTree*(kind: NimNodeKind,
children: varargs[NimNode]): NimNode {.compileTime.} =
## produces a new node with children.
result = newNimNode(kind)
result.add(children)
proc newCall*(theProc: NimNode,
args: varargs[NimNode]): NimNode {.compileTime.} =
## produces a new call node. `theProc` is the proc that is called with
## the arguments ``args[0..]``.
result = newNimNode(nnkCall)
result.add(theProc)
result.add(args)
proc newCall*(theProc: TNimrodIdent,
args: varargs[PNimrodNode]): PNimrodNode {.compileTime.} =
proc newCall*(theProc: NimIdent,
args: varargs[NimNode]): NimNode {.compileTime.} =
## produces a new call node. `theProc` is the proc that is called with
## the arguments ``args[0..]``.
result = newNimNode(nnkCall)
@@ -348,35 +381,40 @@ proc newCall*(theProc: TNimrodIdent,
result.add(args)
proc newCall*(theProc: string,
args: varargs[PNimrodNode]): PNimrodNode {.compileTime.} =
args: varargs[NimNode]): NimNode {.compileTime.} =
## produces a new call node. `theProc` is the proc that is called with
## the arguments ``args[0..]``.
result = newNimNode(nnkCall)
result.add(newIdentNode(theProc))
result.add(args)
proc newLit*(c: char): PNimrodNode {.compileTime.} =
proc newLit*(c: char): NimNode {.compileTime.} =
## produces a new character literal node.
result = newNimNode(nnkCharLit)
result.intVal = ord(c)
proc newLit*(i: BiggestInt): PNimrodNode {.compileTime.} =
proc newLit*(i: BiggestInt): NimNode {.compileTime.} =
## produces a new integer literal node.
result = newNimNode(nnkIntLit)
result.intVal = i
proc newLit*(f: BiggestFloat): PNimrodNode {.compileTime.} =
proc newLit*(b: bool): NimNode {.compileTime.} =
## produces a new boolean literal node.
result = newNimNode(nnkIntLit)
result.intVal = ord(b)
proc newLit*(f: BiggestFloat): NimNode {.compileTime.} =
## produces a new float literal node.
result = newNimNode(nnkFloatLit)
result.floatVal = f
proc newLit*(s: string): PNimrodNode {.compileTime.} =
proc newLit*(s: string): NimNode {.compileTime.} =
## produces a new string literal node.
result = newNimNode(nnkStrLit)
result.strVal = s
proc nestList*(theProc: TNimrodIdent,
x: PNimrodNode): PNimrodNode {.compileTime.} =
proc nestList*(theProc: NimIdent,
x: NimNode): NimNode {.compileTime.} =
## nests the list `x` into a tree of call expressions:
## ``[a, b, c]`` is transformed into ``theProc(a, theProc(c, d))``.
var L = x.len
@@ -387,11 +425,11 @@ proc nestList*(theProc: TNimrodIdent,
# This could easily user code and so should be fixed in evals.nim somehow.
result = newCall(theProc, x[i], copyNimTree(result))
proc treeRepr*(n: PNimrodNode): string {.compileTime.} =
proc treeRepr*(n: NimNode): string {.compileTime, benign.} =
## Convert the AST `n` to a human-readable tree-like string.
##
## See also `repr` and `lispRepr`.
proc traverse(res: var string, level: int, n: PNimrodNode) =
proc traverse(res: var string, level: int, n: NimNode) {.benign.} =
for i in 0..level-1: res.add " "
res.add(($n.kind).substr(3))
@@ -412,7 +450,7 @@ proc treeRepr*(n: PNimrodNode): string {.compileTime.} =
result = ""
traverse(result, 0, n)
proc lispRepr*(n: PNimrodNode): string {.compileTime.} =
proc lispRepr*(n: NimNode): string {.compileTime, benign.} =
## Convert the AST `n` to a human-readable lisp-like string,
##
## See also `repr` and `treeRepr`.
@@ -430,10 +468,11 @@ proc lispRepr*(n: PNimrodNode): string {.compileTime.} =
of nnkSym: add(result, $n.symbol)
of nnkNone: assert false
else:
add(result, lispRepr(n[0]))
for j in 1..n.len-1:
add(result, ", ")
add(result, lispRepr(n[j]))
if n.len > 0:
add(result, lispRepr(n[0]))
for j in 1..n.len-1:
add(result, ", ")
add(result, lispRepr(n[j]))
add(result, ")")
@@ -458,42 +497,56 @@ macro dumpLispImm*(s: stmt): stmt {.immediate, deprecated.} = echo s.lispRepr
## The ``immediate`` version of `dumpLisp`.
proc newEmptyNode*(): PNimrodNode {.compileTime, noSideEffect.} =
## Create a new empty node
proc newEmptyNode*(): NimNode {.compileTime, noSideEffect.} =
## Create a new empty node
result = newNimNode(nnkEmpty)
proc newStmtList*(stmts: varargs[PNimrodNode]): PNimrodNode {.compileTime.}=
proc newStmtList*(stmts: varargs[NimNode]): NimNode {.compileTime.}=
## Create a new statement list
result = newNimNode(nnkStmtList).add(stmts)
proc newBlockStmt*(label, body: PNimrodNode): PNimrodNode {.compileTime.} =
proc newPar*(exprs: varargs[NimNode]): NimNode {.compileTime.}=
## Create a new parentheses-enclosed expression
newNimNode(nnkPar).add(exprs)
proc newBlockStmt*(label, body: NimNode): NimNode {.compileTime.} =
## Create a new block statement with label
return newNimNode(nnkBlockStmt).add(label, body)
proc newBlockStmt*(body: PNimrodNode): PNimrodNode {.compiletime.} =
proc newBlockStmt*(body: NimNode): NimNode {.compiletime.} =
## Create a new block: stmt
return newNimNode(nnkBlockStmt).add(newEmptyNode(), body)
proc newVarStmt*(name, value: PNimrodNode): PNimrodNode {.compiletime.} =
## Create a new var stmt
proc newVarStmt*(name, value: NimNode): NimNode {.compiletime.} =
## Create a new var stmt
return newNimNode(nnkVarSection).add(
newNimNode(nnkIdentDefs).add(name, newNimNode(nnkEmpty), value))
proc newLetStmt*(name, value: PNimrodNode): PNimrodNode {.compiletime.} =
## Create a new let stmt
proc newLetStmt*(name, value: NimNode): NimNode {.compiletime.} =
## Create a new let stmt
return newNimNode(nnkLetSection).add(
newNimNode(nnkIdentDefs).add(name, newNimNode(nnkEmpty), value))
proc newAssignment*(lhs, rhs: PNimrodNode): PNimrodNode {.compileTime.} =
proc newConstStmt*(name, value: NimNode): NimNode {.compileTime.} =
## Create a new const stmt
newNimNode(nnkConstSection).add(
newNimNode(nnkConstDef).add(name, newNimNode(nnkEmpty), value))
proc newAssignment*(lhs, rhs: NimNode): NimNode {.compileTime.} =
return newNimNode(nnkAsgn).add(lhs, rhs)
proc newDotExpr*(a, b: PNimrodNode): PNimrodNode {.compileTime.} =
proc newDotExpr*(a, b: NimNode): NimNode {.compileTime.} =
## Create new dot expression
## a.dot(b) -> `a.b`
return newNimNode(nnkDotExpr).add(a, b)
proc newIdentDefs*(name, kind: PNimrodNode;
default = newEmptyNode()): PNimrodNode {.compileTime.} =
proc newColonExpr*(a, b: NimNode): NimNode {.compileTime.} =
## Create new colon expression
## newColonExpr(a, b) -> `a: b`
newNimNode(nnkExprColonExpr).add(a, b)
proc newIdentDefs*(name, kind: NimNode;
default = newEmptyNode()): NimNode {.compileTime.} =
## Creates a new ``nnkIdentDefs`` node of a specific kind and value.
##
## ``nnkIdentDefs`` need to have at least three children, but they can have
@@ -524,32 +577,30 @@ proc newIdentDefs*(name, kind: PNimrodNode;
## newStrLitNode("Hello"))
newNimNode(nnkIdentDefs).add(name, kind, default)
proc newNilLit*(): PNimrodNode {.compileTime.} =
proc newNilLit*(): NimNode {.compileTime.} =
## New nil literal shortcut
result = newNimNode(nnkNilLit)
proc high*(node: PNimrodNode): int {.compileTime.} = len(node) - 1
proc high*(node: NimNode): int {.compileTime.} = len(node) - 1
## Return the highest index available for a node
proc last*(node: PNimrodNode): PNimrodNode {.compileTime.} = node[node.high]
## Return the last item in nodes children. Same as `node[node.high()]`
proc last*(node: NimNode): NimNode {.compileTime.} = node[node.high]
## Return the last item in nodes children. Same as `node[node.high()]`
const
RoutineNodes* = {nnkProcDef, nnkMethodDef, nnkDo, nnkLambda, nnkIteratorDef}
AtomicNodes* = {nnkNone..nnkNilLit}
CallNodes* = {nnkCall, nnkInfix, nnkPrefix, nnkPostfix, nnkCommand,
CallNodes* = {nnkCall, nnkInfix, nnkPrefix, nnkPostfix, nnkCommand,
nnkCallStrLit, nnkHiddenCallConv}
from strutils import cmpIgnoreStyle, format
proc expectKind*(n: NimNode; k: set[NimNodeKind]) {.compileTime.} =
assert n.kind in k, "Expected one of " & $k & ", got " & $n.kind
proc expectKind*(n: PNimrodNode; k: set[TNimrodNodeKind]) {.compileTime.} =
assert n.kind in k, "Expected one of $1, got $2".format(k, n.kind)
proc newProc*(name = newEmptyNode(); params: openArray[PNimrodNode] = [newEmptyNode()];
body: PNimrodNode = newStmtList(), procType = nnkProcDef): PNimrodNode {.compileTime.} =
proc newProc*(name = newEmptyNode(); params: openArray[NimNode] = [newEmptyNode()];
body: NimNode = newStmtList(), procType = nnkProcDef): NimNode {.compileTime.} =
## shortcut for creating a new proc
##
## The ``params`` array must start with the return type of the proc,
## The ``params`` array must start with the return type of the proc,
## followed by a list of IdentDefs which specify the params.
assert procType in RoutineNodes
result = newNimNode(procType).add(
@@ -561,12 +612,12 @@ proc newProc*(name = newEmptyNode(); params: openArray[PNimrodNode] = [newEmptyN
newEmptyNode(),
body)
proc newIfStmt*(branches: varargs[tuple[cond, body: PNimrodNode]]):
PNimrodNode {.compiletime.} =
proc newIfStmt*(branches: varargs[tuple[cond, body: NimNode]]):
NimNode {.compiletime.} =
## Constructor for ``if`` statements.
##
## .. code-block:: nim
##
##
## newIfStmt(
## (Ident, StmtList),
## ...
@@ -575,37 +626,37 @@ proc newIfStmt*(branches: varargs[tuple[cond, body: PNimrodNode]]):
result = newNimNode(nnkIfStmt)
for i in branches:
result.add(newNimNode(nnkElifBranch).add(i.cond, i.body))
proc copyChildrenTo*(src, dest: PNimrodNode) {.compileTime.}=
proc copyChildrenTo*(src, dest: NimNode) {.compileTime.}=
## Copy all children from `src` to `dest`
for i in 0 .. < src.len:
dest.add src[i].copyNimTree
template expectRoutine(node: PNimrodNode): stmt =
template expectRoutine(node: NimNode): stmt =
expectKind(node, RoutineNodes)
proc name*(someProc: PNimrodNode): PNimrodNode {.compileTime.} =
proc name*(someProc: NimNode): NimNode {.compileTime.} =
someProc.expectRoutine
result = someProc[0]
proc `name=`*(someProc: PNimrodNode; val: PNimrodNode) {.compileTime.} =
proc `name=`*(someProc: NimNode; val: NimNode) {.compileTime.} =
someProc.expectRoutine
someProc[0] = val
proc params*(someProc: PNimrodNode): PNimrodNode {.compileTime.} =
proc params*(someProc: NimNode): NimNode {.compileTime.} =
someProc.expectRoutine
result = someProc[3]
proc `params=`* (someProc: PNimrodNode; params: PNimrodNode) {.compileTime.}=
proc `params=`* (someProc: NimNode; params: NimNode) {.compileTime.}=
someProc.expectRoutine
assert params.kind == nnkFormalParams
someProc[3] = params
proc pragma*(someProc: PNimrodNode): PNimrodNode {.compileTime.} =
proc pragma*(someProc: NimNode): NimNode {.compileTime.} =
## Get the pragma of a proc type
## These will be expanded
someProc.expectRoutine
result = someProc[4]
proc `pragma=`*(someProc: PNimrodNode; val: PNimrodNode){.compileTime.}=
proc `pragma=`*(someProc: NimNode; val: NimNode){.compileTime.}=
## Set the pragma of a proc type
someProc.expectRoutine
assert val.kind in {nnkEmpty, nnkPragma}
@@ -613,9 +664,9 @@ proc `pragma=`*(someProc: PNimrodNode; val: PNimrodNode){.compileTime.}=
template badNodeKind(k; f): stmt{.immediate.} =
assert false, "Invalid node kind $# for macros.`$2`".format(k, f)
assert false, "Invalid node kind " & $k & " for macros.`" & $f & "`"
proc body*(someProc: PNimrodNode): PNimrodNode {.compileTime.} =
proc body*(someProc: NimNode): NimNode {.compileTime.} =
case someProc.kind:
of RoutineNodes:
return someProc[6]
@@ -623,11 +674,11 @@ proc body*(someProc: PNimrodNode): PNimrodNode {.compileTime.} =
return someProc[1]
of nnkForStmt:
return someProc.last
else:
else:
badNodeKind someProc.kind, "body"
proc `body=`*(someProc: PNimrodNode, val: PNimrodNode) {.compileTime.} =
case someProc.kind
proc `body=`*(someProc: NimNode, val: NimNode) {.compileTime.} =
case someProc.kind
of RoutineNodes:
someProc[6] = val
of nnkBlockStmt, nnkWhileStmt:
@@ -635,12 +686,12 @@ proc `body=`*(someProc: PNimrodNode, val: PNimrodNode) {.compileTime.} =
of nnkForStmt:
someProc[high(someProc)] = val
else:
badNodeKind someProc.kind, "body="
badNodeKind someProc.kind, "body="
proc basename*(a: PNimrodNode): PNimrodNode {.compiletime.}
proc basename*(a: NimNode): NimNode {.compiletime, benign.}
proc `$`*(node: PNimrodNode): string {.compileTime.} =
proc `$`*(node: NimNode): string {.compileTime.} =
## Get the string of an identifier node
case node.kind
of nnkIdent:
@@ -651,32 +702,32 @@ proc `$`*(node: PNimrodNode): string {.compileTime.} =
result = node.strVal
of nnkSym:
result = $node.symbol
else:
else:
badNodeKind node.kind, "$"
proc ident*(name: string): PNimrodNode {.compileTime,inline.} = newIdentNode(name)
proc ident*(name: string): NimNode {.compileTime,inline.} = newIdentNode(name)
## Create a new ident node from a string
iterator children*(n: PNimrodNode): PNimrodNode {.inline.}=
iterator children*(n: NimNode): NimNode {.inline.}=
for i in 0 .. high(n):
yield n[i]
template findChild*(n: PNimrodNode; cond: expr): PNimrodNode {.
template findChild*(n: NimNode; cond: expr): NimNode {.
immediate, dirty.} =
## Find the first child node matching condition (or nil).
##
##
## .. code-block:: nim
## var res = findChild(n, it.kind == nnkPostfix and
## it.basename.ident == !"foo")
block:
var result: PNimrodNode
var result: NimNode
for it in n.children:
if cond:
if cond:
result = it
break
result
proc insert*(a: PNimrodNode; pos: int; b: PNimrodNode) {.compileTime.} =
proc insert*(a: NimNode; pos: int; b: NimNode) {.compileTime.} =
## Insert node B into A at pos
if high(a) < pos:
## add some empty nodes first
@@ -691,62 +742,78 @@ proc insert*(a: PNimrodNode; pos: int; b: PNimrodNode) {.compileTime.} =
a[i + 1] = a[i]
a[pos] = b
proc basename*(a: PNimrodNode): PNimrodNode =
proc basename*(a: NimNode): NimNode =
## Pull an identifier from prefix/postfix expressions
case a.kind
of nnkIdent: return a
of nnkPostfix, nnkPrefix: return a[1]
else:
quit "Do not know how to get basename of ("& treeRepr(a) &")\n"& repr(a)
proc `basename=`*(a: PNimrodNode; val: string) {.compileTime.}=
else:
quit "Do not know how to get basename of (" & treeRepr(a) & ")\n" & repr(a)
proc `basename=`*(a: NimNode; val: string) {.compileTime.}=
case a.kind
of nnkIdent: macros.`ident=`(a, !val)
of nnkPostfix, nnkPrefix: a[1] = ident(val)
else:
quit "Do not know how to get basename of ("& treeRepr(a)& ")\n"& repr(a)
quit "Do not know how to get basename of (" & treeRepr(a) & ")\n" & repr(a)
proc postfix*(node: PNimrodNode; op: string): PNimrodNode {.compileTime.} =
proc postfix*(node: NimNode; op: string): NimNode {.compileTime.} =
newNimNode(nnkPostfix).add(ident(op), node)
proc prefix*(node: PNimrodNode; op: string): PNimrodNode {.compileTime.} =
proc prefix*(node: NimNode; op: string): NimNode {.compileTime.} =
newNimNode(nnkPrefix).add(ident(op), node)
proc infix*(a: PNimrodNode; op: string;
b: PNimrodNode): PNimrodNode {.compileTime.} =
proc infix*(a: NimNode; op: string;
b: NimNode): NimNode {.compileTime.} =
newNimNode(nnkInfix).add(ident(op), a, b)
proc unpackPostfix*(node: PNimrodNode): tuple[node: PNimrodNode; op: string] {.
proc unpackPostfix*(node: NimNode): tuple[node: NimNode; op: string] {.
compileTime.} =
node.expectKind nnkPostfix
result = (node[0], $node[1])
proc unpackPrefix*(node: PNimrodNode): tuple[node: PNimrodNode; op: string] {.
proc unpackPrefix*(node: NimNode): tuple[node: NimNode; op: string] {.
compileTime.} =
node.expectKind nnkPrefix
result = (node[0], $node[1])
proc unpackInfix*(node: PNimrodNode): tuple[left: PNimrodNode; op: string;
right: PNimrodNode] {.compileTime.} =
proc unpackInfix*(node: NimNode): tuple[left: NimNode; op: string;
right: NimNode] {.compileTime.} =
assert node.kind == nnkInfix
result = (node[0], $node[1], node[2])
proc copy*(node: PNimrodNode): PNimrodNode {.compileTime.} =
proc copy*(node: NimNode): NimNode {.compileTime.} =
## An alias for copyNimTree().
return node.copyNimTree()
proc cmpIgnoreStyle(a, b: cstring): int {.noSideEffect.} =
proc toLower(c: char): char {.inline.} =
if c in {'A'..'Z'}: result = chr(ord(c) + (ord('a') - ord('A')))
else: result = c
var i = 0
var j = 0
while true:
while a[i] == '_': inc(i)
while b[j] == '_': inc(j) # BUGFIX: typo
var aa = toLower(a[i])
var bb = toLower(b[j])
result = ord(aa) - ord(bb)
if result != 0 or aa == '\0': break
inc(i)
inc(j)
proc eqIdent* (a, b: string): bool = cmpIgnoreStyle(a, b) == 0
## Check if two idents are identical.
proc hasArgOfName* (params: PNimrodNode; name: string): bool {.compiletime.}=
proc hasArgOfName* (params: NimNode; name: string): bool {.compiletime.}=
## Search nnkFormalParams for an argument.
assert params.kind == nnkFormalParams
for i in 1 .. <params.len:
for i in 1 .. <params.len:
template node: expr = params[i]
if name.eqIdent( $ node[0]):
return true
proc addIdentIfAbsent*(dest: PNimrodNode, ident: string) {.compiletime.} =
proc addIdentIfAbsent*(dest: NimNode, ident: string) {.compiletime.} =
## Add ident to dest if it is not present. This is intended for use
## with pragmas.
for node in dest.children:

View File

@@ -66,9 +66,9 @@ type
ppointer = ptr pointer
pbyteArray = ptr array[0.. 0xffff, int8]
TGenSeq = object
TGenericSeq {.importc.} = object
len, space: int
PGenSeq = ptr TGenSeq
PGenSeq = ptr TGenericSeq
const
GenericSeqSize = (2 * sizeof(int))

View File

@@ -11,49 +11,47 @@
## To discourage users from using ``unsigned``, it's not part of ``system``,
## but an extra import.
type
SomeUInt = uint|uint8|uint16|uint32|uint64
proc `not`*[T: SomeUInt](x: T): T {.magic: "BitnotI", noSideEffect.}
proc `not`*[T: SomeUnsignedInt](x: T): T {.magic: "BitnotI", noSideEffect.}
## computes the `bitwise complement` of the integer `x`.
proc `shr`*[T: SomeUInt](x, y: T): T {.magic: "ShrI", noSideEffect.}
proc `shr`*[T: SomeUnsignedInt](x, y: T): T {.magic: "ShrI", noSideEffect.}
## computes the `shift right` operation of `x` and `y`.
proc `shl`*[T: SomeUInt](x, y: T): T {.magic: "ShlI", noSideEffect.}
proc `shl`*[T: SomeUnsignedInt](x, y: T): T {.magic: "ShlI", noSideEffect.}
## computes the `shift left` operation of `x` and `y`.
proc `and`*[T: SomeUInt](x, y: T): T {.magic: "BitandI", noSideEffect.}
proc `and`*[T: SomeUnsignedInt](x, y: T): T {.magic: "BitandI", noSideEffect.}
## computes the `bitwise and` of numbers `x` and `y`.
proc `or`*[T: SomeUInt](x, y: T): T {.magic: "BitorI", noSideEffect.}
proc `or`*[T: SomeUnsignedInt](x, y: T): T {.magic: "BitorI", noSideEffect.}
## computes the `bitwise or` of numbers `x` and `y`.
proc `xor`*[T: SomeUInt](x, y: T): T {.magic: "BitxorI", noSideEffect.}
proc `xor`*[T: SomeUnsignedInt](x, y: T): T {.magic: "BitxorI", noSideEffect.}
## computes the `bitwise xor` of numbers `x` and `y`.
proc `==`*[T: SomeUInt](x, y: T): bool {.magic: "EqI", noSideEffect.}
proc `==`*[T: SomeUnsignedInt](x, y: T): bool {.magic: "EqI", noSideEffect.}
## Compares two unsigned integers for equality.
proc `+`*[T: SomeUInt](x, y: T): T {.magic: "AddU", noSideEffect.}
proc `+`*[T: SomeUnsignedInt](x, y: T): T {.magic: "AddU", noSideEffect.}
## Binary `+` operator for unsigned integers.
proc `-`*[T: SomeUInt](x, y: T): T {.magic: "SubU", noSideEffect.}
proc `-`*[T: SomeUnsignedInt](x, y: T): T {.magic: "SubU", noSideEffect.}
## Binary `-` operator for unsigned integers.
proc `*`*[T: SomeUInt](x, y: T): T {.magic: "MulU", noSideEffect.}
proc `*`*[T: SomeUnsignedInt](x, y: T): T {.magic: "MulU", noSideEffect.}
## Binary `*` operator for unsigned integers.
proc `div`*[T: SomeUInt](x, y: T): T {.magic: "DivU", noSideEffect.}
proc `div`*[T: SomeUnsignedInt](x, y: T): T {.magic: "DivU", noSideEffect.}
## computes the integer division. This is roughly the same as
## ``floor(x/y)``.
proc `mod`*[T: SomeUInt](x, y: T): T {.magic: "ModU", noSideEffect.}
proc `mod`*[T: SomeUnsignedInt](x, y: T): T {.magic: "ModU", noSideEffect.}
## computes the integer modulo operation. This is the same as
## ``x - (x div y) * y``.
proc `<=`*[T: SomeUInt](x, y: T): bool {.magic: "LeU", noSideEffect.}
proc `<=`*[T: SomeUnsignedInt](x, y: T): bool {.magic: "LeU", noSideEffect.}
## Returns true iff ``x <= y``.
proc `<`*[T: SomeUInt](x, y: T): bool {.magic: "LtU", noSideEffect.}
proc `<`*[T: SomeUnsignedInt](x, y: T): bool {.magic: "LtU", noSideEffect.}
## Returns true iff ``unsigned(x) < unsigned(y)``.

View File

@@ -16,11 +16,11 @@ type
TDbConn* = PMySQL ## encapsulates a database connection
TRow* = seq[string] ## a row of a dataset. NULL database values will be
## transformed always to the empty string.
EDb* = object of EIO ## 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
FDb* = object of FIO ## 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
FWriteDb* = object of FDb ## effect that denotes a write operation
@@ -229,3 +229,9 @@ proc open*(connection, user, password, database: string): TDbConn {.
var errmsg = $mysql.error(result)
db_mysql.close(result)
dbError(errmsg)
proc setEncoding*(connection: TDbConn, encoding: string): bool {.
tags: [FDb].} =
## sets the encoding of a database connection, returns true for
## success, false for failure.
result = mysql.set_character_set(connection, encoding) == 0

View File

@@ -1,7 +1,7 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2014 Andreas Rumpf
# (c) Copyright 2015 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@@ -260,3 +260,9 @@ proc open*(connection, user, password, database: string): TDbConn {.
## the nim db api.
result = pqsetdbLogin(nil, nil, nil, nil, database, user, password)
if pqStatus(result) != CONNECTION_OK: dbError(result) # result = nil
proc setEncoding*(connection: TDbConn, encoding: string): bool {.
tags: [FDb].} =
## sets the encoding of a database connection, returns true for
## success, false for failure.
return pqsetClientEncoding(connection, encoding) == 0

View File

@@ -48,6 +48,7 @@ proc dbError*(msg: string) {.noreturn.} =
raise e
proc dbQuote(s: string): string =
if s.isNil: return "NULL"
result = "'"
for c in items(s):
if c == '\'': add(result, "''")
@@ -61,7 +62,7 @@ proc dbFormat(formatstr: TSqlQuery, args: varargs[string]): string =
if c == '?':
add(result, dbQuote(args[a]))
inc(a)
else:
else:
add(result, c)
proc tryExec*(db: TDbConn, query: TSqlQuery,
@@ -191,8 +192,20 @@ proc open*(connection, user, password, database: string): TDbConn {.
result = db
else:
dbError(db)
when isMainModule:
proc setEncoding*(connection: TDbConn, encoding: string): bool {.
tags: [FDb].} =
## sets the encoding of a database connection, returns true for
## success, false for failure.
##
## Note that the encoding cannot be changed once it's been set.
## According to SQLite3 documentation, any attempt to change
## the encoding after the database is created will be silently
## ignored.
exec(connection, sql"PRAGMA encoding = ?", [encoding])
result = connection.getValue(sql"PRAGMA encoding") == encoding
when not defined(testing) and isMainModule:
var db = open("db.sql", "", "", "")
exec(db, sql"create table tbl1(one varchar(10), two smallint)", [])
exec(db, sql"insert into tbl1 values('hello!',10)", [])

View File

@@ -499,7 +499,7 @@ template withEvents*(surf: PSurface, event: expr, actions: stmt): stmt {.
if sdl.init(sdl.INIT_VIDEO) < 0: raiseEGraphics()
if sdl_ttf.init() < 0: raiseEGraphics()
when isMainModule:
when not defined(testing) and isMainModule:
var surf = newScreenSurface(800, 600)
surf.fillSurface(colWhite)

View File

@@ -1,69 +1,10 @@
import posix, strutils, os
#
#
# Nim's Runtime Library
# (c) Copyright 2015 Dominik Picheta
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
when false:
type
Tstatfs {.importc: "struct statfs64",
header: "<sys/statfs.h>", final, pure.} = object
f_type: int
f_bsize: int
f_blocks: int
f_bfree: int
f_bavail: int
f_files: int
f_ffree: int
f_fsid: int
f_namelen: int
proc statfs(path: string, buf: var Tstatfs): int {.
importc, header: "<sys/vfs.h>".}
proc getSystemVersion*(): string =
result = ""
var unix_info: TUtsname
if uname(unix_info) != 0:
os.raiseOSError(osLastError())
if $unix_info.sysname == "Linux":
# Linux
result.add("Linux ")
result.add($unix_info.release & " ")
result.add($unix_info.machine)
elif $unix_info.sysname == "Darwin":
# Darwin
result.add("Mac OS X ")
if "10" in $unix_info.release:
result.add("v10.6 Snow Leopard")
elif "9" in $unix_info.release:
result.add("v10.5 Leopard")
elif "8" in $unix_info.release:
result.add("v10.4 Tiger")
elif "7" in $unix_info.release:
result.add("v10.3 Panther")
elif "6" in $unix_info.release:
result.add("v10.2 Jaguar")
elif "1.4" in $unix_info.release:
result.add("v10.1 Puma")
elif "1.3" in $unix_info.release:
result.add("v10.0 Cheetah")
elif "0" in $unix_info.release:
result.add("Server 1.0 Hera")
else:
result.add($unix_info.sysname & " " & $unix_info.release)
when false:
var unix_info: TUtsname
echo(uname(unix_info))
echo(unix_info.sysname)
echo("8" in $unix_info.release)
echo(getSystemVersion())
var stfs: TStatfs
echo(statfs("sysinfo_posix.nim", stfs))
echo(stfs.f_files)
{.error: "This module has been moved to the 'osinfo' nimble package.".}

View File

@@ -1,404 +1,10 @@
# XXX clean up this mess!
#
#
# Nim's Runtime Library
# (c) Copyright 2015 Dominik Picheta
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
import winlean
const
INVALID_HANDLE_VALUE = int(- 1) # GetStockObject
type
TMEMORYSTATUSEX {.final, pure.} = object
dwLength: int32
dwMemoryLoad: int32
ullTotalPhys: int64
ullAvailPhys: int64
ullTotalPageFile: int64
ullAvailPageFile: int64
ullTotalVirtual: int64
ullAvailVirtual: int64
ullAvailExtendedVirtual: int64
SYSTEM_INFO* {.final, pure.} = object
wProcessorArchitecture*: int16
wReserved*: int16
dwPageSize*: int32
lpMinimumApplicationAddress*: pointer
lpMaximumApplicationAddress*: pointer
dwActiveProcessorMask*: int32
dwNumberOfProcessors*: int32
dwProcessorType*: int32
dwAllocationGranularity*: int32
wProcessorLevel*: int16
wProcessorRevision*: int16
LPSYSTEM_INFO* = ptr SYSTEM_INFO
TSYSTEMINFO* = SYSTEM_INFO
TMemoryInfo* = object
MemoryLoad*: int ## occupied memory, in percent
TotalPhysMem*: int64 ## Total Physical memory, in bytes
AvailablePhysMem*: int64 ## Available physical memory, in bytes
TotalPageFile*: int64 ## The current committed memory limit
## for the system or the current process, whichever is smaller, in bytes.
AvailablePageFile*: int64 ## The maximum amount of memory the current process can commit, in bytes.
TotalVirtualMem*: int64 ## Total virtual memory, in bytes
AvailableVirtualMem*: int64 ## Available virtual memory, in bytes
TOSVERSIONINFOEX {.final, pure.} = object
dwOSVersionInfoSize: int32
dwMajorVersion: int32
dwMinorVersion: int32
dwBuildNumber: int32
dwPlatformId: int32
szCSDVersion: array[0..127, char]
wServicePackMajor: int16
wServicePackMinor: int16
wSuiteMask: int16
wProductType: int8
wReserved: char
TVersionInfo* = object
majorVersion*: int
minorVersion*: int
buildNumber*: int
platformID*: int
SPVersion*: string ## Full Service pack version string
SPMajor*: int ## Major service pack version
SPMinor*: int ## Minor service pack version
SuiteMask*: int
ProductType*: int
TPartitionInfo* = tuple[FreeSpace, TotalSpace: Tfiletime]
const
# SuiteMask - VersionInfo.SuiteMask
VER_SUITE_BACKOFFICE* = 0x00000004
VER_SUITE_BLADE* = 0x00000400
VER_SUITE_COMPUTE_SERVER* = 0x00004000
VER_SUITE_DATACENTER* = 0x00000080
VER_SUITE_ENTERPRISE* = 0x00000002
VER_SUITE_EMBEDDEDNT* = 0x00000040
VER_SUITE_PERSONAL* = 0x00000200
VER_SUITE_SINGLEUSERTS* = 0x00000100
VER_SUITE_SMALLBUSINESS* = 0x00000001
VER_SUITE_SMALLBUSINESS_RESTRICTED* = 0x00000020
VER_SUITE_STORAGE_SERVER* = 0x00002000
VER_SUITE_TERMINAL* = 0x00000010
VER_SUITE_WH_SERVER* = 0x00008000
# ProductType - VersionInfo.ProductType
VER_NT_DOMAIN_CONTROLLER* = 0x0000002
VER_NT_SERVER* = 0x0000003
VER_NT_WORKSTATION* = 0x0000001
VER_PLATFORM_WIN32_NT* = 2
# Product Info - getProductInfo() - (Remove unused ones ?)
PRODUCT_BUSINESS* = 0x00000006
PRODUCT_BUSINESS_N* = 0x00000010
PRODUCT_CLUSTER_SERVER* = 0x00000012
PRODUCT_DATACENTER_SERVER* = 0x00000008
PRODUCT_DATACENTER_SERVER_CORE* = 0x0000000C
PRODUCT_DATACENTER_SERVER_CORE_V* = 0x00000027
PRODUCT_DATACENTER_SERVER_V* = 0x00000025
PRODUCT_ENTERPRISE* = 0x00000004
PRODUCT_ENTERPRISE_E* = 0x00000046
PRODUCT_ENTERPRISE_N* = 0x0000001B
PRODUCT_ENTERPRISE_SERVER* = 0x0000000A
PRODUCT_ENTERPRISE_SERVER_CORE* = 0x0000000E
PRODUCT_ENTERPRISE_SERVER_CORE_V* = 0x00000029
PRODUCT_ENTERPRISE_SERVER_IA64* = 0x0000000F
PRODUCT_ENTERPRISE_SERVER_V* = 0x00000026
PRODUCT_HOME_BASIC* = 0x00000002
PRODUCT_HOME_BASIC_E* = 0x00000043
PRODUCT_HOME_BASIC_N* = 0x00000005
PRODUCT_HOME_PREMIUM* = 0x00000003
PRODUCT_HOME_PREMIUM_E* = 0x00000044
PRODUCT_HOME_PREMIUM_N* = 0x0000001A
PRODUCT_HYPERV* = 0x0000002A
PRODUCT_MEDIUMBUSINESS_SERVER_MANAGEMENT* = 0x0000001E
PRODUCT_MEDIUMBUSINESS_SERVER_MESSAGING* = 0x00000020
PRODUCT_MEDIUMBUSINESS_SERVER_SECURITY* = 0x0000001F
PRODUCT_PROFESSIONAL* = 0x00000030
PRODUCT_PROFESSIONAL_E* = 0x00000045
PRODUCT_PROFESSIONAL_N* = 0x00000031
PRODUCT_SERVER_FOR_SMALLBUSINESS* = 0x00000018
PRODUCT_SERVER_FOR_SMALLBUSINESS_V* = 0x00000023
PRODUCT_SERVER_FOUNDATION* = 0x00000021
PRODUCT_SMALLBUSINESS_SERVER* = 0x00000009
PRODUCT_STANDARD_SERVER* = 0x00000007
PRODUCT_STANDARD_SERVER_CORE * = 0x0000000D
PRODUCT_STANDARD_SERVER_CORE_V* = 0x00000028
PRODUCT_STANDARD_SERVER_V* = 0x00000024
PRODUCT_STARTER* = 0x0000000B
PRODUCT_STARTER_E* = 0x00000042
PRODUCT_STARTER_N* = 0x0000002F
PRODUCT_STORAGE_ENTERPRISE_SERVER* = 0x00000017
PRODUCT_STORAGE_EXPRESS_SERVER* = 0x00000014
PRODUCT_STORAGE_STANDARD_SERVER* = 0x00000015
PRODUCT_STORAGE_WORKGROUP_SERVER* = 0x00000016
PRODUCT_UNDEFINED* = 0x00000000
PRODUCT_ULTIMATE* = 0x00000001
PRODUCT_ULTIMATE_E* = 0x00000047
PRODUCT_ULTIMATE_N* = 0x0000001C
PRODUCT_WEB_SERVER* = 0x00000011
PRODUCT_WEB_SERVER_CORE* = 0x0000001D
PROCESSOR_ARCHITECTURE_AMD64* = 9 ## x64 (AMD or Intel)
PROCESSOR_ARCHITECTURE_IA64* = 6 ## Intel Itanium Processor Family (IPF)
PROCESSOR_ARCHITECTURE_INTEL* = 0 ## x86
PROCESSOR_ARCHITECTURE_UNKNOWN* = 0xffff ## Unknown architecture.
# GetSystemMetrics
SM_SERVERR2 = 89
proc globalMemoryStatusEx*(lpBuffer: var TMEMORYSTATUSEX){.stdcall, dynlib: "kernel32",
importc: "GlobalMemoryStatusEx".}
proc getMemoryInfo*(): TMemoryInfo =
## Retrieves memory info
var statex: TMEMORYSTATUSEX
statex.dwLength = sizeof(statex).int32
globalMemoryStatusEx(statex)
result.MemoryLoad = statex.dwMemoryLoad
result.TotalPhysMem = statex.ullTotalPhys
result.AvailablePhysMem = statex.ullAvailPhys
result.TotalPageFile = statex.ullTotalPageFile
result.AvailablePageFile = statex.ullAvailPageFile
result.TotalVirtualMem = statex.ullTotalVirtual
result.AvailableVirtualMem = statex.ullAvailExtendedVirtual
proc getVersionEx*(lpVersionInformation: var TOSVERSIONINFOEX): WINBOOL{.stdcall,
dynlib: "kernel32", importc: "GetVersionExA".}
proc getProcAddress*(hModule: int, lpProcName: cstring): pointer{.stdcall,
dynlib: "kernel32", importc: "GetProcAddress".}
proc getModuleHandleA*(lpModuleName: cstring): int{.stdcall,
dynlib: "kernel32", importc: "GetModuleHandleA".}
proc getVersionInfo*(): TVersionInfo =
## Retrieves operating system info
var osvi: TOSVERSIONINFOEX
osvi.dwOSVersionInfoSize = sizeof(osvi).int32
discard getVersionEx(osvi)
result.majorVersion = osvi.dwMajorVersion
result.minorVersion = osvi.dwMinorVersion
result.buildNumber = osvi.dwBuildNumber
result.platformID = osvi.dwPlatformId
result.SPVersion = $osvi.szCSDVersion
result.SPMajor = osvi.wServicePackMajor
result.SPMinor = osvi.wServicePackMinor
result.SuiteMask = osvi.wSuiteMask
result.ProductType = osvi.wProductType
proc getProductInfo*(majorVersion, minorVersion, SPMajorVersion,
SPMinorVersion: int): int =
## Retrieves Windows' ProductInfo, this function only works in Vista and 7
var pGPI = cast[proc (dwOSMajorVersion, dwOSMinorVersion,
dwSpMajorVersion, dwSpMinorVersion: int32, outValue: Pint32)](getProcAddress(
getModuleHandleA("kernel32.dll"), "GetProductInfo"))
if pGPI != nil:
var dwType: int32
pGPI(int32(majorVersion), int32(minorVersion), int32(SPMajorVersion), int32(SPMinorVersion), addr(dwType))
result = int(dwType)
else:
return PRODUCT_UNDEFINED
proc getSystemInfo*(lpSystemInfo: LPSYSTEM_INFO){.stdcall, dynlib: "kernel32",
importc: "GetSystemInfo".}
proc getSystemInfo*(): TSYSTEM_INFO =
## Returns the SystemInfo
# Use GetNativeSystemInfo if it's available
var pGNSI = cast[proc (lpSystemInfo: LPSYSTEM_INFO)](getProcAddress(
getModuleHandleA("kernel32.dll"), "GetNativeSystemInfo"))
var systemi: TSYSTEM_INFO
if pGNSI != nil:
pGNSI(addr(systemi))
else:
getSystemInfo(addr(systemi))
return systemi
proc getSystemMetrics*(nIndex: int32): int32{.stdcall, dynlib: "user32",
importc: "GetSystemMetrics".}
proc `$`*(osvi: TVersionInfo): string =
## Turns a VersionInfo object, into a string
if osvi.platformID == VER_PLATFORM_WIN32_NT and osvi.majorVersion > 4:
result = "Microsoft "
var si = getSystemInfo()
# Test for the specific product
if osvi.majorVersion == 6:
if osvi.minorVersion == 0:
if osvi.ProductType == VER_NT_WORKSTATION:
result.add("Windows Vista ")
else: result.add("Windows Server 2008 ")
elif osvi.minorVersion == 1:
if osvi.ProductType == VER_NT_WORKSTATION:
result.add("Windows 7 ")
else: result.add("Windows Server 2008 R2 ")
var dwType = getProductInfo(osvi.majorVersion, osvi.minorVersion, 0, 0)
case dwType
of PRODUCT_ULTIMATE:
result.add("Ultimate Edition")
of PRODUCT_PROFESSIONAL:
result.add("Professional")
of PRODUCT_HOME_PREMIUM:
result.add("Home Premium Edition")
of PRODUCT_HOME_BASIC:
result.add("Home Basic Edition")
of PRODUCT_ENTERPRISE:
result.add("Enterprise Edition")
of PRODUCT_BUSINESS:
result.add("Business Edition")
of PRODUCT_STARTER:
result.add("Starter Edition")
of PRODUCT_CLUSTER_SERVER:
result.add("Cluster Server Edition")
of PRODUCT_DATACENTER_SERVER:
result.add("Datacenter Edition")
of PRODUCT_DATACENTER_SERVER_CORE:
result.add("Datacenter Edition (core installation)")
of PRODUCT_ENTERPRISE_SERVER:
result.add("Enterprise Edition")
of PRODUCT_ENTERPRISE_SERVER_CORE:
result.add("Enterprise Edition (core installation)")
of PRODUCT_ENTERPRISE_SERVER_IA64:
result.add("Enterprise Edition for Itanium-based Systems")
of PRODUCT_SMALLBUSINESS_SERVER:
result.add("Small Business Server")
of PRODUCT_STANDARD_SERVER:
result.add("Standard Edition")
of PRODUCT_STANDARD_SERVER_CORE:
result.add("Standard Edition (core installation)")
of PRODUCT_WEB_SERVER:
result.add("Web Server Edition")
else:
discard
# End of Windows 6.*
if osvi.majorVersion == 5 and osvi.minorVersion == 2:
if getSystemMetrics(SM_SERVERR2) != 0:
result.add("Windows Server 2003 R2, ")
elif (osvi.SuiteMask and VER_SUITE_PERSONAL) != 0: # Not sure if this will work
result.add("Windows Storage Server 2003")
elif (osvi.SuiteMask and VER_SUITE_WH_SERVER) != 0:
result.add("Windows Home Server")
elif osvi.ProductType == VER_NT_WORKSTATION and
si.wProcessorArchitecture==PROCESSOR_ARCHITECTURE_AMD64:
result.add("Windows XP Professional x64 Edition")
else:
result.add("Windows Server 2003, ")
# Test for the specific product
if osvi.ProductType != VER_NT_WORKSTATION:
if ze(si.wProcessorArchitecture) == PROCESSOR_ARCHITECTURE_IA64:
if (osvi.SuiteMask and VER_SUITE_DATACENTER) != 0:
result.add("Datacenter Edition for Itanium-based Systems")
elif (osvi.SuiteMask and VER_SUITE_ENTERPRISE) != 0:
result.add("Enterprise Edition for Itanium-based Systems")
elif ze(si.wProcessorArchitecture) == PROCESSOR_ARCHITECTURE_AMD64:
if (osvi.SuiteMask and VER_SUITE_DATACENTER) != 0:
result.add("Datacenter x64 Edition")
elif (osvi.SuiteMask and VER_SUITE_ENTERPRISE) != 0:
result.add("Enterprise x64 Edition")
else:
result.add("Standard x64 Edition")
else:
if (osvi.SuiteMask and VER_SUITE_COMPUTE_SERVER) != 0:
result.add("Compute Cluster Edition")
elif (osvi.SuiteMask and VER_SUITE_DATACENTER) != 0:
result.add("Datacenter Edition")
elif (osvi.SuiteMask and VER_SUITE_ENTERPRISE) != 0:
result.add("Enterprise Edition")
elif (osvi.SuiteMask and VER_SUITE_BLADE) != 0:
result.add("Web Edition")
else:
result.add("Standard Edition")
# End of 5.2
if osvi.majorVersion == 5 and osvi.minorVersion == 1:
result.add("Windows XP ")
if (osvi.SuiteMask and VER_SUITE_PERSONAL) != 0:
result.add("Home Edition")
else:
result.add("Professional")
# End of 5.1
if osvi.majorVersion == 5 and osvi.minorVersion == 0:
result.add("Windows 2000 ")
if osvi.ProductType == VER_NT_WORKSTATION:
result.add("Professional")
else:
if (osvi.SuiteMask and VER_SUITE_DATACENTER) != 0:
result.add("Datacenter Server")
elif (osvi.SuiteMask and VER_SUITE_ENTERPRISE) != 0:
result.add("Advanced Server")
else:
result.add("Server")
# End of 5.0
# Include service pack (if any) and build number.
if len(osvi.SPVersion) > 0:
result.add(" ")
result.add(osvi.SPVersion)
result.add(" (build " & $osvi.buildNumber & ")")
if osvi.majorVersion >= 6:
if ze(si.wProcessorArchitecture) == PROCESSOR_ARCHITECTURE_AMD64:
result.add(", 64-bit")
elif ze(si.wProcessorArchitecture) == PROCESSOR_ARCHITECTURE_INTEL:
result.add(", 32-bit")
else:
# Windows 98 etc...
result = "Unknown version of windows[Kernel version <= 4]"
proc getFileSize*(file: string): BiggestInt =
var fileData: TWIN32_FIND_DATA
when useWinUnicode:
var aa = newWideCString(file)
var hFile = findFirstFileW(aa, fileData)
else:
var hFile = findFirstFileA(file, fileData)
if hFile == INVALID_HANDLE_VALUE:
raise newException(IOError, $getLastError())
return fileData.nFileSizeLow
proc getDiskFreeSpaceEx*(lpDirectoryName: cstring, lpFreeBytesAvailableToCaller,
lpTotalNumberOfBytes,
lpTotalNumberOfFreeBytes: var TFiletime): WINBOOL{.
stdcall, dynlib: "kernel32", importc: "GetDiskFreeSpaceExA".}
proc getPartitionInfo*(partition: string): TPartitionInfo =
## Retrieves partition info, for example ``partition`` may be ``"C:\"``
var freeBytes, totalBytes, totalFreeBytes: TFiletime
discard getDiskFreeSpaceEx(r"C:\", freeBytes, totalBytes,
totalFreeBytes)
return (freeBytes, totalBytes)
when isMainModule:
var r = getMemoryInfo()
echo("Memory load: ", r.MemoryLoad, "%")
var osvi = getVersionInfo()
echo($osvi)
echo(getFileSize(r"osinfo_win.nim") div 1024 div 1024)
echo(rdFileTime(getPartitionInfo(r"C:\")[0]))
{.error: "This module has been moved to the 'osinfo' nimble package.".}

View File

@@ -1,21 +1,23 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2012 Andreas Rumpf
# (c) Copyright 2015 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module contains code for reading from `stdin`:idx:. On UNIX the GNU
## readline library is wrapped and set up to provide default key bindings
## readline library is wrapped and set up to provide default key bindings
## (e.g. you can navigate with the arrow keys). On Windows ``system.readLine``
## is used. This suffices because Windows' console already provides the
## is used. This suffices because Windows' console already provides the
## wanted functionality.
{.deadCodeElim: on.}
when defined(Windows):
proc readLineFromStdin*(prompt: string): TaintedString {.
tags: [ReadIOEffect, WriteIOEffect].} =
tags: [ReadIOEffect, WriteIOEffect].} =
## Reads a line from stdin.
stdout.write(prompt)
result = readLine(stdin)
@@ -31,9 +33,75 @@ when defined(Windows):
stdout.write(prompt)
result = readLine(stdin, line)
import winlean
const
VK_SHIFT* = 16
VK_CONTROL* = 17
VK_MENU* = 18
KEY_EVENT* = 1
type
KEY_EVENT_RECORD = object
bKeyDown: WinBool
wRepeatCount: uint16
wVirtualKeyCode: uint16
wVirtualScanCode: uint16
unicodeChar: uint16
dwControlKeyState: uint32
INPUT_RECORD = object
eventType*: int16
reserved*: int16
event*: KEY_EVENT_RECORD
safetyBuffer: array[0..5, DWORD]
proc readConsoleInputW*(hConsoleInput: THANDLE, lpBuffer: var INPUTRECORD,
nLength: uint32,
lpNumberOfEventsRead: var uint32): WINBOOL{.
stdcall, dynlib: "kernel32", importc: "ReadConsoleInputW".}
proc getch(): uint16 =
let hStdin = getStdHandle(STD_INPUT_HANDLE)
var
irInputRecord: INPUT_RECORD
dwEventsRead: uint32
while readConsoleInputW(hStdin, irInputRecord, 1, dwEventsRead) != 0:
if irInputRecord.eventType == KEY_EVENT and
irInputRecord.event.wVirtualKeyCode notin {VK_SHIFT, VK_MENU, VK_CONTROL}:
result = irInputRecord.event.unicodeChar
discard readConsoleInputW(hStdin, irInputRecord, 1, dwEventsRead)
return result
from unicode import toUTF8, Rune, runeLenAt
proc readPasswordFromStdin*(prompt: string, password: var TaintedString):
bool {.tags: [ReadIOEffect, WriteIOEffect].} =
## Reads a `password` from stdin without printing it. `password` must not
## be ``nil``! Returns ``false`` if the end of the file has been reached,
## ``true`` otherwise.
password.setLen(0)
stdout.write(prompt)
while true:
let c = getch()
case c.char
of '\r', chr(0xA):
break
of '\b':
# ensure we delete the whole UTF-8 character:
var i = 0
var x = 1
while i < password.len:
x = runeLenAt(password, i)
inc i, x
password.setLen(password.len - x)
else:
password.add(toUTF8(c.Rune))
stdout.write "\n"
else:
import readline, history
import readline, history, termios, unsigned
proc readLineFromStdin*(prompt: string): TaintedString {.
tags: [ReadIOEffect, WriteIOEffect].} =
var buffer = readline.readLine(prompt)
@@ -55,8 +123,26 @@ else:
result = true
# initialization:
# disable auto-complete:
# disable auto-complete:
proc doNothing(a, b: cint): cint {.cdecl, procvar.} = discard
discard readline.bind_key('\t'.ord, doNothing)
proc readPasswordFromStdin*(prompt: string, password: var TaintedString):
bool {.tags: [ReadIOEffect, WriteIOEffect].} =
password.setLen(0)
let fd = stdin.getFileHandle()
var cur, old: Termios
discard fd.tcgetattr(cur.addr)
old = cur
cur.c_lflag = cur.c_lflag and not Tcflag(ECHO)
discard fd.tcsetattr(TCSADRAIN, cur.addr)
stdout.write prompt
result = stdin.readLine(password)
stdout.write "\n"
discard fd.tcsetattr(TCSADRAIN, old.addr)
proc readPasswordFromStdin*(prompt: string): TaintedString =
## Reads a password from stdin without printing it.
result = TaintedString("")
discard readPasswordFromStdin(prompt, result)

View File

@@ -7,8 +7,17 @@
# distribution, for details about the copyright.
#
## Regular expression support for Nim. Consider using the pegs module
## instead.
## Regular expression support for Nim. Consider using the pegs module 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
## syntax** which lets you use whitespace freely to make your regexes readable.
## However, this means to match whitespace ``\s`` or something similar has
## to be used.
##
## This module is implemented by providing a wrapper around the
## `PRCE (Perl-Compatible Regular Expressions) <http://www.pcre.org>`_
## C library. This means that your application will depend on the PRCE
@@ -30,42 +39,42 @@ const
type
RegexFlag* = enum ## options for regular expressions
reIgnoreCase = 0, ## do caseless matching
reMultiLine = 1, ## ``^`` and ``$`` match newlines within data
reMultiLine = 1, ## ``^`` and ``$`` match newlines within data
reDotAll = 2, ## ``.`` matches anything including NL
reExtended = 3, ## ignore whitespace and ``#`` comments
reStudy = 4 ## study the expression (may be omitted if the
## expression will be used only once)
RegexDesc = object
h: PPcre
e: ptr TExtra
h: ptr Pcre
e: ptr ExtraData
Regex* = ref RegexDesc ## a compiled regular expression
RegexError* = object of ValueError
## is raised if the pattern is no valid regular expression.
{.deprecated: [TRegexFlag: RegexFlag, TRegexDesc: RegexDesc, TRegex: Regex,
EInvalidRegEx: RegexError].}
proc raiseInvalidRegex(msg: string) {.noinline, noreturn.} =
proc raiseInvalidRegex(msg: string) {.noinline, noreturn.} =
var e: ref RegexError
new(e)
e.msg = msg
raise e
proc rawCompile(pattern: string, flags: cint): PPcre =
proc rawCompile(pattern: string, flags: cint): ptr Pcre =
var
msg: cstring
offset: cint
result = pcre.compile(pattern, flags, addr(msg), addr(offset), nil)
if result == nil:
raiseInvalidRegex($msg & "\n" & pattern & "\n" & repeatChar(offset) & "^\n")
raiseInvalidRegex($msg & "\n" & pattern & "\n" & spaces(offset) & "^\n")
proc finalizeRegEx(x: Regex) =
proc finalizeRegEx(x: Regex) =
# XXX This is a hack, but PCRE does not export its "free" function properly.
# Sigh. The hack relies on PCRE's implementation (see ``pcre_get.c``).
# Fortunately the implementation is unlikely to change.
# Fortunately the implementation is unlikely to change.
pcre.free_substring(cast[cstring](x.h))
if not isNil(x.e):
pcre.free_substring(cast[cstring](x.e))
@@ -78,7 +87,7 @@ proc re*(s: string, flags = {reExtended, reStudy}): Regex =
result.h = rawCompile(s, cast[cint](flags - {reStudy}))
if reStudy in flags:
var msg: cstring
result.e = pcre.study(result.h, 0, msg)
result.e = pcre.study(result.h, 0, addr msg)
if not isNil(msg): raiseInvalidRegex($msg)
proc matchOrFind(s: string, pattern: Regex, matches: var openArray[string],
@@ -95,10 +104,10 @@ proc matchOrFind(s: string, pattern: Regex, matches: var openArray[string],
if a >= 0'i32: matches[i-1] = substr(s, int(a), int(b)-1)
else: matches[i-1] = nil
return rawMatches[1] - rawMatches[0]
proc findBounds*(s: string, pattern: Regex, matches: var openArray[string],
start = 0): tuple[first, last: int] =
## returns the starting position and end position of `pattern` in `s`
## returns the starting position and end position of `pattern` in `s`
## and the captured
## substrings in the array `matches`. If it does not match, nothing
## is written into `matches` and ``(-1,0)`` is returned.
@@ -114,12 +123,12 @@ proc findBounds*(s: string, pattern: Regex, matches: var openArray[string],
if a >= 0'i32: matches[i-1] = substr(s, int(a), int(b)-1)
else: matches[i-1] = nil
return (rawMatches[0].int, rawMatches[1].int - 1)
proc findBounds*(s: string, pattern: Regex,
proc findBounds*(s: string, pattern: Regex,
matches: var openArray[tuple[first, last: int]],
start = 0): tuple[first, last: int] =
## returns the starting position and end position of ``pattern`` in ``s``
## and the captured substrings in the array `matches`.
## returns the starting position and end position of ``pattern`` in ``s``
## and the captured substrings in the array `matches`.
## If it does not match, nothing is written into `matches` and
## ``(-1,0)`` is returned.
var
@@ -135,7 +144,7 @@ proc findBounds*(s: string, pattern: Regex,
else: matches[i-1] = (-1,0)
return (rawMatches[0].int, rawMatches[1].int - 1)
proc findBounds*(s: string, pattern: Regex,
proc findBounds*(s: string, pattern: Regex,
start = 0): tuple[first, last: int] =
## returns the starting position of `pattern` in `s`. If it does not
## match, ``(-1,0)`` is returned.
@@ -146,7 +155,7 @@ proc findBounds*(s: string, pattern: Regex,
cast[ptr cint](rawMatches), 3)
if res < 0'i32: return (int(res), 0)
return (int(rawMatches[0]), int(rawMatches[1]-1))
proc matchOrFind(s: string, pattern: Regex, start, flags: cint): cint =
var
rtarray = initRtArray[cint](3)
@@ -156,19 +165,6 @@ proc matchOrFind(s: string, pattern: Regex, start, flags: cint): cint =
if result >= 0'i32:
result = rawMatches[1] - rawMatches[0]
proc match*(s: string, pattern: Regex, matches: var openArray[string],
start = 0): bool =
## returns ``true`` if ``s[start..]`` matches the ``pattern`` and
## the captured substrings in the array ``matches``. If it does not
## match, nothing is written into ``matches`` and ``false`` is
## returned.
return matchOrFind(s, pattern, matches, start.cint,
pcre.ANCHORED) == cint(s.len - start)
proc match*(s: string, pattern: Regex, start = 0): bool =
## returns ``true`` if ``s[start..]`` matches the ``pattern``.
return matchOrFind(s, pattern, start.cint, pcre.ANCHORED) == cint(s.len-start)
proc matchLen*(s: string, pattern: Regex, matches: var openArray[string],
start = 0): int =
## the same as ``match``, but it returns the length of the match,
@@ -179,9 +175,21 @@ proc matchLen*(s: string, pattern: Regex, matches: var openArray[string],
proc matchLen*(s: string, pattern: Regex, start = 0): int =
## the same as ``match``, but it returns the length of the match,
## if there is no match, -1 is returned. Note that a match length
## of zero can happen.
## of zero can happen.
return matchOrFind(s, pattern, start.cint, pcre.ANCHORED)
proc match*(s: string, pattern: Regex, start = 0): bool =
## returns ``true`` if ``s[start..]`` matches the ``pattern``.
result = matchLen(s, pattern, start) != -1
proc match*(s: string, pattern: Regex, matches: var openArray[string],
start = 0): bool =
## returns ``true`` if ``s[start..]`` matches the ``pattern`` and
## the captured substrings in the array ``matches``. If it does not
## match, nothing is written into ``matches`` and ``false`` is
## returned.
result = matchLen(s, pattern, matches, start) != -1
proc find*(s: string, pattern: Regex, matches: var openArray[string],
start = 0): int =
## returns the starting position of ``pattern`` in ``s`` and the captured
@@ -211,7 +219,7 @@ proc find*(s: string, pattern: Regex, start = 0): int =
if res < 0'i32: return res
return rawMatches[0]
iterator findAll*(s: string, pattern: Regex, start = 0): string =
iterator findAll*(s: string, pattern: Regex, start = 0): string =
## Yields all matching *substrings* of `s` that match `pattern`.
##
## Note that since this is an iterator you should not modify the string you
@@ -226,10 +234,11 @@ iterator findAll*(s: string, pattern: Regex, start = 0): string =
if res < 0'i32: break
let a = rawMatches[0]
let b = rawMatches[1]
if a == b and a == i: break
yield substr(s, int(a), int(b)-1)
i = b
proc findAll*(s: string, pattern: Regex, start = 0): seq[string] =
proc findAll*(s: string, pattern: Regex, start = 0): seq[string] =
## returns all matching *substrings* of `s` that match `pattern`.
## If it does not match, @[] is returned.
accumulateResult(findAll(s, pattern, start))
@@ -237,13 +246,13 @@ proc findAll*(s: string, pattern: Regex, start = 0): seq[string] =
when not defined(nimhygiene):
{.pragma: inject.}
template `=~` *(s: string, pattern: Regex): expr =
## This calls ``match`` with an implicit declared ``matches`` array that
## can be used in the scope of the ``=~`` call:
##
template `=~` *(s: string, pattern: Regex): expr =
## This calls ``match`` with an implicit declared ``matches`` array that
## can be used in the scope of the ``=~`` call:
##
## .. code-block:: nim
##
## if line =~ re"\s*(\w+)\s*\=\s*(\w+)":
## if line =~ re"\s*(\w+)\s*\=\s*(\w+)":
## # matches a key=value pair:
## echo("Key: ", matches[0])
## echo("Value: ", matches[1])
@@ -255,9 +264,9 @@ template `=~` *(s: string, pattern: Regex): expr =
## else:
## echo("syntax error")
##
bind MaxSubPatterns
bind MaxSubpatterns
when not declaredInScope(matches):
var matches {.inject.}: array[0..MaxSubpatterns-1, string]
var matches {.inject.}: array[MaxSubpatterns, string]
match(s, pattern, matches)
# ------------------------- more string handling ------------------------------
@@ -281,11 +290,11 @@ proc endsWith*(s: string, suffix: Regex): bool =
if matchLen(s, suffix, i) == s.len - i: return true
proc replace*(s: string, sub: Regex, by = ""): string =
## Replaces `sub` in `s` by the string `by`. Captures cannot be
## Replaces `sub` in `s` by the string `by`. Captures cannot be
## accessed in `by`. Examples:
##
## .. code-block:: nim
## "var1=key; var2=key2".replace(re"(\w+)'='(\w+)")
## "var1=key; var2=key2".replace(re"(\w+)=(\w+)")
##
## Results in:
##
@@ -301,13 +310,13 @@ proc replace*(s: string, sub: Regex, by = ""): string =
add(result, by)
prev = match.last + 1
add(result, substr(s, prev))
proc replacef*(s: string, sub: Regex, by: string): string =
## Replaces `sub` in `s` by the string `by`. Captures can be accessed in `by`
## with the notation ``$i`` and ``$#`` (see strutils.`%`). Examples:
##
## .. code-block:: nim
## "var1=key; var2=key2".replacef(re"(\w+)'='(\w+)", "$1<-$2$2")
## "var1=key; var2=key2".replacef(re"(\w+)=(\w+)", "$1<-$2$2")
##
## Results in:
##
@@ -315,7 +324,7 @@ proc replacef*(s: string, sub: Regex, by: string): string =
##
## "var1<-keykey; val2<-key2key2"
result = ""
var caps: array[0..MaxSubpatterns-1, string]
var caps: array[MaxSubpatterns, string]
var prev = 0
while true:
var match = findBounds(s, sub, caps, prev)
@@ -333,7 +342,7 @@ proc parallelReplace*(s: string, subs: openArray[
## applied in parallel.
result = ""
var i = 0
var caps: array[0..MaxSubpatterns-1, string]
var caps: array[MaxSubpatterns, string]
while i < s.len:
block searchSubs:
for j in 0..high(subs):
@@ -354,7 +363,7 @@ proc transformFile*(infile, outfile: string,
## error occurs. This is supposed to be used for quick scripting.
var x = readFile(infile).string
writeFile(outfile, x.parallelReplace(subs))
iterator split*(s: string, sep: Regex): string =
## Splits the string `s` into substrings.
##
@@ -368,64 +377,73 @@ iterator split*(s: string, sep: Regex): string =
## Results in:
##
## .. code-block:: nim
## ""
## "this"
## "is"
## "an"
## "example"
## ""
##
var
first = 0
last = 0
first = -1
last = -1
while last < len(s):
var x = matchLen(s, sep, last)
if x > 0: inc(last, x)
first = last
if x == 0: inc(last)
while last < len(s):
inc(last)
x = matchLen(s, sep, last)
if x > 0: break
if first < last:
if x >= 0: break
inc(last)
if first <= last:
yield substr(s, first, last-1)
proc split*(s: string, sep: Regex): seq[string] =
## Splits the string `s` into substrings.
accumulateResult(split(s, sep))
proc escapeRe*(s: string): string =
## escapes `s` so that it is matched verbatim when used as a regular
proc escapeRe*(s: string): string =
## escapes `s` so that it is matched verbatim when used as a regular
## expression.
result = ""
for c in items(s):
case c
of 'a'..'z', 'A'..'Z', '0'..'9', '_':
result.add(c)
else:
else:
result.add("\\x")
result.add(toHex(ord(c), 2))
const ## common regular expressions
reIdentifier* = r"\b[a-zA-Z_]+[a-zA-Z_0-9]*\b" ## describes an identifier
reNatural* = r"\b\d+\b" ## describes a natural number
reInteger* = r"\b[-+]?\d+\b" ## describes an integer
reHex* = r"\b0[xX][0-9a-fA-F]+\b" ## describes a hexadecimal number
reBinary* = r"\b0[bB][01]+\b" ## describes a binary number (example: 0b11101)
reOctal* = r"\b0[oO][0-7]+\b" ## describes an octal number (example: 0o777)
reFloat* = r"\b[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?\b"
reIdentifier* {.deprecated.} = r"\b[a-zA-Z_]+[a-zA-Z_0-9]*\b"
## describes an identifier
reNatural* {.deprecated.} = r"\b\d+\b"
## describes a natural number
reInteger* {.deprecated.} = r"\b[-+]?\d+\b"
## describes an integer
reHex* {.deprecated.} = r"\b0[xX][0-9a-fA-F]+\b"
## describes a hexadecimal number
reBinary* {.deprecated.} = r"\b0[bB][01]+\b"
## describes a binary number (example: 0b11101)
reOctal* {.deprecated.} = r"\b0[oO][0-7]+\b"
## describes an octal number (example: 0o777)
reFloat* {.deprecated.} = r"\b[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?\b"
## describes a floating point number
reEmail* = r"\b[a-zA-Z0-9!#$%&'*+/=?^_`{|}~\-]+(?:\. &" &
r"[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)" &
r"*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+" &
r"(?:[a-zA-Z]{2}|com|org|" &
r"net|gov|mil|biz|info|mobi|name|aero|jobs|museum)\b"
reEmail* {.deprecated.} = r"\b[a-zA-Z0-9!#$%&'*+/=?^_`{|}~\-]+(?:\. &" &
r"[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@" &
r"(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+" &
r"(?:[a-zA-Z]{2}|com|org|net|gov|mil|biz|" &
r"info|mobi|name|aero|jobs|museum)\b"
## describes a common email address
reURL* = r"\b(http(s)?|ftp|gopher|telnet|file|notes|ms\-help):" &
r"((//)|(\\\\))+[\w\d:#@%/;$()~_?\+\-\=\\\.\&]*\b"
reURL* {.deprecated.} = r"\b(http(s)?|ftp|gopher|telnet|file|notes|ms-help)" &
r":((//)|(\\\\))+[\w\d:#@%/;$()~_?\+\-\=\\\.\&]*\b"
## describes an URL
when isMainModule:
assert match("(a b c)", re"\( .* \)")
assert match("WHiLe", re("while", {reIgnoreCase}))
assert "0158787".match(re"\d+")
assert "ABC 0232".match(re"\w+\s+\d+")
assert "ABC".match(re"\d+ | \w+")
@@ -434,21 +452,21 @@ when isMainModule:
var pattern = re"[a-z0-9]+\s*=\s*[a-z0-9]+"
assert matchLen("key1= cal9", pattern) == 11
assert find("_____abc_______", re"abc") == 5
var matches: array[0..5, string]
if match("abcdefg", re"c(d)ef(g)", matches, 2):
var matches: array[6, string]
if match("abcdefg", re"c(d)ef(g)", matches, 2):
assert matches[0] == "d"
assert matches[1] == "g"
else:
assert false
if "abc" =~ re"(a)bcxyz|(\w+)":
assert matches[1] == "abc"
else:
assert false
if "abc" =~ re"(cba)?.*":
assert matches[0] == nil
else: assert false
@@ -456,23 +474,35 @@ when isMainModule:
if "abc" =~ re"().*":
assert matches[0] == ""
else: assert false
assert "var1=key; var2=key2".endsWith(re"\w+=\w+")
assert("var1=key; var2=key2".replacef(re"(\w+)=(\w+)", "$1<-$2$2") ==
"var1<-keykey; var2<-key2key2")
assert("var1=key; var2=key2".replace(re"(\w+)=(\w+)", "$1<-$2$2") ==
"$1<-$2$2; $1<-$2$2")
var accum: seq[string] = @[]
for word in split("00232this02939is39an22example111", re"\d+"):
writeln(stdout, word)
accum.add(word)
assert(accum == @["", "this", "is", "an", "example", ""])
accum = @[]
for word in split("AAA : : BBB", re"\s*:\s*"):
accum.add(word)
assert(accum == @["AAA", "", "BBB"])
for x in findAll("abcdef", re"^{.}", 3):
assert x == "d"
accum = @[]
for x in findAll("abcdef", re".", 3):
echo x
accum.add(x)
assert(accum == @["d", "e", "f"])
assert("XYZ".find(re"^\d*") == 0)
assert("XYZ".match(re"^\d*") == true)
block:
var matches: array[0..15, string]
var matches: array[16, string]
if match("abcdefghijklmnop", re"(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)(m)(n)(o)(p)", matches):
for i in 0..matches.high:
assert matches[i] == $chr(i + 'a'.ord)

View File

@@ -82,7 +82,7 @@ proc close*(sock: TSecureSocket) =
ERR_print_errors_fp(stderr)
raiseOSError(osLastError())
when isMainModule:
when not defined(testing) and isMainModule:
var s: TSecureSocket
echo connect(s, "smtp.gmail.com", 465)

View File

@@ -36,8 +36,11 @@ type
onsubmit*: proc (event: ref TEvent) {.nimcall.}
onunload*: proc (event: ref TEvent) {.nimcall.}
TWindow* {.importc.} = object of TEventHandlers
document*: ref TDocument
addEventListener*: proc(ev: cstring, cb: proc(ev: ref TEvent), useCapture: bool = false) {.nimcall.}
Window* = ref WindowObj
WindowObj {.importc.} = object of TEventHandlers
document*: Document
event*: ref TEvent
history*: ref THistory
location*: ref TLocation
@@ -65,7 +68,7 @@ type
confirm*: proc (msg: cstring): bool {.nimcall.}
disableExternalCapture*: proc () {.nimcall.}
enableExternalCapture*: proc () {.nimcall.}
find*: proc (text: cstring, caseSensitive = false,
find*: proc (text: cstring, caseSensitive = false,
backwards = false) {.nimcall.}
focus*: proc () {.nimcall.}
forward*: proc () {.nimcall.}
@@ -74,7 +77,7 @@ type
moveBy*: proc (x, y: int) {.nimcall.}
moveTo*: proc (x, y: int) {.nimcall.}
open*: proc (uri, windowname: cstring,
properties: cstring = nil): ref TWindow {.nimcall.}
properties: cstring = nil): Window {.nimcall.}
print*: proc () {.nimcall.}
prompt*: proc (text, default: cstring): cstring {.nimcall.}
releaseEvents*: proc (eventMask: int) {.nimcall.}
@@ -88,113 +91,14 @@ type
stop*: proc () {.nimcall.}
frames*: seq[TFrame]
TFrame* {.importc.} = object of TWindow
Frame* = ref FrameObj
FrameObj {.importc.} = object of WindowObj
TDocument* {.importc.} = object of TEventHandlers
alinkColor*: cstring
bgColor*: cstring
charset*: cstring
cookie*: cstring
defaultCharset*: cstring
fgColor*: cstring
lastModified*: cstring
linkColor*: cstring
referrer*: cstring
title*: cstring
URL*: cstring
vlinkColor*: cstring
captureEvents*: proc (eventMask: int) {.nimcall.}
createAttribute*: proc (identifier: cstring): ref TNode {.nimcall.}
createElement*: proc (identifier: cstring): ref TNode {.nimcall.}
createTextNode*: proc (identifier: cstring): ref TNode {.nimcall.}
getElementById*: proc (id: cstring): ref TNode {.nimcall.}
getElementsByName*: proc (name: cstring): seq[ref TNode] {.nimcall.}
getElementsByTagName*: proc (name: cstring): seq[ref TNode] {.nimcall.}
getSelection*: proc (): cstring {.nimcall.}
handleEvent*: proc (event: ref TEvent) {.nimcall.}
open*: proc () {.nimcall.}
releaseEvents*: proc (eventMask: int) {.nimcall.}
routeEvent*: proc (event: ref TEvent) {.nimcall.}
write*: proc (text: cstring) {.nimcall.}
writeln*: proc (text: cstring) {.nimcall.}
anchors*: seq[ref TAnchor]
forms*: seq[ref TForm]
images*: seq[ref TImage]
applets*: seq[ref TApplet]
embeds*: seq[ref TEmbed]
links*: seq[ref TLink]
TLink* {.importc.} = object of RootObj
name*: cstring
target*: cstring
text*: cstring
x*: int
y*: int
TEmbed* {.importc.} = object of RootObj
height*: int
hspace*: int
name*: cstring
src*: cstring
width*: int
`type`*: cstring
vspace*: int
play*: proc () {.nimcall.}
stop*: proc () {.nimcall.}
TAnchor* {.importc.} = object of RootObj
name*: cstring
text*: cstring
x*, y*: int
TApplet* {.importc.} = object of RootObj
TElement* {.importc.} = object of TEventHandlers
checked*: bool
defaultChecked*: bool
defaultValue*: cstring
disabled*: bool
form*: ref TForm
name*: cstring
readOnly*: bool
`type`*: cstring
value*: cstring
blur*: proc () {.nimcall.}
click*: proc () {.nimcall.}
focus*: proc () {.nimcall.}
handleEvent*: proc (event: ref TEvent) {.nimcall.}
select*: proc () {.nimcall.}
options*: seq[ref TOption]
TOption* {.importc.} = object of RootObj
defaultSelected*: bool
selected*: bool
selectedIndex*: int
text*: cstring
value*: cstring
TForm* {.importc.} = object of TEventHandlers
action*: cstring
encoding*: cstring
`method`*: cstring
name*: cstring
target*: cstring
handleEvent*: proc (event: ref TEvent) {.nimcall.}
reset*: proc () {.nimcall.}
submit*: proc () {.nimcall.}
elements*: seq[ref TElement]
TImage* {.importc.} = object of TEventHandlers
border*: int
complete*: bool
height*: int
hspace*: int
lowsrc*: cstring
name*: cstring
src*: cstring
vspace*: int
width*: int
handleEvent*: proc (event: ref TEvent) {.nimcall.}
ClassList* {.importc.} = object of RootObj
add*: proc (class: cstring) {.nimcall.}
remove*: proc (class: cstring) {.nimcall.}
contains*: proc (class: cstring):bool {.nimcall.}
toggle*: proc (class: cstring) {.nimcall.}
TNodeType* = enum
ElementNode = 1,
@@ -209,38 +113,151 @@ type
DocumentTypeNode,
DocumentFragmentNode,
NotationNode
TNode* {.importc.} = object of RootObj
attributes*: seq[ref TNode]
childNodes*: seq[ref TNode]
Node* = ref NodeObj
NodeObj {.importc.} = object of TEventHandlers
attributes*: seq[Node]
childNodes*: seq[Node]
children*: seq[Node]
data*: cstring
firstChild*: ref TNode
lastChild*: ref TNode
nextSibling*: ref TNode
firstChild*: Node
lastChild*: Node
nextSibling*: Node
nodeName*: cstring
nodeType*: TNodeType
nodeValue*: cstring
parentNode*: ref TNode
previousSibling*: ref TNode
appendChild*: proc (child: ref TNode) {.nimcall.}
parentNode*: Node
previousSibling*: Node
appendChild*: proc (child: Node) {.nimcall.}
appendData*: proc (data: cstring) {.nimcall.}
cloneNode*: proc (copyContent: bool) {.nimcall.}
cloneNode*: proc (copyContent: bool): Node {.nimcall.}
deleteData*: proc (start, len: int) {.nimcall.}
getAttribute*: proc (attr: cstring): cstring {.nimcall.}
getAttributeNode*: proc (attr: cstring): ref TNode {.nimcall.}
getElementsByTagName*: proc (): seq[ref TNode] {.nimcall.}
getAttributeNode*: proc (attr: cstring): Node {.nimcall.}
hasChildNodes*: proc (): bool {.nimcall.}
innerHTML*: cstring
insertBefore*: proc (newNode, before: ref TNode) {.nimcall.}
insertBefore*: proc (newNode, before: Node) {.nimcall.}
insertData*: proc (position: int, data: cstring) {.nimcall.}
removeAttribute*: proc (attr: cstring) {.nimcall.}
removeAttributeNode*: proc (attr: ref TNode) {.nimcall.}
removeChild*: proc (child: ref TNode) {.nimcall.}
replaceChild*: proc (newNode, oldNode: ref TNode) {.nimcall.}
removeAttributeNode*: proc (attr: Node) {.nimcall.}
removeChild*: proc (child: Node) {.nimcall.}
replaceChild*: proc (newNode, oldNode: Node) {.nimcall.}
replaceData*: proc (start, len: int, text: cstring) {.nimcall.}
scrollIntoView*: proc () {.nimcall.}
setAttribute*: proc (name, value: cstring) {.nimcall.}
setAttributeNode*: proc (attr: ref TNode) {.nimcall.}
setAttributeNode*: proc (attr: Node) {.nimcall.}
style*: ref TStyle
Document* = ref DocumentObj
DocumentObj {.importc.} = object of NodeObj
alinkColor*: cstring
bgColor*: cstring
charset*: cstring
cookie*: cstring
defaultCharset*: cstring
fgColor*: cstring
lastModified*: cstring
linkColor*: cstring
referrer*: cstring
title*: cstring
URL*: cstring
vlinkColor*: cstring
captureEvents*: proc (eventMask: int) {.nimcall.}
createAttribute*: proc (identifier: cstring): Node {.nimcall.}
createElement*: proc (identifier: cstring): Element {.nimcall.}
createTextNode*: proc (identifier: cstring): Node {.nimcall.}
getElementById*: proc (id: cstring): Element {.nimcall.}
getElementsByName*: proc (name: cstring): seq[Element] {.nimcall.}
getElementsByTagName*: proc (name: cstring): seq[Element] {.nimcall.}
getElementsByClassName*: proc (name: cstring): seq[Element] {.nimcall.}
getSelection*: proc (): cstring {.nimcall.}
handleEvent*: proc (event: ref TEvent) {.nimcall.}
open*: proc () {.nimcall.}
releaseEvents*: proc (eventMask: int) {.nimcall.}
routeEvent*: proc (event: ref TEvent) {.nimcall.}
write*: proc (text: cstring) {.nimcall.}
writeln*: proc (text: cstring) {.nimcall.}
anchors*: seq[AnchorElement]
forms*: seq[FormElement]
images*: seq[ImageElement]
applets*: seq[ref TApplet]
embeds*: seq[EmbedElement]
links*: seq[LinkElement]
Element* = ref ElementObj
ElementObj {.importc.} = object of NodeObj
classList*: ref Classlist
checked*: bool
defaultChecked*: bool
defaultValue*: cstring
disabled*: bool
form*: FormElement
name*: cstring
readOnly*: bool
blur*: proc () {.nimcall.}
click*: proc () {.nimcall.}
focus*: proc () {.nimcall.}
handleEvent*: proc (event: ref TEvent) {.nimcall.}
select*: proc () {.nimcall.}
options*: seq[OptionElement]
getElementsByTagName*: proc (name: cstring): seq[Element] {.nimcall.}
getElementsByClassName*: proc (name: cstring): seq[Element] {.nimcall.}
LinkElement* = ref LinkObj
LinkObj {.importc.} = object of ElementObj
target*: cstring
text*: cstring
x*: int
y*: int
EmbedElement* = ref EmbedObj
EmbedObj {.importc.} = object of ElementObj
height*: int
hspace*: int
src*: cstring
width*: int
`type`*: cstring
vspace*: int
play*: proc () {.nimcall.}
stop*: proc () {.nimcall.}
AnchorElement* = ref AnchorObj
AnchorObj {.importc.} = object of ElementObj
text*: cstring
x*, y*: int
TApplet* {.importc.} = object of RootObj
OptionElement* = ref OptionObj
OptionObj {.importc.} = object of ElementObj
defaultSelected*: bool
selected*: bool
selectedIndex*: int
text*: cstring
value*: cstring
FormElement* = ref FormObj
FormObj {.importc.} = object of ElementObj
action*: cstring
encoding*: cstring
`method`*: cstring
target*: cstring
reset*: proc () {.nimcall.}
submit*: proc () {.nimcall.}
elements*: seq[Element]
ImageElement* = ref ImageObj
ImageObj {.importc.} = object of ElementObj
border*: int
complete*: bool
height*: int
hspace*: int
lowsrc*: cstring
src*: cstring
vspace*: int
width*: int
TStyle* {.importc.} = object of RootObj
background*: cstring
backgroundAttachment*: cstring
@@ -336,6 +353,7 @@ type
setAttribute*: proc (attr, value: cstring, caseSensitive=false) {.nimcall.}
TEvent* {.importc.} = object of RootObj
target*: Node
altKey*, ctrlKey*, shiftKey*: bool
button*: int
clientX*, clientY*: int
@@ -433,8 +451,8 @@ type
TInterval* {.importc.} = object of RootObj
var
window* {.importc, nodecl.}: ref TWindow
document* {.importc, nodecl.}: ref TDocument
window* {.importc, nodecl.}: Window
document* {.importc, nodecl.}: Document
navigator* {.importc, nodecl.}: ref TNavigator
screen* {.importc, nodecl.}: ref TScreen
@@ -450,3 +468,18 @@ proc isFinite*(x: BiggestFloat): bool {.importc, nodecl.}
proc isNaN*(x: BiggestFloat): bool {.importc, nodecl.}
proc parseFloat*(s: cstring): BiggestFloat {.importc, nodecl.}
proc parseInt*(s: cstring): int {.importc, nodecl.}
proc parseInt*(s: cstring, radix: int):int {.importc, nodecl.}
type
TWindow* {.deprecated.} = WindowObj
TFrame* {.deprecated.} = FrameObj
TNode* {.deprecated.} = NodeObj
TDocument* {.deprecated.} = DocumentObj
TElement* {.deprecated.} = ElementObj
TLink* {.deprecated.} = LinkObj
TEmbed* {.deprecated.} = EmbedObj
TAnchor* {.deprecated.} = AnchorObj
TOption* {.deprecated.} = OptionObj
TForm* {.deprecated.} = FormObj
TImage* {.deprecated.} = ImageObj

View File

@@ -1,7 +1,7 @@
/*
Nim's Runtime Library
(c) Copyright 2014 Andreas Rumpf
(c) Copyright 2015 Andreas Rumpf
See the file "copying.txt", included in this
distribution, for details about the copyright.
@@ -67,7 +67,7 @@ __clang__
#endif
#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__))
# define NIM_THREADVAR __declspec(thread)
# define NIM_THREADVAR __declspec(thread)
#else
# define NIM_THREADVAR __thread
#endif
@@ -103,7 +103,11 @@ __clang__
# define N_FASTCALL_PTR(rettype, name) rettype (__fastcall *name)
# define N_SAFECALL_PTR(rettype, name) rettype (__safecall *name)
# define N_LIB_EXPORT extern __declspec(dllexport)
# ifdef __cplusplus
# define N_LIB_EXPORT extern "C" __declspec(dllexport)
# else
# define N_LIB_EXPORT extern __declspec(dllexport)
# endif
# define N_LIB_IMPORT extern __declspec(dllimport)
#else
# define N_CDECL(rettype, name) rettype name
@@ -118,7 +122,11 @@ __clang__
# define N_FASTCALL_PTR(rettype, name) rettype (*name)
# define N_SAFECALL_PTR(rettype, name) rettype (*name)
# define N_LIB_EXPORT extern
# ifdef __cplusplus
# define N_LIB_EXPORT extern "C"
# else
# define N_LIB_EXPORT extern
# endif
# define N_LIB_IMPORT extern
#endif
@@ -335,17 +343,17 @@ struct TFrame {
};
#define nimfr(proc, file) \
TFrame F; \
F.procname = proc; F.filename = file; F.line = 0; F.len = 0; nimFrame(&F);
TFrame FR; \
FR.procname = proc; FR.filename = file; FR.line = 0; FR.len = 0; nimFrame(&FR);
#define nimfrs(proc, file, slots, length) \
struct {TFrame* prev;NCSTRING procname;NI line;NCSTRING filename; NI len; TVarSlot s[slots];} F; \
F.procname = proc; F.filename = file; F.line = 0; F.len = length; nimFrame((TFrame*)&F);
struct {TFrame* prev;NCSTRING procname;NI line;NCSTRING filename; NI len; TVarSlot s[slots];} FR; \
FR.procname = proc; FR.filename = file; FR.line = 0; FR.len = length; nimFrame((TFrame*)&FR);
#define nimln(n, file) \
F.line = n; F.filename = file;
FR.line = n; FR.filename = file;
#define NIM_POSIX_INIT __attribute__((constructor))
#define NIM_POSIX_INIT __attribute__((constructor))
#if defined(_MSCVER) && defined(__i386__)
__declspec(naked) int __fastcall NimXadd(volatile int* pNum, int val) {
@@ -379,8 +387,8 @@ static inline void GCGuard (void *ptr) { asm volatile ("" :: "X" (ptr)); }
# define GC_GUARD
#endif
/* Test to see if nimrod and the C compiler agree on the size of a pointer.
On disagreement, your C compiler will say something like:
/* Test to see if Nim and the C compiler agree on the size of a pointer.
On disagreement, your C compiler will say something like:
"error: 'assert_numbits' declared as an array with a negative size" */
typedef int assert_numbits[sizeof(NI) == sizeof(void*) && NIM_INTBITS == sizeof(NI)*8 ? 1 : -1];
#endif
@@ -390,3 +398,14 @@ typedef int assert_numbits[sizeof(NI) == sizeof(void*) && NIM_INTBITS == sizeof(
#else
# define NIM_EXTERNC
#endif
/* ---------------- platform specific includes ----------------------- */
/* VxWorks related includes */
#if defined(__VXWORKS__)
# include <sys/types.h>
# include <types/vxWind.h>
# include <tool/gnu/toolMacros.h>
#elif defined(__FreeBSD__)
# include <sys/types.h>
#endif

View File

@@ -14,14 +14,14 @@
import
strutils
type
TTokenClass* = enum
gtEof, gtNone, gtWhitespace, gtDecNumber, gtBinNumber, gtHexNumber,
gtOctNumber, gtFloatNumber, gtIdentifier, gtKeyword, gtStringLit,
type
TTokenClass* = enum
gtEof, gtNone, gtWhitespace, gtDecNumber, gtBinNumber, gtHexNumber,
gtOctNumber, gtFloatNumber, gtIdentifier, gtKeyword, gtStringLit,
gtLongStringLit, gtCharLit, gtEscapeSequence, # escape sequence like \xff
gtOperator, gtPunctuation, gtComment, gtLongComment, gtRegularExpression,
gtTagStart, gtTagEnd, gtKey, gtValue, gtRawData, gtAssembler,
gtPreprocessor, gtDirective, gtCommand, gtRule, gtHyperlink, gtLabel,
gtOperator, gtPunctuation, gtComment, gtLongComment, gtRegularExpression,
gtTagStart, gtTagEnd, gtKey, gtValue, gtRawData, gtAssembler,
gtPreprocessor, gtDirective, gtCommand, gtRule, gtHyperlink, gtLabel,
gtReference, gtOther
TGeneralTokenizer* = object of RootObj
kind*: TTokenClass
@@ -30,27 +30,27 @@ type
pos: int
state: TTokenClass
TSourceLanguage* = enum
TSourceLanguage* = enum
langNone, langNim, langNimrod, langCpp, langCsharp, langC, langJava
const
const
sourceLanguageToStr*: array[TSourceLanguage, string] = ["none",
"Nim", "Nimrod", "C++", "C#", "C", "Java"]
tokenClassToStr*: array[TTokenClass, string] = ["Eof", "None", "Whitespace",
"DecNumber", "BinNumber", "HexNumber", "OctNumber", "FloatNumber",
"Identifier", "Keyword", "StringLit", "LongStringLit", "CharLit",
"EscapeSequence", "Operator", "Punctuation", "Comment", "LongComment",
"RegularExpression", "TagStart", "TagEnd", "Key", "Value", "RawData",
"Assembler", "Preprocessor", "Directive", "Command", "Rule", "Hyperlink",
tokenClassToStr*: array[TTokenClass, string] = ["Eof", "None", "Whitespace",
"DecNumber", "BinNumber", "HexNumber", "OctNumber", "FloatNumber",
"Identifier", "Keyword", "StringLit", "LongStringLit", "CharLit",
"EscapeSequence", "Operator", "Punctuation", "Comment", "LongComment",
"RegularExpression", "TagStart", "TagEnd", "Key", "Value", "RawData",
"Assembler", "Preprocessor", "Directive", "Command", "Rule", "Hyperlink",
"Label", "Reference", "Other"]
# The following list comes from doc/keywords.txt, make sure it is
# synchronized with this array by running the module itself as a test case.
nimKeywords = ["addr", "and", "as", "asm", "atomic", "bind", "block",
"break", "case", "cast", "const", "continue", "converter",
"break", "case", "cast", "concept", "const", "continue", "converter",
"defer", "discard", "distinct", "div", "do",
"elif", "else", "end", "enum", "except", "export",
"finally", "for", "from", "func",
"finally", "for", "from", "func",
"generic", "if", "import", "in", "include",
"interface", "is", "isnot", "iterator", "let", "macro", "method",
"mixin", "mod", "nil", "not", "notin", "object", "of", "or", "out", "proc",
@@ -58,12 +58,12 @@ const
"template", "try", "tuple", "type", "using", "var", "when", "while", "with",
"without", "xor", "yield"]
proc getSourceLanguage*(name: string): TSourceLanguage =
for i in countup(succ(low(TSourceLanguage)), high(TSourceLanguage)):
if cmpIgnoreStyle(name, sourceLanguageToStr[i]) == 0:
proc getSourceLanguage*(name: string): TSourceLanguage =
for i in countup(succ(low(TSourceLanguage)), high(TSourceLanguage)):
if cmpIgnoreStyle(name, sourceLanguageToStr[i]) == 0:
return i
result = langNone
proc initGeneralTokenizer*(g: var TGeneralTokenizer, buf: cstring) =
g.buf = buf
g.kind = low(TTokenClass)
@@ -74,52 +74,52 @@ proc initGeneralTokenizer*(g: var TGeneralTokenizer, buf: cstring) =
while g.buf[pos] in {' ', '\x09'..'\x0D'}: inc(pos)
g.pos = pos
proc initGeneralTokenizer*(g: var TGeneralTokenizer, buf: string) =
proc initGeneralTokenizer*(g: var TGeneralTokenizer, buf: string) =
initGeneralTokenizer(g, cstring(buf))
proc deinitGeneralTokenizer*(g: var TGeneralTokenizer) =
proc deinitGeneralTokenizer*(g: var TGeneralTokenizer) =
discard
proc nimGetKeyword(id: string): TTokenClass =
proc nimGetKeyword(id: string): TTokenClass =
for k in nimKeywords:
if cmpIgnoreStyle(id, k) == 0: return gtKeyword
result = gtIdentifier
when false:
var i = getIdent(id)
if (i.id >= ord(tokKeywordLow) - ord(tkSymbol)) and
(i.id <= ord(tokKeywordHigh) - ord(tkSymbol)):
(i.id <= ord(tokKeywordHigh) - ord(tkSymbol)):
result = gtKeyword
else:
else:
result = gtIdentifier
proc nimNumberPostfix(g: var TGeneralTokenizer, position: int): int =
proc nimNumberPostfix(g: var TGeneralTokenizer, position: int): int =
var pos = position
if g.buf[pos] == '\'':
if g.buf[pos] == '\'':
inc(pos)
case g.buf[pos]
of 'f', 'F':
of 'f', 'F':
g.kind = gtFloatNumber
inc(pos)
if g.buf[pos] in {'0'..'9'}: inc(pos)
if g.buf[pos] in {'0'..'9'}: inc(pos)
of 'i', 'I':
of 'i', 'I':
inc(pos)
if g.buf[pos] in {'0'..'9'}: inc(pos)
if g.buf[pos] in {'0'..'9'}: inc(pos)
else:
else:
discard
result = pos
proc nimNumber(g: var TGeneralTokenizer, position: int): int =
proc nimNumber(g: var TGeneralTokenizer, position: int): int =
const decChars = {'0'..'9', '_'}
var pos = position
g.kind = gtDecNumber
while g.buf[pos] in decChars: inc(pos)
if g.buf[pos] == '.':
if g.buf[pos] == '.':
g.kind = gtFloatNumber
inc(pos)
while g.buf[pos] in decChars: inc(pos)
if g.buf[pos] in {'e', 'E'}:
if g.buf[pos] in {'e', 'E'}:
g.kind = gtFloatNumber
inc(pos)
if g.buf[pos] in {'+', '-'}: inc(pos)
@@ -127,150 +127,150 @@ proc nimNumber(g: var TGeneralTokenizer, position: int): int =
result = nimNumberPostfix(g, pos)
const
OpChars = {'+', '-', '*', '/', '\\', '<', '>', '!', '?', '^', '.',
OpChars = {'+', '-', '*', '/', '\\', '<', '>', '!', '?', '^', '.',
'|', '=', '%', '&', '$', '@', '~', ':', '\x80'..'\xFF'}
proc nimNextToken(g: var TGeneralTokenizer) =
const
proc nimNextToken(g: var TGeneralTokenizer) =
const
hexChars = {'0'..'9', 'A'..'F', 'a'..'f', '_'}
octChars = {'0'..'7', '_'}
binChars = {'0'..'1', '_'}
SymChars = {'a'..'z', 'A'..'Z', '0'..'9', '\x80'..'\xFF'}
var pos = g.pos
g.start = g.pos
if g.state == gtStringLit:
if g.state == gtStringLit:
g.kind = gtStringLit
while true:
while true:
case g.buf[pos]
of '\\':
of '\\':
g.kind = gtEscapeSequence
inc(pos)
case g.buf[pos]
of 'x', 'X':
of 'x', 'X':
inc(pos)
if g.buf[pos] in hexChars: inc(pos)
if g.buf[pos] in hexChars: inc(pos)
of '0'..'9':
of '0'..'9':
while g.buf[pos] in {'0'..'9'}: inc(pos)
of '\0':
of '\0':
g.state = gtNone
else: inc(pos)
break
of '\0', '\x0D', '\x0A':
break
of '\0', '\x0D', '\x0A':
g.state = gtNone
break
of '\"':
break
of '\"':
inc(pos)
g.state = gtNone
break
break
else: inc(pos)
else:
else:
case g.buf[pos]
of ' ', '\x09'..'\x0D':
of ' ', '\x09'..'\x0D':
g.kind = gtWhitespace
while g.buf[pos] in {' ', '\x09'..'\x0D'}: inc(pos)
of '#':
of '#':
g.kind = gtComment
while not (g.buf[pos] in {'\0', '\x0A', '\x0D'}): inc(pos)
of 'a'..'z', 'A'..'Z', '_', '\x80'..'\xFF':
of 'a'..'z', 'A'..'Z', '_', '\x80'..'\xFF':
var id = ""
while g.buf[pos] in SymChars + {'_'}:
while g.buf[pos] in SymChars + {'_'}:
add(id, g.buf[pos])
inc(pos)
if (g.buf[pos] == '\"'):
if (g.buf[pos + 1] == '\"') and (g.buf[pos + 2] == '\"'):
if (g.buf[pos] == '\"'):
if (g.buf[pos + 1] == '\"') and (g.buf[pos + 2] == '\"'):
inc(pos, 3)
g.kind = gtLongStringLit
while true:
while true:
case g.buf[pos]
of '\0':
break
of '\"':
of '\0':
break
of '\"':
inc(pos)
if g.buf[pos] == '\"' and g.buf[pos+1] == '\"' and
g.buf[pos+2] != '\"':
if g.buf[pos] == '\"' and g.buf[pos+1] == '\"' and
g.buf[pos+2] != '\"':
inc(pos, 2)
break
break
else: inc(pos)
else:
else:
g.kind = gtRawData
inc(pos)
while not (g.buf[pos] in {'\0', '\x0A', '\x0D'}):
while not (g.buf[pos] in {'\0', '\x0A', '\x0D'}):
if g.buf[pos] == '"' and g.buf[pos+1] != '"': break
inc(pos)
if g.buf[pos] == '\"': inc(pos)
else:
else:
g.kind = nimGetKeyword(id)
of '0':
of '0':
inc(pos)
case g.buf[pos]
of 'b', 'B':
of 'b', 'B':
inc(pos)
while g.buf[pos] in binChars: inc(pos)
pos = nimNumberPostfix(g, pos)
of 'x', 'X':
of 'x', 'X':
inc(pos)
while g.buf[pos] in hexChars: inc(pos)
pos = nimNumberPostfix(g, pos)
of 'o', 'O':
of 'o', 'O':
inc(pos)
while g.buf[pos] in octChars: inc(pos)
pos = nimNumberPostfix(g, pos)
else: pos = nimNumber(g, pos)
of '1'..'9':
of '1'..'9':
pos = nimNumber(g, pos)
of '\'':
of '\'':
inc(pos)
g.kind = gtCharLit
while true:
while true:
case g.buf[pos]
of '\0', '\x0D', '\x0A':
break
of '\'':
of '\0', '\x0D', '\x0A':
break
of '\'':
inc(pos)
break
of '\\':
break
of '\\':
inc(pos, 2)
else: inc(pos)
of '\"':
of '\"':
inc(pos)
if (g.buf[pos] == '\"') and (g.buf[pos + 1] == '\"'):
if (g.buf[pos] == '\"') and (g.buf[pos + 1] == '\"'):
inc(pos, 2)
g.kind = gtLongStringLit
while true:
while true:
case g.buf[pos]
of '\0':
break
of '\"':
of '\0':
break
of '\"':
inc(pos)
if g.buf[pos] == '\"' and g.buf[pos+1] == '\"' and
g.buf[pos+2] != '\"':
if g.buf[pos] == '\"' and g.buf[pos+1] == '\"' and
g.buf[pos+2] != '\"':
inc(pos, 2)
break
break
else: inc(pos)
else:
else:
g.kind = gtStringLit
while true:
while true:
case g.buf[pos]
of '\0', '\x0D', '\x0A':
break
of '\"':
of '\0', '\x0D', '\x0A':
break
of '\"':
inc(pos)
break
of '\\':
break
of '\\':
g.state = g.kind
break
break
else: inc(pos)
of '(', ')', '[', ']', '{', '}', '`', ':', ',', ';':
of '(', ')', '[', ']', '{', '}', '`', ':', ',', ';':
inc(pos)
g.kind = gtPunctuation
of '\0':
of '\0':
g.kind = gtEof
else:
if g.buf[pos] in OpChars:
else:
if g.buf[pos] in OpChars:
g.kind = gtOperator
while g.buf[pos] in OpChars: inc(pos)
else:
else:
inc(pos)
g.kind = gtNone
g.length = pos - g.pos
@@ -278,211 +278,211 @@ proc nimNextToken(g: var TGeneralTokenizer) =
assert false, "nimNextToken: produced an empty token"
g.pos = pos
proc generalNumber(g: var TGeneralTokenizer, position: int): int =
proc generalNumber(g: var TGeneralTokenizer, position: int): int =
const decChars = {'0'..'9'}
var pos = position
g.kind = gtDecNumber
while g.buf[pos] in decChars: inc(pos)
if g.buf[pos] == '.':
if g.buf[pos] == '.':
g.kind = gtFloatNumber
inc(pos)
while g.buf[pos] in decChars: inc(pos)
if g.buf[pos] in {'e', 'E'}:
if g.buf[pos] in {'e', 'E'}:
g.kind = gtFloatNumber
inc(pos)
if g.buf[pos] in {'+', '-'}: inc(pos)
while g.buf[pos] in decChars: inc(pos)
result = pos
proc generalStrLit(g: var TGeneralTokenizer, position: int): int =
const
proc generalStrLit(g: var TGeneralTokenizer, position: int): int =
const
decChars = {'0'..'9'}
hexChars = {'0'..'9', 'A'..'F', 'a'..'f'}
var pos = position
g.kind = gtStringLit
var c = g.buf[pos]
inc(pos) # skip " or '
while true:
while true:
case g.buf[pos]
of '\0':
break
of '\\':
of '\0':
break
of '\\':
inc(pos)
case g.buf[pos]
of '\0':
break
of '0'..'9':
of '\0':
break
of '0'..'9':
while g.buf[pos] in decChars: inc(pos)
of 'x', 'X':
of 'x', 'X':
inc(pos)
if g.buf[pos] in hexChars: inc(pos)
if g.buf[pos] in hexChars: inc(pos)
else: inc(pos, 2)
else:
if g.buf[pos] == c:
else:
if g.buf[pos] == c:
inc(pos)
break
else:
break
else:
inc(pos)
result = pos
proc isKeyword(x: openArray[string], y: string): int =
proc isKeyword(x: openArray[string], y: string): int =
var a = 0
var b = len(x) - 1
while a <= b:
while a <= b:
var mid = (a + b) div 2
var c = cmp(x[mid], y)
if c < 0:
if c < 0:
a = mid + 1
elif c > 0:
elif c > 0:
b = mid - 1
else:
else:
return mid
result = - 1
proc isKeywordIgnoreCase(x: openArray[string], y: string): int =
proc isKeywordIgnoreCase(x: openArray[string], y: string): int =
var a = 0
var b = len(x) - 1
while a <= b:
while a <= b:
var mid = (a + b) div 2
var c = cmpIgnoreCase(x[mid], y)
if c < 0:
if c < 0:
a = mid + 1
elif c > 0:
elif c > 0:
b = mid - 1
else:
else:
return mid
result = - 1
type
TTokenizerFlag = enum
type
TTokenizerFlag = enum
hasPreprocessor, hasNestedComments
TTokenizerFlags = set[TTokenizerFlag]
proc clikeNextToken(g: var TGeneralTokenizer, keywords: openArray[string],
flags: TTokenizerFlags) =
const
proc clikeNextToken(g: var TGeneralTokenizer, keywords: openArray[string],
flags: TTokenizerFlags) =
const
hexChars = {'0'..'9', 'A'..'F', 'a'..'f'}
octChars = {'0'..'7'}
binChars = {'0'..'1'}
symChars = {'A'..'Z', 'a'..'z', '0'..'9', '_', '\x80'..'\xFF'}
var pos = g.pos
g.start = g.pos
if g.state == gtStringLit:
if g.state == gtStringLit:
g.kind = gtStringLit
while true:
while true:
case g.buf[pos]
of '\\':
of '\\':
g.kind = gtEscapeSequence
inc(pos)
case g.buf[pos]
of 'x', 'X':
of 'x', 'X':
inc(pos)
if g.buf[pos] in hexChars: inc(pos)
if g.buf[pos] in hexChars: inc(pos)
of '0'..'9':
of '0'..'9':
while g.buf[pos] in {'0'..'9'}: inc(pos)
of '\0':
of '\0':
g.state = gtNone
else: inc(pos)
break
of '\0', '\x0D', '\x0A':
break
of '\0', '\x0D', '\x0A':
g.state = gtNone
break
of '\"':
break
of '\"':
inc(pos)
g.state = gtNone
break
break
else: inc(pos)
else:
else:
case g.buf[pos]
of ' ', '\x09'..'\x0D':
of ' ', '\x09'..'\x0D':
g.kind = gtWhitespace
while g.buf[pos] in {' ', '\x09'..'\x0D'}: inc(pos)
of '/':
of '/':
inc(pos)
if g.buf[pos] == '/':
if g.buf[pos] == '/':
g.kind = gtComment
while not (g.buf[pos] in {'\0', '\x0A', '\x0D'}): inc(pos)
elif g.buf[pos] == '*':
elif g.buf[pos] == '*':
g.kind = gtLongComment
var nested = 0
inc(pos)
while true:
while true:
case g.buf[pos]
of '*':
of '*':
inc(pos)
if g.buf[pos] == '/':
if g.buf[pos] == '/':
inc(pos)
if nested == 0: break
of '/':
if nested == 0: break
of '/':
inc(pos)
if g.buf[pos] == '*':
if g.buf[pos] == '*':
inc(pos)
if hasNestedComments in flags: inc(nested)
of '\0':
break
of '\0':
break
else: inc(pos)
of '#':
of '#':
inc(pos)
if hasPreprocessor in flags:
if hasPreprocessor in flags:
g.kind = gtPreprocessor
while g.buf[pos] in {' ', '\t'}: inc(pos)
while g.buf[pos] in symChars: inc(pos)
else:
else:
g.kind = gtOperator
of 'a'..'z', 'A'..'Z', '_', '\x80'..'\xFF':
of 'a'..'z', 'A'..'Z', '_', '\x80'..'\xFF':
var id = ""
while g.buf[pos] in symChars:
while g.buf[pos] in symChars:
add(id, g.buf[pos])
inc(pos)
if isKeyword(keywords, id) >= 0: g.kind = gtKeyword
else: g.kind = gtIdentifier
of '0':
of '0':
inc(pos)
case g.buf[pos]
of 'b', 'B':
of 'b', 'B':
inc(pos)
while g.buf[pos] in binChars: inc(pos)
if g.buf[pos] in {'A'..'Z', 'a'..'z'}: inc(pos)
of 'x', 'X':
of 'x', 'X':
inc(pos)
while g.buf[pos] in hexChars: inc(pos)
if g.buf[pos] in {'A'..'Z', 'a'..'z'}: inc(pos)
of '0'..'7':
of '0'..'7':
inc(pos)
while g.buf[pos] in octChars: inc(pos)
if g.buf[pos] in {'A'..'Z', 'a'..'z'}: inc(pos)
else:
else:
pos = generalNumber(g, pos)
if g.buf[pos] in {'A'..'Z', 'a'..'z'}: inc(pos)
of '1'..'9':
of '1'..'9':
pos = generalNumber(g, pos)
if g.buf[pos] in {'A'..'Z', 'a'..'z'}: inc(pos)
of '\'':
of '\'':
pos = generalStrLit(g, pos)
g.kind = gtCharLit
of '\"':
of '\"':
inc(pos)
g.kind = gtStringLit
while true:
while true:
case g.buf[pos]
of '\0':
break
of '\"':
of '\0':
break
of '\"':
inc(pos)
break
of '\\':
break
of '\\':
g.state = g.kind
break
break
else: inc(pos)
of '(', ')', '[', ']', '{', '}', ':', ',', ';', '.':
of '(', ')', '[', ']', '{', '}', ':', ',', ';', '.':
inc(pos)
g.kind = gtPunctuation
of '\0':
of '\0':
g.kind = gtEof
else:
if g.buf[pos] in OpChars:
else:
if g.buf[pos] in OpChars:
g.kind = gtOperator
while g.buf[pos] in OpChars: inc(pos)
else:
@@ -493,55 +493,55 @@ proc clikeNextToken(g: var TGeneralTokenizer, keywords: openArray[string],
assert false, "clikeNextToken: produced an empty token"
g.pos = pos
proc cNextToken(g: var TGeneralTokenizer) =
const
keywords: array[0..36, string] = ["_Bool", "_Complex", "_Imaginary", "auto",
"break", "case", "char", "const", "continue", "default", "do", "double",
"else", "enum", "extern", "float", "for", "goto", "if", "inline", "int",
"long", "register", "restrict", "return", "short", "signed", "sizeof",
"static", "struct", "switch", "typedef", "union", "unsigned", "void",
proc cNextToken(g: var TGeneralTokenizer) =
const
keywords: array[0..36, string] = ["_Bool", "_Complex", "_Imaginary", "auto",
"break", "case", "char", "const", "continue", "default", "do", "double",
"else", "enum", "extern", "float", "for", "goto", "if", "inline", "int",
"long", "register", "restrict", "return", "short", "signed", "sizeof",
"static", "struct", "switch", "typedef", "union", "unsigned", "void",
"volatile", "while"]
clikeNextToken(g, keywords, {hasPreprocessor})
proc cppNextToken(g: var TGeneralTokenizer) =
const
keywords: array[0..47, string] = ["asm", "auto", "break", "case", "catch",
"char", "class", "const", "continue", "default", "delete", "do", "double",
"else", "enum", "extern", "float", "for", "friend", "goto", "if",
"inline", "int", "long", "new", "operator", "private", "protected",
"public", "register", "return", "short", "signed", "sizeof", "static",
"struct", "switch", "template", "this", "throw", "try", "typedef",
proc cppNextToken(g: var TGeneralTokenizer) =
const
keywords: array[0..47, string] = ["asm", "auto", "break", "case", "catch",
"char", "class", "const", "continue", "default", "delete", "do", "double",
"else", "enum", "extern", "float", "for", "friend", "goto", "if",
"inline", "int", "long", "new", "operator", "private", "protected",
"public", "register", "return", "short", "signed", "sizeof", "static",
"struct", "switch", "template", "this", "throw", "try", "typedef",
"union", "unsigned", "virtual", "void", "volatile", "while"]
clikeNextToken(g, keywords, {hasPreprocessor})
proc csharpNextToken(g: var TGeneralTokenizer) =
const
keywords: array[0..76, string] = ["abstract", "as", "base", "bool", "break",
"byte", "case", "catch", "char", "checked", "class", "const", "continue",
"decimal", "default", "delegate", "do", "double", "else", "enum", "event",
"explicit", "extern", "false", "finally", "fixed", "float", "for",
"foreach", "goto", "if", "implicit", "in", "int", "interface", "internal",
"is", "lock", "long", "namespace", "new", "null", "object", "operator",
"out", "override", "params", "private", "protected", "public", "readonly",
"ref", "return", "sbyte", "sealed", "short", "sizeof", "stackalloc",
"static", "string", "struct", "switch", "this", "throw", "true", "try",
"typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using",
proc csharpNextToken(g: var TGeneralTokenizer) =
const
keywords: array[0..76, string] = ["abstract", "as", "base", "bool", "break",
"byte", "case", "catch", "char", "checked", "class", "const", "continue",
"decimal", "default", "delegate", "do", "double", "else", "enum", "event",
"explicit", "extern", "false", "finally", "fixed", "float", "for",
"foreach", "goto", "if", "implicit", "in", "int", "interface", "internal",
"is", "lock", "long", "namespace", "new", "null", "object", "operator",
"out", "override", "params", "private", "protected", "public", "readonly",
"ref", "return", "sbyte", "sealed", "short", "sizeof", "stackalloc",
"static", "string", "struct", "switch", "this", "throw", "true", "try",
"typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using",
"virtual", "void", "volatile", "while"]
clikeNextToken(g, keywords, {hasPreprocessor})
proc javaNextToken(g: var TGeneralTokenizer) =
const
keywords: array[0..52, string] = ["abstract", "assert", "boolean", "break",
"byte", "case", "catch", "char", "class", "const", "continue", "default",
"do", "double", "else", "enum", "extends", "false", "final", "finally",
"float", "for", "goto", "if", "implements", "import", "instanceof", "int",
"interface", "long", "native", "new", "null", "package", "private",
"protected", "public", "return", "short", "static", "strictfp", "super",
"switch", "synchronized", "this", "throw", "throws", "transient", "true",
proc javaNextToken(g: var TGeneralTokenizer) =
const
keywords: array[0..52, string] = ["abstract", "assert", "boolean", "break",
"byte", "case", "catch", "char", "class", "const", "continue", "default",
"do", "double", "else", "enum", "extends", "false", "final", "finally",
"float", "for", "goto", "if", "implements", "import", "instanceof", "int",
"interface", "long", "native", "new", "null", "package", "private",
"protected", "public", "return", "short", "static", "strictfp", "super",
"switch", "synchronized", "this", "throw", "throws", "transient", "true",
"try", "void", "volatile", "while"]
clikeNextToken(g, keywords, {})
proc getNextToken*(g: var TGeneralTokenizer, lang: TSourceLanguage) =
proc getNextToken*(g: var TGeneralTokenizer, lang: TSourceLanguage) =
case lang
of langNone: assert false
of langNim, langNimrod: nimNextToken(g)
@@ -549,15 +549,17 @@ proc getNextToken*(g: var TGeneralTokenizer, lang: TSourceLanguage) =
of langCsharp: csharpNextToken(g)
of langC: cNextToken(g)
of langJava: javaNextToken(g)
when isMainModule:
var keywords: seq[string]
# Try to work running in both the subdir or at the root.
for filename in ["doc/keywords.txt", "../../../doc/keywords.txt"]:
except: echo filename, " not found"
let input = string(readFile(filename))
keywords = input.split()
break
try:
let input = string(readFile(filename))
keywords = input.split()
break
except:
echo filename, " not found"
doAssert(not keywords.isNil, "Couldn't read any keywords.txt file!")
doAssert keywords.len == nimKeywords.len, "No matching lengths"
for i in 0..keywords.len-1:

File diff suppressed because it is too large Load Diff

View File

@@ -30,7 +30,7 @@ type
rnField, # a field item
rnFieldName, # consisting of a field name ...
rnFieldBody, # ... and a field body
rnOptionList, rnOptionListItem, rnOptionGroup, rnOption, rnOptionString,
rnOptionList, rnOptionListItem, rnOptionGroup, rnOption, rnOptionString,
rnOptionArgument, rnDescription, rnLiteralBlock, rnQuotedLiteralBlock,
rnLineBlock, # the | thingie
rnLineBlockItem, # sons of the | thing
@@ -50,7 +50,7 @@ type
# * `file#id <file#id>'_
rnSubstitutionDef, # a definition of a substitution
rnGeneralRole, # Inline markup:
rnSub, rnSup, rnIdx,
rnSub, rnSup, rnIdx,
rnEmphasis, # "*"
rnStrongEmphasis, # "**"
rnTripleEmphasis, # "***"
@@ -71,25 +71,25 @@ type
level*: int ## valid for some node kinds
sons*: TRstNodeSeq ## the node's sons
proc len*(n: PRstNode): int =
proc len*(n: PRstNode): int =
result = len(n.sons)
proc newRstNode*(kind: TRstNodeKind): PRstNode =
proc newRstNode*(kind: TRstNodeKind): PRstNode =
new(result)
result.sons = @[]
result.kind = kind
proc newRstNode*(kind: TRstNodeKind, s: string): PRstNode =
proc newRstNode*(kind: TRstNodeKind, s: string): PRstNode =
result = newRstNode(kind)
result.text = s
proc lastSon*(n: PRstNode): PRstNode =
proc lastSon*(n: PRstNode): PRstNode =
result = n.sons[len(n.sons)-1]
proc add*(father, son: PRstNode) =
add(father.sons, son)
proc addIfNotNil*(father, son: PRstNode) =
proc addIfNotNil*(father, son: PRstNode) =
if son != nil: add(father, son)
@@ -98,62 +98,63 @@ type
indent: int
verbatim: int
proc renderRstToRst(d: var TRenderContext, n: PRstNode, result: var string)
proc renderRstToRst(d: var TRenderContext, n: PRstNode,
result: var string) {.gcsafe.}
proc renderRstSons(d: var TRenderContext, n: PRstNode, result: var string) =
for i in countup(0, len(n) - 1):
proc renderRstSons(d: var TRenderContext, n: PRstNode, result: var string) =
for i in countup(0, len(n) - 1):
renderRstToRst(d, n.sons[i], result)
proc renderRstToRst(d: var TRenderContext, n: PRstNode, result: var string) =
# this is needed for the index generation; it may also be useful for
# debugging, but most code is already debugged...
const
const
lvlToChar: array[0..8, char] = ['!', '=', '-', '~', '`', '<', '*', '|', '+']
if n == nil: return
var ind = repeatChar(d.indent)
var ind = spaces(d.indent)
case n.kind
of rnInner:
of rnInner:
renderRstSons(d, n, result)
of rnHeadline:
result.add("\n")
result.add(ind)
let oldLen = result.len
renderRstSons(d, n, result)
let headlineLen = result.len - oldLen
result.add("\n")
result.add(ind)
result.add repeatChar(headlineLen, lvlToChar[n.level])
result.add repeat(lvlToChar[n.level], headlineLen)
of rnOverline:
result.add("\n")
result.add(ind)
var headline = ""
renderRstSons(d, n, headline)
let lvl = repeatChar(headline.len - d.indent, lvlToChar[n.level])
let lvl = repeat(lvlToChar[n.level], headline.len - d.indent)
result.add(lvl)
result.add("\n")
result.add(headline)
result.add("\n")
result.add(ind)
result.add(lvl)
of rnTransition:
of rnTransition:
result.add("\n\n")
result.add(ind)
result.add repeatChar(78-d.indent, '-')
result.add repeat('-', 78-d.indent)
result.add("\n\n")
of rnParagraph:
result.add("\n\n")
result.add(ind)
renderRstSons(d, n, result)
of rnBulletItem:
of rnBulletItem:
inc(d.indent, 2)
var tmp = ""
renderRstSons(d, n, tmp)
if tmp.len > 0:
if tmp.len > 0:
result.add("\n")
result.add(ind)
result.add("* ")
@@ -163,22 +164,22 @@ proc renderRstToRst(d: var TRenderContext, n: PRstNode, result: var string) =
inc(d.indent, 4)
var tmp = ""
renderRstSons(d, n, tmp)
if tmp.len > 0:
if tmp.len > 0:
result.add("\n")
result.add(ind)
result.add("(#) ")
result.add(tmp)
dec(d.indent, 4)
of rnOptionList, rnFieldList, rnDefList, rnDefItem, rnLineBlock, rnFieldName,
rnFieldBody, rnStandaloneHyperlink, rnBulletList, rnEnumList:
of rnOptionList, rnFieldList, rnDefList, rnDefItem, rnLineBlock, rnFieldName,
rnFieldBody, rnStandaloneHyperlink, rnBulletList, rnEnumList:
renderRstSons(d, n, result)
of rnDefName:
of rnDefName:
result.add("\n\n")
result.add(ind)
renderRstSons(d, n, result)
of rnDefBody:
inc(d.indent, 2)
if n.sons[0].kind != rnBulletList:
if n.sons[0].kind != rnBulletList:
result.add("\n")
result.add(ind)
result.add(" ")
@@ -187,20 +188,20 @@ proc renderRstToRst(d: var TRenderContext, n: PRstNode, result: var string) =
of rnField:
var tmp = ""
renderRstToRst(d, n.sons[0], tmp)
var L = max(tmp.len + 3, 30)
inc(d.indent, L)
result.add "\n"
result.add ind
result.add ':'
result.add tmp
result.add ':'
result.add repeatChar(L - tmp.len - 2)
result.add spaces(L - tmp.len - 2)
renderRstToRst(d, n.sons[1], result)
dec(d.indent, L)
of rnLineBlockItem:
of rnLineBlockItem:
result.add("\n")
result.add(ind)
result.add("| ")
@@ -209,11 +210,11 @@ proc renderRstToRst(d: var TRenderContext, n: PRstNode, result: var string) =
inc(d.indent, 2)
renderRstSons(d, n, result)
dec(d.indent, 2)
of rnRef:
of rnRef:
result.add("`")
renderRstSons(d, n, result)
result.add("`_")
of rnHyperlink:
of rnHyperlink:
result.add('`')
renderRstToRst(d, n.sons[0], result)
result.add(" <")
@@ -225,23 +226,23 @@ proc renderRstToRst(d: var TRenderContext, n: PRstNode, result: var string) =
result.add("`:")
renderRstToRst(d, n.sons[1],result)
result.add(':')
of rnSub:
of rnSub:
result.add('`')
renderRstSons(d, n, result)
result.add("`:sub:")
of rnSup:
of rnSup:
result.add('`')
renderRstSons(d, n, result)
result.add("`:sup:")
of rnIdx:
of rnIdx:
result.add('`')
renderRstSons(d, n, result)
result.add("`:idx:")
of rnEmphasis:
of rnEmphasis:
result.add("*")
renderRstSons(d, n, result)
result.add("*")
of rnStrongEmphasis:
of rnStrongEmphasis:
result.add("**")
renderRstSons(d, n, result)
result.add("**")
@@ -249,11 +250,11 @@ proc renderRstToRst(d: var TRenderContext, n: PRstNode, result: var string) =
result.add("***")
renderRstSons(d, n, result)
result.add("***")
of rnInterpretedText:
of rnInterpretedText:
result.add('`')
renderRstSons(d, n, result)
result.add('`')
of rnInlineLiteral:
of rnInlineLiteral:
inc(d.verbatim)
result.add("``")
renderRstSons(d, n, result)
@@ -266,11 +267,11 @@ proc renderRstToRst(d: var TRenderContext, n: PRstNode, result: var string) =
result.add("\\\\") # XXX: escape more special characters!
else:
result.add(n.text)
of rnIndex:
of rnIndex:
result.add("\n\n")
result.add(ind)
result.add(".. index::\n")
inc(d.indent, 3)
if n.sons[2] != nil: renderRstSons(d, n.sons[2], result)
dec(d.indent, 3)
@@ -280,7 +281,7 @@ proc renderRstToRst(d: var TRenderContext, n: PRstNode, result: var string) =
result.add(".. contents::")
else:
result.add("Error: cannot render: " & $n.kind)
proc renderRstToRst*(n: PRstNode, result: var string) =
## renders `n` into its string representation and appends to `result`.
var d: TRenderContext
@@ -302,7 +303,7 @@ proc renderRstToJsonNode(node: PRstNode): JsonNode =
proc renderRstToJson*(node: PRstNode): string =
## Writes the given RST node as JSON that is in the form
## ::
## ::
## {
## "kind":string node.kind,
## "text":optional string node.text,

View File

@@ -34,14 +34,14 @@ type
TOutputTarget* = enum ## which document type to generate
outHtml, # output is HTML
outLatex # output is Latex
TTocEntry = object
TTocEntry = object
n*: PRstNode
refname*, header*: string
TMetaEnum* = enum
TMetaEnum* = enum
metaNone, metaTitle, metaSubtitle, metaAuthor, metaVersion
TRstGenerator* = object of RootObj
target*: TOutputTarget
config*: StringTableRef
@@ -60,7 +60,7 @@ type
seenIndexTerms: Table[string, int] ## \
## Keeps count of same text index terms to generate different identifiers
## for hyperlinks. See renderIndexTerm proc for details.
PDoc = var TRstGenerator ## Alias to type less.
CodeBlockParams = object ## Stores code block params.
@@ -136,7 +136,7 @@ proc initRstGenerator*(g: var TRstGenerator, target: TOutputTarget,
g.currentSection = "Module " & fileParts.name
g.seenIndexTerms = initTable[string, int]()
g.msgHandler = msgHandler
let s = config["split.item.toc"]
if s != "": g.splitAfter = parseInt(s)
for i in low(g.meta)..high(g.meta): g.meta[i] = ""
@@ -147,23 +147,23 @@ proc writeIndexFile*(g: var TRstGenerator, outfile: string) =
## You previously need to add entries to the index with the `setIndexTerm()
## <#setIndexTerm>`_ proc. If the index is empty the file won't be created.
if g.theIndex.len > 0: writeFile(outfile, g.theIndex)
proc addXmlChar(dest: var string, c: char) =
proc addXmlChar(dest: var string, c: char) =
case c
of '&': add(dest, "&amp;")
of '<': add(dest, "&lt;")
of '>': add(dest, "&gt;")
of '\"': add(dest, "&quot;")
else: add(dest, c)
proc addRtfChar(dest: var string, c: char) =
proc addRtfChar(dest: var string, c: char) =
case c
of '{': add(dest, "\\{")
of '}': add(dest, "\\}")
of '\\': add(dest, "\\\\")
else: add(dest, c)
proc addTexChar(dest: var string, c: char) =
proc addTexChar(dest: var string, c: char) =
case c
of '_': add(dest, "\\_")
of '{': add(dest, "\\symbol{123}")
@@ -183,54 +183,54 @@ proc addTexChar(dest: var string, c: char) =
var splitter*: string = "<wbr />"
proc escChar*(target: TOutputTarget, dest: var string, c: char) {.inline.} =
proc escChar*(target: TOutputTarget, dest: var string, c: char) {.inline.} =
case target
of outHtml: addXmlChar(dest, c)
of outLatex: addTexChar(dest, c)
proc nextSplitPoint*(s: string, start: int): int =
proc nextSplitPoint*(s: string, start: int): int =
result = start
while result < len(s) + 0:
while result < len(s) + 0:
case s[result]
of '_': return
of 'a'..'z':
if result + 1 < len(s) + 0:
if s[result + 1] in {'A'..'Z'}: return
of '_': return
of 'a'..'z':
if result + 1 < len(s) + 0:
if s[result + 1] in {'A'..'Z'}: return
else: discard
inc(result)
dec(result) # last valid index
proc esc*(target: TOutputTarget, s: string, splitAfter = -1): string =
proc esc*(target: TOutputTarget, s: string, splitAfter = -1): string =
result = ""
if splitAfter >= 0:
if splitAfter >= 0:
var partLen = 0
var j = 0
while j < len(s):
while j < len(s):
var k = nextSplitPoint(s, j)
if (splitter != " ") or (partLen + k - j + 1 > splitAfter):
if (splitter != " ") or (partLen + k - j + 1 > splitAfter):
partLen = 0
add(result, splitter)
for i in countup(j, k): escChar(target, result, s[i])
inc(partLen, k - j + 1)
j = k + 1
else:
else:
for i in countup(0, len(s) - 1): escChar(target, result, s[i])
proc disp(target: TOutputTarget, xml, tex: string): string =
if target != outLatex: result = xml
if target != outLatex: result = xml
else: result = tex
proc dispF(target: TOutputTarget, xml, tex: string,
args: varargs[string]): string =
if target != outLatex: result = xml % args
proc dispF(target: TOutputTarget, xml, tex: string,
args: varargs[string]): string =
if target != outLatex: result = xml % args
else: result = tex % args
proc dispA(target: TOutputTarget, dest: var string,
proc dispA(target: TOutputTarget, dest: var string,
xml, tex: string, args: varargs[string]) =
if target != outLatex: addf(dest, xml, args)
else: addf(dest, tex, args)
proc `or`(x, y: string): string {.inline.} =
result = if x.isNil: y else: x
@@ -248,7 +248,7 @@ proc renderRstToOut*(d: var TRstGenerator, n: PRstNode, result: var string)
## renderRstToOut(gen, rst, generatedHTML)
## echo generatedHTML
proc renderAux(d: PDoc, n: PRstNode, result: var string) =
proc renderAux(d: PDoc, n: PRstNode, result: var string) =
for i in countup(0, len(n)-1): renderRstToOut(d, n.sons[i], result)
proc renderAux(d: PDoc, n: PRstNode, frmtA, frmtB: string, result: var string) =
@@ -347,7 +347,7 @@ proc renderIndexTerm*(d: PDoc, n: PRstNode, result: var string) =
var term = ""
renderAux(d, n, term)
setIndexTerm(d, id, term, d.currentSection)
dispA(d.target, result, "<span id=\"$1\">$2</span>", "$2\\label{$1}",
dispA(d.target, result, "<span id=\"$1\">$2</span>", "$2\\label{$1}",
[id, term])
type
@@ -417,10 +417,12 @@ proc generateSymbolIndex(symbols: seq[TIndexEntry]): string =
result = ""
var i = 0
while i < symbols.len:
result.addf("<dt><span>$1:</span></dt><ul class=\"simple\"><dd>\n",
[symbols[i].keyword])
let keyword= symbols[i].keyword
let cleaned_keyword = keyword[1..keyword.high - 1]
result.addf("<dt><a name=\"$2\" href=\"#$2\"><span>$1:</span></a></dt><ul class=\"simple\"><dd>\n",
[keyword, cleaned_keyword])
var j = i
while j < symbols.len and symbols[i].keyword == symbols[j].keyword:
while j < symbols.len and keyword == symbols[j].keyword:
let
url = symbols[j].link
text = if not symbols[j].linkTitle.isNil: symbols[j].linkTitle else: url
@@ -459,9 +461,9 @@ proc indentToLevel(level: var int, newLevel: int): string =
if level == newLevel:
return
if newLevel > level:
result = repeatStr(newLevel - level, "<ul>")
result = repeat("<ul>", newLevel - level)
else:
result = repeatStr(level - newLevel, "</ul>")
result = repeat("</ul>", level - newLevel)
level = newLevel
proc generateDocumentationTOC(entries: seq[TIndexEntry]): string =
@@ -654,7 +656,7 @@ proc mergeIndexes*(dir: string): string =
result.add("<h2>API symbols</h2>\n")
result.add(generateSymbolIndex(symbols))
# ----------------------------------------------------------------------------
proc stripTOCHTML(s: string): string =
@@ -675,11 +677,18 @@ proc stripTOCHTML(s: string): string =
result.delete(first, last)
first = result.find('<', first)
proc renderHeadline(d: PDoc, n: PRstNode, result: var string) =
proc renderHeadline(d: PDoc, n: PRstNode, result: var string) =
var tmp = ""
for i in countup(0, len(n) - 1): renderRstToOut(d, n.sons[i], tmp)
d.currentSection = tmp
var refname = rstnodeToRefname(n)
# Find the last higher level section for unique reference name
var sectionPrefix = ""
for i in countdown(d.tocPart.high, 0):
let n2 = d.tocPart[i].n
if n2.level < n.level:
sectionPrefix = rstnodeToRefname(n2) & "-"
break
var refname = sectionPrefix & rstnodeToRefname(n)
if d.hasToc:
var length = len(d.tocPart)
setLen(d.tocPart, length + 1)
@@ -691,17 +700,17 @@ proc renderHeadline(d: PDoc, n: PRstNode, result: var string) =
"id=\"$2\" href=\"#$2\">$3</a></h$1>", "\\rsth$4{$3}\\label{$2}\n",
[$n.level, d.tocPart[length].refname, tmp, $chr(n.level - 1 + ord('A'))])
else:
dispA(d.target, result, "\n<h$1 id=\"$2\">$3</h$1>",
dispA(d.target, result, "\n<h$1 id=\"$2\">$3</h$1>",
"\\rsth$4{$3}\\label{$2}\n", [
$n.level, refname, tmp,
$n.level, refname, tmp,
$chr(n.level - 1 + ord('A'))])
# Generate index entry using spaces to indicate TOC level for the output HTML.
assert n.level >= 0
setIndexTerm(d, refname, tmp.stripTOCHTML,
repeatChar(max(0, n.level), ' ') & tmp)
spaces(max(0, n.level)) & tmp)
proc renderOverline(d: PDoc, n: PRstNode, result: var string) =
proc renderOverline(d: PDoc, n: PRstNode, result: var string) =
if d.meta[metaTitle].len == 0:
for i in countup(0, len(n)-1):
renderRstToOut(d, n.sons[i], d.meta[metaTitle])
@@ -714,14 +723,14 @@ proc renderOverline(d: PDoc, n: PRstNode, result: var string) =
var tmp = ""
for i in countup(0, len(n) - 1): renderRstToOut(d, n.sons[i], tmp)
d.currentSection = tmp
dispA(d.target, result, "<h$1 id=\"$2\"><center>$3</center></h$1>",
dispA(d.target, result, "<h$1 id=\"$2\"><center>$3</center></h$1>",
"\\rstov$4{$3}\\label{$2}\n", [$n.level,
rstnodeToRefname(n), tmp, $chr(n.level - 1 + ord('A'))])
proc renderTocEntry(d: PDoc, e: TTocEntry, result: var string) =
proc renderTocEntry(d: PDoc, e: TTocEntry, result: var string) =
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])
proc renderTocEntries*(d: var TRstGenerator, j: var int, lvl: int,
@@ -750,33 +759,33 @@ proc renderImage(d: PDoc, n: PRstNode, result: var string) =
var options = ""
var s = getFieldValue(n, "scale")
if s.valid: dispA(d.target, options, " scale=\"$1\"", " scale=$1", [strip(s)])
s = getFieldValue(n, "height")
if s.valid: dispA(d.target, options, " height=\"$1\"", " height=$1", [strip(s)])
s = getFieldValue(n, "width")
if s.valid: dispA(d.target, options, " width=\"$1\"", " width=$1", [strip(s)])
s = getFieldValue(n, "alt")
if s.valid: dispA(d.target, options, " alt=\"$1\"", "", [strip(s)])
s = getFieldValue(n, "align")
if s.valid: dispA(d.target, options, " align=\"$1\"", "", [strip(s)])
if options.len > 0: options = dispF(d.target, "$1", "[$1]", [options])
let arg = getArgument(n)
if arg.valid:
dispA(d.target, result, "<img src=\"$1\"$2 />", "\\includegraphics$2{$1}",
dispA(d.target, result, "<img src=\"$1\"$2 />", "\\includegraphics$2{$1}",
[arg, options])
if len(n) >= 3: renderRstToOut(d, n.sons[2], result)
proc renderSmiley(d: PDoc, n: PRstNode, result: var string) =
dispA(d.target, result,
"""<img src="$1" width="15"
"""<img src="$1" width="15"
height="17" hspace="2" vspace="2" class="smiley" />""",
"\\includegraphics{$1}", [d.config["doc.smiley_format"] % n.text])
proc parseCodeBlockField(d: PDoc, n: PRstNode, params: var CodeBlockParams) =
## Parses useful fields which can appear before a code block.
##
@@ -871,11 +880,11 @@ proc renderCodeBlock(d: PDoc, n: PRstNode, result: var string) =
else:
var g: TGeneralTokenizer
initGeneralTokenizer(g, m.text)
while true:
while true:
getNextToken(g, params.lang)
case g.kind
of gtEof: break
of gtNone, gtWhitespace:
of gtEof: break
of gtNone, gtWhitespace:
add(result, substr(m.text, g.start, g.length + g.start - 1))
else:
dispA(d.target, result, "<span class=\"$2\">$1</span>", "\\span$2{$1}", [
@@ -884,36 +893,36 @@ proc renderCodeBlock(d: PDoc, n: PRstNode, result: var string) =
deinitGeneralTokenizer(g)
dispA(d.target, result, blockEnd, "\n\\end{rstpre}\n")
proc renderContainer(d: PDoc, n: PRstNode, result: var string) =
proc renderContainer(d: PDoc, n: PRstNode, result: var string) =
var tmp = ""
renderRstToOut(d, n.sons[2], tmp)
var arg = strip(getArgument(n))
if arg == "":
if arg == "":
dispA(d.target, result, "<div>$1</div>", "$1", [tmp])
else:
dispA(d.target, result, "<div class=\"$1\">$2</div>", "$2", [arg, tmp])
proc texColumns(n: PRstNode): string =
proc texColumns(n: PRstNode): string =
result = ""
for i in countup(1, len(n)): add(result, "|X")
proc renderField(d: PDoc, n: PRstNode, result: var string) =
proc renderField(d: PDoc, n: PRstNode, result: var string) =
var b = false
if d.target == outLatex:
if d.target == outLatex:
var fieldname = addNodes(n.sons[0])
var fieldval = esc(d.target, strip(addNodes(n.sons[1])))
if cmpIgnoreStyle(fieldname, "author") == 0 or
if cmpIgnoreStyle(fieldname, "author") == 0 or
cmpIgnoreStyle(fieldname, "authors") == 0:
if d.meta[metaAuthor].len == 0:
d.meta[metaAuthor] = fieldval
b = true
elif cmpIgnoreStyle(fieldname, "version") == 0:
elif cmpIgnoreStyle(fieldname, "version") == 0:
if d.meta[metaVersion].len == 0:
d.meta[metaVersion] = fieldval
b = true
if not b:
renderAux(d, n, "<tr>$1</tr>\n", "$1", result)
proc renderRstToOut(d: PDoc, n: PRstNode, result: var string) =
if n == nil: return
case n.kind
@@ -938,54 +947,54 @@ proc renderRstToOut(d: PDoc, n: PRstNode, result: var string) =
of rnDefBody: renderAux(d, n, "<dd>$1</dd>\n", "$1\n", result)
of rnFieldList:
var tmp = ""
for i in countup(0, len(n) - 1):
for i in countup(0, len(n) - 1):
renderRstToOut(d, n.sons[i], tmp)
if tmp.len != 0:
if tmp.len != 0:
dispA(d.target, result,
"<table class=\"docinfo\" frame=\"void\" rules=\"none\">" &
"<col class=\"docinfo-name\" />" &
"<col class=\"docinfo-content\" />" &
"<col class=\"docinfo-content\" />" &
"<tbody valign=\"top\">$1" &
"</tbody></table>",
"\\begin{description}$1\\end{description}\n",
"</tbody></table>",
"\\begin{description}$1\\end{description}\n",
[tmp])
of rnField: renderField(d, n, result)
of rnFieldName:
of rnFieldName:
renderAux(d, n, "<th class=\"docinfo-name\">$1:</th>",
"\\item[$1:]", result)
of rnFieldBody:
of rnFieldBody:
renderAux(d, n, "<td>$1</td>", " $1\n", result)
of rnIndex:
of rnIndex:
renderRstToOut(d, n.sons[2], result)
of rnOptionList:
renderAux(d, n, "<table frame=\"void\">$1</table>",
of rnOptionList:
renderAux(d, n, "<table frame=\"void\">$1</table>",
"\\begin{description}\n$1\\end{description}\n", result)
of rnOptionListItem:
of rnOptionListItem:
renderAux(d, n, "<tr>$1</tr>\n", "$1", result)
of rnOptionGroup:
of rnOptionGroup:
renderAux(d, n, "<th align=\"left\">$1</th>", "\\item[$1]", result)
of rnDescription:
of rnDescription:
renderAux(d, n, "<td align=\"left\">$1</td>\n", " $1\n", result)
of rnOption, rnOptionString, rnOptionArgument:
of rnOption, rnOptionString, rnOptionArgument:
doAssert false, "renderRstToOut"
of rnLiteralBlock:
renderAux(d, n, "<pre>$1</pre>\n",
renderAux(d, n, "<pre>$1</pre>\n",
"\\begin{rstpre}\n$1\n\\end{rstpre}\n", result)
of rnQuotedLiteralBlock:
of rnQuotedLiteralBlock:
doAssert false, "renderRstToOut"
of rnLineBlock:
of rnLineBlock:
renderAux(d, n, "<p>$1</p>", "$1\n\n", result)
of rnLineBlockItem:
of rnLineBlockItem:
renderAux(d, n, "$1<br />", "$1\\\\\n", result)
of rnBlockQuote:
renderAux(d, n, "<blockquote><p>$1</p></blockquote>\n",
of rnBlockQuote:
renderAux(d, n, "<blockquote><p>$1</p></blockquote>\n",
"\\begin{quote}$1\\end{quote}\n", result)
of rnTable, rnGridTable:
renderAux(d, n,
"<table border=\"1\" class=\"docutils\">$1</table>",
of rnTable, rnGridTable:
renderAux(d, n,
"<table border=\"1\" class=\"docutils\">$1</table>",
"\\begin{table}\\begin{rsttab}{" &
texColumns(n) & "|}\n\\hline\n$1\\end{rsttab}\\end{table}", result)
of rnTableRow:
of rnTableRow:
if len(n) >= 1:
if d.target == outLatex:
#var tmp = ""
@@ -998,25 +1007,25 @@ proc renderRstToOut(d: PDoc, n: PRstNode, result: var string) =
result.add("<tr>")
renderAux(d, n, result)
result.add("</tr>\n")
of rnTableDataCell:
of rnTableDataCell:
renderAux(d, n, "<td>$1</td>", "$1", result)
of rnTableHeaderCell:
of rnTableHeaderCell:
renderAux(d, n, "<th>$1</th>", "\\textbf{$1}", result)
of rnLabel:
of rnLabel:
doAssert false, "renderRstToOut" # used for footnotes and other
of rnFootnote:
of rnFootnote:
doAssert false, "renderRstToOut" # a footnote
of rnCitation:
of rnCitation:
doAssert false, "renderRstToOut" # similar to footnote
of rnRef:
of rnRef:
var tmp = ""
renderAux(d, n, tmp)
dispA(d.target, result,
"<a class=\"reference external\" href=\"#$2\">$1</a>",
"$1\\ref{$2}", [tmp, rstnodeToRefname(n)])
of rnStandaloneHyperlink:
renderAux(d, n,
"<a class=\"reference external\" href=\"$1\">$1</a>",
of rnStandaloneHyperlink:
renderAux(d, n,
"<a class=\"reference external\" href=\"$1\">$1</a>",
"\\href{$1}{$1}", result)
of rnHyperlink:
var tmp0 = ""
@@ -1033,11 +1042,11 @@ proc renderRstToOut(d: PDoc, n: PRstNode, result: var string) =
of rnRawLatex:
if d.target == outLatex:
result.add addNodes(lastSon(n))
of rnImage, rnFigure: renderImage(d, n, result)
of rnCodeBlock: renderCodeBlock(d, n, result)
of rnContainer: renderContainer(d, n, result)
of rnSubstitutionReferences, rnSubstitutionDef:
of rnSubstitutionReferences, rnSubstitutionDef:
renderAux(d, n, "|$1|", "|$1|", result)
of rnDirective:
renderAux(d, n, "", "", result)
@@ -1054,15 +1063,15 @@ proc renderRstToOut(d: PDoc, n: PRstNode, result: var string) =
of rnStrongEmphasis:
renderAux(d, n, "<strong>$1</strong>", "\\textbf{$1}", result)
of rnTripleEmphasis:
renderAux(d, n, "<strong><em>$1</em></strong>",
renderAux(d, n, "<strong><em>$1</em></strong>",
"\\textbf{emph{$1}}", result)
of rnInterpretedText:
renderAux(d, n, "<cite>$1</cite>", "\\emph{$1}", result)
of rnIdx:
renderIndexTerm(d, n, result)
of rnInlineLiteral:
renderAux(d, n,
"<tt class=\"docutils literal\"><span class=\"pre\">$1</span></tt>",
of rnInlineLiteral:
renderAux(d, n,
"<tt class=\"docutils literal\"><span class=\"pre\">$1</span></tt>",
"\\texttt{$1}", result)
of rnSmiley: renderSmiley(d, n, result)
of rnLeaf: result.add(esc(d.target, n.text))
@@ -1073,55 +1082,55 @@ proc renderRstToOut(d: PDoc, n: PRstNode, result: var string) =
# -----------------------------------------------------------------------------
proc getVarIdx(varnames: openArray[string], id: string): int =
for i in countup(0, high(varnames)):
if cmpIgnoreStyle(varnames[i], id) == 0:
proc getVarIdx(varnames: openArray[string], id: string): int =
for i in countup(0, high(varnames)):
if cmpIgnoreStyle(varnames[i], id) == 0:
return i
result = -1
proc formatNamedVars*(frmt: string, varnames: openArray[string],
varvalues: openArray[string]): string =
proc formatNamedVars*(frmt: string, varnames: openArray[string],
varvalues: openArray[string]): string =
var i = 0
var L = len(frmt)
result = ""
var num = 0
while i < L:
if frmt[i] == '$':
while i < L:
if frmt[i] == '$':
inc(i) # skip '$'
case frmt[i]
of '#':
of '#':
add(result, varvalues[num])
inc(num)
inc(i)
of '$':
of '$':
add(result, "$")
inc(i)
of '0'..'9':
of '0'..'9':
var j = 0
while true:
while true:
j = (j * 10) + ord(frmt[i]) - ord('0')
inc(i)
if i > L-1 or frmt[i] notin {'0'..'9'}: break
if i > L-1 or frmt[i] notin {'0'..'9'}: break
if j > high(varvalues) + 1:
raise newException(ValueError, "invalid index: " & $j)
num = j
add(result, varvalues[j - 1])
of 'A'..'Z', 'a'..'z', '\x80'..'\xFF':
of 'A'..'Z', 'a'..'z', '\x80'..'\xFF':
var id = ""
while true:
while true:
add(id, frmt[i])
inc(i)
if frmt[i] notin {'A'..'Z', '_', 'a'..'z', '\x80'..'\xFF'}: break
if frmt[i] notin {'A'..'Z', '_', 'a'..'z', '\x80'..'\xFF'}: break
var idx = getVarIdx(varnames, id)
if idx >= 0:
if idx >= 0:
add(result, varvalues[idx])
else:
raise newException(ValueError, "unknown substitution var: " & id)
of '{':
of '{':
var id = ""
inc(i)
while frmt[i] != '}':
if frmt[i] == '\0':
while frmt[i] != '}':
if frmt[i] == '\0':
raise newException(ValueError, "'}' expected")
add(id, frmt[i])
inc(i)
@@ -1129,12 +1138,12 @@ proc formatNamedVars*(frmt: string, varnames: openArray[string],
# search for the variable:
var idx = getVarIdx(varnames, id)
if idx >= 0: add(result, varvalues[idx])
else:
else:
raise newException(ValueError, "unknown substitution var: " & id)
else:
raise newException(ValueError, "unknown substitution: $" & $frmt[i])
var start = i
while i < L:
while i < L:
if frmt[i] != '$': inc(i)
else: break
if i-1 >= start: add(result, substr(frmt, start, i - 1))
@@ -1143,7 +1152,7 @@ proc formatNamedVars*(frmt: string, varnames: openArray[string],
proc defaultConfig*(): StringTableRef =
## Returns a default configuration for embedded HTML generation.
##
## The returned ``StringTableRef`` contains the paramters used by the HTML
## The returned ``StringTableRef`` contains the parameters used by the HTML
## engine to build the final output. For information on what these parameters
## are and their purpose, please look up the file ``config/nimdoc.cfg``
## bundled with the compiler.
@@ -1154,10 +1163,10 @@ proc defaultConfig*(): StringTableRef =
## pages, while this proc returns just the content for procs like
## ``rstToHtml`` to generate the bare minimum HTML.
result = newStringTable(modeStyleInsensitive)
template setConfigVar(key, val: expr) =
result[key] = val
# If you need to modify these values, it might be worth updating the template
# file in config/nimdoc.cfg.
setConfigVar("split.item.toc", "20")
@@ -1205,7 +1214,7 @@ $content
# ---------- forum ---------------------------------------------------------
proc rstToHtml*(s: string, options: TRstParseOptions,
proc rstToHtml*(s: string, options: TRstParseOptions,
config: StringTableRef): string =
## Converts an input rst string into embeddable HTML.
##
@@ -1227,13 +1236,13 @@ proc rstToHtml*(s: string, options: TRstParseOptions,
## output you have to create your own ``TRstGenerator`` with
## ``initRstGenerator`` and related procs.
proc myFindFile(filename: string): string =
proc myFindFile(filename: string): string =
# we don't find any files in online mode:
result = ""
const filen = "input"
var d: TRstGenerator
initRstGenerator(d, outHtml, config, filen, options, myFindFile,
initRstGenerator(d, outHtml, config, filen, options, myFindFile,
rst.defaultMsgHandler)
var dummyHasToc = false
var rst = rstParse(s, filen, 0, 1, dummyHasToc, options)
@@ -1242,5 +1251,6 @@ proc rstToHtml*(s: string, options: TRstParseOptions,
when isMainModule:
echo rstToHtml("*Hello* **world**!", {},
newStringTable(modeStyleInsensitive))
assert rstToHtml("*Hello* **world**!", {},
newStringTable(modeStyleInsensitive)) ==
"<em>Hello</em> <strong>world</strong>!"

View File

@@ -70,17 +70,20 @@ const
STDIN_FILENO* = 0 ## File number of stdin;
STDOUT_FILENO* = 1 ## File number of stdout;
when defined(endb):
# to not break bootstrapping again ...
type
TDIR* {.importc: "DIR", header: "<dirent.h>",
final, pure, incompleteStruct.} = object
## A type representing a directory stream.
else:
type
TDIR* {.importc: "DIR", header: "<dirent.h>",
final, pure.} = object
## A type representing a directory stream.
DT_UNKNOWN* = 0 ## Unknown file type.
DT_FIFO* = 1 ## Named pipe, or FIFO.
DT_CHR* = 2 ## Character device.
DT_DIR* = 4 ## Directory.
DT_BLK* = 6 ## Block device.
DT_REG* = 8 ## Regular file.
DT_LNK* = 10 ## Symbolic link.
DT_SOCK* = 12 ## UNIX domain socket.
DT_WHT* = 14
type
TDIR* {.importc: "DIR", header: "<dirent.h>",
incompleteStruct.} = object
## A type representing a directory stream.
type
SocketHandle* = distinct cint # The type used to represent socket descriptors
@@ -91,6 +94,12 @@ type
Tdirent* {.importc: "struct dirent",
header: "<dirent.h>", final, pure.} = object ## dirent_t struct
d_ino*: Tino ## File serial number.
when defined(linux) or defined(macosx) or defined(bsd):
d_reclen*: cshort ## Length of this record. (not POSIX)
d_type*: int8 ## Type of file; not supported by all filesystem types.
## (not POSIX)
when defined(linux) or defined(bsd):
d_off*: TOff ## Not an offset. Value that ``telldir()`` would return.
d_name*: array [0..255, char] ## Name of entry.
Tflock* {.importc: "struct flock", final, pure,
@@ -1405,14 +1414,6 @@ var
## Report status of stopped child process.
WEXITSTATUS* {.importc, header: "<sys/wait.h>".}: cint
## Return exit status.
WIFCONTINUED* {.importc, header: "<sys/wait.h>".}: cint
## True if child has been continued.
WIFEXITED* {.importc, header: "<sys/wait.h>".}: cint
## True if child exited normally.
WIFSIGNALED* {.importc, header: "<sys/wait.h>".}: cint
## True if child exited due to uncaught signal.
WIFSTOPPED* {.importc, header: "<sys/wait.h>".}: cint
## True if child is currently stopped.
WSTOPSIG* {.importc, header: "<sys/wait.h>".}: cint
## Return signal number that caused process to stop.
WTERMSIG* {.importc, header: "<sys/wait.h>".}: cint
@@ -1559,6 +1560,14 @@ var
MSG_OOB* {.importc, header: "<sys/socket.h>".}: cint
## Out-of-band data.
proc WIFCONTINUED*(s:cint) : bool {.importc, header: "<sys/wait.h>".}
## True if child has been continued.
proc WIFEXITED*(s:cint) : bool {.importc, header: "<sys/wait.h>".}
## True if child exited normally.
proc WIFSIGNALED*(s:cint) : bool {.importc, header: "<sys/wait.h>".}
## True if child exited due to uncaught signal.
proc WIFSTOPPED*(s:cint) : bool {.importc, header: "<sys/wait.h>".}
## True if child is currently stopped.
when defined(linux):
var
@@ -1570,9 +1579,12 @@ else:
when defined(macosx):
# We can't use the NOSIGNAL flag in the ``send`` function, it has no effect
# Instead we should use SO_NOSIGPIPE in setsockopt
const
MSG_NOSIGNAL* = 0'i32
var
MSG_HAVEMORE* {.importc, header: "<sys/socket.h>".}: cint
MSG_NOSIGNAL* = MSG_HAVEMORE
SO_NOSIGPIPE* {.importc, header: "<sys/socket.h>".}: cint
else:
var
MSG_NOSIGNAL* {.importc, header: "<sys/socket.h>".}: cint
@@ -1739,7 +1751,10 @@ when hasSpawnH:
when defined(linux):
# better be safe than sorry; Linux has this flag, macosx doesn't, don't
# know about the other OSes
var POSIX_SPAWN_USEVFORK* {.importc, header: "<spawn.h>".}: cint
# Non-GNU systems like TCC and musl-libc don't define __USE_GNU, so we
# can't get the magic number from spawn.h
const POSIX_SPAWN_USEVFORK* = cint(0x40)
else:
# macosx lacks this, so we define the constant to be 0 to not affect
# OR'ing of flags:

261
lib/posix/termios.nim Normal file
View File

@@ -0,0 +1,261 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2015 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
{.deadCodeElim: on.}
import posix
type
Speed* = cuint
Tcflag* = cuint
const
NCCS* = 32
type
Termios* {.importc: "struct termios", header: "<termios.h>".} = object
c_iflag*: Tcflag # input mode flags
c_oflag*: Tcflag # output mode flags
c_cflag*: Tcflag # control mode flags
c_lflag*: Tcflag # local mode flags
c_line*: cuchar # line discipline
c_cc*: array[NCCS, cuchar] # control characters
# cc characters
const
VINTR* = 0
VQUIT* = 1
VERASE* = 2
VKILL* = 3
VEOF* = 4
VTIME* = 5
VMIN* = 6
VSWTC* = 7
VSTART* = 8
VSTOP* = 9
VSUSP* = 10
VEOL* = 11
VREPRINT* = 12
VDISCARD* = 13
VWERASE* = 14
VLNEXT* = 15
VEOL2* = 16
# iflag bits
const
IGNBRK* = 1
BRKINT* = 2
IGNPAR* = 4
PARMRK* = 10
INPCK* = 20
ISTRIP* = 40
INLCR* = 100
IGNCR* = 200
ICRNL* = 400
IUCLC* = 1000
IXON* = 2000
IXANY* = 4000
IXOFF* = 10000
IMAXBEL* = 20000
IUTF8* = 40000
# oflag bits
const
OPOST* = 1
OLCUC* = 2
ONLCR* = 4
OCRNL* = 10
ONOCR* = 20
ONLRET* = 40
OFILL* = 100
OFDEL* = 200
NLDLY* = 400
NL0* = 0
NL1* = 400
CRDLY* = 3000
CR0* = 0
CR1* = 1000
CR2* = 2000
CR3* = 3000
TABDLY* = 14000
TAB0* = 0
TAB1* = 4000
TAB2* = 10000
TAB3* = 14000
BSDLY* = 20000
BS0* = 0
BS1* = 20000
FFDLY* = 0o000000100000
FF0* = 0
FF1* = 0o000000100000
VTDLY* = 40000
VT0* = 0
VT1* = 40000
XTABS* = 14000
# cflag bit meaning
const
CBAUD* = 10017
B0* = 0
B50* = 1
B75* = 2
B110* = 3
B134* = 4
B150* = 5
B200* = 6
B300* = 7
B600* = 10
B1200* = 11
B1800* = 12
B2400* = 13
B4800* = 14
B9600* = 15
B19200* = 16
B38400* = 17
EXTA* = B19200
EXTB* = B38400
CSIZE* = 60
CS5* = 0
CS6* = 20
CS7* = 40
CS8* = 60
CSTOPB* = 100
CREAD* = 200
PARENB* = 400
PARODD* = 1000
HUPCL* = 2000
CLOCAL* = 4000
CBAUDEX* = 10000
B57600* = 10001
B115200* = 10002
B230400* = 10003
B460800* = 10004
B500000* = 10005
B576000* = 10006
B921600* = 10007
B1000000* = 10010
B1152000* = 10011
B1500000* = 10012
B2000000* = 10013
B2500000* = 10014
B3000000* = 10015
B3500000* = 10016
B4000000* = 10017
MAX_BAUD* = B4000000
CIBAUD* = 2003600000
CMSPAR* = 0o010000000000
CRTSCTS* = 0o020000000000
# lflag bits
const
ISIG* = 1
ICANON* = 2
XCASE* = 4
ECHO* = 10
ECHOE* = 20
ECHOK* = 40
ECHONL* = 100
NOFLSH* = 200
TOSTOP* = 400
ECHOCTL* = 1000
ECHOPRT* = 2000
ECHOKE* = 4000
FLUSHO* = 10000
PENDIN* = 40000
IEXTEN* = 0o000000100000
EXTPROC* = 0o000000200000
# tcflow() and TCXONC use these
const
TCOOFF* = 0
TCOON* = 1
TCIOFF* = 2
TCION* = 3
# tcflush() and TCFLSH use these
const
TCIFLUSH* = 0
TCOFLUSH* = 1
TCIOFLUSH* = 2
# tcsetattr uses these
const
TCSANOW* = 0
TCSADRAIN* = 1
TCSAFLUSH* = 2
# Compare a character C to a value VAL from the `cc' array in a
# `struct termios'. If VAL is _POSIX_VDISABLE, no character can match it.
template cceq*(val, c: expr): expr =
c == val and val != POSIX_VDISABLE
# Return the output baud rate stored in *TERMIOS_P.
proc cfGetOspeed*(termios: ptr Termios): Speed {.importc: "cfgetospeed",
header: "<termios.h>".}
# Return the input baud rate stored in *TERMIOS_P.
proc cfGetIspeed*(termios: ptr Termios): Speed {.importc: "cfgetispeed",
header: "<termios.h>".}
# Set the output baud rate stored in *TERMIOS_P to SPEED.
proc cfSetOspeed*(termios: ptr Termios; speed: Speed): cint {.
importc: "cfsetospeed", header: "<termios.h>".}
# Set the input baud rate stored in *TERMIOS_P to SPEED.
proc cfSetIspeed*(termios: ptr Termios; speed: Speed): cint {.
importc: "cfsetispeed", header: "<termios.h>".}
# Set both the input and output baud rates in *TERMIOS_OP to SPEED.
proc cfSetSpeed*(termios: ptr Termios; speed: Speed): cint {.
importc: "cfsetspeed", header: "<termios.h>".}
# Put the state of FD into *TERMIOS_P.
proc tcGetAttr*(fd: cint; termios: ptr Termios): cint {.
importc: "tcgetattr", header: "<termios.h>".}
# Set the state of FD to *TERMIOS_P.
# Values for OPTIONAL_ACTIONS (TCSA*) are in <bits/termios.h>.
proc tcSetAttr*(fd: cint; optional_actions: cint; termios: ptr Termios): cint {.
importc: "tcsetattr", header: "<termios.h>".}
# Set *TERMIOS_P to indicate raw mode.
proc cfMakeRaw*(termios: ptr Termios) {.importc: "cfmakeraw",
header: "<termios.h>".}
# Send zero bits on FD.
proc tcSendBreak*(fd: cint; duration: cint): cint {.importc: "tcsendbreak",
header: "<termios.h>".}
# Wait for pending output to be written on FD.
#
# This function is a cancellation point and therefore not marked with
# .
proc tcDrain*(fd: cint): cint {.importc: "tcdrain", header: "<termios.h>".}
# Flush pending data on FD.
# Values for QUEUE_SELECTOR (TC{I,O,IO}FLUSH) are in <bits/termios.h>.
proc tcFlush*(fd: cint; queue_selector: cint): cint {.importc: "tcflush",
header: "<termios.h>".}
# Suspend or restart transmission on FD.
# Values for ACTION (TC[IO]{OFF,ON}) are in <bits/termios.h>.
proc tcFlow*(fd: cint; action: cint): cint {.importc: "tcflow",
header: "<termios.h>".}
# Get process group ID for session leader for controlling terminal FD.
proc tcGetSid*(fd: cint): TPid {.importc: "tcgetsid", header: "<termios.h>".}

View File

@@ -200,7 +200,7 @@ template schedule =
if minIdx >= 0:
p.actors[minIdx].i.send(t)
else:
raise newException(EDeadThread, "cannot send message; thread died")
raise newException(DeadThreadError, "cannot send message; thread died")
proc spawn*[TIn, TOut](p: var TActorPool[TIn, TOut], input: TIn,
action: proc (input: TIn): TOut {.thread.}
@@ -221,7 +221,7 @@ proc spawn*[TIn](p: var TActorPool[TIn, void], input: TIn,
setupTask()
schedule()
when isMainModule:
when not defined(testing) and isMainModule:
var
a: TActorPool[int, void]
createActorPool(a)

View File

@@ -11,20 +11,20 @@
type
SortOrder* = enum ## sort order
Descending, Ascending
Descending, Ascending
{.deprecated: [TSortOrder: SortOrder].}
proc `*`*(x: int, order: SortOrder): int {.inline.} =
proc `*`*(x: int, order: SortOrder): int {.inline.} =
## flips `x` if ``order == Descending``;
## if ``order == Ascending`` then `x` is returned.
## `x` is supposed to be the result of a comparator, ie ``< 0`` for
## `x` is supposed to be the result of a comparator, ie ``< 0`` for
## *less than*, ``== 0`` for *equal*, ``> 0`` for *greater than*.
var y = order.ord - 1
result = (x xor y) - y
proc reverse*[T](a: var openArray[T], first, last: int) =
proc reverse*[T](a: var openArray[T], first, last: Natural) =
## reverses the array ``a[first..last]``.
var x = first
var y = last
@@ -37,11 +37,11 @@ proc reverse*[T](a: var openArray[T]) =
## reverses the array `a`.
reverse(a, 0, a.high)
proc reversed*[T](a: openArray[T], first, last: int): seq[T] =
proc reversed*[T](a: openArray[T], first, last: Natural): seq[T] =
## returns the reverse of the array `a[first..last]`.
result = newSeq[T](last - first + 1)
var x = first
var y = last
var x = first.int
var y = last.int
while x <= last:
result[x] = a[y]
dec(y)
@@ -73,14 +73,15 @@ const
onlySafeCode = true
proc lowerBound*[T](a: openArray[T], key: T, cmp: proc(x,y: T): int {.closure.}): int =
## same as binarySearch except that if key is not in `a` then this
## same as binarySearch except that if key is not in `a` then this
## returns the location where `key` would be if it were. In other
## words if you have a sorted sequence and you call insert(thing, elm, lowerBound(thing, elm))
## the sequence will still be sorted
## words if you have a sorted sequence and you call
## insert(thing, elm, lowerBound(thing, elm))
## the sequence will still be sorted.
##
## `cmp` is the comparator function to use, the expected return values are
## the same as that of system.cmp.
##
## `cmp` is the comparator function to use, the expected return values are the same as
## that of system.cmp
##
## example::
##
## var arr = @[1,2,3,5,6,7,8,9]
@@ -102,9 +103,9 @@ proc lowerBound*[T](a: openArray[T], key: T, cmp: proc(x,y: T): int {.closure.})
count = step
proc lowerBound*[T](a: openArray[T], key: T): int = lowerBound(a, key, cmp[T])
proc merge[T](a, b: var openArray[T], lo, m, hi: int,
proc merge[T](a, b: var openArray[T], lo, m, hi: int,
cmp: proc (x, y: T): int {.closure.}, order: SortOrder) =
template `<-` (a, b: expr) =
template `<-` (a, b: expr) =
when false:
a = b
elif onlySafeCode:
@@ -151,10 +152,10 @@ proc merge[T](a, b: var openArray[T], lo, m, hi: int,
proc sort*[T](a: var openArray[T],
cmp: proc (x, y: T): int {.closure.},
order = SortOrder.Ascending) =
## Default Nim sort. The sorting is guaranteed to be stable and
## Default Nim sort. The sorting is guaranteed to be stable and
## the worst case is guaranteed to be O(n log n).
## The current implementation uses an iterative
## mergesort to achieve this. It uses a temporary sequence of
## mergesort to achieve this. It uses a temporary sequence of
## length ``a.len div 2``. Currently Nim does not support a
## sensible default argument for ``cmp``, so you have to provide one
## of your own. However, the ``system.cmp`` procs can be used:
@@ -187,6 +188,48 @@ proc sort*[T](a: var openArray[T],
dec(m, s*2)
s = s*2
proc sorted*[T](a: openArray[T], cmp: proc(x, y: T): int {.closure.},
order = SortOrder.Ascending): seq[T] =
## returns `a` sorted by `cmp` in the specified `order`.
result = newSeq[T](a.len)
for i in 0 .. a.high:
result[i] = a[i]
sort(result, cmp, order)
template sortedByIt*(seq1, op: expr): expr =
## Convenience template around the ``sorted`` proc to reduce typing.
##
## The template injects the ``it`` variable which you can use directly in an
## expression. Example:
##
## .. code-block:: nim
##
## type Person = tuple[name: string, age: int]
## var
## p1: Person = (name: "p1", age: 60)
## p2: Person = (name: "p2", age: 20)
## p3: Person = (name: "p3", age: 30)
## p4: Person = (name: "p4", age: 30)
##
## people = @[p1,p2,p4,p3]
##
## echo people.sortedByIt(it.name)
##
## Because the underlying ``cmp()`` is defined for tuples you can do
## a nested sort like in the following example:
##
## .. code-block:: nim
##
## echo people.sortedByIt((it.age, it.name))
##
var result {.gensym.} = sorted(seq1, proc(x, y: type(seq1[0])): int =
var it {.inject.} = x
let a = op
it = y
let b = op
result = cmp(a, b))
result
proc product*[T](x: openArray[seq[T]]): seq[seq[T]] =
## produces the Cartesian product of the array. Warning: complexity
## may explode.
@@ -210,13 +253,72 @@ proc product*[T](x: openArray[seq[T]]): seq[seq[T]] =
while true:
while indexes[index] == -1:
indexes[index] = initial[index]
index +=1
index += 1
if index == x.len: return
indexes[index] -=1
indexes[index] -= 1
for ni, i in indexes:
next[ni] = x[ni][i]
var res: seq[T]
shallowCopy(res, next)
result.add(res)
index = 0
indexes[index] -=1
indexes[index] -= 1
proc nextPermutation*[T](x: var openarray[T]): bool {.discardable.} =
## Calculates the next lexicographic permutation, directly modifying ``x``.
## The result is whether a permutation happened, otherwise we have reached
## the last-ordered permutation.
##
## .. code-block:: nim
##
## var v = @[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
## v.nextPermutation()
## echo v
if x.len < 2:
return false
var i = x.high
while i > 0 and x[i-1] >= x[i]:
dec i
if i == 0:
return false
var j = x.high
while j >= i and x[j] <= x[i-1]:
dec j
swap x[j], x[i-1]
x.reverse(i, x.high)
result = true
proc prevPermutation*[T](x: var openarray[T]): bool {.discardable.} =
## Calculates the previous lexicographic permutation, directly modifying
## ``x``. The result is whether a permutation happened, otherwise we have
## reached the first-ordered permutation.
##
## .. code-block:: nim
##
## var v = @[0, 1, 2, 3, 4, 5, 6, 7, 9, 8]
## v.prevPermutation()
## echo v
if x.len < 2:
return false
var i = x.high
while i > 0 and x[i-1] <= x[i]:
dec i
if i == 0:
return false
x.reverse(i, x.high)
var j = x.high
while j >= i and x[j-1] < x[i-1]:
dec j
swap x[i-1], x[j]
result = true

View File

@@ -1,7 +1,7 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2014 Dominik Picheta
# (c) Copyright 2015 Dominik Picheta
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@@ -121,7 +121,7 @@ export Port, SocketFlag
##
## Limitations/Bugs
## ----------------
##
##
## * ``except`` statement (without `try`) does not work inside async procedures.
## * The effect system (``raises: []``) does not work with async procedures.
## * Can't await in a ``except`` body
@@ -379,7 +379,7 @@ when defined(windows) or defined(nimdoc):
if p.handles.len == 0 and p.timers.len == 0:
raise newException(ValueError,
"No handles or timers registered in dispatcher.")
let llTimeout =
if timeout == -1: winlean.INFINITE
else: timeout.int32
@@ -436,12 +436,12 @@ when defined(windows) or defined(nimdoc):
if not initPointer(dummySock, getAcceptExSockAddrsPtr, WSAID_GETACCEPTEXSOCKADDRS):
raiseOSError(osLastError())
proc connectEx(s: SocketHandle, name: ptr SockAddr, namelen: cint,
proc connectEx(s: SocketHandle, name: ptr SockAddr, namelen: cint,
lpSendBuffer: pointer, dwSendDataLength: Dword,
lpdwBytesSent: PDword, lpOverlapped: POVERLAPPED): bool =
if connectExPtr.isNil: raise newException(ValueError, "Need to initialise ConnectEx().")
let fun =
cast[proc (s: SocketHandle, name: ptr SockAddr, namelen: cint,
cast[proc (s: SocketHandle, name: ptr SockAddr, namelen: cint,
lpSendBuffer: pointer, dwSendDataLength: Dword,
lpdwBytesSent: PDword, lpOverlapped: POVERLAPPED): bool {.stdcall,gcsafe.}](connectExPtr)
@@ -475,7 +475,7 @@ when defined(windows) or defined(nimdoc):
dwRemoteAddressLength: Dword, LocalSockaddr: ptr ptr SockAddr,
LocalSockaddrLength: LPInt, RemoteSockaddr: ptr ptr SockAddr,
RemoteSockaddrLength: LPInt) {.stdcall,gcsafe.}](getAcceptExSockAddrsPtr)
fun(lpOutputBuffer, dwReceiveDataLength, dwLocalAddressLength,
dwRemoteAddressLength, LocalSockaddr, LocalSockaddrLength,
RemoteSockaddr, RemoteSockaddrLength)
@@ -514,7 +514,7 @@ when defined(windows) or defined(nimdoc):
else:
retFuture.fail(newException(OSError, osErrorMsg(errcode)))
)
var ret = connectEx(socket.SocketHandle, it.ai_addr,
sizeof(Sockaddr_in).cint, nil, 0, nil,
cast[POVERLAPPED](ol))
@@ -565,7 +565,7 @@ when defined(windows) or defined(nimdoc):
var dataBuf: TWSABuf
dataBuf.buf = cast[cstring](alloc0(size))
dataBuf.len = size
var bytesReceived: Dword
var flagsio = flags.toOSFlags().Dword
var ol = PCustomOverlapped()
@@ -606,15 +606,15 @@ when defined(windows) or defined(nimdoc):
retFuture.fail(newException(OSError, osErrorMsg(err)))
elif ret == 0 and bytesReceived == 0 and dataBuf.buf[0] == '\0':
# We have to ensure that the buffer is empty because WSARecv will tell
# us immediatelly when it was disconnected, even when there is still
# us immediately when it was disconnected, even when there is still
# data in the buffer.
# We want to give the user as much data as we can. So we only return
# the empty string (which signals a disconnection) when there is
# nothing left to read.
retFuture.complete("")
# TODO: "For message-oriented sockets, where a zero byte message is often
# allowable, a failure with an error code of WSAEDISCON is used to
# indicate graceful closure."
# TODO: "For message-oriented sockets, where a zero byte message is often
# allowable, a failure with an error code of WSAEDISCON is used to
# indicate graceful closure."
# ~ http://msdn.microsoft.com/en-us/library/ms741688%28v=vs.85%29.aspx
else:
# Request to read completed immediately.
@@ -634,6 +634,93 @@ when defined(windows) or defined(nimdoc):
# free ``ol``.
return retFuture
proc recvInto*(socket: TAsyncFD, buf: cstring, size: int,
flags = {SocketFlag.SafeDisconn}): Future[int] =
## Reads **up to** ``size`` bytes from ``socket`` into ``buf``, which must
## at least be of that size. Returned future will complete once all the
## data requested is read, a part of the data has been read, or the socket
## has disconnected in which case the future will complete with a value of
## ``0``.
##
## **Warning**: The ``Peek`` socket flag is not supported on Windows.
# Things to note:
# * When WSARecv completes immediately then ``bytesReceived`` is very
# unreliable.
# * Still need to implement message-oriented socket disconnection,
# '\0' in the message currently signifies a socket disconnect. Who
# knows what will happen when someone sends that to our socket.
verifyPresence(socket)
assert SocketFlag.Peek notin flags, "Peek not supported on Windows."
var retFuture = newFuture[int]("recvInto")
#buf[] = '\0'
var dataBuf: TWSABuf
dataBuf.buf = buf
dataBuf.len = size
var bytesReceived: Dword
var flagsio = flags.toOSFlags().Dword
var ol = PCustomOverlapped()
GC_ref(ol)
ol.data = TCompletionData(fd: socket, cb:
proc (fd: TAsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
if not retFuture.finished:
if errcode == OSErrorCode(-1):
if bytesCount == 0 and dataBuf.buf[0] == '\0':
retFuture.complete(0)
else:
retFuture.complete(bytesCount)
else:
if flags.isDisconnectionError(errcode):
retFuture.complete(0)
else:
retFuture.fail(newException(OSError, osErrorMsg(errcode)))
if dataBuf.buf != nil:
dataBuf.buf = nil
)
let ret = WSARecv(socket.SocketHandle, addr dataBuf, 1, addr bytesReceived,
addr flagsio, cast[POVERLAPPED](ol), nil)
if ret == -1:
let err = osLastError()
if err.int32 != ERROR_IO_PENDING:
if dataBuf.buf != nil:
dataBuf.buf = nil
GC_unref(ol)
if flags.isDisconnectionError(err):
retFuture.complete(0)
else:
retFuture.fail(newException(OSError, osErrorMsg(err)))
elif ret == 0 and bytesReceived == 0 and dataBuf.buf[0] == '\0':
# We have to ensure that the buffer is empty because WSARecv will tell
# us immediately when it was disconnected, even when there is still
# data in the buffer.
# We want to give the user as much data as we can. So we only return
# the empty string (which signals a disconnection) when there is
# nothing left to read.
retFuture.complete(0)
# TODO: "For message-oriented sockets, where a zero byte message is often
# allowable, a failure with an error code of WSAEDISCON is used to
# indicate graceful closure."
# ~ http://msdn.microsoft.com/en-us/library/ms741688%28v=vs.85%29.aspx
else:
# Request to read completed immediately.
# From my tests bytesReceived isn't reliable.
let realSize =
if bytesReceived == 0:
size
else:
bytesReceived
assert realSize <= size
retFuture.complete(realSize)
# We don't deallocate ``ol`` here because even though this completed
# immediately poll will still be notified about its completion and it will
# free ``ol``.
return retFuture
proc send*(socket: TAsyncFD, data: string,
flags = {SocketFlag.SafeDisconn}): Future[void] =
## Sends ``data`` to ``socket``. The returned future will complete once all
@@ -748,7 +835,7 @@ when defined(windows) or defined(nimdoc):
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms737524%28v=vs.85%29.aspx
let ret = acceptEx(socket.SocketHandle, clientSock, addr lpOutputBuf[0],
dwReceiveDataLength,
dwReceiveDataLength,
dwLocalAddressLength,
dwRemoteAddressLength,
addr dwBytesReceived, cast[POVERLAPPED](ol))
@@ -803,7 +890,7 @@ else:
else:
from posix import EINTR, EAGAIN, EINPROGRESS, EWOULDBLOCK, MSG_PEEK,
MSG_NOSIGNAL
type
TAsyncFD* = distinct cint
TCallback = proc (fd: TAsyncFD): bool {.closure,gcsafe.}
@@ -841,6 +928,8 @@ else:
proc newAsyncRawSocket*(domain: cint, typ: cint, protocol: cint): TAsyncFD =
result = newRawSocket(domain, typ, protocol).TAsyncFD
result.SocketHandle.setBlocking(false)
when defined(macosx):
result.SocketHandle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1)
register(result)
proc newAsyncRawSocket*(domain: Domain = AF_INET,
@@ -848,8 +937,10 @@ else:
protocol: Protocol = IPPROTO_TCP): TAsyncFD =
result = newRawSocket(domain, typ, protocol).TAsyncFD
result.SocketHandle.setBlocking(false)
when defined(macosx):
result.SocketHandle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1)
register(result)
proc closeSocket*(sock: TAsyncFD) =
let disp = getGlobalDispatcher()
sock.SocketHandle.close()
@@ -864,20 +955,24 @@ else:
raise newException(ValueError, "File descriptor not registered.")
p.selector[fd.SocketHandle].data.PData.readCBs.add(cb)
update(fd, p.selector[fd.SocketHandle].events + {EvRead})
proc addWrite*(fd: TAsyncFD, cb: TCallback) =
let p = getGlobalDispatcher()
if fd.SocketHandle notin p.selector:
raise newException(ValueError, "File descriptor not registered.")
p.selector[fd.SocketHandle].data.PData.writeCBs.add(cb)
update(fd, p.selector[fd.SocketHandle].events + {EvWrite})
proc poll*(timeout = 500) =
let p = getGlobalDispatcher()
for info in p.selector.select(timeout):
let data = PData(info.key.data)
assert data.fd == info.key.fd.TAsyncFD
#echo("In poll ", data.fd.cint)
if EvError in info.events:
closeSocket(data.fd)
continue
if EvRead in info.events:
# Callback may add items to ``data.readCBs`` which causes issues if
# we are iterating over ``data.readCBs`` at the same time. We therefore
@@ -888,7 +983,7 @@ else:
if not cb(data.fd):
# Callback wants to be called again.
data.readCBs.add(cb)
if EvWrite in info.events:
let currentCBs = data.writeCBs
data.writeCBs = @[]
@@ -896,7 +991,7 @@ else:
if not cb(data.fd):
# Callback wants to be called again.
data.writeCBs.add(cb)
if info.key in p.selector:
var newEvents: set[Event]
if data.readCBs.len != 0: newEvents = {EvRead}
@@ -909,16 +1004,16 @@ else:
discard
processTimers(p)
proc connect*(socket: TAsyncFD, address: string, port: Port,
af = AF_INET): Future[void] =
var retFuture = newFuture[void]("connect")
proc cb(fd: TAsyncFD): bool =
# We have connected.
retFuture.complete()
return true
var aiList = getAddrInfo(address, port, af)
var success = false
var lastError: OSErrorCode
@@ -948,14 +1043,13 @@ else:
proc recv*(socket: TAsyncFD, size: int,
flags = {SocketFlag.SafeDisconn}): Future[string] =
var retFuture = newFuture[string]("recv")
var readBuffer = newString(size)
proc cb(sock: TAsyncFD): bool =
result = true
let res = recv(sock.SocketHandle, addr readBuffer[0], size.cint,
flags.toOSFlags())
#echo("recv cb res: ", res)
if res < 0:
let lastError = osLastError()
if lastError.int32 notin {EINTR, EWOULDBLOCK, EAGAIN}:
@@ -976,12 +1070,36 @@ else:
addRead(socket, cb)
return retFuture
proc recvInto*(socket: TAsyncFD, buf: cstring, size: int,
flags = {SocketFlag.SafeDisconn}): Future[int] =
var retFuture = newFuture[int]("recvInto")
proc cb(sock: TAsyncFD): bool =
result = true
let res = recv(sock.SocketHandle, buf, size.cint,
flags.toOSFlags())
if res < 0:
let lastError = osLastError()
if lastError.int32 notin {EINTR, EWOULDBLOCK, EAGAIN}:
if flags.isDisconnectionError(lastError):
retFuture.complete(0)
else:
retFuture.fail(newException(OSError, osErrorMsg(lastError)))
else:
result = false # We still want this callback to be called.
else:
retFuture.complete(res)
# TODO: The following causes a massive slowdown.
#if not cb(socket):
addRead(socket, cb)
return retFuture
proc send*(socket: TAsyncFD, data: string,
flags = {SocketFlag.SafeDisconn}): Future[void] =
var retFuture = newFuture[void]("send")
var written = 0
proc cb(sock: TAsyncFD): bool =
result = true
let netSize = data.len-written
@@ -1060,6 +1178,17 @@ proc accept*(socket: TAsyncFD,
# -- Await Macro
proc skipUntilStmtList(node: NimNode): NimNode {.compileTime.} =
# Skips a nest of StmtList's.
result = node
if node[0].kind == nnkStmtList:
result = skipUntilStmtList(node[0])
proc skipStmtList(node: NimNode): NimNode {.compileTime.} =
result = node
if node[0].kind == nnkStmtList:
result = node[0]
template createCb(retFutureSym, iteratorNameSym,
name: expr): stmt {.immediate.} =
var nameIterVar = iteratorNameSym
@@ -1083,11 +1212,11 @@ template createCb(retFutureSym, iteratorNameSym,
cb()
#{.pop.}
proc generateExceptionCheck(futSym,
tryStmt, rootReceiver, fromNode: PNimrodNode): PNimrodNode {.compileTime.} =
tryStmt, rootReceiver, fromNode: NimNode): NimNode {.compileTime.} =
if tryStmt.kind == nnkNilLit:
result = rootReceiver
else:
var exceptionChecks: seq[tuple[cond, body: PNimrodNode]] = @[]
var exceptionChecks: seq[tuple[cond, body: NimNode]] = @[]
let errorNode = newDotExpr(futSym, newIdentNode("error"))
for i in 1 .. <tryStmt.len:
let exceptBranch = tryStmt[i]
@@ -1095,7 +1224,7 @@ proc generateExceptionCheck(futSym,
exceptionChecks.add((newIdentNode("true"), exceptBranch[0]))
else:
var exceptIdentCount = 0
var ifCond: PNimrodNode
var ifCond: NimNode
for i in 0 .. <exceptBranch.len:
let child = exceptBranch[i]
if child.kind == nnkIdent:
@@ -1129,10 +1258,10 @@ proc generateExceptionCheck(futSym,
)
result.add elseNode
template createVar(result: var PNimrodNode, futSymName: string,
asyncProc: PNimrodNode,
template createVar(result: var NimNode, futSymName: string,
asyncProc: NimNode,
valueReceiver, rootReceiver: expr,
fromNode: PNimrodNode) =
fromNode: NimNode) =
result = newNimNode(nnkStmtList, fromNode)
var futSym = genSym(nskVar, "future")
result.add newVarStmt(futSym, asyncProc) # -> var future<x> = y
@@ -1140,9 +1269,9 @@ template createVar(result: var PNimrodNode, futSymName: string,
valueReceiver = newDotExpr(futSym, newIdentNode("read")) # -> future<x>.read
result.add generateExceptionCheck(futSym, tryStmt, rootReceiver, fromNode)
proc processBody(node, retFutureSym: PNimrodNode,
proc processBody(node, retFutureSym: NimNode,
subTypeIsVoid: bool,
tryStmt: PNimrodNode): PNimrodNode {.compileTime.} =
tryStmt: NimNode): NimNode {.compileTime.} =
#echo(node.treeRepr)
result = node
case node.kind
@@ -1168,7 +1297,7 @@ proc processBody(node, retFutureSym: PNimrodNode,
result = newNimNode(nnkYieldStmt, node).add(node[1]) # -> yield x
of nnkCall, nnkCommand:
# await foo(p, x)
var futureValue: PNimrodNode
var futureValue: NimNode
result.createVar("future" & $node[1][0].toStrLit, node[1], futureValue,
futureValue, node)
else:
@@ -1207,33 +1336,60 @@ proc processBody(node, retFutureSym: PNimrodNode,
of nnkTryStmt:
# try: await x; except: ...
result = newNimNode(nnkStmtList, node)
proc processForTry(n: PNimrodNode, i: var int,
res: PNimrodNode): bool {.compileTime.} =
template wrapInTry(n, tryBody: expr) =
var temp = n
n[0] = tryBody
tryBody = temp
# Transform ``except`` body.
# TODO: Could we perform some ``await`` transformation here to get it
# working in ``except``?
tryBody[1] = processBody(n[1], retFutureSym, subTypeIsVoid, nil)
proc processForTry(n: NimNode, i: var int,
res: NimNode): bool {.compileTime.} =
## Transforms the body of the tryStmt. Does not transform the
## body in ``except``.
## Returns true if the tryStmt node was transformed into an ifStmt.
result = false
while i < n[0].len:
var processed = processBody(n[0][i], retFutureSym, subTypeIsVoid, n)
if processed.kind != n[0][i].kind or processed.len != n[0][i].len:
var skipped = n.skipStmtList()
while i < skipped.len:
var processed = processBody(skipped[i], retFutureSym,
subTypeIsVoid, n)
# Check if we transformed the node into an exception check.
# This suggests skipped[i] contains ``await``.
if processed.kind != skipped[i].kind or processed.len != skipped[i].len:
processed = processed.skipUntilStmtList()
expectKind(processed, nnkStmtList)
expectKind(processed[2][1], nnkElse)
i.inc
discard processForTry(n, i, processed[2][1][0])
if not processForTry(n, i, processed[2][1][0]):
# We need to wrap the nnkElse nodes back into a tryStmt.
# As they are executed if an exception does not happen
# inside the awaited future.
# The following code will wrap the nodes inside the
# original tryStmt.
wrapInTry(n, processed[2][1][0])
res.add processed
result = true
else:
res.add n[0][i]
res.add skipped[i]
i.inc
var i = 0
if not processForTry(node, i, result):
var temp = node
temp[0] = result
result = temp
# If the tryStmt hasn't been transformed we can just put the body
# back into it.
wrapInTry(node, result)
return
else: discard
for i in 0 .. <result.len:
result[i] = processBody(result[i], retFutureSym, subTypeIsVoid, tryStmt)
result[i] = processBody(result[i], retFutureSym, subTypeIsVoid, nil)
proc getName(node: PNimrodNode): string {.compileTime.} =
proc getName(node: NimNode): string {.compileTime.} =
case node.kind
of nnkPostfix:
return $node[1].ident
@@ -1273,29 +1429,40 @@ macro async*(prc: stmt): stmt {.immediate.} =
if returnType.kind == nnkEmpty: newIdentNode("void")
else: returnType[1]
outerProcBody.add(
newVarStmt(retFutureSym,
newVarStmt(retFutureSym,
newCall(
newNimNode(nnkBracketExpr, prc[6]).add(
newIdentNode(!"newFuture"), # TODO: Strange bug here? Remove the `!`.
subRetType),
newLit(prc[0].getName)))) # Get type from return type of this proc
# -> iterator nameIter(): FutureBase {.closure.} =
# -> iterator nameIter(): FutureBase {.closure.} =
# -> {.push warning[resultshadowed]: off.}
# -> var result: T
# -> {.pop.}
# -> <proc_body>
# -> complete(retFuture, result)
var iteratorNameSym = genSym(nskIterator, $prc[0].getName & "Iter")
var procBody = prc[6].processBody(retFutureSym, subtypeIsVoid, nil)
if not subtypeIsVoid:
procBody.insert(0, newNimNode(nnkVarSection, prc[6]).add(
procBody.insert(0, newNimNode(nnkPragma).add(newIdentNode("push"),
newNimNode(nnkExprColonExpr).add(newNimNode(nnkBracketExpr).add(
newIdentNode("warning"), newIdentNode("resultshadowed")),
newIdentNode("off")))) # -> {.push warning[resultshadowed]: off.}
procBody.insert(1, newNimNode(nnkVarSection, prc[6]).add(
newIdentDefs(newIdentNode("result"), returnType[1]))) # -> var result: T
procBody.insert(2, newNimNode(nnkPragma).add(
newIdentNode("pop"))) # -> {.pop.})
procBody.add(
newCall(newIdentNode("complete"),
retFutureSym, newIdentNode("result"))) # -> complete(retFuture, result)
else:
# -> complete(retFuture)
procBody.add(newCall(newIdentNode("complete"), retFutureSym))
var closureIterator = newProc(iteratorNameSym, [newIdentNode("FutureBase")],
procBody, nnkIteratorDef)
closureIterator[4] = newNimNode(nnkPragma, prc[6]).add(newIdentNode("closure"))
@@ -1309,7 +1476,7 @@ macro async*(prc: stmt): stmt {.immediate.} =
# -> return retFuture
outerProcBody.add newNimNode(nnkReturnStmt, prc[6][prc[6].len-1]).add(retFutureSym)
result = prc
# Remove the 'async' pragma.
@@ -1325,7 +1492,7 @@ macro async*(prc: stmt): stmt {.immediate.} =
result[6] = outerProcBody
#echo(treeRepr(result))
#if prc[0].getName == "catch":
#if prc[0].getName == "test":
# echo(toStrLit(result))
proc recvLine*(socket: TAsyncFD): Future[string] {.async.} =
@@ -1335,7 +1502,7 @@ proc recvLine*(socket: TAsyncFD): Future[string] {.async.} =
## If a full line is read ``\r\L`` is not
## added to ``line``, however if solely ``\r\L`` is read then ``line``
## will be set to it.
##
##
## If the socket is disconnected, ``line`` will be set to ``""``.
##
## If the socket is disconnected in the middle of a line (before ``\r\L``
@@ -1346,7 +1513,7 @@ proc recvLine*(socket: TAsyncFD): Future[string] {.async.} =
##
## **Note**: This procedure is mostly used for testing. You likely want to
## use ``asyncnet.recvLine`` instead.
template addNLIfEmpty(): stmt =
if result.len == 0:
result.add("\c\L")

View File

@@ -1,13 +1,13 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2014 Dominik Picheta
# (c) Copyright 2015 Dominik Picheta
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module implements asynchronous file handling.
## This module implements asynchronous file reading and writing.
##
## .. code-block:: Nim
## import asyncfile, asyncdispatch, os
@@ -24,18 +24,18 @@
import asyncdispatch, os
when defined(windows):
when defined(windows) or defined(nimdoc):
import winlean
else:
import posix
type
AsyncFile = ref object
AsyncFile* = ref object
fd: TAsyncFd
offset: int64
when defined(windows):
proc getDesiredAccess(mode: TFileMode): int32 =
when defined(windows) or defined(nimdoc):
proc getDesiredAccess(mode: FileMode): int32 =
case mode
of fmRead:
result = GENERIC_READ
@@ -44,7 +44,7 @@ when defined(windows):
of fmReadWrite, fmReadWriteExisting:
result = GENERIC_READ or GENERIC_WRITE
proc getCreationDisposition(mode: TFileMode, filename: string): int32 =
proc getCreationDisposition(mode: FileMode, filename: string): int32 =
case mode
of fmRead, fmReadWriteExisting:
OPEN_EXISTING
@@ -54,7 +54,7 @@ when defined(windows):
else:
CREATE_NEW
else:
proc getPosixFlags(mode: TFileMode): cint =
proc getPosixFlags(mode: FileMode): cint =
case mode
of fmRead:
result = O_RDONLY
@@ -70,18 +70,18 @@ else:
proc getFileSize(f: AsyncFile): int64 =
## Retrieves the specified file's size.
when defined(windows):
when defined(windows) or defined(nimdoc):
var high: DWord
let low = getFileSize(f.fd.THandle, addr high)
if low == INVALID_FILE_SIZE:
raiseOSError()
raiseOSError(osLastError())
return (high shl 32) or low
proc openAsync*(filename: string, mode = fmRead): AsyncFile =
## Opens a file specified by the path in ``filename`` using
## the specified ``mode`` asynchronously.
new result
when defined(windows):
when defined(windows) or defined(nimdoc):
let flags = FILE_FLAG_OVERLAPPED or FILE_ATTRIBUTE_NORMAL
let desiredAccess = getDesiredAccess(mode)
let creationDisposition = getCreationDisposition(mode, filename)
@@ -95,7 +95,7 @@ proc openAsync*(filename: string, mode = fmRead): AsyncFile =
nil, creationDisposition, flags, 0).TAsyncFd
if result.fd.THandle == INVALID_HANDLE_VALUE:
raiseOSError()
raiseOSError(osLastError())
register(result.fd)
@@ -108,7 +108,7 @@ proc openAsync*(filename: string, mode = fmRead): AsyncFile =
let perm = S_IRUSR or S_IWUSR or S_IRGRP or S_IWGRP or S_IROTH
result.fd = open(filename, flags, perm).TAsyncFD
if result.fd.cint == -1:
raiseOSError()
raiseOSError(osLastError())
register(result.fd)
@@ -120,7 +120,7 @@ proc read*(f: AsyncFile, size: int): Future[string] =
## returned.
var retFuture = newFuture[string]("asyncfile.read")
when defined(windows):
when defined(windows) or defined(nimdoc):
var buffer = alloc0(size)
var ol = PCustomOverlapped()
@@ -185,7 +185,7 @@ proc read*(f: AsyncFile, size: int): Future[string] =
if res < 0:
let lastError = osLastError()
if lastError.int32 != EAGAIN:
retFuture.fail(newException(EOS, osErrorMsg(lastError)))
retFuture.fail(newException(OSError, osErrorMsg(lastError)))
else:
result = false # We still want this callback to be called.
elif res == 0:
@@ -224,10 +224,10 @@ proc setFilePos*(f: AsyncFile, pos: int64) =
## Sets the position of the file pointer that is used for read/write
## operations. The file's first byte has the index zero.
f.offset = pos
when not defined(windows):
when not defined(windows) and not defined(nimdoc):
let ret = lseek(f.fd.cint, pos, SEEK_SET)
if ret == -1:
raiseOSError()
raiseOSError(osLastError())
proc readAll*(f: AsyncFile): Future[string] {.async.} =
## Reads all data from the specified file.
@@ -245,7 +245,7 @@ proc write*(f: AsyncFile, data: string): Future[void] =
## specified file.
var retFuture = newFuture[void]("asyncfile.write")
var copy = data
when defined(windows):
when defined(windows) or defined(nimdoc):
var buffer = alloc0(data.len)
copyMem(buffer, addr copy[0], data.len)
@@ -299,7 +299,7 @@ proc write*(f: AsyncFile, data: string): Future[void] =
if res < 0:
let lastError = osLastError()
if lastError.int32 != EAGAIN:
retFuture.fail(newException(EOS, osErrorMsg(lastError)))
retFuture.fail(newException(OSError, osErrorMsg(lastError)))
else:
result = false # We still want this callback to be called.
else:
@@ -316,10 +316,10 @@ proc write*(f: AsyncFile, data: string): Future[void] =
proc close*(f: AsyncFile) =
## Closes the file specified.
when defined(windows):
when defined(windows) or defined(nimdoc):
if not closeHandle(f.fd.THandle).bool:
raiseOSError()
raiseOSError(osLastError())
else:
if close(f.fd.cint) == -1:
raiseOSError()
raiseOSError(osLastError())

View File

@@ -1,7 +1,7 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2014 Dominik Picheta
# (c) Copyright 2015 Dominik Picheta
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
@@ -61,8 +61,8 @@ proc pasv(ftp: AsyncFtpClient) {.async.} =
assertReply(pasvMsg, "227")
var betweenParens = captureBetween(pasvMsg.string, '(', ')')
var nums = betweenParens.split(',')
var ip = nums[0.. -3]
var port = nums[-2.. -1]
var ip = nums[0.. ^3]
var port = nums[^2.. ^1]
var properPort = port[0].parseInt()*256+port[1].parseInt()
await ftp.dsock.connect(ip.join("."), Port(properPort.toU16))
ftp.dsockConnected = true
@@ -149,7 +149,7 @@ proc createDir*(ftp: AsyncFtpClient, dir: string, recursive = false){.async.} =
assertReply reply, "257"
proc chmod*(ftp: AsyncFtpClient, path: string,
permissions: set[TFilePermission]) {.async.} =
permissions: set[FilePermission]) {.async.} =
## Changes permission of ``path`` to ``permissions``.
var userOctal = 0
var groupOctal = 0
@@ -188,7 +188,7 @@ proc retrText*(ftp: AsyncFtpClient, file: string): Future[string] {.async.} =
result = await ftp.getLines()
proc getFile(ftp: AsyncFtpClient, file: TFile, total: BiggestInt,
proc getFile(ftp: AsyncFtpClient, file: File, total: BiggestInt,
onProgressChanged: ProgressChangedProc) {.async.} =
assert ftp.dsockConnected
var progress = 0
@@ -240,7 +240,7 @@ proc retrFile*(ftp: AsyncFtpClient, file, dest: string,
await getFile(ftp, destFile, fileSize, onProgressChanged)
proc doUpload(ftp: AsyncFtpClient, file: TFile,
proc doUpload(ftp: AsyncFtpClient, file: File,
onProgressChanged: ProgressChangedProc) {.async.} =
assert ftp.dsockConnected
@@ -300,7 +300,7 @@ proc newAsyncFtpClient*(address: string, port = Port(21),
result.dsockConnected = false
result.csock = newAsyncSocket()
when isMainModule:
when not defined(testing) and isMainModule:
var ftp = newAsyncFtpClient("example.com", user = "test", pass = "test")
proc main(ftp: AsyncFtpClient) {.async.} =
await ftp.connect()

View File

@@ -1,7 +1,7 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2014 Dominik Picheta
# (c) Copyright 2015 Dominik Picheta
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@@ -17,12 +17,13 @@
## as the response body.
##
## .. code-block::nim
## import asynchttpserver, asyncdispatch
##
## var server = newAsyncHttpServer()
## proc cb(req: Request) {.async.} =
## await req.respond(Http200, "Hello World")
##
## asyncCheck server.serve(Port(8080), cb)
## runForever()
## waitFor server.serve(Port(8080), cb)
import strtabs, asyncnet, asyncdispatch, parseutils, uri, strutils
type
@@ -107,22 +108,19 @@ proc sendHeaders*(req: Request, headers: StringTableRef): Future[void] =
addHeaders(msg, headers)
return req.client.send(msg)
proc respond*(req: Request, code: HttpCode,
content: string, headers = newStringTable()) {.async.} =
proc respond*(req: Request, code: HttpCode, content: string,
headers: StringTableRef = nil): Future[void] =
## Responds to the request with the specified ``HttpCode``, headers and
## content.
##
## This procedure will **not** close the client socket.
var customHeaders = headers
customHeaders["Content-Length"] = $content.len
var msg = "HTTP/1.1 " & $code & "\c\L"
msg.addHeaders(customHeaders)
await req.client.send(msg & "\c\L" & content)
proc newRequest(): Request =
result.headers = newStringTable(modeCaseInsensitive)
result.hostname = ""
result.body = ""
if headers != nil:
msg.addHeaders(headers)
msg.add("Content-Length: " & $content.len & "\c\L\c\L")
msg.add(content)
result = req.client.send(msg)
proc parseHeader(line: string): tuple[key, value: string] =
var i = 0
@@ -147,59 +145,65 @@ proc sendStatus(client: AsyncSocket, status: string): Future[void] =
proc processClient(client: AsyncSocket, address: string,
callback: proc (request: Request):
Future[void] {.closure, gcsafe.}) {.async.} =
var request: Request
request.url = initUri()
request.headers = newStringTable(modeCaseInsensitive)
var line = newStringOfCap(80)
var key, value = ""
while not client.isClosed:
# GET /path HTTP/1.1
# Header: val
# \n
var request = newRequest()
request.hostname = address
request.headers.clear(modeCaseInsensitive)
request.hostname.shallowCopy(address)
assert client != nil
request.client = client
# First line - GET /path HTTP/1.1
let line = await client.recvLine() # TODO: Timeouts.
line.setLen(0)
await client.recvLineInto(addr line) # TODO: Timeouts.
if line == "":
client.close()
return
let lineParts = line.split(' ')
if lineParts.len != 3:
await request.respond(Http400, "Invalid request. Got: " & line)
continue
let reqMethod = lineParts[0]
let path = lineParts[1]
let protocol = lineParts[2]
var i = 0
for linePart in line.split(' '):
case i
of 0: request.reqMethod.shallowCopy(linePart.normalize)
of 1: parseUri(linePart, request.url)
of 2:
try:
request.protocol = parseProtocol(linePart)
except ValueError:
asyncCheck request.respond(Http400,
"Invalid request protocol. Got: " & linePart)
continue
else:
await request.respond(Http400, "Invalid request. Got: " & line)
continue
inc i
# Headers
var i = 0
while true:
i = 0
let headerLine = await client.recvLine()
if headerLine == "":
line.setLen(0)
await client.recvLineInto(addr line)
if line == "":
client.close(); return
if headerLine == "\c\L": break
# TODO: Compiler crash
#let (key, value) = parseHeader(headerLine)
let kv = parseHeader(headerLine)
request.headers[kv.key] = kv.value
if line == "\c\L": break
let (key, value) = parseHeader(line)
request.headers[key] = value
request.reqMethod = reqMethod
request.url = parseUri(path)
try:
request.protocol = protocol.parseProtocol()
except ValueError:
asyncCheck request.respond(Http400, "Invalid request protocol. Got: " &
protocol)
continue
if reqMethod.normalize == "post":
if request.reqMethod == "post":
# Check for Expect header
if request.headers.hasKey("Expect"):
if request.headers["Expect"].toLower == "100-continue":
await client.sendStatus("100 Continue")
else:
await client.sendStatus("417 Expectation Failed")
# Read the body
# - Check for Content-length header
if request.headers.hasKey("Content-Length"):
@@ -213,11 +217,11 @@ proc processClient(client: AsyncSocket, address: string,
await request.respond(Http400, "Bad Request. No Content-Length.")
continue
case reqMethod.normalize
case request.reqMethod
of "get", "post", "head", "put", "delete", "trace", "options", "connect", "patch":
await callback(request)
else:
await request.respond(Http400, "Invalid request method. Got: " & reqMethod)
await request.respond(Http400, "Invalid request method. Got: " & request.reqMethod)
# Persistent connections
if (request.protocol == HttpVer11 and
@@ -245,7 +249,7 @@ proc serve*(server: AsyncHttpServer, port: Port,
server.socket.setSockOpt(OptReuseAddr, true)
server.socket.bindAddr(port, address)
server.socket.listen()
while true:
# TODO: Causes compiler crash.
#var (address, client) = await server.socket.acceptAddr()
@@ -258,7 +262,7 @@ proc close*(server: AsyncHttpServer) =
## Terminates the async http server instance.
server.socket.close()
when isMainModule:
when not defined(testing) and isMainModule:
proc main =
var server = newAsyncHttpServer()
proc cb(req: Request) {.async.} =

View File

@@ -18,8 +18,8 @@ import sockets, os
## This module implements an asynchronous event loop together with asynchronous
## sockets which use this event loop.
## It is akin to Python's asyncore module. Many modules that use sockets
## have an implementation for this module, those modules should all have a
## ``register`` function which you should use to add the desired objects to a
## have an implementation for this module, those modules should all have a
## ``register`` function which you should use to add the desired objects to a
## dispatcher which you created so
## that you can receive the events associated with that module's object.
##
@@ -27,19 +27,19 @@ import sockets, os
## function in a while loop.
##
## **Note:** Most modules have tasks which need to be ran regularly, this is
## why you should not call ``poll`` with a infinite timeout, or even a
## why you should not call ``poll`` with a infinite timeout, or even a
## very long one. In most cases the default timeout is fine.
##
## **Note:** This module currently only supports select(), this is limited by
## FD_SETSIZE, which is usually 1024. So you may only be able to use 1024
## sockets at a time.
##
##
## Most (if not all) modules that use asyncio provide a userArg which is passed
## on with the events. The type that you set userArg to must be inheriting from
## ``RootObj``!
##
## **Note:** If you want to provide async ability to your module please do not
## use the ``Delegate`` object, instead use ``AsyncSocket``. It is possible
## **Note:** If you want to provide async ability to your module please do not
## use the ``Delegate`` object, instead use ``AsyncSocket``. It is possible
## that in the future this type's fields will not be exported therefore breaking
## your code.
##
@@ -59,11 +59,11 @@ import sockets, os
## socket which will give you the client which is connecting. You should then
## set any events that you want to use on that client and add it to your dispatcher
## using the ``register`` procedure.
##
##
## An example ``handleAccept`` follows:
##
##
## .. code-block:: nim
##
##
## var disp = newDispatcher()
## ...
## proc handleAccept(s: AsyncSocket) =
@@ -74,7 +74,7 @@ import sockets, os
## client.handleRead = ...
## disp.register(client)
## ...
##
##
## For client sockets you should only be interested in the ``handleRead`` and
## ``handleConnect`` events. The former gets called whenever the socket has
## received messages and can be read from and the latter gets called whenever
@@ -83,14 +83,14 @@ import sockets, os
##
## Getting a blocking client from an AsyncSocket
## =============================================
##
##
## If you need a asynchronous server socket but you wish to process the clients
## synchronously then you can use the ``getSocket`` converter to get
## a ``Socket`` from the ``AsyncSocket`` object, this can then be combined
## with ``accept`` like so:
##
## .. code-block:: nim
##
##
## proc handleAccept(s: AsyncSocket) =
## var client: Socket
## getSocket(s).accept(client)
@@ -113,11 +113,11 @@ type
handleWrite*: proc (h: RootRef) {.nimcall, gcsafe.}
handleError*: proc (h: RootRef) {.nimcall, gcsafe.}
hasDataBuffered*: proc (h: RootRef): bool {.nimcall, gcsafe.}
open*: bool
task*: proc (h: RootRef) {.nimcall, gcsafe.}
mode*: FileMode
Delegate* = ref DelegateObj
Dispatcher* = ref DispatcherObj
@@ -144,7 +144,7 @@ type
deleg: Delegate
SocketStatus* = enum
SockIdle, SockConnecting, SockConnected, SockListening, SockClosed,
SockIdle, SockConnecting, SockConnected, SockListening, SockClosed,
SockUDPBound
{.deprecated: [TDelegate: DelegateObj, PDelegate: Delegate,
@@ -176,8 +176,8 @@ proc newAsyncSocket(): AsyncSocket =
result.lineBuffer = "".TaintedString
result.sendBuffer = ""
proc asyncSocket*(domain: Domain = AF_INET, typ: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP,
proc asyncSocket*(domain: Domain = AF_INET, typ: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP,
buffered = true): AsyncSocket =
## Initialises an AsyncSocket object. If a socket cannot be initialised
## EOS is raised.
@@ -236,7 +236,7 @@ proc asyncSockHandleWrite(h: RootRef) =
if AsyncSocket(h).socket.isSSL and not
AsyncSocket(h).socket.gotHandshake:
return
if AsyncSocket(h).info == SockConnecting:
AsyncSocket(h).handleConnect(AsyncSocket(h))
AsyncSocket(h).info = SockConnected
@@ -256,10 +256,10 @@ proc asyncSockHandleWrite(h: RootRef) =
# do nothing instead.
discard
elif bytesSent != sock.sendBuffer.len:
sock.sendBuffer = sock.sendBuffer[bytesSent .. -1]
sock.sendBuffer = sock.sendBuffer[bytesSent .. ^1]
elif bytesSent == sock.sendBuffer.len:
sock.sendBuffer = ""
if AsyncSocket(h).handleWrite != nil:
AsyncSocket(h).handleWrite(AsyncSocket(h))
except OSError:
@@ -284,7 +284,7 @@ when defined(ssl):
else:
# handshake will set socket's ``sslNoHandshake`` field.
discard AsyncSocket(h).socket.handshake()
proc asyncSockTask(h: RootRef) =
when defined(ssl):
@@ -377,9 +377,9 @@ proc acceptAddr*(server: AsyncSocket, client: var AsyncSocket,
if c == invalidSocket: raiseSocketError(server.socket)
c.setBlocking(false) # TODO: Needs to be tested.
# deleg.open is set in ``toDelegate``.
client.socket = c
client.lineBuffer = "".TaintedString
client.sendBuffer = ""
@@ -393,7 +393,7 @@ proc accept*(server: AsyncSocket, client: var AsyncSocket) =
proc acceptAddr*(server: AsyncSocket): tuple[sock: AsyncSocket,
address: string] {.deprecated.} =
## Equivalent to ``sockets.acceptAddr``.
##
##
## **Deprecated since version 0.9.0:** Please use the function above.
var client = newAsyncSocket()
var address: string = ""
@@ -441,17 +441,17 @@ proc isConnected*(s: AsyncSocket): bool =
## Determines whether ``s`` is connected.
return s.info == SockConnected
proc isListening*(s: AsyncSocket): bool =
## Determines whether ``s`` is listening for incoming connections.
## Determines whether ``s`` is listening for incoming connections.
return s.info == SockListening
proc isConnecting*(s: AsyncSocket): bool =
## Determines whether ``s`` is connecting.
## Determines whether ``s`` is connecting.
return s.info == SockConnecting
proc isClosed*(s: AsyncSocket): bool =
## Determines whether ``s`` has been closed.
return s.info == SockClosed
proc isSendDataBuffered*(s: AsyncSocket): bool =
## Determines whether ``s`` has data waiting to be sent, i.e. whether this
## socket's sendBuffer contains data.
## socket's sendBuffer contains data.
return s.sendBuffer.len != 0
proc setHandleWrite*(s: AsyncSocket,
@@ -550,7 +550,7 @@ proc send*(sock: AsyncSocket, data: string) =
sock.sendBuffer.add(data)
sock.deleg.mode = fmReadWrite
elif bytesSent != data.len:
sock.sendBuffer.add(data[bytesSent .. -1])
sock.sendBuffer.add(data[bytesSent .. ^1])
sock.deleg.mode = fmReadWrite
proc timeValFromMilliseconds(timeout = 500): Timeval =
@@ -561,10 +561,10 @@ proc timeValFromMilliseconds(timeout = 500): Timeval =
proc createFdSet(fd: var TFdSet, s: seq[Delegate], m: var int) =
FD_ZERO(fd)
for i in items(s):
for i in items(s):
m = max(m, int(i.fd))
FD_SET(i.fd, fd)
proc pruneSocketSet(s: var seq[Delegate], fd: var TFdSet) =
var i = 0
var L = s.len
@@ -576,16 +576,16 @@ proc pruneSocketSet(s: var seq[Delegate], fd: var TFdSet) =
inc(i)
setLen(s, L)
proc select(readfds, writefds, exceptfds: var seq[Delegate],
proc select(readfds, writefds, exceptfds: var seq[Delegate],
timeout = 500): int =
var tv {.noInit.}: Timeval = timeValFromMilliseconds(timeout)
var rd, wr, ex: TFdSet
var m = 0
createFdSet(rd, readfds, m)
createFdSet(wr, writefds, m)
createFdSet(ex, exceptfds, m)
if timeout != -1:
result = int(select(cint(m+1), addr(rd), addr(wr), addr(ex), addr(tv)))
else:
@@ -599,7 +599,7 @@ proc poll*(d: Dispatcher, timeout: int = 500): bool =
## This function checks for events on all the delegates in the `PDispatcher`.
## It then proceeds to call the correct event handler.
##
## This function returns ``True`` if there are file descriptors that are still
## This function returns ``True`` if there are file descriptors that are still
## open, otherwise ``False``. File descriptors that have been
## closed are immediately removed from the dispatcher automatically.
##
@@ -611,7 +611,7 @@ proc poll*(d: Dispatcher, timeout: int = 500): bool =
var readDg, writeDg, errorDg: seq[Delegate] = @[]
var len = d.delegates.len
var dc = 0
while dc < len:
let deleg = d.delegates[dc]
if (deleg.mode != fmWrite or deleg.mode != fmAppend) and deleg.open:
@@ -625,20 +625,20 @@ proc poll*(d: Dispatcher, timeout: int = 500): bool =
# File/socket has been closed. Remove it from dispatcher.
d.delegates[dc] = d.delegates[len-1]
dec len
d.delegates.setLen(len)
var hasDataBufferedCount = 0
for d in d.delegates:
if d.hasDataBuffered(d.deleVal):
hasDataBufferedCount.inc()
d.handleRead(d.deleVal)
if hasDataBufferedCount > 0: return true
if readDg.len() == 0 and writeDg.len() == 0:
## TODO: Perhaps this shouldn't return if errorDg has something?
return false
if select(readDg, writeDg, errorDg, timeout) != 0:
for i in 0..len(d.delegates)-1:
if i > len(d.delegates)-1: break # One delegate might've been removed.
@@ -651,7 +651,7 @@ proc poll*(d: Dispatcher, timeout: int = 500): bool =
deleg.handleWrite(deleg.deleVal)
if deleg notin errorDg:
deleg.handleError(deleg.deleVal)
# Execute tasks
for i in items(d.delegates):
i.task(i.deleVal)
@@ -660,11 +660,11 @@ proc len*(disp: Dispatcher): int =
## Retrieves the amount of delegates in ``disp``.
return disp.delegates.len
when isMainModule:
when not defined(testing) and isMainModule:
proc testConnect(s: AsyncSocket, no: int) =
echo("Connected! " & $no)
proc testRead(s: AsyncSocket, no: int) =
echo("Reading! " & $no)
var data = ""
@@ -675,38 +675,38 @@ when isMainModule:
echo(data)
echo("Finished reading! " & $no)
proc testAccept(s: AsyncSocket, disp: PDispatcher, no: int) =
proc testAccept(s: AsyncSocket, disp: Dispatcher, no: int) =
echo("Accepting client! " & $no)
var client: AsyncSocket
new(client)
var address = ""
s.acceptAddr(client, address)
echo("Accepted ", address)
client.handleRead =
client.handleRead =
proc (s: AsyncSocket) =
testRead(s, 2)
disp.register(client)
proc main =
var d = newDispatcher()
var s = asyncSocket()
s.connect("amber.tenthbit.net", TPort(6667))
s.handleConnect =
s.connect("amber.tenthbit.net", Port(6667))
s.handleConnect =
proc (s: AsyncSocket) =
testConnect(s, 1)
s.handleRead =
s.handleRead =
proc (s: AsyncSocket) =
testRead(s, 1)
d.register(s)
var server = asyncSocket()
server.handleAccept =
proc (s: AsyncSocket) =
proc (s: AsyncSocket) =
testAccept(s, d, 78)
server.bindAddr(TPort(5555))
server.bindAddr(Port(5555))
server.listen()
d.register(server)
while d.poll(-1): discard
main()

View File

@@ -1,7 +1,7 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2014 Dominik Picheta
# (c) Copyright 2015 Dominik Picheta
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@@ -24,7 +24,7 @@
##
## Chat server
## ^^^^^^^^^^^
##
##
## The following example demonstrates a simple chat server.
##
## .. code-block::nim
@@ -182,26 +182,30 @@ proc connect*(socket: AsyncSocket, address: string, port: Port,
sslSetConnectState(socket.sslHandle)
sslLoop(socket, flags, sslDoHandshake(socket.sslHandle))
proc readInto(buf: cstring, size: int, socket: AsyncSocket,
flags: set[SocketFlag]): Future[int] {.async.} =
template readInto(buf: cstring, size: int, socket: AsyncSocket,
flags: set[SocketFlag]): int =
## Reads **up to** ``size`` bytes from ``socket`` into ``buf``. Note that
## this is a template and not a proc.
var res = 0
if socket.isSsl:
when defined(ssl):
# SSL mode.
sslLoop(socket, flags,
sslRead(socket.sslHandle, buf, size.cint))
result = opResult
res = opResult
else:
var data = await recv(socket.fd.TAsyncFD, size, flags)
if data.len != 0:
copyMem(buf, addr data[0], data.len)
var recvIntoFut = recvInto(socket.fd.TAsyncFD, buf, size, flags)
yield recvIntoFut
# Not in SSL mode.
result = data.len
res = recvIntoFut.read()
res
proc readIntoBuf(socket: AsyncSocket,
flags: set[SocketFlag]): Future[int] {.async.} =
result = await readInto(addr socket.buffer[0], BufferSize, socket, flags)
template readIntoBuf(socket: AsyncSocket,
flags: set[SocketFlag]): int =
var size = readInto(addr socket.buffer[0], BufferSize, socket, flags)
socket.currPos = 0
socket.bufLen = result
socket.bufLen = size
size
proc recv*(socket: AsyncSocket, size: int,
flags = {SocketFlag.SafeDisconn}): Future[string] {.async.} =
@@ -222,10 +226,11 @@ proc recv*(socket: AsyncSocket, size: int,
## to be read then the future will complete with a value of ``""``.
if socket.isBuffered:
result = newString(size)
shallow(result)
let originalBufPos = socket.currPos
if socket.bufLen == 0:
let res = await socket.readIntoBuf(flags - {SocketFlag.Peek})
let res = socket.readIntoBuf(flags - {SocketFlag.Peek})
if res == 0:
result.setLen(0)
return
@@ -236,7 +241,7 @@ proc recv*(socket: AsyncSocket, size: int,
if SocketFlag.Peek in flags:
# We don't want to get another buffer if we're peeking.
break
let res = await socket.readIntoBuf(flags - {SocketFlag.Peek})
let res = socket.readIntoBuf(flags - {SocketFlag.Peek})
if res == 0:
break
@@ -251,7 +256,7 @@ proc recv*(socket: AsyncSocket, size: int,
result.setLen(read)
else:
result = newString(size)
let read = await readInto(addr result[0], size, socket, flags)
let read = readInto(addr result[0], size, socket, flags)
result.setLen(read)
proc send*(socket: AsyncSocket, data: string,
@@ -302,15 +307,14 @@ proc accept*(socket: AsyncSocket,
retFut.complete(future.read.client)
return retFut
proc recvLine*(socket: AsyncSocket,
flags = {SocketFlag.SafeDisconn}): Future[string] {.async.} =
## Reads a line of data from ``socket``. Returned future will complete once
## a full line is read or an error occurs.
proc recvLineInto*(socket: AsyncSocket, resString: ptr string,
flags = {SocketFlag.SafeDisconn}) {.async.} =
## Reads a line of data from ``socket`` into ``resString``.
##
## If a full line is read ``\r\L`` is not
## added to ``line``, however if solely ``\r\L`` is read then ``line``
## will be set to it.
##
##
## If the socket is disconnected, ``line`` will be set to ``""``.
##
## If the socket is disconnected in the middle of a line (before ``\r\L``
@@ -318,27 +322,32 @@ proc recvLine*(socket: AsyncSocket,
## The partial line **will be lost**.
##
## **Warning**: The ``Peek`` flag is not yet implemented.
##
## **Warning**: ``recvLine`` on unbuffered sockets assumes that the protocol
## uses ``\r\L`` to delimit a new line.
template addNLIfEmpty(): stmt =
if result.len == 0:
result.add("\c\L")
##
## **Warning**: ``recvLineInto`` on unbuffered sockets assumes that the
## protocol uses ``\r\L`` to delimit a new line.
##
## **Warning**: ``recvLineInto`` currently uses a raw pointer to a string for
## performance reasons. This will likely change soon to use FutureVars.
assert SocketFlag.Peek notin flags ## TODO:
result = newFuture[void]("asyncnet.recvLineInto")
template addNLIfEmpty(): stmt =
if resString[].len == 0:
resString[].add("\c\L")
if socket.isBuffered:
result = ""
if socket.bufLen == 0:
let res = await socket.readIntoBuf(flags)
let res = socket.readIntoBuf(flags)
if res == 0:
return
var lastR = false
while true:
if socket.currPos >= socket.bufLen:
let res = await socket.readIntoBuf(flags)
let res = socket.readIntoBuf(flags)
if res == 0:
result = ""
break
resString[].setLen(0)
return
case socket.buffer[socket.currPos]
of '\r':
@@ -353,24 +362,53 @@ proc recvLine*(socket: AsyncSocket,
socket.currPos.inc()
return
else:
result.add socket.buffer[socket.currPos]
resString[].add socket.buffer[socket.currPos]
socket.currPos.inc()
else:
result = ""
var c = ""
while true:
c = await recv(socket, 1, flags)
let recvFut = recv(socket, 1, flags)
c = recvFut.read()
if c.len == 0:
return ""
resString[].setLen(0)
return
if c == "\r":
c = await recv(socket, 1, flags) # Skip \L
let recvFut = recv(socket, 1, flags) # Skip \L
c = recvFut.read()
assert c == "\L"
addNLIfEmpty()
return
elif c == "\L":
addNLIfEmpty()
return
add(result.string, c)
resString[].add c
proc recvLine*(socket: AsyncSocket,
flags = {SocketFlag.SafeDisconn}): Future[string] {.async.} =
## Reads a line of data from ``socket``. Returned future will complete once
## a full line is read or an error occurs.
##
## If a full line is read ``\r\L`` is not
## added to ``line``, however if solely ``\r\L`` is read then ``line``
## will be set to it.
##
## If the socket is disconnected, ``line`` will be set to ``""``.
##
## If the socket is disconnected in the middle of a line (before ``\r\L``
## is read) then line will be set to ``""``.
## The partial line **will be lost**.
##
## **Warning**: The ``Peek`` flag is not yet implemented.
##
## **Warning**: ``recvLine`` on unbuffered sockets assumes that the protocol
## uses ``\r\L`` to delimit a new line.
template addNLIfEmpty(): stmt =
if result.len == 0:
result.add("\c\L")
assert SocketFlag.Peek notin flags ## TODO:
result = ""
await socket.recvLineInto(addr result, flags)
proc listen*(socket: AsyncSocket, backlog = SOMAXCONN) {.tags: [ReadIOEffect].} =
## Marks ``socket`` as accepting connections.
@@ -458,7 +496,7 @@ proc isClosed*(socket: AsyncSocket): bool =
## Determines whether the socket has been closed.
return socket.closed
when isMainModule:
when not defined(testing) and isMainModule:
type
TestCases = enum
HighClient, LowClient, LowServer
@@ -500,11 +538,11 @@ when isMainModule:
proc (future: Future[void]) =
echo("Send")
client.close()
var f = accept(sock)
f.callback = onAccept
var f = accept(sock)
f.callback = onAccept
runForever()

View File

@@ -18,7 +18,7 @@ import strutils
##
## Quick start example:
##
## # Create a matrix wich 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)
##
@@ -256,7 +256,7 @@ proc `$`* (t:TMatrix2d):string {.noInit.} =
proc isUniform*(t:TMatrix2d,tol=1.0e-6):bool=
## Checks if the transform is uniform, that is
## perpendicular axes of equal lenght, which means (for example)
## perpendicular axes of equal length, which means (for example)
## it cannot transform a circle into an ellipse.
## `tol` is used as tolerance for both equal length comparison
## and perp. comparison.
@@ -305,7 +305,7 @@ proc equals*(m1:TMatrix2d,m2:TMatrix2d,tol=1.0e-6):bool=
abs(m1.ty-m2.ty)<=tol
proc `=~`*(m1,m2:TMatrix2d):bool=
## Checks if `m1`and `m2` is aproximately equal, using a
## Checks if `m1`and `m2` is approximately equal, using a
## tolerance of 1e-6.
equals(m1,m2)

View File

@@ -16,33 +16,33 @@ import times
## Vectors are implemented as direction vectors, ie. when transformed with a matrix
## the translation part of matrix is ignored. The coordinate system used is
## right handed, because its compatible with 2d coordinate system (rotation around
## zaxis equals 2d rotation).
## zaxis equals 2d rotation).
## Operators `+` , `-` , `*` , `/` , `+=` , `-=` , `*=` and `/=` are implemented
## for vectors and scalars.
##
##
## Quick start example:
##
## # Create a matrix wich 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)
##
##
## # 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:TPoint3d=point3d(100.0,150.0,200.0)
##
## var vec:TVector3d=vector3d(5.0,2.0,3.0)
##
##
##
##
## pt &= m # transforms pt in place
##
##
## var pt2:TPoint3d=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
type
type
TMatrix3d* =object
## Implements a row major 3d matrix, which means
## transformations are applied the order they are concatenated.
@@ -53,12 +53,12 @@ type
## [ tx ty tz tw ]
ax*,ay*,az*,aw*, bx*,by*,bz*,bw*, cx*,cy*,cz*,cw*, tx*,ty*,tz*,tw*:float
TPoint3d* = object
## Implements a non-homegeneous 2d point stored as
## Implements a non-homegeneous 2d point stored as
## an `x` , `y` and `z` coordinate.
x*,y*,z*:float
TVector3d* = object
## Implements a 3d **direction vector** stored as
## an `x` , `y` and `z` coordinate. Direction vector means,
TVector3d* = object
## Implements a 3d **direction vector** stored as
## an `x` , `y` and `z` coordinate. Direction vector means,
## that when transforming a vector with a matrix, the translational
## part of the matrix is ignored.
x*,y*,z*:float
@@ -67,7 +67,7 @@ type
# Some forward declarations
proc matrix3d*(ax,ay,az,aw,bx,by,bz,bw,cx,cy,cz,cw,tx,ty,tz,tw:float):TMatrix3d {.noInit.}
## Creates a new 4x4 3d transformation matrix.
## Creates a new 4x4 3d transformation matrix.
## `ax` , `ay` , `az` is the local x axis.
## `bx` , `by` , `bz` is the local y axis.
## `cx` , `cy` , `cz` is the local z axis.
@@ -76,7 +76,7 @@ proc vector3d*(x,y,z:float):TVector3d {.noInit,inline.}
## Returns a new 3d vector (`x`,`y`,`z`)
proc point3d*(x,y,z:float):TPoint3d {.noInit,inline.}
## Returns a new 4d point (`x`,`y`,`z`)
proc tryNormalize*(v:var TVector3d):bool
proc tryNormalize*(v:var TVector3d):bool
## 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
## returned, otherwise true is returned.
@@ -85,7 +85,7 @@ proc tryNormalize*(v:var TVector3d):bool
let
IDMATRIX*:TMatrix3d=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,0.0,1.0,0.0,
0.0,0.0,0.0,1.0)
@@ -114,20 +114,20 @@ proc safeArccos(v:float):float=
## due to rounding issues
return arccos(clamp(v,-1.0,1.0))
template makeBinOpVector(s:expr)=
template makeBinOpVector(s:expr)=
## implements binary operators + , - , * and / for vectors
proc s*(a,b:TVector3d):TVector3d {.inline,noInit.} =
proc s*(a,b:TVector3d):TVector3d {.inline,noInit.} =
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:TVector3d,b:float):TVector3d {.inline,noInit.} =
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:TVector3d):TVector3d {.inline,noInit.} =
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
proc s*(a:var TVector3d,b:TVector3d) {.inline.} =
proc s*(a:var TVector3d,b:TVector3d) {.inline.} =
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 TVector3d,b:float) {.inline.} =
s(a.x,b) ; s(a.y,b) ; s(a.z,b)
@@ -188,20 +188,20 @@ proc scale*(s:float):TMatrix3d {.noInit.} =
proc scale*(s:float,org:TPoint3d):TMatrix3d {.noInit.} =
## 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)
proc stretch*(sx,sy,sz:float):TMatrix3d {.noInit.} =
## Returns new a stretch matrix, which is a
## 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)
proc stretch*(sx,sy,sz:float,org:TPoint3d):TMatrix3d {.noInit.} =
## Returns a new stretch matrix, which is a
## scale matrix with non uniform scale in x,y and z.
## `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)
proc move*(dx,dy,dz:float):TMatrix3d {.noInit.} =
## Returns a new translation matrix.
result.setElements(1,0,0,0, 0,1,0,0, 0,0,1,0, dx,dy,dz,1)
@@ -235,7 +235,7 @@ proc rotate*(angle:float,axis:TVector3d):TMatrix3d {.noInit.}=
uvomc=normax.x*normax.y*omc
uwomc=normax.x*normax.z*omc
vwomc=normax.y*normax.z*omc
result.setElements(
u2+(1.0-u2)*cs, uvomc+wsi, uwomc-vsi, 0.0,
uvomc-wsi, v2+(1.0-v2)*cs, vwomc+usi, 0.0,
@@ -248,11 +248,11 @@ proc rotate*(angle:float,org:TPoint3d,axis:TVector3d):TMatrix3d {.noInit.}=
# see PDF document http://inside.mines.edu/~gmurray/ArbitraryAxisRotation/ArbitraryAxisRotation.pdf
# for how this is computed
var normax=axis
if not normax.tryNormalize: #simplifies matrix computation below a lot
raise newException(DivByZeroError,"Cannot rotate around zero length axis")
let
u=normax.x
v=normax.y
@@ -272,7 +272,7 @@ proc rotate*(angle:float,org:TPoint3d,axis:TVector3d):TMatrix3d {.noInit.}=
uvomc=normax.x*normax.y*omc
uwomc=normax.x*normax.z*omc
vwomc=normax.y*normax.z*omc
result.setElements(
u2+(v2+w2)*cs, uvomc+wsi, uwomc-vsi, 0.0,
uvomc-wsi, v2+(u2+w2)*cs, vwomc+usi, 0.0,
@@ -305,7 +305,7 @@ proc rotateY*(angle:float):TMatrix3d {.noInit.}=
0,1,0,0,
s,0,c,0,
0,0,0,1)
proc rotateZ*(angle:float):TMatrix3d {.noInit.}=
## Creates a matrix that rotates around the z-axis with `angle` radians,
## which is also called a 'yaw' matrix.
@@ -317,19 +317,19 @@ proc rotateZ*(angle:float):TMatrix3d {.noInit.}=
-s,c,0,0,
0,0,1,0,
0,0,0,1)
proc isUniform*(m:TMatrix3d,tol=1.0e-6):bool=
## Checks if the transform is uniform, that is
## perpendicular axes of equal lenght, which means (for example)
## Checks if the transform is uniform, that is
## perpendicular axes of equal length, which means (for example)
## it cannot transform a sphere into an ellipsoid.
## `tol` is used as tolerance for both equal length comparison
## `tol` is used as tolerance for both equal length comparison
## and perpendicular comparison.
#dot product=0 means perpendicular coord. system, check xaxis vs yaxis and xaxis vs zaxis
if abs(m.ax*m.bx+m.ay*m.by+m.az*m.bz)<=tol and # x vs y
abs(m.ax*m.cx+m.ay*m.cy+m.az*m.cz)<=tol and #x vs z
abs(m.bx*m.cx+m.by*m.cy+m.bz*m.cz)<=tol: #y vs z
#subtract squared lengths of axes to check if uniform scaling:
let
sqxlen=(m.ax*m.ax+m.ay*m.ay+m.az*m.az)
@@ -340,16 +340,16 @@ proc isUniform*(m:TMatrix3d,tol=1.0e-6):bool=
return false
proc mirror*(planeperp:TVector3d):TMatrix3d {.noInit.}=
## Creates a matrix that mirrors over the plane that has `planeperp` as normal,
## and passes through origo. `planeperp` does not need to be normalized.
# https://en.wikipedia.org/wiki/Transformation_matrix
var n=planeperp
if not n.tryNormalize:
raise newException(DivByZeroError,"Cannot mirror over a plane with a zero length normal")
let
a=n.x
b=n.y
@@ -357,7 +357,7 @@ proc mirror*(planeperp:TVector3d):TMatrix3d {.noInit.}=
ab=a*b
ac=a*c
bc=b*c
result.setElements(
1-2*a*a , -2*ab,-2*ac,0,
-2*ab , 1-2*b*b, -2*bc, 0,
@@ -376,7 +376,7 @@ proc mirror*(org:TPoint3d,planeperp:TVector3d):TMatrix3d {.noInit.}=
var n=planeperp
if not n.tryNormalize:
raise newException(DivByZeroError,"Cannot mirror over a plane with a zero length normal")
let
a=n.x
b=n.y
@@ -390,7 +390,7 @@ proc mirror*(org:TPoint3d,planeperp:TVector3d):TMatrix3d {.noInit.}=
tx=org.x
ty=org.y
tz=org.z
result.setElements(
1-2*aa , -2*ab,-2*ac,0,
-2*ab , 1-2*bb, -2*bc, 0,
@@ -402,8 +402,8 @@ proc mirror*(org:TPoint3d,planeperp:TVector3d):TMatrix3d {.noInit.}=
proc determinant*(m:TMatrix3d):float=
## Computes the determinant of matrix `m`.
# This computation is gotten from ratsimp(optimize(determinant(m)))
# This computation is gotten from ratsimp(optimize(determinant(m)))
# in maxima CAS
let
O1=m.cx*m.tw-m.cw*m.tx
@@ -423,10 +423,10 @@ proc inverse*(m:TMatrix3d):TMatrix3d {.noInit.}=
## Computes the inverse of matrix `m`. If the matrix
## determinant is zero, thus not invertible, a EDivByZero
## will be raised.
# this computation comes from optimize(invert(m)) in maxima CAS
let
let
det=m.determinant
O2=m.cy*m.tw-m.cw*m.ty
O3=m.cz*m.tw-m.cw*m.tz
@@ -464,7 +464,7 @@ proc inverse*(m:TMatrix3d):TMatrix3d {.noInit.}=
proc equals*(m1:TMatrix3d,m2:TMatrix3d,tol=1.0e-6):bool=
## Checks if all elements of `m1`and `m2` is equal within
## a given tolerance `tol`.
return
return
abs(m1.ax-m2.ax)<=tol and
abs(m1.ay-m2.ay)<=tol and
abs(m1.az-m2.az)<=tol and
@@ -483,14 +483,14 @@ proc equals*(m1:TMatrix3d,m2:TMatrix3d,tol=1.0e-6):bool=
abs(m1.tw-m2.tw)<=tol
proc `=~`*(m1,m2:TMatrix3d):bool=
## Checks if `m1` and `m2` is aproximately equal, using a
## Checks if `m1` and `m2` is approximately equal, using a
## tolerance of 1e-6.
equals(m1,m2)
proc transpose*(m:TMatrix3d):TMatrix3d {.noInit.}=
## 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)
proc getXAxis*(m:TMatrix3d):TVector3d {.noInit.}=
## Gets the local x axis of `m`
result.x=m.ax
@@ -509,26 +509,26 @@ proc getZAxis*(m:TMatrix3d):TVector3d {.noInit.}=
result.y=m.cy
result.z=m.cz
proc `$`*(m:TMatrix3d):string=
## String representation of `m`
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.cx) & "," & rtos(m.cy) & "," &rtos(m.cz) & "," & rtos(m.cw) &
"\n" & rtos(m.tx) & "," & rtos(m.ty) & "," &rtos(m.tz) & "," & rtos(m.tw)
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.cx) & "," & rtos(m.cy) & "," & rtos(m.cz) & "," & rtos(m.cw) &
"\n" & rtos(m.tx) & "," & rtos(m.ty) & "," & rtos(m.tz) & "," & rtos(m.tw)
proc apply*(m:TMatrix3d, x,y,z:var float, translate=false)=
## Applies transformation `m` onto `x` , `y` , `z` , optionally
## using the translation part of the matrix.
let
let
oldx=x
oldy=y
oldz=z
x=m.cx*oldz+m.bx*oldy+m.ax*oldx
y=m.cy*oldz+m.by*oldy+m.ay*oldx
z=m.cz*oldz+m.bz*oldy+m.az*oldx
if translate:
x+=m.tx
y+=m.ty
@@ -552,13 +552,13 @@ proc `len=`*(v:var TVector3d,newlen:float) {.noInit.} =
## an arbitrary vector of the requested length is returned.
let fac=newlen/v.len
if newlen==0.0:
v.x=0.0
v.y=0.0
v.z=0.0
return
if fac==Inf or fac==NegInf:
#to short for float accuracy
#do as good as possible:
@@ -588,7 +588,7 @@ proc `&` *(v:TVector3d,m:TMatrix3d):TVector3d {.noInit.} =
## Concatenate vector `v` with a transformation matrix.
## Transforming a vector ignores the translational part
## of the matrix.
# | AX AY AZ AW |
# | X Y Z 1 | * | BX BY BZ BW |
# | CX CY CZ CW |
@@ -605,12 +605,12 @@ proc `&=` *(v:var TVector3d,m:TMatrix3d) {.noInit.} =
## Applies transformation `m` onto `v` in place.
## Transforming a vector ignores the translational part
## of the matrix.
# | AX AY AZ AW |
# | X Y Z 1 | * | BX BY BZ BW |
# | CX CY CZ CW |
# | 0 0 0 1 |
let
newx=m.cx*v.z+m.bx*v.y+m.ax*v.x
newy=m.cy*v.z+m.by*v.y+m.ay*v.x
@@ -620,38 +620,38 @@ proc `&=` *(v:var TVector3d,m:TMatrix3d) {.noInit.} =
proc transformNorm*(v:var TVector3d,m:TMatrix3d)=
## Applies a normal direction transformation `m` onto `v` in place.
## The resulting vector is *not* normalized. Transforming a vector ignores the
## translational part of the matrix. If the matrix is not invertible
## The resulting vector is *not* normalized. Transforming a vector ignores the
## translational part of the matrix. If the matrix is not invertible
## (determinant=0), an EDivByZero will be raised.
# transforming a normal is done by transforming
# by the transpose of the inverse of the original matrix
# Major reason this simple function is here is that this function can be optimized in the future,
# (possibly by hardware) as well as having a consistent API with the 2d version.
v&=transpose(inverse(m))
proc transformInv*(v:var TVector3d,m:TMatrix3d)=
## Applies the inverse of `m` on vector `v`. Transforming a vector ignores
## the translational part of the matrix. Transforming a vector ignores the
## Applies the inverse of `m` on vector `v`. Transforming a vector ignores
## the translational part of the matrix. Transforming a vector ignores the
## translational part of the matrix.
## If the matrix is not invertible (determinant=0), an EDivByZero
## will be raised.
# Major reason this simple function is here is that this function can be optimized in the future,
# (possibly by hardware) as well as having a consistent API with the 2d version.
v&=m.inverse
proc transformNormInv*(vec:var TVector3d,m:TMatrix3d)=
## Applies an inverse normal direction transformation `m` onto `v` in place.
## This is faster than creating an inverse
## matrix and transformNorm(...) it. Transforming a vector ignores the
## This is faster than creating an inverse
## matrix and transformNorm(...) it. Transforming a vector ignores the
## translational part of the matrix.
# see vector2d:s equivalent for a deeper look how/why this works
vec&=m.transpose
proc tryNormalize*(v:var TVector3d):bool=
proc tryNormalize*(v:var TVector3d):bool=
## 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
## returned, otherwise true is returned.
@@ -663,26 +663,26 @@ proc tryNormalize*(v:var TVector3d):bool=
v.x/=mag
v.y/=mag
v.z/=mag
return true
proc normalize*(v:var TVector3d) {.inline.}=
proc normalize*(v:var TVector3d) {.inline.}=
## Modifies `v` to have a length of 1.0, keeping its angle.
## If `v` has zero length, an EDivByZero will be raised.
if not tryNormalize(v):
raise newException(DivByZeroError,"Cannot normalize zero length vector")
proc rotate*(vec:var TVector3d,angle:float,axis:TVector3d)=
## Rotates `vec` in place, with `angle` radians over `axis`, which passes
## Rotates `vec` in place, with `angle` radians over `axis`, which passes
## through origo.
# see PDF document http://inside.mines.edu/~gmurray/ArbitraryAxisRotation/ArbitraryAxisRotation.pdf
# for how this is computed
var normax=axis
if not normax.tryNormalize:
raise newException(DivByZeroError,"Cannot rotate around zero length axis")
let
cs=cos(angle)
si=sin(angle)
@@ -694,11 +694,11 @@ proc rotate*(vec:var TVector3d,angle:float,axis:TVector3d)=
y=vec.y
z=vec.z
uxyzomc=(u*x+v*y+w*z)*omc
vec.x=u*uxyzomc+x*cs+(v*z-w*y)*si
vec.y=v*uxyzomc+y*cs+(w*x-u*z)*si
vec.z=w*uxyzomc+z*cs+(u*y-v*x)*si
proc scale*(v:var TVector3d,s:float)=
## Scales the vector in place with factor `s`
v.x*=s
@@ -713,12 +713,12 @@ proc stretch*(v:var TVector3d,sx,sy,sz:float)=
proc mirror*(v:var TVector3d,planeperp:TVector3d)=
## 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.
var n=planeperp
n.normalize
let
x=v.x
y=v.y
@@ -729,7 +729,7 @@ proc mirror*(v:var TVector3d,planeperp:TVector3d)=
ac=a*c
ab=a*b
bc=b*c
v.x= -2*(ac*z+ab*y+a*a*x)+x
v.y= -2*(bc*z+b*b*y+ab*x)+y
v.z= -2*(c*c*z+bc*y+ac*x)+z
@@ -740,7 +740,7 @@ proc `-` *(v:TVector3d):TVector3d=
result.x= -v.x
result.y= -v.y
result.z= -v.z
# declare templated binary operators
makeBinOpVector(`+`)
makeBinOpVector(`-`)
@@ -752,7 +752,7 @@ makeBinOpAssignVector(`*=`)
makeBinOpAssignVector(`/=`)
proc dot*(v1,v2:TVector3d):float {.inline.}=
## Computes the dot product of two vectors.
## Computes the dot product of two vectors.
## Returns 0.0 if the vectors are perpendicular.
return v1.x*v2.x+v1.y*v2.y+v1.z*v2.z
@@ -769,12 +769,12 @@ proc cross*(v1,v2:TVector3d):TVector3d {.inline.}=
proc equals*(v1,v2:TVector3d,tol=1.0e-6):bool=
## 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
proc `=~` *(v1,v2:TVector3d):bool=
## Checks if two vectors approximately equals with a
## Checks if two vectors approximately equals with a
## hardcoded tolerance 1e-6
equals(v1,v2)
proc angleTo*(v1,v2:TVector3d):float=
## Returns the smallest angle between v1 and v2,
## which is in range 0-PI
@@ -788,7 +788,7 @@ proc angleTo*(v1,v2:TVector3d):float=
proc arbitraryAxis*(norm:TVector3d):TMatrix3d {.noInit.}=
## Computes the rotation matrix that would transform
## world z vector into `norm`. The inverse of this matrix
## is useful to tranform 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.
const lim=1.0/64.0
var ax,ay,az:TVector3d
@@ -801,7 +801,7 @@ proc arbitraryAxis*(norm:TVector3d):TMatrix3d {.noInit.}=
ay=cross(norm,ax)
ay.normalize()
az=cross(ax,ay)
result.setElements(
ax.x,ax.y,ax.z,0.0,
ay.x,ay.y,ay.z,0.0,
@@ -811,20 +811,20 @@ proc arbitraryAxis*(norm:TVector3d):TMatrix3d {.noInit.}=
proc bisect*(v1,v2:TVector3d):TVector3d {.noInit.}=
## Computes the bisector between v1 and v2 as a normalized vector.
## If one of the input vectors has zero length, a normalized version
## of the other is returned. If both input vectors has zero length,
## an arbitrary normalized vector `v1`is returned.
## of the other is returned. If both input vectors has zero length,
## an arbitrary normalized vector `v1` is returned.
var
vmag1=v1.len
vmag2=v2.len
# zero length vector equals arbitrary vector, just change
# zero length vector equals arbitrary vector, just change
# magnitude to one to avoid zero division
if vmag1==0.0:
if vmag1==0.0:
if vmag2==0: #both are zero length return any normalized vector
return XAXIS
vmag1=1.0
if vmag2==0.0: vmag2=1.0
if vmag2==0.0: vmag2=1.0
let
x1=v1.x/vmag1
y1=v1.y/vmag1
@@ -832,14 +832,14 @@ proc bisect*(v1,v2:TVector3d):TVector3d {.noInit.}=
x2=v2.x/vmag2
y2=v2.y/vmag2
z2=v2.z/vmag2
result.x=(x1 + x2) * 0.5
result.y=(y1 + y2) * 0.5
result.z=(z1 + z2) * 0.5
if not result.tryNormalize():
# This can happen if vectors are colinear. In this special case
# there are actually inifinitely many bisectors, we select just
# there are actually inifinitely many bisectors, we select just
# one of them.
result=v1.cross(XAXIS)
if result.sqrLen<1.0e-9:
@@ -857,14 +857,14 @@ proc point3d*(x,y,z:float):TPoint3d=
result.x=x
result.y=y
result.z=z
proc sqrDist*(a,b:TPoint3d):float=
## Computes the squared distance between `a`and `b`
let dx=b.x-a.x
let dy=b.y-a.y
let dz=b.z-a.z
result=dx*dx+dy*dy+dz*dz
proc dist*(a,b:TPoint3d):float {.inline.}=
## Computes the absolute distance between `a`and `b`
result=sqrt(sqrDist(a,b))
@@ -876,7 +876,7 @@ proc `$` *(p:TPoint3d):string=
result.add(rtos(p.y))
result.add(",")
result.add(rtos(p.z))
proc `&`*(p:TPoint3d,m:TMatrix3d):TPoint3d=
## Concatenates a point `p` with a transform `m`,
## resulting in a new, transformed point.
@@ -893,18 +893,18 @@ proc `&=` *(p:var TPoint3d,m:TMatrix3d)=
p.x=m.cx*z+m.bx*y+m.ax*x+m.tx
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
proc transformInv*(p:var TPoint3d,m:TMatrix3d)=
## Applies the inverse of transformation `m` onto `p` in place.
## If the matrix is not invertable (determinant=0) , EDivByZero will
## be raised.
# can possibly be more optimized in the future so use this function when possible
p&=inverse(m)
proc `+`*(p:TPoint3d,v:TVector3d):TPoint3d {.noInit,inline.} =
## Adds a vector `v` to a point `p`, resulting
## Adds a vector `v` to a point `p`, resulting
## in a new point.
result.x=p.x+v.x
result.y=p.y+v.y
@@ -917,7 +917,7 @@ proc `+=`*(p:var TPoint3d,v:TVector3d) {.noInit,inline.} =
p.z+=v.z
proc `-`*(p:TPoint3d,v:TVector3d):TPoint3d {.noInit,inline.} =
## Subtracts a vector `v` from a point `p`, resulting
## Subtracts a vector `v` from a point `p`, resulting
## in a new point.
result.x=p.x-v.x
result.y=p.y-v.y
@@ -933,37 +933,37 @@ proc `-=`*(p:var TPoint3d,v:TVector3d) {.noInit,inline.} =
## Subtracts a vector `v` from a point `p` in place.
p.x-=v.x
p.y-=v.y
p.z-=v.z
p.z-=v.z
proc equals(p1,p2:TPoint3d,tol=1.0e-6):bool {.inline.}=
## 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
proc `=~`*(p1,p2:TPoint3d):bool {.inline.}=
## Checks if two vectors approximately equals with a
## Checks if two vectors approximately equals with a
## hardcoded tolerance 1e-6
equals(p1,p2)
proc rotate*(p:var TPoint3d,rad:float,axis:TVector3d)=
## Rotates point `p` in place `rad` radians about an axis
## Rotates point `p` in place `rad` radians about an axis
## passing through origo.
var v=vector3d(p.x,p.y,p.z)
v.rotate(rad,axis) # reuse this code here since doing the same thing and quite complicated
p.x=v.x
p.y=v.y
p.z=v.z
proc rotate*(p:var TPoint3d,angle:float,org:TPoint3d,axis:TVector3d)=
## Rotates point `p` in place `rad` radians about an axis
## Rotates point `p` in place `rad` radians about an axis
## passing through `org`
# see PDF document http://inside.mines.edu/~gmurray/ArbitraryAxisRotation/ArbitraryAxisRotation.pdf
# for how this is computed
var normax=axis
normax.normalize
let
cs=cos(angle)
omc=1.0-cs
@@ -987,17 +987,17 @@ proc rotate*(p:var TPoint3d,angle:float,org:TPoint3d,axis:TVector3d)=
bv=b*v
cw=c*w
uxmvymwz=ux-vy-wz
p.x=(a*(vv+ww)-u*(bv+cw-uxmvymwz))*omc + x*cs + (b*w+v*z-c*v-w*y)*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
proc scale*(p:var TPoint3d,fac:float) {.inline.}=
## Scales a point in place `fac` times with world origo as origin.
p.x*=fac
p.y*=fac
p.z*=fac
proc scale*(p:var TPoint3d,fac:float,org:TPoint3d){.inline.}=
## Scales the point in place `fac` times with `org` as origin.
p.x=(p.x - org.x) * fac + org.x
@@ -1005,7 +1005,7 @@ proc scale*(p:var TPoint3d,fac:float,org:TPoint3d){.inline.}=
p.z=(p.z - org.z) * fac + org.z
proc stretch*(p:var TPoint3d,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.
p.x*=facx
p.y*=facy
@@ -1017,7 +1017,7 @@ proc stretch*(p:var TPoint3d,facx,facy,facz:float,org:TPoint3d){.inline.}=
p.x=(p.x - org.x) * facx + org.x
p.y=(p.y - org.y) * facy + org.y
p.z=(p.z - org.z) * facz + org.z
proc move*(p:var TPoint3d,dx,dy,dz:float){.inline.}=
## Translates a point `dx` , `dy` , `dz` in place.
@@ -1033,7 +1033,7 @@ proc move*(p:var TPoint3d,v:TVector3d){.inline.}=
proc area*(a,b,c:TPoint3d):float {.inline.}=
## 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
# product of two edge vectors. Taking this time 0.5 gives the triangle area.
return cross(b-a,c-a).len*0.5

View File

@@ -42,7 +42,7 @@ proc openDefaultBrowser*(url: string) =
for b in getEnv("BROWSER").string.split(PathSep):
try:
# we use ``startProcess`` here because we don't want to block!
discard startProcess(command=b, args=[url], options={poUseShell})
discard startProcess(command=b, args=[url], options={poUsePath})
return
except OSError:
discard

View File

@@ -404,7 +404,7 @@ proc setVal[K,V](table: var PConcTable[K,V], key: int, val: int,
#echo("tomb old slot then set in new table")
nextTable = copySlotAndCheck(table,idx)
return setVal(nextTable, key, val, expVal, match)
# Finaly ready to add new val to table
# Finally ready to add new val to table
while true:
if match and oldVal != expVal:
#echo("set failed, no match oldVal= " & $oldVal & " expVal= " & $expVal)
@@ -525,7 +525,7 @@ proc get*[K,V](table: var PConcTable[K,V], key: var K): V =
#Tests ----------------------------
when isMainModule:
when not defined(testing) and isMainModule:
import locks, times, mersenne
const

View File

@@ -12,7 +12,7 @@
## by Adam Langley.
type
NodeObj[T] = object {.pure, final, acyclic.}
NodeObj[T] = object {.acyclic.}
byte: int ## byte index of the difference
otherbits: char
case isLeaf: bool
@@ -23,11 +23,10 @@ type
val: T
Node[T] = ref NodeObj[T]
CritBitTree*[T] = object {.
pure, final.} ## The crit bit tree can either be used
## as a mapping from strings to
## some type ``T`` or as a set of
## strings if ``T`` is void.
CritBitTree*[T] = object ## The crit bit tree can either be used
## as a mapping from strings to
## some type ``T`` or as a set of
## strings if ``T`` is void.
root: Node[T]
count: int
@@ -175,7 +174,7 @@ proc excl*[T](c: var CritBitTree[T], key: string) =
iterator leaves[T](n: Node[T]): Node[T] =
if n != nil:
# XXX actually we could compute the necessary stack size in advance:
# it's rougly log2(c.count).
# it's roughly log2(c.count).
var stack = @[n]
while stack.len > 0:
var it = stack.pop
@@ -287,18 +286,19 @@ proc `$`*[T](c: CritBitTree[T]): string =
result.add("}")
when isMainModule:
import sequtils
var r: CritBitTree[void]
r.incl "abc"
r.incl "xyz"
r.incl "def"
r.incl "definition"
r.incl "prefix"
doAssert r.contains"def"
#r.del "def"
for w in r.items:
echo w
for w in r.itemsWithPrefix("de"):
echo w
r.excl "def"
assert toSeq(r.items) == @["abc", "definition", "prefix", "xyz"]
assert toSeq(r.itemsWithPrefix("de")) == @["definition"]

View File

@@ -8,18 +8,18 @@
#
## The ``intsets`` module implements an efficient int set implemented as a
## sparse bit set.
## `sparse bit set`:idx:.
## **Note**: Since Nim currently does not allow the assignment operator to
## be overloaded, ``=`` for int sets performs some rather meaningless shallow
## copy; use ``assign`` to get a deep copy.
import
os, hashes, math
hashes, math
type
BitScalar = int
const
const
InitIntSetSize = 8 # must be a power of two!
TrunkShift = 9
BitsPerTrunk = 1 shl TrunkShift # needs to be a power of 2 and
@@ -31,11 +31,11 @@ const
type
PTrunk = ref TTrunk
TTrunk {.final.} = object
TTrunk {.final.} = object
next: PTrunk # all nodes are connected with this pointer
key: int # start address at bit 0
bits: array[0..IntsPerTrunk - 1, BitScalar] # a bit vector
TTrunkSeq = seq[PTrunk]
IntSet* = object ## an efficient set of 'int' implemented as a sparse bit set
counter, max: int
@@ -44,42 +44,42 @@ type
{.deprecated: [TIntSet: IntSet].}
proc mustRehash(length, counter: int): bool {.inline.} =
proc mustRehash(length, counter: int): bool {.inline.} =
assert(length > counter)
result = (length * 2 < counter * 3) or (length - counter < 4)
proc nextTry(h, maxHash: THash): THash {.inline.} =
result = ((5 * h) + 1) and maxHash
proc nextTry(h, maxHash: THash): THash {.inline.} =
result = ((5 * h) + 1) and maxHash
proc intSetGet(t: IntSet, key: int): PTrunk =
proc intSetGet(t: IntSet, key: int): PTrunk =
var h = key and t.max
while t.data[h] != nil:
if t.data[h].key == key:
while t.data[h] != nil:
if t.data[h].key == key:
return t.data[h]
h = nextTry(h, t.max)
result = nil
proc intSetRawInsert(t: IntSet, data: var TTrunkSeq, desc: PTrunk) =
proc intSetRawInsert(t: IntSet, data: var TTrunkSeq, desc: PTrunk) =
var h = desc.key and t.max
while data[h] != nil:
while data[h] != nil:
assert(data[h] != desc)
h = nextTry(h, t.max)
assert(data[h] == nil)
data[h] = desc
proc intSetEnlarge(t: var IntSet) =
proc intSetEnlarge(t: var IntSet) =
var n: TTrunkSeq
var oldMax = t.max
t.max = ((t.max + 1) * 2) - 1
newSeq(n, t.max + 1)
for i in countup(0, oldMax):
for i in countup(0, oldMax):
if t.data[i] != nil: intSetRawInsert(t, n, t.data[i])
swap(t.data, n)
proc intSetPut(t: var IntSet, key: int): PTrunk =
proc intSetPut(t: var IntSet, key: int): PTrunk =
var h = key and t.max
while t.data[h] != nil:
if t.data[h].key == key:
while t.data[h] != nil:
if t.data[h].key == key:
return t.data[h]
h = nextTry(h, t.max)
if mustRehash(t.max + 1, t.counter): intSetEnlarge(t)
@@ -94,43 +94,43 @@ proc intSetPut(t: var IntSet, key: int): PTrunk =
t.data[h] = result
proc contains*(s: IntSet, key: int): bool =
## returns true iff `key` is in `s`.
## returns true iff `key` is in `s`.
var t = intSetGet(s, `shr`(key, TrunkShift))
if t != nil:
if t != nil:
var u = key and TrunkMask
result = (t.bits[`shr`(u, IntShift)] and `shl`(1, u and IntMask)) != 0
else:
else:
result = false
proc incl*(s: var IntSet, key: int) =
proc incl*(s: var IntSet, key: int) =
## includes an element `key` in `s`.
var t = intSetPut(s, `shr`(key, TrunkShift))
var u = key and TrunkMask
t.bits[`shr`(u, IntShift)] = t.bits[`shr`(u, IntShift)] or
`shl`(1, u and IntMask)
proc excl*(s: var IntSet, key: int) =
proc excl*(s: var IntSet, key: int) =
## excludes `key` from the set `s`.
var t = intSetGet(s, `shr`(key, TrunkShift))
if t != nil:
if t != nil:
var u = key and TrunkMask
t.bits[`shr`(u, IntShift)] = t.bits[`shr`(u, IntShift)] and
not `shl`(1, u and IntMask)
proc containsOrIncl*(s: var IntSet, key: int): bool =
proc containsOrIncl*(s: var IntSet, key: int): bool =
## returns true if `s` contains `key`, otherwise `key` is included in `s`
## and false is returned.
var t = intSetGet(s, `shr`(key, TrunkShift))
if t != nil:
if t != nil:
var u = key and TrunkMask
result = (t.bits[`shr`(u, IntShift)] and `shl`(1, u and IntMask)) != 0
if not result:
if not result:
t.bits[`shr`(u, IntShift)] = t.bits[`shr`(u, IntShift)] or
`shl`(1, u and IntMask)
else:
else:
incl(s, key)
result = false
proc initIntSet*: IntSet =
## creates a new int set that is empty.
newSeq(result.data, InitIntSetSize)
@@ -140,14 +140,14 @@ proc initIntSet*: IntSet =
proc assign*(dest: var IntSet, src: IntSet) =
## copies `src` to `dest`. `dest` does not need to be initialized by
## `initIntSet`.
## `initIntSet`.
dest.counter = src.counter
dest.max = src.max
newSeq(dest.data, src.data.len)
var it = src.head
while it != nil:
var h = it.key and dest.max
while dest.data[h] != nil: h = nextTry(h, dest.max)
assert(dest.data[h] == nil)
@@ -168,7 +168,7 @@ iterator items*(s: IntSet): int {.inline.} =
while r != nil:
var i = 0
while i <= high(r.bits):
var w = r.bits[i]
var w = r.bits[i]
# taking a copy of r.bits[i] here is correct, because
# modifying operations are not allowed during traversation
var j = 0
@@ -191,20 +191,28 @@ proc `$`*(s: IntSet): string =
## The `$` operator for int sets.
dollarImpl()
proc empty*(s: IntSet): bool {.inline.} =
proc empty*(s: IntSet): bool {.inline, deprecated.} =
## returns true if `s` is empty. This is safe to call even before
## the set has been initialized with `initIntSet`.
## the set has been initialized with `initIntSet`. Note this never
## worked reliably and so is deprecated.
result = s.counter == 0
when isMainModule:
import sequtils, algorithm
var x = initIntSet()
x.incl(1)
x.incl(2)
x.incl(7)
x.incl(1056)
for e in items(x): echo e
var y: TIntSet
var xs = toSeq(items(x))
xs.sort(cmp[int])
assert xs == @[1, 2, 7, 1056]
var y: IntSet
assign(y, x)
for e in items(y): echo e
var ys = toSeq(items(y))
ys.sort(cmp[int])
assert ys == @[1, 2, 7, 1056]

View File

@@ -32,7 +32,7 @@ type
head*, tail*: DoublyLinkedNode[T]
SinglyLinkedRing*[T] = object ## a singly linked ring
head*: SinglyLinkedNode[T]
head*, tail*: SinglyLinkedNode[T]
DoublyLinkedRing*[T] = object ## a doubly linked ring
head*: DoublyLinkedNode[T]
@@ -122,6 +122,22 @@ iterator items*[T](L: DoublyLinkedRing[T]): T =
## yields every value of `L`.
itemsRingImpl()
iterator mitems*[T](L: var DoublyLinkedList[T]): var T =
## yields every value of `L` so that you can modify it.
itemsListImpl()
iterator mitems*[T](L: var SinglyLinkedList[T]): var T =
## yields every value of `L` so that you can modify it.
itemsListImpl()
iterator mitems*[T](L: var SinglyLinkedRing[T]): var T =
## yields every value of `L` so that you can modify it.
itemsRingImpl()
iterator mitems*[T](L: var DoublyLinkedRing[T]): var T =
## yields every value of `L` so that you can modify it.
itemsRingImpl()
iterator nodes*[T](L: SinglyLinkedList[T]): SinglyLinkedNode[T] =
## iterates over every node of `x`. Removing the current node from the
## list during traversal is supported.
@@ -251,13 +267,31 @@ proc remove*[T](L: var DoublyLinkedList[T], n: DoublyLinkedNode[T]) =
if n.prev != nil: n.prev.next = n.next
proc append*[T](L: var SinglyLinkedRing[T], n: SinglyLinkedNode[T]) =
## appends a node `n` to `L`. Efficiency: O(1).
if L.head != nil:
n.next = L.head
assert(L.tail != nil)
L.tail.next = n
L.tail = n
else:
n.next = n
L.head = n
L.tail = n
proc append*[T](L: var SinglyLinkedRing[T], value: T) =
## appends a value to `L`. Efficiency: O(1).
append(L, newSinglyLinkedNode(value))
proc prepend*[T](L: var SinglyLinkedRing[T], n: SinglyLinkedNode[T]) =
## prepends a node `n` to `L`. Efficiency: O(1).
if L.head != nil:
n.next = L.head
L.head.next = n
else:
if L.head != nil:
n.next = L.head
assert(L.tail != nil)
L.tail.next = n
else:
n.next = n
L.tail = n
L.head = n
proc prepend*[T](L: var SinglyLinkedRing[T], value: T) =

View File

@@ -7,7 +7,7 @@
# distribution, for details about the copyright.
#
## Implementation of a queue. The underlying implementation uses a ``seq``.
## Implementation of a `queue`:idx:. The underlying implementation uses a ``seq``.
## Note: For inter thread communication use
## a `TChannel <channels.html>`_ instead.
@@ -39,6 +39,15 @@ iterator items*[T](q: Queue[T]): T =
yield q.data[i]
i = (i + 1) and q.mask
iterator mitems*[T](q: var Queue[T]): var T =
## yields every element of `q`.
var i = q.rd
var c = q.count
while c > 0:
dec c
yield q.data[i]
i = (i + 1) and q.mask
proc add*[T](q: var Queue[T], item: T) =
## adds an `item` to the end of the queue `q`.
var cap = q.mask+1

View File

@@ -1,7 +1,7 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2014 Andreas Rumpf
# (c) Copyright 2015 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.

View File

@@ -47,6 +47,24 @@ proc concat*[T](seqs: varargs[seq[T]]): seq[T] =
result[i] = itm
inc(i)
proc repeat*[T](s: seq[T], n: Natural): seq[T] =
## Returns a new sequence with the items of `s` repeated `n` times.
##
## Example:
##
## .. code-block:
##
## let
## s = @[1, 2, 3]
## total = s.repeat(3)
## assert total == @[1, 2, 3, 1, 2, 3, 1, 2, 3]
result = newSeq[T](n * s.len)
var o = 0
for x in 1..n:
for e in s:
result[o] = e
inc o
proc deduplicate*[T](seq1: seq[T]): seq[T] =
## Returns a new sequence without duplicates.
##
@@ -63,7 +81,7 @@ proc deduplicate*[T](seq1: seq[T]): seq[T] =
if not result.contains(itm): result.add(itm)
{.deprecated: [distnct: deduplicate].}
proc zip*[S, T](seq1: seq[S], seq2: seq[T]): seq[tuple[a: S, b: T]] =
## Returns a new sequence with a combination of the two input sequences.
##
@@ -86,7 +104,7 @@ proc zip*[S, T](seq1: seq[S], seq2: seq[T]): seq[tuple[a: S, b: T]] =
newSeq(result, m)
for i in 0 .. m-1: result[i] = (seq1[i], seq2[i])
proc distribute*[T](s: seq[T], num: int, spread = true): seq[seq[T]] =
proc distribute*[T](s: seq[T], num: Positive, spread = true): seq[seq[T]] =
## Splits and distributes a sequence `s` into `num` sub sequences.
##
## Returns a sequence of `num` sequences. For some input values this is the
@@ -113,11 +131,12 @@ proc distribute*[T](s: seq[T], num: int, spread = true): seq[seq[T]] =
## assert numbers.distribute(6)[0] == @[1, 2]
## assert numbers.distribute(6)[5] == @[7]
assert(not s.isNil, "`s` can't be nil")
assert(num > 0, "`num` has to be greater than zero")
if num < 2:
result = @[s]
return
let num = int(num) # XXX probably only needed because of .. bug
# Create the result and calculate the stride size and the remainder if any.
result = newSeq[seq[T]](num)
var
@@ -162,7 +181,7 @@ iterator filter*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): T =
## for n in filter(numbers, proc (x: int): bool = x mod 2 == 0):
## echo($n)
## # echoes 4, 8, 4 in separate lines
for i in countup(0, len(seq1) -1):
for i in countup(0, len(seq1)-1):
var item = seq1[i]
if pred(item): yield seq1[i]
@@ -209,7 +228,7 @@ proc delete*[T](s: var seq[T], first=0, last=0) =
## var dest = @[1,1,1,2,2,2,2,2,2,1,1,1,1,1]
## dest.delete(3, 8)
## assert outcome == dest
var i = first
var j = last+1
var newLen = len(s)-j+i
@@ -227,12 +246,12 @@ proc insert*[T](dest: var seq[T], src: openArray[T], pos=0) =
##
##.. code-block::
## var dest = @[1,1,1,1,1,1,1,1]
## let
## let
## src = @[2,2,2,2,2,2]
## outcome = @[1,1,1,2,2,2,2,2,2,1,1,1,1,1]
## dest.insert(src, 3)
## assert dest == outcome
var j = len(dest) - 1
var i = len(dest) + len(src) - 1
dest.setLen(i + 1)
@@ -320,7 +339,7 @@ template foldl*(sequence, operation: expr): expr =
##
## The ``operation`` parameter should be an expression which uses the
## variables ``a`` and ``b`` for each step of the fold. Since this is a left
## fold, for non associative binary operations like substraction think that
## fold, for non associative binary operations like subtraction think that
## the sequence of numbers 1, 2 and 3 will be parenthesized as (((1) - 2) -
## 3). Example:
##
@@ -328,12 +347,12 @@ template foldl*(sequence, operation: expr): expr =
## let
## numbers = @[5, 9, 11]
## addition = foldl(numbers, a + b)
## substraction = foldl(numbers, a - b)
## subtraction = foldl(numbers, a - b)
## multiplication = foldl(numbers, a * b)
## words = @["nim", "is", "cool"]
## concatenation = foldl(words, a & b)
## assert addition == 25, "Addition is (((5)+9)+11)"
## assert substraction == -15, "Substraction is (((5)-9)-11)"
## assert subtraction == -15, "Subtraction is (((5)-9)-11)"
## assert multiplication == 495, "Multiplication is (((5)*9)*11)"
## assert concatenation == "nimiscool"
assert sequence.len > 0, "Can't fold empty sequences"
@@ -356,7 +375,7 @@ template foldr*(sequence, operation: expr): expr =
##
## The ``operation`` parameter should be an expression which uses the
## variables ``a`` and ``b`` for each step of the fold. Since this is a right
## fold, for non associative binary operations like substraction think that
## fold, for non associative binary operations like subtraction think that
## the sequence of numbers 1, 2 and 3 will be parenthesized as (1 - (2 -
## (3))). Example:
##
@@ -364,12 +383,12 @@ template foldr*(sequence, operation: expr): expr =
## let
## numbers = @[5, 9, 11]
## addition = foldr(numbers, a + b)
## substraction = foldr(numbers, a - b)
## subtraction = foldr(numbers, a - b)
## multiplication = foldr(numbers, a * b)
## words = @["nim", "is", "cool"]
## concatenation = foldr(words, a & b)
## assert addition == 25, "Addition is (5+(9+(11)))"
## assert substraction == 7, "Substraction is (5-(9-(11)))"
## assert subtraction == 7, "Subtraction is (5-(9-(11)))"
## assert multiplication == 495, "Multiplication is (5*(9*(11)))"
## assert concatenation == "nimiscool"
assert sequence.len > 0, "Can't fold empty sequences"
@@ -382,7 +401,7 @@ template foldr*(sequence, operation: expr): expr =
result = operation
result
template mapIt*(seq1, typ, pred: expr): expr =
template mapIt*(seq1, typ, op: expr): expr =
## Convenience template around the ``map`` proc to reduce typing.
##
## The template injects the ``it`` variable which you can use directly in an
@@ -397,10 +416,10 @@ template mapIt*(seq1, typ, pred: expr): expr =
## assert strings == @["4", "8", "12", "16"]
var result {.gensym.}: seq[typ] = @[]
for it {.inject.} in items(seq1):
result.add(pred)
result.add(op)
result
template mapIt*(varSeq, pred: expr) =
template mapIt*(varSeq, op: expr) =
## Convenience template around the mutable ``map`` proc to reduce typing.
##
## The template injects the ``it`` variable which you can use directly in an
@@ -413,7 +432,7 @@ template mapIt*(varSeq, pred: expr) =
## assert nums[0] + nums[3] == 15
for i in 0 .. <len(varSeq):
let it {.inject.} = varSeq[i]
varSeq[i] = pred
varSeq[i] = op
template newSeqWith*(len: int, init: expr): expr =
## creates a new sequence, calling `init` to initialize each value. Example:
@@ -473,9 +492,8 @@ when isMainModule:
block: # filter iterator test
let numbers = @[1, 4, 5, 8, 9, 7, 4]
for n in filter(numbers, proc (x: int): bool = x mod 2 == 0):
echo($n)
# echoes 4, 8, 4 in separate lines
assert toSeq(filter(numbers, proc (x: int): bool = x mod 2 == 0)) ==
@[4, 8, 4]
block: # keepIf test
var floats = @[13.0, 12.5, 5.8, 2.0, 6.1, 9.9, 10.1]
@@ -507,12 +525,12 @@ when isMainModule:
let
numbers = @[5, 9, 11]
addition = foldl(numbers, a + b)
substraction = foldl(numbers, a - b)
subtraction = foldl(numbers, a - b)
multiplication = foldl(numbers, a * b)
words = @["nim", "is", "cool"]
concatenation = foldl(words, a & b)
assert addition == 25, "Addition is (((5)+9)+11)"
assert substraction == -15, "Substraction is (((5)-9)-11)"
assert subtraction == -15, "Subtraction is (((5)-9)-11)"
assert multiplication == 495, "Multiplication is (((5)*9)*11)"
assert concatenation == "nimiscool"
@@ -520,12 +538,12 @@ when isMainModule:
let
numbers = @[5, 9, 11]
addition = foldr(numbers, a + b)
substraction = foldr(numbers, a - b)
subtraction = foldr(numbers, a - b)
multiplication = foldr(numbers, a * b)
words = @["nim", "is", "cool"]
concatenation = foldr(words, a & b)
assert addition == 25, "Addition is (5+(9+(11)))"
assert substraction == 7, "Substraction is (5-(9-(11)))"
assert subtraction == 7, "Subtraction is (5-(9-(11)))"
assert multiplication == 495, "Multiplication is (5*(9*(11)))"
assert concatenation == "nimiscool"
@@ -534,12 +552,12 @@ when isMainModule:
var dest = @[1,1,1,2,2,2,2,2,2,1,1,1,1,1]
dest.delete(3, 8)
assert outcome == dest, """\
Deleting range 3-9 from [1,1,1,2,2,2,2,2,2,1,1,1,1,1]
Deleting range 3-9 from [1,1,1,2,2,2,2,2,2,1,1,1,1,1]
is [1,1,1,1,1,1,1,1]"""
block: # insert tests
var dest = @[1,1,1,1,1,1,1,1]
let
let
src = @[2,2,2,2,2,2]
outcome = @[1,1,1,2,2,2,2,2,2,1,1,1,1,1]
dest.insert(src, 3)
@@ -587,4 +605,15 @@ when isMainModule:
seq2D[0][1] = true
doAssert seq2D == @[@[true, true], @[true, false], @[false, false], @[false, false]]
echo "Finished doc tests"
block: # repeat tests
let
a = @[1, 2, 3]
b: seq[int] = @[]
doAssert a.repeat(3) == @[1, 2, 3, 1, 2, 3, 1, 2, 3]
doAssert a.repeat(0) == @[]
#doAssert a.repeat(-1) == @[] # will not compile!
doAssert b.repeat(3) == @[]
when not defined(testing):
echo "Finished doc tests"

View File

@@ -7,7 +7,8 @@
# distribution, for details about the copyright.
#
## The ``sets`` module implements an efficient hash set and ordered hash set.
## The ``sets`` module implements an efficient `hash set`:idx: and
## ordered hash set.
##
## Hash sets are different from the `built in set type
## <manual.html#set-type>`_. Sets allow you to store any value that can be
@@ -23,9 +24,12 @@ import
when not defined(nimhygiene):
{.pragma: dirty.}
# For "integer-like A" that are too big for intsets/bit-vectors to be practical,
# it would be best to shrink hcode to the same size as the integer. Larger
# 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.
type
SlotEnum = enum seEmpty, seFilled, seDeleted
KeyValuePair[A] = tuple[slot: SlotEnum, key: A]
KeyValuePair[A] = tuple[hcode: THash, key: A]
KeyValuePairSeq[A] = seq[KeyValuePair[A]]
HashSet* {.myShallow.}[A] = object ## \
## A generic hash set.
@@ -37,6 +41,14 @@ type
{.deprecated: [TSet: HashSet].}
# 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.
proc isEmpty(hcode: THash): bool {.inline.} =
result = hcode == 0
proc isFilled(hcode: THash): bool {.inline.} =
result = hcode != 0
proc isValid*[A](s: HashSet[A]): bool =
## Returns `true` if the set has been initialized with `initSet <#initSet>`_.
##
@@ -93,7 +105,7 @@ iterator items*[A](s: HashSet[A]): A =
## # --> {(a: 1, b: 3), (a: 0, b: 4)}
assert s.isValid, "The set needs to be initialized."
for h in 0..high(s.data):
if s.data[h].slot == seFilled: yield s.data[h].key
if isFilled(s.data[h].hcode): yield s.data[h].key
const
growthFactor = 2
@@ -102,25 +114,44 @@ proc mustRehash(length, counter: int): bool {.inline.} =
assert(length > counter)
result = (length * 2 < counter * 3) or (length - counter < 4)
proc nextTry(h, maxHash: THash): THash {.inline.} =
result = ((5 * h) + 1) and maxHash
proc rightSize*(count: Natural): int {.inline.} =
## Return the value of `initialSize` to support `count` items.
##
## If more items are expected to be added, simply add that
## expected extra amount to the parameter before calling this.
##
## Internally, we want mustRehash(rightSize(x), x) == false.
result = nextPowerOfTwo(count * 3 div 2 + 4)
template rawGetImpl() {.dirty.} =
var h: THash = hash(key) and high(s.data) # start with real hash value
while s.data[h].slot != seEmpty:
if s.data[h].key == key and s.data[h].slot == seFilled:
proc nextTry(h, maxHash: THash): THash {.inline.} =
result = (h + 1) and maxHash
template rawGetKnownHCImpl() {.dirty.} =
var h: THash = hc and high(s.data) # start with real hash value
while isFilled(s.data[h].hcode):
# 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.
# It does slow down succeeding lookups by one extra THash cmp&and..usually
# 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
return h
h = nextTry(h, high(s.data))
result = -1
result = -1 - h # < 0 => MISSING; insert idx = -1 - result
template rawGetImpl() {.dirty.} =
hc = hash(key)
if hc == 0: # This almost never taken branch should be very predictable.
hc = 314159265 # Value doesn't matter; Any non-zero favorite is fine.
rawGetKnownHCImpl()
template rawInsertImpl() {.dirty.} =
var h: THash = hash(key) and high(data)
while data[h].slot == seFilled:
h = nextTry(h, high(data))
data[h].key = key
data[h].slot = seFilled
data[h].hcode = hc
proc rawGet[A](s: HashSet[A], key: A): int =
proc rawGetKnownHC[A](s: HashSet[A], key: A, hc: THash): int {.inline.} =
rawGetKnownHCImpl()
proc rawGet[A](s: HashSet[A], key: A, hc: var THash): int {.inline.} =
rawGetImpl()
proc mget*[A](s: var HashSet[A], key: A): var A =
@@ -129,7 +160,8 @@ proc mget*[A](s: var HashSet[A], key: A): var A =
## when one overloaded 'hash' and '==' but still needs reference semantics
## for sharing.
assert s.isValid, "The set needs to be initialized."
var index = rawGet(s, key)
var hc: THash
var index = rawGet(s, key, hc)
if index >= 0: result = s.data[index].key
else: raise newException(KeyError, "key not found: " & $key)
@@ -146,33 +178,43 @@ proc contains*[A](s: HashSet[A], key: A): bool =
## values.excl(2)
## assert(not values.contains(2))
assert s.isValid, "The set needs to be initialized."
var index = rawGet(s, key)
var hc: THash
var index = rawGet(s, key, hc)
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) =
rawInsertImpl()
proc enlarge[A](s: var HashSet[A]) =
var n: KeyValuePairSeq[A]
newSeq(n, len(s.data) * growthFactor)
for i in countup(0, high(s.data)):
if s.data[i].slot == seFilled: rawInsert(s, n, s.data[i].key)
swap(s.data, n)
swap(s.data, n) # n is now old seq
for i in countup(0, high(n)):
if isFilled(n[i].hcode):
var j = -1 - rawGetKnownHC(s, n[i].key, n[i].hcode)
rawInsert(s, s.data, n[i].key, n[i].hcode, j)
template inclImpl() {.dirty.} =
var index = rawGet(s, key)
var hc: THash
var index = rawGet(s, key, hc)
if index < 0:
if mustRehash(len(s.data), s.counter): enlarge(s)
rawInsert(s, s.data, key)
if mustRehash(len(s.data), s.counter):
enlarge(s)
index = rawGetKnownHC(s, key, hc)
rawInsert(s, s.data, key, hc, -1 - index)
inc(s.counter)
template containsOrInclImpl() {.dirty.} =
var index = rawGet(s, key)
var hc: THash
var index = rawGet(s, key, hc)
if index >= 0:
result = true
else:
if mustRehash(len(s.data), s.counter): enlarge(s)
rawInsert(s, s.data, key)
if mustRehash(len(s.data), s.counter):
enlarge(s)
index = rawGetKnownHC(s, key, hc)
rawInsert(s, s.data, key, hc, -1 - index)
inc(s.counter)
proc incl*[A](s: var HashSet[A], key: A) =
@@ -203,6 +245,11 @@ proc incl*[A](s: var HashSet[A], other: HashSet[A]) =
assert other.isValid, "The set `other` needs to be initialized."
for item in other: incl(s, item)
template doWhile(a: expr, b: stmt): stmt =
while true:
b
if not a: break
proc excl*[A](s: var HashSet[A], key: A) =
## Excludes `key` from the set `s`.
##
@@ -214,10 +261,22 @@ proc excl*[A](s: var HashSet[A], key: A) =
## s.excl(2)
## assert s.len == 3
assert s.isValid, "The set needs to be initialized."
var index = rawGet(s, key)
if index >= 0:
s.data[index].slot = seDeleted
var hc: THash
var i = rawGet(s, key, hc)
var msk = high(s.data)
if i >= 0:
s.data[i].hcode = 0
dec(s.counter)
while true: # KnuthV3 Algo6.4R adapted for i=i+1 instead of i=i-1
var j = i # The correctness of this depends on (h+1) in nextTry,
var r = j # though may be adaptable to other simple sequences.
s.data[i].hcode = 0 # mark current EMPTY
doWhile ((i >= r and r > j) or (r > j and j > i) or (j > i and i >= r)):
i = (i + 1) and msk # increment mod table size
if isEmpty(s.data[i].hcode): # end of collision cluster; So all done
return
r = s.data[i].hcode and msk # "home" location of key@i
shallowCopy(s.data[j], s.data[i]) # data[j] will be marked EMPTY next loop
proc excl*[A](s: var HashSet[A], other: HashSet[A]) =
## Excludes everything in `other` from `s`.
@@ -253,10 +312,10 @@ proc containsOrIncl*[A](s: var HashSet[A], key: A): bool =
proc init*[A](s: var HashSet[A], initialSize=64) =
## Initializes a hash set.
##
## The `initialSize` parameter needs to be a power of too. You can use
## `math.nextPowerOfTwo() <math.html#nextPowerOfTwo>`_ to guarantee that at
## runtime. All set variables have to be initialized before you can use them
## with other procs from this module with the exception of `isValid()
## The `initialSize` parameter needs to be a power of two. You can use
## `math.nextPowerOfTwo() <math.html#nextPowerOfTwo>`_ or `rightSize` to
## guarantee that at runtime. All set variables must be initialized before
## use with other procs from this module with the exception of `isValid()
## <#isValid,TSet[A]>`_ and `len() <#len,TSet[A]>`_.
##
## You can call this proc on a previously initialized hash set, which will
@@ -294,7 +353,7 @@ proc toSet*[A](keys: openArray[A]): HashSet[A] =
## var numbers = toSet([1, 2, 3, 4, 5])
## assert numbers.contains(2)
## assert numbers.contains(4)
result = initSet[A](nextPowerOfTwo(keys.len+10))
result = initSet[A](rightSize(keys.len))
for key in items(keys): result.incl(key)
template dollarImpl(): stmt {.dirty.} =
@@ -493,7 +552,7 @@ proc map*[A, B](data: HashSet[A], op: proc (x: A): B {.closure.}): HashSet[B] =
type
OrderedKeyValuePair[A] = tuple[
slot: SlotEnum, next: int, key: A]
hcode: THash, next: int, key: A]
OrderedKeyValuePairSeq[A] = seq[OrderedKeyValuePair[A]]
OrderedSet* {.myShallow.}[A] = object ## \
## A generic hash set that remembers insertion order.
@@ -545,7 +604,7 @@ template forAllOrderedPairs(yieldStmt: stmt) {.dirty, immediate.} =
var h = s.first
while h >= 0:
var nxt = s.data[h].next
if s.data[h].slot == seFilled: yieldStmt
if isFilled(s.data[h].hcode): yieldStmt
h = nxt
iterator items*[A](s: OrderedSet[A]): A =
@@ -570,7 +629,10 @@ iterator items*[A](s: OrderedSet[A]): A =
forAllOrderedPairs:
yield s.data[h].key
proc rawGet[A](s: OrderedSet[A], key: A): int =
proc rawGetKnownHC[A](s: OrderedSet[A], key: A, hc: THash): int {.inline.} =
rawGetKnownHCImpl()
proc rawGet[A](s: OrderedSet[A], key: A, hc: var THash): int {.inline.} =
rawGetImpl()
proc contains*[A](s: OrderedSet[A], key: A): bool =
@@ -584,11 +646,12 @@ proc contains*[A](s: OrderedSet[A], key: A): bool =
## values.incl(2)
## assert values.contains(2)
assert s.isValid, "The set needs to be initialized."
var index = rawGet(s, key)
var hc: THash
var index = rawGet(s, key, hc)
result = index >= 0
proc rawInsert[A](s: var OrderedSet[A],
data: var OrderedKeyValuePairSeq[A], key: A) =
proc rawInsert[A](s: var OrderedSet[A], data: var OrderedKeyValuePairSeq[A],
key: A, hc: THash, h: THash) =
rawInsertImpl()
data[h].next = -1
if s.first < 0: s.first = h
@@ -601,12 +664,13 @@ proc enlarge[A](s: var OrderedSet[A]) =
var h = s.first
s.first = -1
s.last = -1
while h >= 0:
var nxt = s.data[h].next
if s.data[h].slot == seFilled:
rawInsert(s, n, s.data[h].key)
h = nxt
swap(s.data, n)
while h >= 0:
var nxt = n[h].next
if isFilled(n[h].hcode):
var j = -1 - rawGetKnownHC(s, n[h].key, n[h].hcode)
rawInsert(s, s.data, n[h].key, n[h].hcode, j)
h = nxt
proc incl*[A](s: var OrderedSet[A], key: A) =
## Includes an element `key` in `s`.
@@ -654,10 +718,10 @@ proc containsOrIncl*[A](s: var OrderedSet[A], key: A): bool =
proc init*[A](s: var OrderedSet[A], initialSize=64) =
## Initializes an ordered hash set.
##
## The `initialSize` parameter needs to be a power of too. You can use
## `math.nextPowerOfTwo() <math.html#nextPowerOfTwo>`_ to guarantee that at
## runtime. All set variables have to be initialized before you can use them
## with other procs from this module with the exception of `isValid()
## The `initialSize` parameter needs to be a power of two. You can use
## `math.nextPowerOfTwo() <math.html#nextPowerOfTwo>`_ or `rightSize` to
## guarantee that at runtime. All set variables must be initialized before
## use with other procs from this module with the exception of `isValid()
## <#isValid,TOrderedSet[A]>`_ and `len() <#len,TOrderedSet[A]>`_.
##
## You can call this proc on a previously initialized ordered hash set to
@@ -697,7 +761,7 @@ proc toOrderedSet*[A](keys: openArray[A]): OrderedSet[A] =
## var numbers = toOrderedSet([1, 2, 3, 4, 5])
## assert numbers.contains(2)
## assert numbers.contains(4)
result = initOrderedSet[A](nextPowerOfTwo(keys.len+10))
result = initOrderedSet[A](rightSize(keys.len))
for key in items(keys): result.incl(key)
proc `$`*[A](s: OrderedSet[A]): string =
@@ -725,7 +789,7 @@ proc `==`*[A](s, t: OrderedSet[A]): bool =
while h >= 0 and g >= 0:
var nxh = s.data[h].next
var nxg = t.data[g].next
if s.data[h].slot == seFilled and s.data[g].slot == seFilled:
if isFilled(s.data[h].hcode) and isFilled(s.data[g].hcode):
if s.data[h].key == s.data[g].key:
inc compared
else:
@@ -734,172 +798,179 @@ proc `==`*[A](s, t: OrderedSet[A]): bool =
g = nxg
result = compared == s.counter
proc testModule() =
## Internal micro test to validate docstrings and such.
block isValidTest:
var options: HashSet[string]
proc savePreferences(options: HashSet[string]) =
assert options.isValid, "Pass an initialized set!"
options = initSet[string]()
options.savePreferences
when isMainModule and not defined(release):
proc testModule() =
## Internal micro test to validate docstrings and such.
block isValidTest:
var options: HashSet[string]
proc savePreferences(options: HashSet[string]) =
assert options.isValid, "Pass an initialized set!"
options = initSet[string]()
options.savePreferences
block lenTest:
var values: HashSet[int]
assert(not values.isValid)
assert values.len == 0
assert values.card == 0
block lenTest:
var values: HashSet[int]
assert(not values.isValid)
assert values.len == 0
assert values.card == 0
block setIterator:
type pair = tuple[a, b: int]
var a, b = initSet[pair]()
a.incl((2, 3))
a.incl((3, 2))
a.incl((2, 3))
for x, y in a.items:
b.incl((x - 2, y + 1))
assert a.len == b.card
assert a.len == 2
#echo b
block setIterator:
type pair = tuple[a, b: int]
var a, b = initSet[pair]()
a.incl((2, 3))
a.incl((3, 2))
a.incl((2, 3))
for x, y in a.items:
b.incl((x - 2, y + 1))
assert a.len == b.card
assert a.len == 2
#echo b
block setContains:
var values = initSet[int]()
assert(not values.contains(2))
values.incl(2)
assert values.contains(2)
values.excl(2)
assert(not values.contains(2))
block setContains:
var values = initSet[int]()
assert(not values.contains(2))
values.incl(2)
assert values.contains(2)
values.excl(2)
assert(not values.contains(2))
values.incl(4)
var others = toSet([6, 7])
values.incl(others)
assert values.len == 3
values.incl(4)
var others = toSet([6, 7])
values.incl(others)
assert values.len == 3
values.init
assert values.containsOrIncl(2) == false
assert values.containsOrIncl(2) == true
var
a = toSet([1, 2])
b = toSet([1])
b.incl(2)
assert a == b
values.init
assert values.containsOrIncl(2) == false
assert values.containsOrIncl(2) == true
var
a = toSet([1, 2])
b = toSet([1])
b.incl(2)
assert a == b
block exclusions:
var s = toSet([2, 3, 6, 7])
s.excl(2)
s.excl(2)
assert s.len == 3
block exclusions:
var s = toSet([2, 3, 6, 7])
s.excl(2)
s.excl(2)
assert s.len == 3
var
numbers = toSet([1, 2, 3, 4, 5])
even = toSet([2, 4, 6, 8])
numbers.excl(even)
#echo numbers
# --> {1, 3, 5}
var
numbers = toSet([1, 2, 3, 4, 5])
even = toSet([2, 4, 6, 8])
numbers.excl(even)
#echo numbers
# --> {1, 3, 5}
block toSeqAndString:
var a = toSet([2, 4, 5])
var b = initSet[int]()
for x in [2, 4, 5]: b.incl(x)
assert($a == $b)
#echo a
#echo toSet(["no", "esc'aping", "is \" provided"])
block toSeqAndString:
var a = toSet([2, 4, 5])
var b = initSet[int]()
for x in [2, 4, 5]: b.incl(x)
assert($a == $b)
#echo a
#echo toSet(["no", "esc'aping", "is \" provided"])
#block orderedToSeqAndString:
# echo toOrderedSet([2, 4, 5])
# echo toOrderedSet(["no", "esc'aping", "is \" provided"])
#block orderedToSeqAndString:
# echo toOrderedSet([2, 4, 5])
# echo toOrderedSet(["no", "esc'aping", "is \" provided"])
block setOperations:
var
a = toSet(["a", "b"])
b = toSet(["b", "c"])
c = union(a, b)
assert c == toSet(["a", "b", "c"])
var d = intersection(a, b)
assert d == toSet(["b"])
var e = difference(a, b)
assert e == toSet(["a"])
var f = symmetricDifference(a, b)
assert f == toSet(["a", "c"])
assert d < a and d < b
assert((a < a) == false)
assert d <= a and d <= b
assert((a <= a))
# Alias test.
assert a + b == toSet(["a", "b", "c"])
assert a * b == toSet(["b"])
assert a - b == toSet(["a"])
assert a -+- b == toSet(["a", "c"])
assert disjoint(a, b) == false
assert disjoint(a, b - a) == true
block setOperations:
var
a = toSet(["a", "b"])
b = toSet(["b", "c"])
c = union(a, b)
assert c == toSet(["a", "b", "c"])
var d = intersection(a, b)
assert d == toSet(["b"])
var e = difference(a, b)
assert e == toSet(["a"])
var f = symmetricDifference(a, b)
assert f == toSet(["a", "c"])
assert d < a and d < b
assert((a < a) == false)
assert d <= a and d <= b
assert((a <= a))
# Alias test.
assert a + b == toSet(["a", "b", "c"])
assert a * b == toSet(["b"])
assert a - b == toSet(["a"])
assert a -+- b == toSet(["a", "c"])
assert disjoint(a, b) == false
assert disjoint(a, b - a) == true
block mapSet:
var a = toSet([1, 2, 3])
var b = a.map(proc (x: int): string = $x)
assert b == toSet(["1", "2", "3"])
block mapSet:
var a = toSet([1, 2, 3])
var b = a.map(proc (x: int): string = $x)
assert b == toSet(["1", "2", "3"])
block isValidTest:
var cards: OrderedSet[string]
proc saveTarotCards(cards: OrderedSet[string]) =
assert cards.isValid, "Pass an initialized set!"
cards = initOrderedSet[string]()
cards.saveTarotCards
block isValidTest:
var cards: OrderedSet[string]
proc saveTarotCards(cards: OrderedSet[string]) =
assert cards.isValid, "Pass an initialized set!"
cards = initOrderedSet[string]()
cards.saveTarotCards
block lenTest:
var values: OrderedSet[int]
assert(not values.isValid)
assert values.len == 0
assert values.card == 0
block lenTest:
var values: OrderedSet[int]
assert(not values.isValid)
assert values.len == 0
assert values.card == 0
block setIterator:
type pair = tuple[a, b: int]
var a, b = initOrderedSet[pair]()
a.incl((2, 3))
a.incl((3, 2))
a.incl((2, 3))
for x, y in a.items:
b.incl((x - 2, y + 1))
assert a.len == b.card
assert a.len == 2
block setIterator:
type pair = tuple[a, b: int]
var a, b = initOrderedSet[pair]()
a.incl((2, 3))
a.incl((3, 2))
a.incl((2, 3))
for x, y in a.items:
b.incl((x - 2, y + 1))
assert a.len == b.card
assert a.len == 2
#block orderedSetIterator:
# var a = initOrderedSet[int]()
# for value in [9, 2, 1, 5, 1, 8, 4, 2]:
# a.incl(value)
# for value in a.items:
# echo "Got ", value
#block orderedSetIterator:
# var a = initOrderedSet[int]()
# for value in [9, 2, 1, 5, 1, 8, 4, 2]:
# a.incl(value)
# for value in a.items:
# echo "Got ", value
block setContains:
var values = initOrderedSet[int]()
assert(not values.contains(2))
values.incl(2)
assert values.contains(2)
block setContains:
var values = initOrderedSet[int]()
assert(not values.contains(2))
values.incl(2)
assert values.contains(2)
block toSeqAndString:
var a = toOrderedSet([2, 4, 5])
var b = initOrderedSet[int]()
for x in [2, 4, 5]: b.incl(x)
assert($a == $b)
assert(a == b) # https://github.com/Araq/Nimrod/issues/1413
block toSeqAndString:
var a = toOrderedSet([2, 4, 5])
var b = initOrderedSet[int]()
for x in [2, 4, 5]: b.incl(x)
assert($a == $b)
assert(a == b) # https://github.com/Araq/Nimrod/issues/1413
block initBlocks:
var a: OrderedSet[int]
a.init(4)
a.incl(2)
a.init
assert a.len == 0 and a.isValid
a = initOrderedSet[int](4)
a.incl(2)
assert a.len == 1
block initBlocks:
var a: OrderedSet[int]
a.init(4)
a.incl(2)
a.init
assert a.len == 0 and a.isValid
a = initOrderedSet[int](4)
a.incl(2)
assert a.len == 1
var b: HashSet[int]
b.init(4)
b.incl(2)
b.init
assert b.len == 0 and b.isValid
b = initSet[int](4)
b.incl(2)
assert b.len == 1
var b: HashSet[int]
b.init(4)
b.incl(2)
b.init
assert b.len == 0 and b.isValid
b = initSet[int](4)
b.incl(2)
assert b.len == 1
echo "Micro tests run successfully."
for i in 0 .. 32:
var s = rightSize(i)
if s <= i or mustRehash(s, i):
echo "performance issue: rightSize() will not elide enlarge() at ", i
when isMainModule and not defined(release): testModule()
when not defined(testing):
echo "Micro tests run successfully."
testModule()

View File

@@ -1,16 +1,17 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2014 Andreas Rumpf
# (c) Copyright 2015 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## The ``tables`` module implements variants of an efficient hash table that is
## The ``tables`` module implements variants of an efficient `hash table`:idx:
## (also often named `dictionary`:idx: in other programming languages) that is
## a mapping from keys to values. ``Table`` is the usual hash table,
## ``OrderedTable`` is like ``Table`` but remembers insertion order
## and ``CountTable`` is a mapping from a key to its number of occurances.
## and ``CountTable`` is a mapping from a key to its number of occurrences.
## For consistency with every other data type in Nim these have **value**
## semantics, this means that ``=`` performs a copy of the hash table.
## For **reference** semantics use the ``Ref`` variant: ``TableRef``,
@@ -70,8 +71,7 @@ import
{.pragma: myShallow.}
type
SlotEnum = enum seEmpty, seFilled, seDeleted
KeyValuePair[A, B] = tuple[slot: SlotEnum, key: A, val: B]
KeyValuePair[A, B] = tuple[hcode: THash, key: A, val: B]
KeyValuePairSeq[A, B] = seq[KeyValuePair[A, B]]
Table* {.myShallow.}[A, B] = object ## generic hash table
data: KeyValuePairSeq[A, B]
@@ -83,35 +83,43 @@ type
when not defined(nimhygiene):
{.pragma: dirty.}
# 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.
proc isEmpty(hcode: THash): bool {.inline.} =
result = hcode == 0
proc isFilled(hcode: THash): bool {.inline.} =
result = hcode != 0
proc len*[A, B](t: Table[A, B]): int =
## returns the number of keys in `t`.
result = t.counter
iterator pairs*[A, B](t: Table[A, B]): tuple[key: A, val: B] =
iterator pairs*[A, B](t: Table[A, B]): (A, B) =
## iterates over any (key, value) pair in the table `t`.
for h in 0..high(t.data):
if t.data[h].slot == seFilled: yield (t.data[h].key, t.data[h].val)
if isFilled(t.data[h].hcode): yield (t.data[h].key, t.data[h].val)
iterator mpairs*[A, B](t: var Table[A, B]): tuple[key: A, val: var B] =
iterator mpairs*[A, B](t: var Table[A, B]): (A, var B) =
## iterates over any (key, value) pair in the table `t`. The values
## can be modified.
for h in 0..high(t.data):
if t.data[h].slot == seFilled: yield (t.data[h].key, t.data[h].val)
if isFilled(t.data[h].hcode): yield (t.data[h].key, t.data[h].val)
iterator keys*[A, B](t: Table[A, B]): A =
## iterates over any key in the table `t`.
for h in 0..high(t.data):
if t.data[h].slot == seFilled: yield t.data[h].key
if isFilled(t.data[h].hcode): yield t.data[h].key
iterator values*[A, B](t: Table[A, B]): B =
## iterates over any value in the table `t`.
for h in 0..high(t.data):
if t.data[h].slot == seFilled: yield t.data[h].val
if isFilled(t.data[h].hcode): yield t.data[h].val
iterator mvalues*[A, B](t: var Table[A, B]): var B =
## iterates over any value in the table `t`. The values can be modified.
for h in 0..high(t.data):
if t.data[h].slot == seFilled: yield t.data[h].val
if isFilled(t.data[h].hcode): yield t.data[h].val
const
growthFactor = 2
@@ -120,26 +128,57 @@ proc mustRehash(length, counter: int): bool {.inline.} =
assert(length > counter)
result = (length * 2 < counter * 3) or (length - counter < 4)
proc nextTry(h, maxHash: THash): THash {.inline.} =
result = ((5 * h) + 1) and maxHash
proc rightSize*(count: Natural): int {.inline.} =
## Return the value of `initialSize` to support `count` items.
##
## If more items are expected to be added, simply add that
## expected extra amount to the parameter before calling this.
##
## Internally, we want mustRehash(rightSize(x), x) == false.
result = nextPowerOfTwo(count * 3 div 2 + 4)
template rawGetImpl() {.dirty.} =
var h: THash = hash(key) and high(t.data) # start with real hash value
while t.data[h].slot != seEmpty:
if t.data[h].key == key and t.data[h].slot == seFilled:
proc nextTry(h, maxHash: THash): THash {.inline.} =
result = (h + 1) and maxHash
template rawGetKnownHCImpl() {.dirty.} =
var h: THash = hc and high(t.data) # start with real hash value
while isFilled(t.data[h].hcode):
# 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.
# It does slow down succeeding lookups by one extra THash cmp&and..usually
# 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:
return h
h = nextTry(h, high(t.data))
result = -1
result = -1 - h # < 0 => MISSING; insert idx = -1 - result
template rawGetImpl() {.dirty.} =
hc = hash(key)
if hc == 0: # This almost never taken branch should be very predictable.
hc = 314159265 # Value doesn't matter; Any non-zero favorite is fine.
rawGetKnownHCImpl()
template rawGetDeepImpl() {.dirty.} = # Search algo for unconditional add
hc = hash(key)
if hc == 0:
hc = 314159265
var h: THash = hc and high(t.data)
while isFilled(t.data[h].hcode):
h = nextTry(h, high(t.data))
result = h
template rawInsertImpl() {.dirty.} =
var h: THash = hash(key) and high(data)
while data[h].slot == seFilled:
h = nextTry(h, high(data))
data[h].key = key
data[h].val = val
data[h].slot = seFilled
data[h].hcode = hc
proc rawGet[A, B](t: Table[A, B], key: A): int =
proc rawGetKnownHC[A, B](t: Table[A, B], key: A, hc: THash): int {.inline.} =
rawGetKnownHCImpl()
proc rawGetDeep[A, B](t: Table[A, B], key: A, hc: var THash): int {.inline.} =
rawGetDeepImpl()
proc rawGet[A, B](t: Table[A, B], key: A, hc: var THash): int {.inline.} =
rawGetImpl()
proc `[]`*[A, B](t: Table[A, B], key: A): B =
@@ -147,63 +186,91 @@ proc `[]`*[A, B](t: Table[A, B], key: A): B =
## default empty value for the type `B` is returned
## and no exception is raised. One can check with ``hasKey`` whether the key
## exists.
var index = rawGet(t, key)
var hc: THash
var index = rawGet(t, key, hc)
if index >= 0: result = t.data[index].val
proc mget*[A, B](t: var Table[A, B], key: A): var B =
## retrieves the value at ``t[key]``. The value can be modified.
## If `key` is not in `t`, the ``EInvalidKey`` exception is raised.
var index = rawGet(t, key)
## If `key` is not in `t`, the ``KeyError`` exception is raised.
var hc: THash
var index = rawGet(t, key, hc)
if index >= 0: result = t.data[index].val
else: raise newException(KeyError, "key not found: " & $key)
else:
when compiles($key):
raise newException(KeyError, "key not found: " & $key)
else:
raise newException(KeyError, "key not found")
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`.
var h: THash = hash(key) and high(t.data)
while t.data[h].slot != seEmpty:
if t.data[h].key == key and t.data[h].slot == seFilled:
while isFilled(t.data[h].hcode):
if t.data[h].key == key:
yield t.data[h].val
h = nextTry(h, high(t.data))
proc hasKey*[A, B](t: Table[A, B], key: A): bool =
## returns true iff `key` is in the table `t`.
result = rawGet(t, key) >= 0
var hc: THash
result = rawGet(t, key, hc) >= 0
proc rawInsert[A, B](t: var Table[A, B], data: var KeyValuePairSeq[A, B],
key: A, val: B) =
key: A, val: B, hc: THash, h: THash) =
rawInsertImpl()
proc enlarge[A, B](t: var Table[A, B]) =
var n: KeyValuePairSeq[A, B]
newSeq(n, len(t.data) * growthFactor)
for i in countup(0, high(t.data)):
if t.data[i].slot == seFilled: rawInsert(t, n, t.data[i].key, t.data[i].val)
swap(t.data, n)
for i in countup(0, high(n)):
if isFilled(n[i].hcode):
var j = -1 - rawGetKnownHC(t, n[i].key, n[i].hcode)
rawInsert(t, t.data, n[i].key, n[i].val, n[i].hcode, j)
template addImpl() {.dirty.} =
if mustRehash(len(t.data), t.counter): enlarge(t)
rawInsert(t, t.data, key, val)
var hc: THash
var j = rawGetDeep(t, key, hc)
rawInsert(t, t.data, key, val, hc, j)
inc(t.counter)
template maybeRehashPutImpl() {.dirty.} =
if mustRehash(len(t.data), t.counter):
enlarge(t)
index = rawGetKnownHC(t, key, hc)
index = -1 - index # important to transform for mgetOrPutImpl
rawInsert(t, t.data, key, val, hc, index)
inc(t.counter)
template putImpl() {.dirty.} =
var index = rawGet(t, key)
if index >= 0:
t.data[index].val = val
else:
addImpl()
var hc: THash
var index = rawGet(t, key, hc)
if index >= 0: t.data[index].val = val
else: maybeRehashPutImpl()
when false:
# not yet used:
template hasKeyOrPutImpl() {.dirty.} =
var index = rawGet(t, key)
if index >= 0:
t.data[index].val = val
result = true
else:
if mustRehash(len(t.data), t.counter): enlarge(t)
rawInsert(t, t.data, key, val)
inc(t.counter)
result = false
template mgetOrPutImpl() {.dirty.} =
var hc: THash
var index = rawGet(t, key, hc)
if index < 0: maybeRehashPutImpl() # not present: insert (flipping index)
result = t.data[index].val # either way return modifiable val
template hasKeyOrPutImpl() {.dirty.} =
var hc: THash
var index = rawGet(t, key, hc)
if index < 0:
result = false
maybeRehashPutImpl()
else: result = true
proc mgetOrPut*[A, B](t: var Table[A, B], key: A, val: B): var B =
## retrieves value at ``t[key]`` or puts ``val`` if not present, either way
## returning a value which can be modified.
mgetOrPutImpl()
proc hasKeyOrPut*[A, B](t: var Table[A, B], key: A, val: B): bool =
## returns true iff `key` is in the table, otherwise inserts `value`.
hasKeyOrPutImpl()
proc `[]=`*[A, B](t: var Table[A, B], key: A, val: B) =
## puts a (key, value)-pair into `t`.
@@ -212,28 +279,45 @@ proc `[]=`*[A, B](t: var Table[A, B], key: A, val: B) =
proc add*[A, B](t: var Table[A, B], key: A, val: B) =
## puts a new (key, value)-pair into `t` even if ``t[key]`` already exists.
addImpl()
template doWhile(a: expr, b: stmt): stmt =
while true:
b
if not a: break
proc del*[A, B](t: var Table[A, B], key: A) =
## deletes `key` from hash table `t`.
let index = rawGet(t, key)
if index >= 0:
t.data[index].slot = seDeleted
var hc: THash
var i = rawGet(t, key, hc)
let msk = high(t.data)
if i >= 0:
t.data[i].hcode = 0
dec(t.counter)
while true: # KnuthV3 Algo6.4R adapted for i=i+1 instead of i=i-1
var j = i # The correctness of this depends on (h+1) in nextTry,
var r = j # though may be adaptable to other simple sequences.
t.data[i].hcode = 0 # mark current EMPTY
doWhile ((i >= r and r > j) or (r > j and j > i) or (j > i and i >= r)):
i = (i + 1) and msk # increment mod table size
if isEmpty(t.data[i].hcode): # end of collision cluster; So all done
return
r = t.data[i].hcode and msk # "home" location of key@i
shallowCopy(t.data[j], t.data[i]) # data[j] will be marked EMPTY next loop
proc initTable*[A, B](initialSize=64): Table[A, B] =
## creates a new hash table that is empty.
##
## `initialSize` needs to be a power of two. If you need to accept runtime
## values for this you could use the ``nextPowerOfTwo`` proc from the
## `math <math.html>`_ module.
## `math <math.html>`_ module or the ``rightSize`` proc from this module.
assert isPowerOfTwo(initialSize)
result.counter = 0
newSeq(result.data, initialSize)
proc toTable*[A, B](pairs: openArray[tuple[key: A,
val: B]]): Table[A, B] =
proc toTable*[A, B](pairs: openArray[(A,
B)]): Table[A, B] =
## creates a new hash table that contains the given `pairs`.
result = initTable[A, B](nextPowerOfTwo(pairs.len+10))
result = initTable[A, B](rightSize(pairs.len))
for key, val in items(pairs): result[key] = val
template dollarImpl(): stmt {.dirty.} =
@@ -251,7 +335,7 @@ template dollarImpl(): stmt {.dirty.} =
proc `$`*[A, B](t: Table[A, B]): string =
## The `$` operator for hash tables.
dollarImpl()
template equalsImpl() =
if s.counter == t.counter:
# different insertion orders mean different 'data' seqs, so we have
@@ -261,10 +345,10 @@ template equalsImpl() =
if not t.hasKey(key): return false
if t[key] != val: return false
return true
proc `==`*[A, B](s, t: Table[A, B]): bool =
equalsImpl()
proc indexBy*[A, B, C](collection: A, index: proc(x: B): C): Table[C, B] =
## Index the collection with the proc provided.
# TODO: As soon as supported, change collection: A to collection: A[B]
@@ -276,31 +360,31 @@ proc len*[A, B](t: TableRef[A, B]): int =
## returns the number of keys in `t`.
result = t.counter
iterator pairs*[A, B](t: TableRef[A, B]): tuple[key: A, val: B] =
iterator pairs*[A, B](t: TableRef[A, B]): (A, B) =
## iterates over any (key, value) pair in the table `t`.
for h in 0..high(t.data):
if t.data[h].slot == seFilled: yield (t.data[h].key, t.data[h].val)
if isFilled(t.data[h].hcode): yield (t.data[h].key, t.data[h].val)
iterator mpairs*[A, B](t: TableRef[A, B]): tuple[key: A, val: var B] =
iterator mpairs*[A, B](t: TableRef[A, B]): (A, var B) =
## iterates over any (key, value) pair in the table `t`. The values
## can be modified.
for h in 0..high(t.data):
if t.data[h].slot == seFilled: yield (t.data[h].key, t.data[h].val)
if isFilled(t.data[h].hcode): yield (t.data[h].key, t.data[h].val)
iterator keys*[A, B](t: TableRef[A, B]): A =
## iterates over any key in the table `t`.
for h in 0..high(t.data):
if t.data[h].slot == seFilled: yield t.data[h].key
if isFilled(t.data[h].hcode): yield t.data[h].key
iterator values*[A, B](t: TableRef[A, B]): B =
## iterates over any value in the table `t`.
for h in 0..high(t.data):
if t.data[h].slot == seFilled: yield t.data[h].val
if isFilled(t.data[h].hcode): yield t.data[h].val
iterator mvalues*[A, B](t: TableRef[A, B]): var B =
## iterates over any value in the table `t`. The values can be modified.
for h in 0..high(t.data):
if t.data[h].slot == seFilled: yield t.data[h].val
if isFilled(t.data[h].hcode): yield t.data[h].val
proc `[]`*[A, B](t: TableRef[A, B], key: A): B =
## retrieves the value at ``t[key]``. If `key` is not in `t`,
@@ -314,6 +398,15 @@ proc mget*[A, B](t: TableRef[A, B], key: A): var B =
## If `key` is not in `t`, the ``EInvalidKey`` exception is raised.
t[].mget(key)
proc mgetOrPut*[A, B](t: TableRef[A, B], key: A, val: B): var B =
## retrieves value at ``t[key]`` or puts ``val`` if not present, either way
## returning a value which can be modified.
t[].mgetOrPut(key, val)
proc hasKeyOrPut*[A, B](t: var TableRef[A, B], key: A, val: B): bool =
## returns true iff `key` is in the table, otherwise inserts `value`.
t[].hasKeyOrPut(key, val)
proc hasKey*[A, B](t: TableRef[A, B], key: A): bool =
## returns true iff `key` is in the table `t`.
result = t[].hasKey(key)
@@ -325,7 +418,7 @@ proc `[]=`*[A, B](t: TableRef[A, B], key: A, val: B) =
proc add*[A, B](t: TableRef[A, B], key: A, val: B) =
## puts a new (key, value)-pair into `t` even if ``t[key]`` already exists.
t[].add(key, val)
proc del*[A, B](t: TableRef[A, B], key: A) =
## deletes `key` from hash table `t`.
t[].del(key)
@@ -334,7 +427,7 @@ proc newTable*[A, B](initialSize=64): TableRef[A, B] =
new(result)
result[] = initTable[A, B](initialSize)
proc newTable*[A, B](pairs: openArray[tuple[key: A, val: B]]): TableRef[A, B] =
proc newTable*[A, B](pairs: openArray[(A, B)]): TableRef[A, B] =
## creates a new hash table that contains the given `pairs`.
new(result)
result[] = toTable[A, B](pairs)
@@ -346,7 +439,7 @@ proc `$`*[A, B](t: TableRef[A, B]): string =
proc `==`*[A, B](s, t: TableRef[A, B]): bool =
if isNil(s): result = isNil(t)
elif isNil(t): result = false
else: result = equalsImpl()
else: equalsImpl()
proc newTableFrom*[A, B, C](collection: A, index: proc(x: B): C): TableRef[C, B] =
## Index the collection with the proc provided.
@@ -359,7 +452,7 @@ proc newTableFrom*[A, B, C](collection: A, index: proc(x: B): C): TableRef[C, B]
type
OrderedKeyValuePair[A, B] = tuple[
slot: SlotEnum, next: int, key: A, val: B]
hcode: THash, next: int, key: A, val: B]
OrderedKeyValuePairSeq[A, B] = seq[OrderedKeyValuePair[A, B]]
OrderedTable* {.
myShallow.}[A, B] = object ## table that remembers insertion order
@@ -377,16 +470,16 @@ template forAllOrderedPairs(yieldStmt: stmt) {.dirty, immediate.} =
var h = t.first
while h >= 0:
var nxt = t.data[h].next
if t.data[h].slot == seFilled: yieldStmt
if isFilled(t.data[h].hcode): yieldStmt
h = nxt
iterator pairs*[A, B](t: OrderedTable[A, B]): tuple[key: A, val: B] =
iterator pairs*[A, B](t: OrderedTable[A, B]): (A, B) =
## iterates over any (key, value) pair in the table `t` in insertion
## order.
forAllOrderedPairs:
yield (t.data[h].key, t.data[h].val)
iterator mpairs*[A, B](t: var OrderedTable[A, B]): tuple[key: A, val: var B] =
iterator mpairs*[A, B](t: var OrderedTable[A, B]): (A, var B) =
## iterates over any (key, value) pair in the table `t` in insertion
## order. The values can be modified.
forAllOrderedPairs:
@@ -408,7 +501,13 @@ iterator mvalues*[A, B](t: var OrderedTable[A, B]): var B =
forAllOrderedPairs:
yield t.data[h].val
proc rawGet[A, B](t: OrderedTable[A, B], key: A): int =
proc rawGetKnownHC[A, B](t: OrderedTable[A, B], key: A, hc: THash): int =
rawGetKnownHCImpl()
proc rawGetDeep[A, B](t: OrderedTable[A, B], key: A, hc: var THash): int {.inline.} =
rawGetDeepImpl()
proc rawGet[A, B](t: OrderedTable[A, B], key: A, hc: var THash): int =
rawGetImpl()
proc `[]`*[A, B](t: OrderedTable[A, B], key: A): B =
@@ -416,23 +515,26 @@ proc `[]`*[A, B](t: OrderedTable[A, B], key: A): B =
## default empty value for the type `B` is returned
## and no exception is raised. One can check with ``hasKey`` whether the key
## exists.
var index = rawGet(t, key)
var hc: THash
var index = rawGet(t, key, hc)
if index >= 0: result = t.data[index].val
proc mget*[A, B](t: var OrderedTable[A, B], key: A): var B =
## retrieves the value at ``t[key]``. The value can be modified.
## If `key` is not in `t`, the ``EInvalidKey`` exception is raised.
var index = rawGet(t, key)
var hc: THash
var index = rawGet(t, key, hc)
if index >= 0: result = t.data[index].val
else: raise newException(KeyError, "key not found: " & $key)
proc hasKey*[A, B](t: OrderedTable[A, B], key: A): bool =
## returns true iff `key` is in the table `t`.
result = rawGet(t, key) >= 0
var hc: THash
result = rawGet(t, key, hc) >= 0
proc rawInsert[A, B](t: var OrderedTable[A, B],
proc rawInsert[A, B](t: var OrderedTable[A, B],
data: var OrderedKeyValuePairSeq[A, B],
key: A, val: B) =
key: A, val: B, hc: THash, h: THash) =
rawInsertImpl()
data[h].next = -1
if t.first < 0: t.first = h
@@ -445,12 +547,13 @@ proc enlarge[A, B](t: var OrderedTable[A, B]) =
var h = t.first
t.first = -1
t.last = -1
while h >= 0:
var nxt = t.data[h].next
if t.data[h].slot == seFilled:
rawInsert(t, n, t.data[h].key, t.data[h].val)
h = nxt
swap(t.data, n)
while h >= 0:
var nxt = n[h].next
if isFilled(n[h].hcode):
var j = -1 - rawGetKnownHC(t, n[h].key, n[h].hcode)
rawInsert(t, t.data, n[h].key, n[h].val, n[h].hcode, j)
h = nxt
proc `[]=`*[A, B](t: var OrderedTable[A, B], key: A, val: B) =
## puts a (key, value)-pair into `t`.
@@ -460,30 +563,39 @@ proc add*[A, B](t: var OrderedTable[A, B], key: A, val: B) =
## puts a new (key, value)-pair into `t` even if ``t[key]`` already exists.
addImpl()
proc mgetOrPut*[A, B](t: var OrderedTable[A, B], key: A, val: B): var B =
## retrieves value at ``t[key]`` or puts ``value`` if not present, either way
## returning a value which can be modified.
mgetOrPutImpl()
proc hasKeyOrPut*[A, B](t: var OrderedTable[A, B], key: A, val: B): bool =
## returns true iff `key` is in the table, otherwise inserts `value`.
hasKeyOrPutImpl()
proc initOrderedTable*[A, B](initialSize=64): OrderedTable[A, B] =
## creates a new ordered hash table that is empty.
##
## `initialSize` needs to be a power of two. If you need to accept runtime
## values for this you could use the ``nextPowerOfTwo`` proc from the
## `math <math.html>`_ module.
## `math <math.html>`_ module or the ``rightSize`` proc from this module.
assert isPowerOfTwo(initialSize)
result.counter = 0
result.first = -1
result.last = -1
newSeq(result.data, initialSize)
proc toOrderedTable*[A, B](pairs: openArray[tuple[key: A,
val: B]]): OrderedTable[A, B] =
proc toOrderedTable*[A, B](pairs: openArray[(A,
B)]): OrderedTable[A, B] =
## creates a new ordered hash table that contains the given `pairs`.
result = initOrderedTable[A, B](nextPowerOfTwo(pairs.len+10))
result = initOrderedTable[A, B](rightSize(pairs.len))
for key, val in items(pairs): result[key] = val
proc `$`*[A, B](t: OrderedTable[A, B]): string =
## The `$` operator for ordered hash tables.
dollarImpl()
proc sort*[A, B](t: var OrderedTable[A, B],
cmp: proc (x,y: tuple[key: A, val: B]): int) =
proc sort*[A, B](t: var OrderedTable[A, B],
cmp: proc (x,y: (A, B)): int) =
## sorts `t` according to `cmp`. This modifies the internal list
## that kept the insertion order, so insertion order is lost after this
## call but key lookup and insertions remain possible after `sort` (in
@@ -505,7 +617,7 @@ proc sort*[A, B](t: var OrderedTable[A, B],
while i < insize:
inc(psize)
q = t.data[q].next
if q < 0: break
if q < 0: break
inc(i)
qsize = insize
while psize > 0 or (qsize > 0 and q >= 0):
@@ -513,7 +625,7 @@ proc sort*[A, B](t: var OrderedTable[A, B],
e = q; q = t.data[q].next; dec(qsize)
elif qsize == 0 or q < 0:
e = p; p = t.data[p].next; dec(psize)
elif cmp((t.data[p].key, t.data[p].val),
elif cmp((t.data[p].key, t.data[p].val),
(t.data[q].key, t.data[q].val)) <= 0:
e = p; p = t.data[p].next; dec(psize)
else:
@@ -536,16 +648,16 @@ template forAllOrderedPairs(yieldStmt: stmt) {.dirty, immediate.} =
var h = t.first
while h >= 0:
var nxt = t.data[h].next
if t.data[h].slot == seFilled: yieldStmt
if isFilled(t.data[h].hcode): yieldStmt
h = nxt
iterator pairs*[A, B](t: OrderedTableRef[A, B]): tuple[key: A, val: B] =
iterator pairs*[A, B](t: OrderedTableRef[A, B]): (A, B) =
## iterates over any (key, value) pair in the table `t` in insertion
## order.
forAllOrderedPairs:
yield (t.data[h].key, t.data[h].val)
iterator mpairs*[A, B](t: OrderedTableRef[A, B]): tuple[key: A, val: var B] =
iterator mpairs*[A, B](t: OrderedTableRef[A, B]): (A, var B) =
## iterates over any (key, value) pair in the table `t` in insertion
## order. The values can be modified.
forAllOrderedPairs:
@@ -579,6 +691,15 @@ proc mget*[A, B](t: OrderedTableRef[A, B], key: A): var B =
## If `key` is not in `t`, the ``EInvalidKey`` exception is raised.
result = t[].mget(key)
proc mgetOrPut*[A, B](t: OrderedTableRef[A, B], key: A, val: B): var B =
## retrieves value at ``t[key]`` or puts ``val`` if not present, either way
## returning a value which can be modified.
result = t[].mgetOrPut(key, val)
proc hasKeyOrPut*[A, B](t: var OrderedTableRef[A, B], key: A, val: B): bool =
## returns true iff `key` is in the table, otherwise inserts `val`.
result = t[].hasKeyOrPut(key, val)
proc hasKey*[A, B](t: OrderedTableRef[A, B], key: A): bool =
## returns true iff `key` is in the table `t`.
result = t[].hasKey(key)
@@ -596,22 +717,21 @@ proc newOrderedTable*[A, B](initialSize=64): OrderedTableRef[A, B] =
##
## `initialSize` needs to be a power of two. If you need to accept runtime
## values for this you could use the ``nextPowerOfTwo`` proc from the
## `math <math.html>`_ module.
## `math <math.html>`_ module or the ``rightSize`` proc from this module.
new(result)
result[] = initOrderedTable[A, B]()
proc newOrderedTable*[A, B](pairs: openArray[tuple[key: A,
val: B]]): OrderedTableRef[A, B] =
proc newOrderedTable*[A, B](pairs: openArray[(A, B)]): OrderedTableRef[A, B] =
## creates a new ordered hash table that contains the given `pairs`.
result = newOrderedTable[A, B](nextPowerOfTwo(pairs.len+10))
result = newOrderedTable[A, B](rightSize(pairs.len))
for key, val in items(pairs): result[key] = val
proc `$`*[A, B](t: OrderedTableRef[A, B]): string =
## The `$` operator for ordered hash tables.
dollarImpl()
proc sort*[A, B](t: OrderedTableRef[A, B],
cmp: proc (x,y: tuple[key: A, val: B]): int) =
proc sort*[A, B](t: OrderedTableRef[A, B],
cmp: proc (x,y: (A, B)): int) =
## sorts `t` according to `cmp`. This modifies the internal list
## that kept the insertion order, so insertion order is lost after this
## call but key lookup and insertions remain possible after `sort` (in
@@ -633,12 +753,12 @@ proc len*[A](t: CountTable[A]): int =
## returns the number of keys in `t`.
result = t.counter
iterator pairs*[A](t: CountTable[A]): tuple[key: A, val: int] =
iterator pairs*[A](t: CountTable[A]): (A, int) =
## iterates over any (key, value) pair in the table `t`.
for h in 0..high(t.data):
if t.data[h].val != 0: yield (t.data[h].key, t.data[h].val)
iterator mpairs*[A](t: var CountTable[A]): tuple[key: A, val: var int] =
iterator mpairs*[A](t: var CountTable[A]): (A, var int) =
## iterates over any (key, value) pair in the table `t`. The values can
## be modified.
for h in 0..high(t.data):
@@ -664,7 +784,7 @@ proc rawGet[A](t: CountTable[A], key: A): int =
while t.data[h].val != 0:
if t.data[h].key == key: return h
h = nextTry(h, high(t.data))
result = -1
result = -1 - h # < 0 => MISSING; insert idx = -1 - result
proc `[]`*[A](t: CountTable[A], key: A): int =
## retrieves the value at ``t[key]``. If `key` is not in `t`,
@@ -701,28 +821,34 @@ proc enlarge[A](t: var CountTable[A]) =
proc `[]=`*[A](t: var CountTable[A], key: A, val: int) =
## puts a (key, value)-pair into `t`. `val` has to be positive.
assert val > 0
putImpl()
var h = rawGet(t, key)
if h >= 0:
t.data[h].val = val
else:
h = -1 - h
t.data[h].key = key
t.data[h].val = val
proc initCountTable*[A](initialSize=64): CountTable[A] =
## creates a new count table that is empty.
##
## `initialSize` needs to be a power of two. If you need to accept runtime
## values for this you could use the ``nextPowerOfTwo`` proc from the
## `math <math.html>`_ module.
## `math <math.html>`_ module or the ``rightSize`` proc in this module.
assert isPowerOfTwo(initialSize)
result.counter = 0
newSeq(result.data, initialSize)
proc toCountTable*[A](keys: openArray[A]): CountTable[A] =
## creates a new count table with every key in `keys` having a count of 1.
result = initCountTable[A](nextPowerOfTwo(keys.len+10))
result = initCountTable[A](rightSize(keys.len))
for key in items(keys): result[key] = 1
proc `$`*[A](t: CountTable[A]): string =
## The `$` operator for count tables.
dollarImpl()
proc inc*[A](t: var CountTable[A], key: A, val = 1) =
proc inc*[A](t: var CountTable[A], key: A, val = 1) =
## increments `t[key]` by `val`.
var index = rawGet(t, key)
if index >= 0:
@@ -775,12 +901,12 @@ proc len*[A](t: CountTableRef[A]): int =
## returns the number of keys in `t`.
result = t.counter
iterator pairs*[A](t: CountTableRef[A]): tuple[key: A, val: int] =
iterator pairs*[A](t: CountTableRef[A]): (A, int) =
## iterates over any (key, value) pair in the table `t`.
for h in 0..high(t.data):
if t.data[h].val != 0: yield (t.data[h].key, t.data[h].val)
iterator mpairs*[A](t: CountTableRef[A]): tuple[key: A, val: var int] =
iterator mpairs*[A](t: CountTableRef[A]): (A, var int) =
## iterates over any (key, value) pair in the table `t`. The values can
## be modified.
for h in 0..high(t.data):
@@ -826,28 +952,28 @@ proc newCountTable*[A](initialSize=64): CountTableRef[A] =
##
## `initialSize` needs to be a power of two. If you need to accept runtime
## values for this you could use the ``nextPowerOfTwo`` proc from the
## `math <math.html>`_ module.
## `math <math.html>`_ module or the ``rightSize`` method in this module.
new(result)
result[] = initCountTable[A](initialSize)
proc newCountTable*[A](keys: openArray[A]): CountTableRef[A] =
## creates a new count table with every key in `keys` having a count of 1.
result = newCountTable[A](nextPowerOfTwo(keys.len+10))
result = newCountTable[A](rightSize(keys.len))
for key in items(keys): result[key] = 1
proc `$`*[A](t: CountTableRef[A]): string =
## The `$` operator for count tables.
dollarImpl()
proc inc*[A](t: CountTableRef[A], key: A, val = 1) =
proc inc*[A](t: CountTableRef[A], key: A, val = 1) =
## increments `t[key]` by `val`.
t[].inc(key, val)
proc smallest*[A](t: CountTableRef[A]): tuple[key: A, val: int] =
proc smallest*[A](t: CountTableRef[A]): (A, int) =
## returns the largest (key,val)-pair. Efficiency: O(n)
t[].smallest
proc largest*[A](t: CountTableRef[A]): tuple[key: A, val: int] =
proc largest*[A](t: CountTableRef[A]): (A, int) =
## returns the (key,val)-pair with the largest `val`. Efficiency: O(n)
t[].largest
@@ -858,6 +984,22 @@ proc sort*[A](t: CountTableRef[A]) =
## `t` in the sorted order.
t[].sort
proc merge*[A](s: var CountTable[A], t: CountTable[A]) =
## merges the second table into the first one
for key, value in t:
s.inc(key, value)
proc merge*[A](s, t: CountTable[A]): CountTable[A] =
## merges the two tables into a new one
result = initCountTable[A](nextPowerOfTwo(max(s.len, t.len)))
for table in @[s, t]:
for key, value in table:
result.inc(key, value)
proc merge*[A](s, t: CountTableRef[A]) =
## merges the second table into the first one
s[].merge(t[])
when isMainModule:
type
Person = object
@@ -886,3 +1028,48 @@ when isMainModule:
s2[p2] = 45_000
s3[p1] = 30_000
s3[p2] = 45_000
var
t1 = initCountTable[string]()
t2 = initCountTable[string]()
t1.inc("foo")
t1.inc("bar", 2)
t1.inc("baz", 3)
t2.inc("foo", 4)
t2.inc("bar")
t2.inc("baz", 11)
merge(t1, t2)
assert(t1["foo"] == 5)
assert(t1["bar"] == 3)
assert(t1["baz"] == 14)
let
t1r = newCountTable[string]()
t2r = newCountTable[string]()
t1r.inc("foo")
t1r.inc("bar", 2)
t1r.inc("baz", 3)
t2r.inc("foo", 4)
t2r.inc("bar")
t2r.inc("baz", 11)
merge(t1r, t2r)
assert(t1r["foo"] == 5)
assert(t1r["bar"] == 3)
assert(t1r["baz"] == 14)
var
t1l = initCountTable[string]()
t2l = initCountTable[string]()
t1l.inc("foo")
t1l.inc("bar", 2)
t1l.inc("baz", 3)
t2l.inc("foo", 4)
t2l.inc("bar")
t2l.inc("baz", 11)
let
t1merging = t1l
t2merging = t2l
let merged = merge(t1merging, t2merging)
assert(merged["foo"] == 5)
assert(merged["bar"] == 3)
assert(merged["baz"] == 14)

View File

@@ -46,7 +46,7 @@ proc `+`*(a, b: Color): Color =
colorOp(satPlus)
proc `-`*(a, b: Color): Color =
## substracts two colors: This uses saturated artithmetic, so that each color
## subtracts two colors: This uses saturated artithmetic, so that each color
## component cannot overflow (255 is used as a maximum).
colorOp(satMinus)
@@ -392,7 +392,7 @@ proc parseColor*(name: string): Color =
result = Color(parseHexInt(name))
else:
var idx = binaryStrSearch(colorNames, name)
if idx < 0: raise newException(ValueError, "unkown color: " & name)
if idx < 0: raise newException(ValueError, "unknown color: " & name)
result = colorNames[idx][1]
proc isColor*(name: string): bool =

View File

@@ -27,6 +27,11 @@ type
{.deprecated: [TComplex: Complex].}
proc toComplex*(x: SomeInteger): Complex =
## Convert some integer ``x`` to a complex number.
result.re = x
result.im = 0
proc `==` *(x, y: Complex): bool =
## Compare two complex numbers `x` and `y` for equality.
result = x.re == y.re and x.im == y.im
@@ -171,6 +176,12 @@ proc abs*(z: Complex): float =
result = y * sqrt(1.0 + temp * temp)
proc conjugate*(z: Complex): Complex =
## Conjugate of complex number `z`.
result.re = z.re
result.im = -z.im
proc sqrt*(z: Complex): Complex =
## Square root for a complex number `z`.
var x, y, w, r: float
@@ -263,27 +274,101 @@ proc tan*(z: Complex): Complex =
## Returns the tangent of `z`.
result = sin(z)/cos(z)
proc arctan*(z: Complex): Complex =
## Returns the inverse tangent of `z`.
var i: Complex = (0.0,1.0)
result = 0.5*i*(ln(1-i*z)-ln(1+i*z))
proc cot*(z: Complex): Complex =
## Returns the cotangent of `z`.
result = cos(z)/sin(z)
proc arccot*(z: Complex): Complex =
## Returns the inverse cotangent of `z`.
var i: Complex = (0.0,1.0)
result = 0.5*i*(ln(1-i/z)-ln(1+i/z))
proc sec*(z: Complex): Complex =
## Returns the secant of `z`.
result = 1.0/cos(z)
proc arcsec*(z: Complex): Complex =
## Returns the inverse secant of `z`.
var i: Complex = (0.0,1.0)
result = -i*ln(i*sqrt(1-1/(z*z))+1/z)
proc csc*(z: Complex): Complex =
## Returns the cosecant of `z`.
result = 1.0/sin(z)
proc arccsc*(z: Complex): Complex =
## Returns the inverse cosecant of `z`.
var i: Complex = (0.0,1.0)
result = -i*ln(sqrt(1-1/(z*z))+i/z)
proc sinh*(z: Complex): Complex =
## Returns the hyperbolic sine of `z`.
result = 0.5*(exp(z)-exp(-z))
proc arcsinh*(z: Complex): Complex =
## Returns the inverse hyperbolic sine of `z`.
result = ln(z+sqrt(z*z+1))
proc cosh*(z: Complex): Complex =
## Returns the hyperbolic cosine of `z`.
result = 0.5*(exp(z)+exp(-z))
proc arccosh*(z: Complex): Complex =
## Returns the inverse hyperbolic cosine of `z`.
result = ln(z+sqrt(z*z-1))
proc tanh*(z: Complex): Complex =
## Returns the hyperbolic tangent of `z`.
result = sinh(z)/cosh(z)
proc arctanh*(z: Complex): Complex =
## Returns the inverse hyperbolic tangent of `z`.
result = 0.5*(ln((1+z)/(1-z)))
proc sech*(z: Complex): Complex =
## Returns the hyperbolic secant of `z`.
result = 2/(exp(z)+exp(-z))
proc arcsech*(z: Complex): Complex =
## Returns the inverse hyperbolic secant of `z`.
result = ln(1/z+sqrt(1/z+1)*sqrt(1/z-1))
proc csch*(z: Complex): Complex =
## Returns the hyperbolic cosecant of `z`.
result = 2/(exp(z)-exp(-z))
proc arccsch*(z: Complex): Complex =
## Returns the inverse hyperbolic cosecant of `z`.
result = ln(1/z+sqrt(1/(z*z)+1))
proc coth*(z: Complex): Complex =
## Returns the hyperbolic cotangent of `z`.
result = cosh(z)/sinh(z)
proc arccoth*(z: Complex): Complex =
## Returns the inverse hyperbolic cotangent of `z`.
result = 0.5*(ln(1+1/z)-ln(1-1/z))
proc phase*(z: Complex): float =
## Returns the phase of `z`.
arctan2(z.im, z.re)
proc polar*(z: Complex): tuple[r, phi: float] =
## Returns `z` in polar coordinates.
result.r = abs(z)
result.phi = phase(z)
proc rect*(r: float, phi: float): Complex =
## Returns the complex number with polar coordinates `r` and `phi`.
result.re = r * cos(phi)
result.im = r * sin(phi)
proc `$`*(z: Complex): string =
## Returns `z`'s string representation as ``"(re, im)"``.
@@ -303,41 +388,56 @@ when isMainModule:
var tt = (10.0, 20.0)
var ipi = (0.0, -PI)
assert( a == a )
assert( (a-a) == z )
assert( (a+b) == z )
assert( (a/b) == m1 )
assert( (1.0/a) == (0.2, -0.4) )
assert( (a*b) == (3.0, -4.0) )
assert( 10.0*a == tt )
assert( a*10.0 == tt )
assert( tt/10.0 == a )
assert( a == a )
assert( (a-a) == z )
assert( (a+b) == z )
assert( (a/b) == m1 )
assert( (1.0/a) == (0.2, -0.4) )
assert( (a*b) == (3.0, -4.0) )
assert( 10.0*a == tt )
assert( a*10.0 == tt )
assert( tt/10.0 == a )
assert( oo+(-1.0) == i )
assert( (-1.0)+oo == i )
assert( abs(oo) == sqrt(2.0) )
assert( sqrt(m1) == i )
assert( exp(ipi) =~ m1 )
assert( abs(oo) == sqrt(2.0) )
assert( conjugate(a) == (1.0, -2.0) )
assert( sqrt(m1) == i )
assert( exp(ipi) =~ m1 )
assert( pow(a,b) =~ (-3.72999124927876, -1.68815826725068) )
assert( pow(z,a) =~ (0.0, 0.0) )
assert( pow(z,z) =~ (1.0, 0.0) )
assert( pow(a,b) =~ (-3.72999124927876, -1.68815826725068) )
assert( pow(z,a) =~ (0.0, 0.0) )
assert( pow(z,z) =~ (1.0, 0.0) )
assert( pow(a,one) =~ a )
assert( pow(a,m1) =~ (0.2, -0.4) )
assert( pow(a,m1) =~ (0.2, -0.4) )
assert( ln(a) =~ (0.804718956217050, 1.107148717794090) )
assert( ln(a) =~ (0.804718956217050, 1.107148717794090) )
assert( log10(a) =~ (0.349485002168009, 0.480828578784234) )
assert( log2(a) =~ (1.16096404744368, 1.59727796468811) )
assert( log2(a) =~ (1.16096404744368, 1.59727796468811) )
assert( sin(a) =~ (3.16577851321617, 1.95960104142161) )
assert( cos(a) =~ (2.03272300701967, -3.05189779915180) )
assert( tan(a) =~ (0.0338128260798967, 1.0147936161466335) )
assert( cot(a) =~ 1.0/tan(a) )
assert( sec(a) =~ 1.0/cos(a) )
assert( csc(a) =~ 1.0/sin(a) )
assert( sin(a) =~ (3.16577851321617, 1.95960104142161) )
assert( cos(a) =~ (2.03272300701967, -3.05189779915180) )
assert( tan(a) =~ (0.0338128260798967, 1.0147936161466335) )
assert( cot(a) =~ 1.0/tan(a) )
assert( sec(a) =~ 1.0/cos(a) )
assert( csc(a) =~ 1.0/sin(a) )
assert( arcsin(a) =~ (0.427078586392476, 1.528570919480998) )
assert( arccos(a) =~ (1.14371774040242, -1.52857091948100) )
assert( arctan(a) =~ (1.338972522294494, 0.402359478108525) )
assert( cosh(a) =~ (-0.642148124715520, 1.068607421382778) )
assert( cosh(a) =~ (-0.642148124715520, 1.068607421382778) )
assert( sinh(a) =~ (-0.489056259041294, 1.403119250622040) )
assert( tanh(a) =~ (1.1667362572409199,-0.243458201185725) )
assert( sech(a) =~ 1/cosh(a) )
assert( csch(a) =~ 1/sinh(a) )
assert( coth(a) =~ 1/tanh(a) )
assert( arccosh(a) =~ (1.528570919480998, 1.14371774040242) )
assert( arcsinh(a) =~ (1.469351744368185, 1.06344002357775) )
assert( arctanh(a) =~ (0.173286795139986, 1.17809724509617) )
assert( arcsech(a) =~ arccosh(1/a) )
assert( arccsch(a) =~ arcsinh(1/a) )
assert( arccoth(a) =~ arctanh(1/a) )
assert( phase(a) == 1.1071487177940904 )
var t = polar(a)
assert( rect(t.r, t.phi) =~ a )
assert( rect(1.0, 2.0) =~ (-0.4161468365471424, 0.9092974268256817) )

View File

@@ -1,7 +1,7 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2014 Andreas Rumpf
# (c) Copyright 2015 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.

View File

@@ -1,7 +1,7 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2014 Andreas Rumpf
# (c) Copyright 2015 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@@ -78,7 +78,7 @@ proc advice*(s: var ThreadPoolState): ThreadPoolAdvice =
result = doNothing
inc s.calls
when isMainModule:
when not defined(testing) and isMainModule:
proc busyLoop() =
while true:
discard random(80)

View File

@@ -1,7 +1,7 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2014 Andreas Rumpf
# (c) Copyright 2015 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@@ -204,7 +204,7 @@ proc nimFlowVarSignal(fv: FlowVarBase) {.compilerProc.} =
inc fv.ai.cv.counter
release(fv.ai.cv.L)
signal(fv.ai.cv.c)
if fv.usesSemaphore:
if fv.usesSemaphore:
signal(fv.cv)
proc awaitAndThen*[T](fv: FlowVar[T]; action: proc (x: T) {.closure.}) =

View File

@@ -56,6 +56,12 @@ proc setCookie*(key, value: string, expires: TimeInfo,
when isMainModule:
var tim = Time(int(getTime()) + 76 * (60 * 60 * 24))
echo(setCookie("test", "value", tim.getGMTime()))
let cookie = setCookie("test", "value", tim.getGMTime())
when not defined(testing):
echo cookie
let start = "Set-Cookie: test=value; Expires="
assert cookie[0..start.high] == start
echo parseCookies("uid=1; kp=2")
let table = parseCookies("uid=1; kp=2")
assert table["uid"] == "1"
assert table["kp"] == "2"

View File

@@ -1,7 +1,7 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2014 Andreas Rumpf
# (c) Copyright 2015 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@@ -301,7 +301,7 @@ proc getCurrentEncoding*(): string =
proc open*(destEncoding = "UTF-8", srcEncoding = "CP1252"): EncodingConverter =
## opens a converter that can convert from `srcEncoding` to `destEncoding`.
## Raises `EIO` if it cannot fullfill the request.
## Raises `EIO` if it cannot fulfill the request.
when not defined(windows):
result = iconvOpen(destEncoding, srcEncoding)
if result == nil:
@@ -451,7 +451,7 @@ proc convert*(s: string, destEncoding = "UTF-8",
finally:
close(c)
when isMainModule:
when not defined(testing) and isMainModule:
let
orig = "öäüß"
cp1252 = convert(orig, "CP1252", "UTF-8")

View File

@@ -9,7 +9,7 @@
## :Author: Alex Mitchell
##
## This module implements an event system that is not dependant on external
## This module implements an event system that is not dependent on external
## graphical toolkits. It was originally called ``NimEE`` because
## it was inspired by Python's PyEE module. There are two ways you can use
## events: one is a python-inspired way; the other is more of a C-style way.

View File

@@ -1,7 +1,7 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2014 Andreas Rumpf
# (c) Copyright 2015 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@@ -101,3 +101,81 @@ proc feupdateenv*(envp: ptr Tfenv): cint {.importc, header: "<fenv.h>".}
## Save current exceptions in temporary storage, install environment
## represented by object pointed to by `envp` and raise exceptions
## according to saved exceptions.
var FP_RADIX_INTERNAL {. importc: "FLT_RADIX" header: "<float.h>" .} : int
template fpRadix* : int = FP_RADIX_INTERNAL
## The (integer) value of the radix used to represent any floating
## point type on the architecture used to build the program.
var FLT_MANT_DIG {. importc: "FLT_MANT_DIG" header: "<float.h>" .} : int
var FLT_DIG {. importc: "FLT_DIG" header: "<float.h>" .} : int
var FLT_MIN_EXP {. importc: "FLT_MIN_EXP" header: "<float.h>" .} : int
var FLT_MAX_EXP {. importc: "FLT_MAX_EXP" header: "<float.h>" .} : int
var FLT_MIN_10_EXP {. importc: "FLT_MIN_10_EXP" header: "<float.h>" .} : int
var FLT_MAX_10_EXP {. importc: "FLT_MAX_10_EXP" header: "<float.h>" .} : int
var FLT_MIN {. importc: "FLT_MIN" header: "<float.h>" .} : cfloat
var FLT_MAX {. importc: "FLT_MAX" header: "<float.h>" .} : cfloat
var FLT_EPSILON {. importc: "FLT_EPSILON" header: "<float.h>" .} : cfloat
var DBL_MANT_DIG {. importc: "DBL_MANT_DIG" header: "<float.h>" .} : int
var DBL_DIG {. importc: "DBL_DIG" header: "<float.h>" .} : int
var DBL_MIN_EXP {. importc: "DBL_MIN_EXP" header: "<float.h>" .} : int
var DBL_MAX_EXP {. importc: "DBL_MAX_EXP" header: "<float.h>" .} : int
var DBL_MIN_10_EXP {. importc: "DBL_MIN_10_EXP" header: "<float.h>" .} : int
var DBL_MAX_10_EXP {. importc: "DBL_MAX_10_EXP" header: "<float.h>" .} : int
var DBL_MIN {. importc: "DBL_MIN" header: "<float.h>" .} : cdouble
var DBL_MAX {. importc: "DBL_MAX" header: "<float.h>" .} : cdouble
var DBL_EPSILON {. importc: "DBL_EPSILON" header: "<float.h>" .} : cdouble
template mantissaDigits*(T : typedesc[float32]) : int = FLT_MANT_DIG
## Number of digits (in base ``floatingPointRadix``) in the mantissa
## of 32-bit floating-point numbers.
template digits*(T : typedesc[float32]) : int = FLT_DIG
## Number of decimal digits that can be represented in a
## 32-bit floating-point type without losing precision.
template minExponent*(T : typedesc[float32]) : int = FLT_MIN_EXP
## Minimum (negative) exponent for 32-bit floating-point numbers.
template maxExponent*(T : typedesc[float32]) : int = FLT_MAX_EXP
## Maximum (positive) exponent for 32-bit floating-point numbers.
template min10Exponent*(T : typedesc[float32]) : int = FLT_MIN_10_EXP
## Minimum (negative) exponent in base 10 for 32-bit floating-point
## numbers.
template max10Exponent*(T : typedesc[float32]) : int = FLT_MAX_10_EXP
## Maximum (positive) exponent in base 10 for 32-bit floating-point
## numbers.
template minimumPositiveValue*(T : typedesc[float32]) : float32 = FLT_MIN
## The smallest positive (nonzero) number that can be represented in a
## 32-bit floating-point type.
template maximumPositiveValue*(T : typedesc[float32]) : float32 = FLT_MAX
## The largest positive number that can be represented in a 32-bit
## floating-point type.
template epsilon*(T : typedesc[float32]): float32 = FLT_EPSILON
## The difference between 1.0 and the smallest number greater than
## 1.0 that can be represented in a 32-bit floating-point type.
template mantissaDigits*(T : typedesc[float64]) : int = DBL_MANT_DIG
## Number of digits (in base ``floatingPointRadix``) in the mantissa
## of 64-bit floating-point numbers.
template digits*(T : typedesc[float64]) : int = DBL_DIG
## Number of decimal digits that can be represented in a
## 64-bit floating-point type without losing precision.
template minExponent*(T : typedesc[float64]) : int = DBL_MIN_EXP
## Minimum (negative) exponent for 64-bit floating-point numbers.
template maxExponent*(T : typedesc[float64]) : int = DBL_MAX_EXP
## Maximum (positive) exponent for 64-bit floating-point numbers.
template min10Exponent*(T : typedesc[float64]) : int = DBL_MIN_10_EXP
## Minimum (negative) exponent in base 10 for 64-bit floating-point
## numbers.
template max10Exponent*(T : typedesc[float64]) : int = DBL_MAX_10_EXP
## Maximum (positive) exponent in base 10 for 64-bit floating-point
## numbers.
template minimumPositiveValue*(T : typedesc[float64]) : float64 = DBL_MIN
## The smallest positive (nonzero) number that can be represented in a
## 64-bit floating-point type.
template maximumPositiveValue*(T : typedesc[float64]) : float64 = DBL_MAX
## The largest positive number that can be represented in a 64-bit
## floating-point type.
template epsilon*(T : typedesc[float64]): float64 = DBL_EPSILON
## The difference between 1.0 and the smallest number greater than
## 1.0 that can be represented in a 64-bit floating-point type.

View File

@@ -29,7 +29,7 @@ type
FSMonitorObj = object of RootObj
fd: cint
handleEvent: proc (m: FSMonitor, ev: MonitorEvent) {.closure.}
targets: TTable[cint, string]
targets: Table[cint, string]
MonitorEventType* = enum ## Monitor event type
MonitorAccess, ## File was accessed.
@@ -64,7 +64,7 @@ type
const
MaxEvents = 100
proc newMonitor*(): PFSMonitor =
proc newMonitor*(): FSMonitor =
## Creates a new file system monitor.
new(result)
result.targets = initTable[cint, string]()
@@ -72,7 +72,7 @@ proc newMonitor*(): PFSMonitor =
if result.fd < 0:
raiseOSError(osLastError())
proc add*(monitor: PFSMonitor, target: string,
proc add*(monitor: FSMonitor, target: string,
filters = {MonitorAll}): cint {.discardable.} =
## Adds ``target`` which may be a directory or a file to the list of
## watched paths of ``monitor``.
@@ -99,14 +99,14 @@ proc add*(monitor: PFSMonitor, target: string,
raiseOSError(osLastError())
monitor.targets.add(result, target)
proc del*(monitor: PFSMonitor, wd: cint) =
proc del*(monitor: FSMonitor, wd: cint) =
## Removes watched directory or file as specified by ``wd`` from ``monitor``.
##
## If ``wd`` is not a part of ``monitor`` an EOS error is raised.
if inotifyRmWatch(monitor.fd, wd) < 0:
raiseOSError(osLastError())
proc getEvent(m: PFSMonitor, fd: cint): seq[TMonitorEvent] =
proc getEvent(m: FSMonitor, fd: cint): seq[MonitorEvent] =
result = @[]
let size = (sizeof(TINotifyEvent)+2000)*MaxEvents
var buffer = newString(size)
@@ -118,7 +118,7 @@ proc getEvent(m: PFSMonitor, fd: cint): seq[TMonitorEvent] =
var i = 0
while i < le:
var event = cast[ptr TINotifyEvent](addr(buffer[i]))
var mev: TMonitorEvent
var mev: MonitorEvent
mev.wd = event.wd
if event.len.int != 0:
let cstr = event.name.addr.cstring
@@ -137,7 +137,7 @@ proc getEvent(m: PFSMonitor, fd: cint): seq[TMonitorEvent] =
# Find the MovedFrom event.
mev.oldPath = movedFrom[event.cookie.cint].old
mev.newPath = "" # Set later
# Delete it from the TTable
# Delete it from the Table
movedFrom.del(event.cookie.cint)
elif (event.mask.int and IN_ACCESS) != 0: mev.kind = MonitorAccess
elif (event.mask.int and IN_ATTRIB) != 0: mev.kind = MonitorAttrib
@@ -164,26 +164,26 @@ proc getEvent(m: PFSMonitor, fd: cint): seq[TMonitorEvent] =
# If movedFrom events have not been matched with a moveTo. File has
# been moved to an unwatched location, emit a MonitorDelete.
for cookie, t in pairs(movedFrom):
var mev: TMonitorEvent
var mev: MonitorEvent
mev.kind = MonitorDelete
mev.wd = t.wd
mev.name = t.old
result.add(mev)
proc FSMonitorRead(h: PObject) =
var events = PFSMonitor(h).getEvent(PFSMonitor(h).fd)
#var newEv: TMonitorEvent
proc FSMonitorRead(h: RootRef) =
var events = FSMonitor(h).getEvent(FSMonitor(h).fd)
#var newEv: MonitorEvent
for ev in events:
var target = PFSMonitor(h).targets[ev.wd]
var target = FSMonitor(h).targets[ev.wd]
var newEv = ev
if newEv.kind == MonitorMoved:
newEv.oldPath = target / newEv.oldPath
newEv.newPath = target / newEv.name
else:
newEv.fullName = target / newEv.name
PFSMonitor(h).handleEvent(PFSMonitor(h), newEv)
FSMonitor(h).handleEvent(FSMonitor(h), newEv)
proc toDelegate(m: PFSMonitor): PDelegate =
proc toDelegate(m: FSMonitor): Delegate =
result = newDelegate()
result.deleVal = m
result.fd = (type(result.fd))(m.fd)
@@ -191,20 +191,20 @@ proc toDelegate(m: PFSMonitor): PDelegate =
result.handleRead = FSMonitorRead
result.open = true
proc register*(d: PDispatcher, monitor: PFSMonitor,
handleEvent: proc (m: PFSMonitor, ev: TMonitorEvent) {.closure.}) =
proc register*(d: Dispatcher, monitor: FSMonitor,
handleEvent: proc (m: FSMonitor, ev: MonitorEvent) {.closure.}) =
## Registers ``monitor`` with dispatcher ``d``.
monitor.handleEvent = handleEvent
var deleg = toDelegate(monitor)
d.register(deleg)
when isMainModule:
when not defined(testing) and isMainModule:
proc main =
var disp = newDispatcher()
var monitor = newMonitor()
echo monitor.add("/home/dom/inotifytests/")
disp.register(monitor,
proc (m: PFSMonitor, ev: TMonitorEvent) =
proc (m: FSMonitor, ev: MonitorEvent) =
echo("Got event: ", ev.kind)
if ev.kind == MonitorMoved:
echo("From ", ev.oldPath, " to ", ev.newPath)

View File

@@ -1,7 +1,7 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2014 Dominik Picheta
# (c) Copyright 2015 Dominik Picheta
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
@@ -15,8 +15,8 @@ from rawsockets import nil
from asyncdispatch import PFuture
## This module **partially** implements an FTP client as specified
## by `RFC 959 <http://tools.ietf.org/html/rfc959>`_.
##
## by `RFC 959 <http://tools.ietf.org/html/rfc959>`_.
##
## This module provides both a synchronous and asynchronous implementation.
## The asynchronous implementation requires you to use the ``asyncFTPClient``
## function. You are then required to register the ``AsyncFTPClient`` with a
@@ -27,7 +27,7 @@ from asyncdispatch import PFuture
## file transfers, calls to functions which use the command socket will block.
##
## Here is some example usage of this module:
##
##
## .. code-block:: Nim
## var ftp = ftpClient("example.org", user = "user", pass = "pass")
## ftp.connect()
@@ -51,7 +51,7 @@ type
port*: rawsockets.Port
else:
port*: Port
jobInProgress*: bool
job*: FTPJob[SockType]
@@ -91,7 +91,7 @@ type
of EvLines:
lines*: string ## Lines that have been transferred.
of EvRetr, EvStore: ## Retr/Store operation finished.
nil
nil
of EvTransferProgress:
bytesTotal*: BiggestInt ## Bytes total.
bytesFinished*: BiggestInt ## Bytes transferred.
@@ -107,7 +107,7 @@ type
EInvalidReply: ReplyError, EFTP: FTPError
].}
proc ftpClient*(address: string, port = TPort(21),
proc ftpClient*(address: string, port = Port(21),
user, pass = ""): FtpClient =
## Create a ``FtpClient`` object.
new(result)
@@ -120,10 +120,10 @@ proc ftpClient*(address: string, port = TPort(21),
result.csock = socket()
if result.csock == invalidSocket: raiseOSError(osLastError())
template blockingOperation(sock: TSocket, body: stmt) {.immediate.} =
template blockingOperation(sock: Socket, body: stmt) {.immediate.} =
body
template blockingOperation(sock: asyncio.PAsyncSocket, body: stmt) {.immediate.} =
template blockingOperation(sock: asyncio.AsyncSocket, body: stmt) {.immediate.} =
sock.setBlocking(true)
body
sock.setBlocking(false)
@@ -145,14 +145,14 @@ proc send*[T](ftp: FtpBase[T], m: string): TaintedString =
proc assertReply(received: TaintedString, expected: string) =
if not received.string.startsWith(expected):
raise newException(EInvalidReply,
raise newException(ReplyError,
"Expected reply '$1' got: $2" % [
expected, received.string])
proc assertReply(received: TaintedString, expected: varargs[string]) =
for i in items(expected):
if received.string.startsWith(i): return
raise newException(EInvalidReply,
raise newException(ReplyError,
"Expected reply '$1' got: $2" %
[expected.join("' or '"), received.string])
@@ -161,7 +161,7 @@ proc createJob[T](ftp: FtpBase[T],
nimcall,gcsafe.},
cmd: FTPJobType) =
if ftp.jobInProgress:
raise newException(EFTP, "Unable to do two jobs at once.")
raise newException(FTPError, "Unable to do two jobs at once.")
ftp.jobInProgress = true
new(ftp.job)
ftp.job.prc = prc
@@ -182,11 +182,11 @@ proc deleteJob[T](ftp: FtpBase[T]) =
ftp.job.file.close()
ftp.dsock.close()
proc handleTask(s: PAsyncSocket, ftp: PAsyncFTPClient) =
proc handleTask(s: AsyncSocket, ftp: AsyncFTPClient) =
if ftp.jobInProgress:
if ftp.job.typ in {JRetr, JStore}:
if epochTime() - ftp.job.lastProgressReport >= 1.0:
var r: TFTPEvent
var r: FTPEvent
ftp.job.lastProgressReport = epochTime()
r.typ = EvTransferProgress
r.bytesTotal = ftp.job.total
@@ -195,57 +195,57 @@ proc handleTask(s: PAsyncSocket, ftp: PAsyncFTPClient) =
r.filename = ftp.job.filename
r.currentJob = ftp.job.typ
ftp.job.oneSecond = 0
ftp.handleEvent(PAsyncFTPClient(ftp), r)
ftp.handleEvent(ftp, r)
proc handleWrite(s: PAsyncSocket, ftp: PAsyncFTPClient) =
proc handleWrite(s: AsyncSocket, ftp: AsyncFTPClient) =
if ftp.jobInProgress:
if ftp.job.typ == JStore:
assert (not ftp.job.prc(ftp, true))
proc handleConnect(s: PAsyncSocket, ftp: PAsyncFTPClient) =
proc handleConnect(s: AsyncSocket, ftp: AsyncFTPClient) =
ftp.dsockConnected = true
assert(ftp.jobInProgress)
if ftp.job.typ == JStore:
s.setHandleWrite(proc (s: PAsyncSocket) = handleWrite(s, ftp))
s.setHandleWrite(proc (s: AsyncSocket) = handleWrite(s, ftp))
else:
s.delHandleWrite()
proc handleRead(s: PAsyncSocket, ftp: PAsyncFTPClient) =
proc handleRead(s: AsyncSocket, ftp: AsyncFTPClient) =
assert ftp.jobInProgress
assert ftp.job.typ != JStore
# This can never return true, because it shouldn't check for code
# This can never return true, because it shouldn't check for code
# 226 from csock.
assert(not ftp.job.prc(ftp, true))
proc pasv[T](ftp: FtpBase[T]) =
## Negotiate a data connection.
when T is TSocket:
when T is Socket:
ftp.dsock = socket()
if ftp.dsock == invalidSocket: raiseOSError(osLastError())
elif T is PAsyncSocket:
elif T is AsyncSocket:
ftp.dsock = asyncSocket()
ftp.dsock.handleRead =
proc (s: PAsyncSocket) =
proc (s: AsyncSocket) =
handleRead(s, ftp)
ftp.dsock.handleConnect =
proc (s: PAsyncSocket) =
proc (s: AsyncSocket) =
handleConnect(s, ftp)
ftp.dsock.handleTask =
proc (s: PAsyncSocket) =
proc (s: AsyncSocket) =
handleTask(s, ftp)
ftp.disp.register(ftp.dsock)
else:
{.fatal: "Incorrect socket instantiation".}
var pasvMsg = ftp.send("PASV").string.strip.TaintedString
assertReply(pasvMsg, "227")
var betweenParens = captureBetween(pasvMsg.string, '(', ')')
var nums = betweenParens.split(',')
var ip = nums[0.. -3]
var port = nums[-2.. -1]
var ip = nums[0.. ^3]
var port = nums[^2.. ^1]
var properPort = port[0].parseInt()*256+port[1].parseInt()
ftp.dsock.connect(ip.join("."), TPort(properPort.toU16))
when T is PAsyncSocket:
ftp.dsock.connect(ip.join("."), Port(properPort.toU16))
when T is AsyncSocket:
ftp.dsockConnected = false
else:
ftp.dsockConnected = true
@@ -255,10 +255,10 @@ proc normalizePathSep(path: string): string =
proc connect*[T](ftp: FtpBase[T]) =
## Connect to the FTP server specified by ``ftp``.
when T is PAsyncSocket:
when T is AsyncSocket:
blockingOperation(ftp.csock):
ftp.csock.connect(ftp.address, ftp.port)
elif T is TSocket:
elif T is Socket:
ftp.csock.connect(ftp.address, ftp.port)
else:
{.fatal: "Incorrect socket instantiation".}
@@ -292,13 +292,13 @@ proc getLines[T](ftp: FtpBase[T], async: bool = false): bool =
## It doesn't if `async` is true, because it doesn't check for 226 then.
if ftp.dsockConnected:
var r = TaintedString""
when T is PAsyncSocket:
when T is AsyncSocket:
if ftp.asyncDSock.readLine(r):
if r.string == "":
ftp.dsockConnected = false
else:
ftp.job.lines.add(r.string & "\n")
elif T is TSocket:
elif T is Socket:
assert(not async)
ftp.dsock.readLine(r)
if r.string == "":
@@ -307,9 +307,9 @@ proc getLines[T](ftp: FtpBase[T], async: bool = false): bool =
ftp.job.lines.add(r.string & "\n")
else:
{.fatal: "Incorrect socket instantiation".}
if not async:
var readSocks: seq[TSocket] = @[ftp.csock]
var readSocks: seq[Socket] = @[ftp.csock]
# This is only needed here. Asyncio gets this socket...
blockingOperation(ftp.csock):
if readSocks.select(1) != 0 and ftp.csock in readSocks:
@@ -372,7 +372,7 @@ proc createDir*[T](ftp: FtpBase[T], dir: string, recursive: bool = false) =
assertReply reply, "257"
proc chmod*[T](ftp: FtpBase[T], path: string,
permissions: set[TFilePermission]) =
permissions: set[FilePermission]) =
## Changes permission of ``path`` to ``permissions``.
var userOctal = 0
var groupOctal = 0
@@ -396,7 +396,7 @@ proc chmod*[T](ftp: FtpBase[T], path: string,
proc list*[T](ftp: FtpBase[T], dir: string = "", async = false): string =
## Lists all files in ``dir``. If ``dir`` is ``""``, uses the current
## working directory. If ``async`` is true, this function will return
## immediately and it will be your job to call asyncio's
## immediately and it will be your job to call asyncio's
## ``poll`` to progress this operation.
ftp.createJob(getLines[T], JRetrText)
ftp.pasv()
@@ -417,7 +417,7 @@ proc retrText*[T](ftp: FtpBase[T], file: string, async = false): string =
ftp.createJob(getLines[T], JRetrText)
ftp.pasv()
assertReply ftp.send("RETR " & file.normalizePathSep), ["125", "150"]
if not async:
while not ftp.job.prc(ftp, false): discard
result = ftp.job.lines
@@ -431,12 +431,12 @@ proc getFile[T](ftp: FtpBase[T], async = false): bool =
var bytesRead = 0
var returned = false
if async:
when T is TSocket:
raise newException(EFTP, "FTPClient must be async.")
when T is Socket:
raise newException(FTPError, "FTPClient must be async.")
else:
bytesRead = ftp.dsock.recvAsync(r, BufferSize)
returned = bytesRead != -1
else:
else:
bytesRead = ftp.dsock.recv(r, BufferSize)
returned = true
let r2 = r.string
@@ -447,9 +447,9 @@ proc getFile[T](ftp: FtpBase[T], async = false): bool =
elif returned and r2 == "":
ftp.dsockConnected = false
when T is TSocket:
when T is Socket:
if not async:
var readSocks: seq[TSocket] = @[ftp.csock]
var readSocks: seq[Socket] = @[ftp.csock]
blockingOperation(ftp.csock):
if readSocks.select(1) != 0 and ftp.csock in readSocks:
assertReply ftp.expectReply(), "226"
@@ -458,7 +458,7 @@ proc getFile[T](ftp: FtpBase[T], async = false): bool =
proc retrFile*[T](ftp: FtpBase[T], file, dest: string, async = false) =
## Downloads ``file`` and saves it to ``dest``. Usage of this function
## asynchronously is recommended to view the progress of the download.
## The ``EvRetr`` event is passed to the specified ``handleEvent`` function
## The ``EvRetr`` event is passed to the specified ``handleEvent`` function
## when the download is finished, and the ``filename`` field will be equal
## to ``file``.
ftp.createJob(getFile[T], JRetr)
@@ -467,11 +467,11 @@ proc retrFile*[T](ftp: FtpBase[T], file, dest: string, async = false) =
var reply = ftp.send("RETR " & file.normalizePathSep)
assertReply reply, ["125", "150"]
if {'(', ')'} notin reply.string:
raise newException(EInvalidReply, "Reply has no file size.")
raise newException(ReplyError, "Reply has no file size.")
var fileSize: BiggestInt
if reply.string.captureBetween('(', ')').parseBiggestInt(fileSize) == 0:
raise newException(EInvalidReply, "Reply has no file size.")
raise newException(ReplyError, "Reply has no file size.")
ftp.job.total = fileSize
ftp.job.lastProgressReport = epochTime()
ftp.job.filename = file.normalizePathSep
@@ -488,7 +488,7 @@ proc doUpload[T](ftp: FtpBase[T], async = false): bool =
if bytesSent == ftp.job.toStore.len:
ftp.job.toStore = ""
elif bytesSent != ftp.job.toStore.len and bytesSent != 0:
ftp.job.toStore = ftp.job.toStore[bytesSent .. -1]
ftp.job.toStore = ftp.job.toStore[bytesSent .. ^1]
ftp.job.progress.inc(bytesSent)
ftp.job.oneSecond.inc(bytesSent)
else:
@@ -499,12 +499,12 @@ proc doUpload[T](ftp: FtpBase[T], async = false): bool =
# File finished uploading.
ftp.dsock.close()
ftp.dsockConnected = false
if not async:
assertReply ftp.expectReply(), "226"
return true
return false
if not async:
ftp.dsock.send(s)
else:
@@ -512,9 +512,9 @@ proc doUpload[T](ftp: FtpBase[T], async = false): bool =
if bytesSent == 0:
ftp.job.toStore.add(s)
elif bytesSent != s.len:
ftp.job.toStore.add(s[bytesSent .. -1])
ftp.job.toStore.add(s[bytesSent .. ^1])
len = bytesSent
ftp.job.progress.inc(len)
ftp.job.oneSecond.inc(len)
@@ -522,8 +522,8 @@ proc store*[T](ftp: FtpBase[T], file, dest: string, async = false) =
## Uploads ``file`` to ``dest`` on the remote FTP server. Usage of this
## function asynchronously is recommended to view the progress of
## the download.
## The ``EvStore`` event is passed to the specified ``handleEvent`` function
## when the upload is finished, and the ``filename`` field will be
## The ``EvStore`` event is passed to the specified ``handleEvent`` function
## when the upload is finished, and the ``filename`` field will be
## equal to ``file``.
ftp.createJob(doUpload[T], JStore)
ftp.job.file = open(file)
@@ -531,7 +531,7 @@ proc store*[T](ftp: FtpBase[T], file, dest: string, async = false) =
ftp.job.lastProgressReport = epochTime()
ftp.job.filename = file
ftp.pasv()
assertReply ftp.send("STOR " & dest.normalizePathSep), ["125", "150"]
if not async:
@@ -545,10 +545,10 @@ proc close*[T](ftp: FtpBase[T]) =
ftp.csock.close()
ftp.dsock.close()
proc csockHandleRead(s: PAsyncSocket, ftp: PAsyncFTPClient) =
proc csockHandleRead(s: AsyncSocket, ftp: AsyncFTPClient) =
if ftp.jobInProgress:
assertReply ftp.expectReply(), "226" # Make sure the transfer completed.
var r: TFTPEvent
var r: FTPEvent
case ftp.job.typ
of JRetrText:
r.typ = EvLines
@@ -557,21 +557,21 @@ proc csockHandleRead(s: PAsyncSocket, ftp: PAsyncFTPClient) =
r.typ = EvRetr
r.filename = ftp.job.filename
if ftp.job.progress != ftp.job.total:
raise newException(EFTP, "Didn't download full file.")
raise newException(FTPError, "Didn't download full file.")
of JStore:
r.typ = EvStore
r.filename = ftp.job.filename
if ftp.job.progress != ftp.job.total:
raise newException(EFTP, "Didn't upload full file.")
raise newException(FTPError, "Didn't upload full file.")
ftp.deleteJob()
ftp.handleEvent(ftp, r)
proc asyncFTPClient*(address: string, port = TPort(21),
proc asyncFTPClient*(address: string, port = Port(21),
user, pass = "",
handleEvent: proc (ftp: PAsyncFTPClient, ev: TFTPEvent) {.closure,gcsafe.} =
(proc (ftp: PAsyncFTPClient, ev: TFTPEvent) = discard)): PAsyncFTPClient =
## Create a ``PAsyncFTPClient`` object.
handleEvent: proc (ftp: AsyncFTPClient, ev: FTPEvent) {.closure,gcsafe.} =
(proc (ftp: AsyncFTPClient, ev: FTPEvent) = discard)): AsyncFTPClient =
## Create a ``AsyncFTPClient`` object.
##
## Use this if you want to use asyncio's dispatcher.
var dres: AsyncFtpClient
@@ -588,12 +588,12 @@ proc asyncFTPClient*(address: string, port = TPort(21),
csockHandleRead(s, dres)
result = dres
proc register*(d: PDispatcher, ftp: PAsyncFTPClient): PDelegate {.discardable.} =
proc register*(d: Dispatcher, ftp: AsyncFTPClient): Delegate {.discardable.} =
## Registers ``ftp`` with dispatcher ``d``.
ftp.disp = d
return ftp.disp.register(ftp.csock)
when isMainModule:
when not defined(testing) and isMainModule:
proc main =
var d = newDispatcher()
let hev =
@@ -617,7 +617,7 @@ when isMainModule:
echo d.len
else: assert(false)
var ftp = asyncFTPClient("example.com", user = "foo", pass = "bar", handleEvent = hev)
d.register(ftp)
d.len.echo()
ftp.connect()
@@ -629,7 +629,7 @@ when isMainModule:
if not d.poll(): break
main()
when isMainModule and false:
when not defined(testing) and isMainModule:
var ftp = ftpClient("example.com", user = "foo", pass = "bar")
ftp.connect()
echo ftp.pwd()

View File

@@ -1,7 +1,7 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2014 Dominik Picheta
# (c) Copyright 2015 Dominik Picheta
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@@ -12,7 +12,7 @@
import macros
proc createProcType(p, b: PNimrodNode): PNimrodNode {.compileTime.} =
proc createProcType(p, b: NimNode): NimNode {.compileTime.} =
#echo treeRepr(p)
#echo treeRepr(b)
result = newNimNode(nnkProcTy)
@@ -44,7 +44,7 @@ proc createProcType(p, b: PNimrodNode): PNimrodNode {.compileTime.} =
formalParams.add identDefs
else:
error("Incorrect type list in proc type declaration.")
result.add formalParams
result.add newEmptyNode()
#echo(treeRepr(result))
@@ -59,10 +59,10 @@ macro `=>`*(p, b: expr): expr {.immediate.} =
## f(2, 2)
##
## passTwoAndTwo((x, y) => x + y) # 4
#echo treeRepr(p)
#echo(treeRepr(b))
var params: seq[PNimrodNode] = @[newIdentNode("auto")]
var params: seq[NimNode] = @[newIdentNode("auto")]
case p.kind
of nnkPar:
@@ -118,7 +118,7 @@ macro `->`*(p, b: expr): expr {.immediate.} =
##
## proc pass2(f: (float, float) -> float): float =
## f(2, 2)
##
##
## # is the same as:
##
## proc pass2(f: proc (x, y: float): float): float =
@@ -155,7 +155,6 @@ macro `[]`*(lc: ListComprehension, comp, typ: expr): expr =
for i in countdown(comp[2].len-1, 0):
let x = comp[2][i]
expectKind(x, nnkInfix)
expectMinLen(x, 1)
if x[0].kind == nnkIdent and $x[0].ident == "<-":
expectLen(x, 3)

View File

@@ -9,7 +9,7 @@
## The ``gentabs`` module implements an efficient hash table that is a
## key-value mapping. The keys are required to be strings, but the values
## may be any Nim or user defined type. This module supports matching
## may be any Nim or user defined type. This module supports matching
## of keys in case-sensitive, case-insensitive and style-insensitive modes.
{.deprecated.}
@@ -22,7 +22,7 @@ type
modeCaseSensitive, ## case sensitive matching of keys
modeCaseInsensitive, ## case insensitive matching of keys
modeStyleInsensitive ## style sensitive matching of keys
TGenKeyValuePair[T] = tuple[key: string, val: T]
TGenKeyValuePairSeq[T] = seq[TGenKeyValuePair[T]]
TGenTable*[T] = object of RootObj
@@ -83,7 +83,7 @@ proc rawGet[T](tbl: PGenTable[T], key: string): int =
h = nextTry(h, high(tbl.data))
result = - 1
proc rawInsert[T](tbl: PGenTable[T], data: var TGenKeyValuePairSeq[T],
proc rawInsert[T](tbl: PGenTable[T], data: var TGenKeyValuePairSeq[T],
key: string, val: T) =
var h: THash
h = myhash(tbl, key) and high(data)
@@ -96,7 +96,7 @@ proc enlarge[T](tbl: PGenTable[T]) =
var n: TGenKeyValuePairSeq[T]
newSeq(n, len(tbl.data) * growthFactor)
for i in countup(0, high(tbl.data)):
if not isNil(tbl.data[i].key):
if not isNil(tbl.data[i].key):
rawInsert[T](tbl, n, tbl.data[i].key, tbl.data[i].val)
swap(tbl.data, n)
@@ -141,20 +141,20 @@ when isMainModule:
assert(not x.hasKey("NOPE")) # ...but key "NOPE" is not in the table.
for k,v in pairs(x): # make sure the 'pairs' iterator works
assert(x[k]==v)
#
# Verify a table of user-defined types
#
type
TMyType = tuple[first, second: string] # a pair of strings
var y = newGenTable[TMyType](modeCaseInsensitive) # hash table where each
# value is TMyType tuple
#var junk: TMyType = ("OK", "Here")
#echo junk.first, " ", junk.second
y["Hello"] = ("Hello", "World")
y["Goodbye"] = ("Goodbye", "Everyone")
#y["Hello"] = TMyType( ("Hello", "World") )
@@ -163,31 +163,44 @@ when isMainModule:
assert( not isNil(y["Hello"].first) )
assert( y["Hello"].first == "Hello" )
assert( y["Hello"].second == "World" )
#
# Verify table of tables
#
var z: PGenTable[ PGenTable[int] ] # hash table where each value is
var z: PGenTable[ PGenTable[int] ] # hash table where each value is
# a hash table of ints
z = newGenTable[PGenTable[int]](modeCaseInsensitive)
z["first"] = newGenTable[int](modeCaseInsensitive)
z["first"]["one"] = 1
z["first"]["two"] = 2
z["first"]["three"] = 3
z["second"] = newGenTable[int](modeCaseInsensitive)
z["second"]["red"] = 10
z["second"]["blue"] = 20
assert(len(z) == 2) # length of outer table
assert(len(z["first"]) == 3) # length of "first" table
assert(len(z["second"]) == 2) # length of "second" table
assert( z["first"]["one"] == 1) # retrieve from first inner table
assert( z["second"]["red"] == 10) # retrieve from second inner table
for k,v in pairs(z):
echo( "$# ($#) ->" % [k,$len(v)] )
#for k2,v2 in pairs(v):
# echo( " $# <-> $#" % [k2,$v2] )
echo()
when false:
# disabled: depends on hash order:
var output = ""
for k, v in pairs(z):
output.add( "$# ($#) ->\L" % [k,$len(v)] )
for k2,v2 in pairs(v):
output.add( " $# <-> $#\L" % [k2,$v2] )
let expected = unindent """
first (3) ->
two <-> 2
three <-> 3
one <-> 1
second (2) ->
red <-> 10
blue <-> 20
"""
assert output == expected

View File

@@ -33,8 +33,8 @@
## proc hash(x: Something): THash =
## ## Computes a THash from `x`.
## var h: THash = 0
## h = h &! hash(x.foo)
## h = h &! hash(x.bar)
## h = h !& hash(x.foo)
## h = h !& hash(x.bar)
## result = !$h
import

View File

@@ -1,7 +1,7 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2014 Andreas Rumpf
# (c) Copyright 2015 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@@ -12,7 +12,7 @@
## as ``from htmlgen import nil`` and then fully qualify the macros.
##
##
## This module implements a simple `XML`:idx: and `HTML`:idx: code
## This module implements a simple `XML`:idx: and `HTML`:idx: code
## generator. Each commonly used HTML tag has a corresponding macro
## that generates a string with its HTML representation.
##
@@ -21,9 +21,9 @@
## .. code-block:: Nim
## var nim = "Nim"
## echo h1(a(href="http://nim-lang.org", nim))
##
##
## Writes the string::
##
##
## <h1><a href="http://nim-lang.org">Nim</a></h1>
##
@@ -36,16 +36,16 @@ const
"onmouseover onmousemove onmouseout onkeypress onkeydown onkeyup "
commonAttr* = coreAttr & eventAttr
proc getIdent(e: PNimrodNode): string {.compileTime.} =
proc getIdent(e: NimNode): string {.compileTime.} =
case e.kind
of nnkIdent: result = normalize($e.ident)
of nnkAccQuoted:
of nnkAccQuoted:
result = getIdent(e[0])
for i in 1 .. e.len-1:
result.add getIdent(e[i])
else: error("cannot extract identifier from node: " & toStrLit(e).strVal)
proc delete[T](s: var seq[T], attr: T): bool =
proc delete[T](s: var seq[T], attr: T): bool =
var idx = find(s, attr)
if idx >= 0:
var L = s.len
@@ -53,10 +53,10 @@ proc delete[T](s: var seq[T], attr: T): bool =
setLen(s, L-1)
result = true
proc xmlCheckedTag*(e: PNimrodNode, tag: string, optAttr = "", reqAttr = "",
isLeaf = false): PNimrodNode {.compileTime.} =
proc xmlCheckedTag*(e: NimNode, tag: string, optAttr = "", reqAttr = "",
isLeaf = false): NimNode {.compileTime.} =
## use this procedure to define a new XML tag
# copy the attributes; when iterating over them these lists
# will be modified, so that each attribute is only given one value
var req = split(reqAttr)
@@ -66,7 +66,7 @@ proc xmlCheckedTag*(e: PNimrodNode, tag: string, optAttr = "", reqAttr = "",
result.add(newStrLitNode(tag))
# first pass over attributes:
for i in 1..e.len-1:
if e[i].kind == nnkExprEqExpr:
if e[i].kind == nnkExprEqExpr:
var name = getIdent(e[i][0])
if delete(req, name) or delete(opt, name):
result.add(newStrLitNode(" "))
@@ -81,7 +81,7 @@ proc xmlCheckedTag*(e: PNimrodNode, tag: string, optAttr = "", reqAttr = "",
error(req[0] & " attribute for '" & tag & "' element expected")
if isLeaf:
for i in 1..e.len-1:
if e[i].kind != nnkExprEqExpr:
if e[i].kind != nnkExprEqExpr:
error("element " & tag & " cannot be nested")
result.add(newStrLitNode(" />"))
else:
@@ -95,395 +95,396 @@ proc xmlCheckedTag*(e: PNimrodNode, tag: string, optAttr = "", reqAttr = "",
result = nestList(!"&", result)
macro a*(e: expr): expr {.immediate.} =
macro a*(e: expr): expr {.immediate.} =
## generates the HTML ``a`` element.
let e = callsite()
result = xmlCheckedTag(e, "a", "href charset type hreflang rel rev " &
"accesskey tabindex" & commonAttr)
macro acronym*(e: expr): expr {.immediate.} =
macro acronym*(e: expr): expr {.immediate.} =
## generates the HTML ``acronym`` element.
let e = callsite()
result = xmlCheckedTag(e, "acronym", commonAttr)
macro address*(e: expr): expr {.immediate.} =
macro address*(e: expr): expr {.immediate.} =
## generates the HTML ``address`` element.
let e = callsite()
result = xmlCheckedTag(e, "address", commonAttr)
macro area*(e: expr): expr {.immediate.} =
macro area*(e: expr): expr {.immediate.} =
## generates the HTML ``area`` element.
let e = callsite()
result = xmlCheckedTag(e, "area", "shape coords href nohref" &
" accesskey tabindex" & commonAttr, "alt", true)
macro b*(e: expr): expr {.immediate.} =
macro b*(e: expr): expr {.immediate.} =
## generates the HTML ``b`` element.
let e = callsite()
result = xmlCheckedTag(e, "b", commonAttr)
macro base*(e: expr): expr {.immediate.} =
macro base*(e: expr): expr {.immediate.} =
## generates the HTML ``base`` element.
let e = callsite()
result = xmlCheckedTag(e, "base", "", "href", true)
macro big*(e: expr): expr {.immediate.} =
macro big*(e: expr): expr {.immediate.} =
## generates the HTML ``big`` element.
let e = callsite()
result = xmlCheckedTag(e, "big", commonAttr)
macro blockquote*(e: expr): expr {.immediate.} =
macro blockquote*(e: expr): expr {.immediate.} =
## generates the HTML ``blockquote`` element.
let e = callsite()
result = xmlCheckedTag(e, "blockquote", " cite" & commonAttr)
macro body*(e: expr): expr {.immediate.} =
macro body*(e: expr): expr {.immediate.} =
## generates the HTML ``body`` element.
let e = callsite()
result = xmlCheckedTag(e, "body", commonAttr)
macro br*(e: expr): expr {.immediate.} =
macro br*(e: expr): expr {.immediate.} =
## generates the HTML ``br`` element.
let e = callsite()
result = xmlCheckedTag(e, "br", "", "", true)
macro button*(e: expr): expr {.immediate.} =
macro button*(e: expr): expr {.immediate.} =
## generates the HTML ``button`` element.
let e = callsite()
result = xmlCheckedTag(e, "button", "accesskey tabindex " &
"disabled name type value" & commonAttr)
macro caption*(e: expr): expr {.immediate.} =
macro caption*(e: expr): expr {.immediate.} =
## generates the HTML ``caption`` element.
let e = callsite()
result = xmlCheckedTag(e, "caption", commonAttr)
macro cite*(e: expr): expr {.immediate.} =
macro cite*(e: expr): expr {.immediate.} =
## generates the HTML ``cite`` element.
let e = callsite()
result = xmlCheckedTag(e, "cite", commonAttr)
macro code*(e: expr): expr {.immediate.} =
macro code*(e: expr): expr {.immediate.} =
## generates the HTML ``code`` element.
let e = callsite()
result = xmlCheckedTag(e, "code", commonAttr)
macro col*(e: expr): expr {.immediate.} =
macro col*(e: expr): expr {.immediate.} =
## generates the HTML ``col`` element.
let e = callsite()
result = xmlCheckedTag(e, "col", "span align valign" & commonAttr, "", true)
macro colgroup*(e: expr): expr {.immediate.} =
macro colgroup*(e: expr): expr {.immediate.} =
## generates the HTML ``colgroup`` element.
let e = callsite()
result = xmlCheckedTag(e, "colgroup", "span align valign" & commonAttr)
macro dd*(e: expr): expr {.immediate.} =
macro dd*(e: expr): expr {.immediate.} =
## generates the HTML ``dd`` element.
let e = callsite()
result = xmlCheckedTag(e, "dd", commonAttr)
macro del*(e: expr): expr {.immediate.} =
macro del*(e: expr): expr {.immediate.} =
## generates the HTML ``del`` element.
let e = callsite()
result = xmlCheckedTag(e, "del", "cite datetime" & commonAttr)
macro dfn*(e: expr): expr {.immediate.} =
macro dfn*(e: expr): expr {.immediate.} =
## generates the HTML ``dfn`` element.
let e = callsite()
result = xmlCheckedTag(e, "dfn", commonAttr)
macro `div`*(e: expr): expr {.immediate.} =
macro `div`*(e: expr): expr {.immediate.} =
## generates the HTML ``div`` element.
let e = callsite()
result = xmlCheckedTag(e, "div", commonAttr)
macro dl*(e: expr): expr {.immediate.} =
macro dl*(e: expr): expr {.immediate.} =
## generates the HTML ``dl`` element.
let e = callsite()
result = xmlCheckedTag(e, "dl", commonAttr)
macro dt*(e: expr): expr {.immediate.} =
macro dt*(e: expr): expr {.immediate.} =
## generates the HTML ``dt`` element.
let e = callsite()
result = xmlCheckedTag(e, "dt", commonAttr)
macro em*(e: expr): expr {.immediate.} =
macro em*(e: expr): expr {.immediate.} =
## generates the HTML ``em`` element.
let e = callsite()
result = xmlCheckedTag(e, "em", commonAttr)
macro fieldset*(e: expr): expr {.immediate.} =
macro fieldset*(e: expr): expr {.immediate.} =
## generates the HTML ``fieldset`` element.
let e = callsite()
result = xmlCheckedTag(e, "fieldset", commonAttr)
macro form*(e: expr): expr {.immediate.} =
macro form*(e: expr): expr {.immediate.} =
## generates the HTML ``form`` element.
let e = callsite()
result = xmlCheckedTag(e, "form", "method encype accept accept-charset" &
result = xmlCheckedTag(e, "form", "method encype accept accept-charset" &
commonAttr, "action")
macro h1*(e: expr): expr {.immediate.} =
macro h1*(e: expr): expr {.immediate.} =
## generates the HTML ``h1`` element.
let e = callsite()
result = xmlCheckedTag(e, "h1", commonAttr)
macro h2*(e: expr): expr {.immediate.} =
macro h2*(e: expr): expr {.immediate.} =
## generates the HTML ``h2`` element.
let e = callsite()
result = xmlCheckedTag(e, "h2", commonAttr)
macro h3*(e: expr): expr {.immediate.} =
macro h3*(e: expr): expr {.immediate.} =
## generates the HTML ``h3`` element.
let e = callsite()
result = xmlCheckedTag(e, "h3", commonAttr)
macro h4*(e: expr): expr {.immediate.} =
macro h4*(e: expr): expr {.immediate.} =
## generates the HTML ``h4`` element.
let e = callsite()
result = xmlCheckedTag(e, "h4", commonAttr)
macro h5*(e: expr): expr {.immediate.} =
macro h5*(e: expr): expr {.immediate.} =
## generates the HTML ``h5`` element.
let e = callsite()
result = xmlCheckedTag(e, "h5", commonAttr)
macro h6*(e: expr): expr {.immediate.} =
macro h6*(e: expr): expr {.immediate.} =
## generates the HTML ``h6`` element.
let e = callsite()
result = xmlCheckedTag(e, "h6", commonAttr)
macro head*(e: expr): expr {.immediate.} =
macro head*(e: expr): expr {.immediate.} =
## generates the HTML ``head`` element.
let e = callsite()
result = xmlCheckedTag(e, "head", "profile")
macro html*(e: expr): expr {.immediate.} =
macro html*(e: expr): expr {.immediate.} =
## generates the HTML ``html`` element.
let e = callsite()
result = xmlCheckedTag(e, "html", "xmlns", "")
macro hr*(): expr {.immediate.} =
macro hr*(): expr {.immediate.} =
## generates the HTML ``hr`` element.
let e = callsite()
result = xmlCheckedTag(e, "hr", commonAttr, "", true)
macro i*(e: expr): expr {.immediate.} =
macro i*(e: expr): expr {.immediate.} =
## generates the HTML ``i`` element.
let e = callsite()
result = xmlCheckedTag(e, "i", commonAttr)
macro img*(e: expr): expr {.immediate.} =
macro img*(e: expr): expr {.immediate.} =
## generates the HTML ``img`` element.
let e = callsite()
result = xmlCheckedTag(e, "img", "longdesc height width", "src alt", true)
macro input*(e: expr): expr {.immediate.} =
macro input*(e: expr): expr {.immediate.} =
## generates the HTML ``input`` element.
let e = callsite()
result = xmlCheckedTag(e, "input", "name type value checked maxlength src" &
" alt accept disabled readonly accesskey tabindex" & commonAttr, "", true)
macro ins*(e: expr): expr {.immediate.} =
macro ins*(e: expr): expr {.immediate.} =
## generates the HTML ``ins`` element.
let e = callsite()
result = xmlCheckedTag(e, "ins", "cite datetime" & commonAttr)
macro kbd*(e: expr): expr {.immediate.} =
macro kbd*(e: expr): expr {.immediate.} =
## generates the HTML ``kbd`` element.
let e = callsite()
result = xmlCheckedTag(e, "kbd", commonAttr)
macro label*(e: expr): expr {.immediate.} =
macro label*(e: expr): expr {.immediate.} =
## generates the HTML ``label`` element.
let e = callsite()
result = xmlCheckedTag(e, "label", "for accesskey" & commonAttr)
macro legend*(e: expr): expr {.immediate.} =
macro legend*(e: expr): expr {.immediate.} =
## generates the HTML ``legend`` element.
let e = callsite()
result = xmlCheckedTag(e, "legend", "accesskey" & commonAttr)
macro li*(e: expr): expr {.immediate.} =
macro li*(e: expr): expr {.immediate.} =
## generates the HTML ``li`` element.
let e = callsite()
result = xmlCheckedTag(e, "li", commonAttr)
macro link*(e: expr): expr {.immediate.} =
macro link*(e: expr): expr {.immediate.} =
## generates the HTML ``link`` element.
let e = callsite()
result = xmlCheckedTag(e, "link", "href charset hreflang type rel rev media" &
result = xmlCheckedTag(e, "link", "href charset hreflang type rel rev media" &
commonAttr, "", true)
macro map*(e: expr): expr {.immediate.} =
macro map*(e: expr): expr {.immediate.} =
## generates the HTML ``map`` element.
let e = callsite()
result = xmlCheckedTag(e, "map", "class title" & eventAttr, "id", false)
macro meta*(e: expr): expr {.immediate.} =
macro meta*(e: expr): expr {.immediate.} =
## generates the HTML ``meta`` element.
let e = callsite()
result = xmlCheckedTag(e, "meta", "name http-equiv scheme", "content", true)
macro noscript*(e: expr): expr {.immediate.} =
macro noscript*(e: expr): expr {.immediate.} =
## generates the HTML ``noscript`` element.
let e = callsite()
result = xmlCheckedTag(e, "noscript", commonAttr)
macro `object`*(e: expr): expr {.immediate.} =
macro `object`*(e: expr): expr {.immediate.} =
## generates the HTML ``object`` element.
let e = callsite()
result = xmlCheckedTag(e, "object", "classid data codebase declare type " &
"codetype archive standby width height name tabindex" & commonAttr)
macro ol*(e: expr): expr {.immediate.} =
macro ol*(e: expr): expr {.immediate.} =
## generates the HTML ``ol`` element.
let e = callsite()
result = xmlCheckedTag(e, "ol", commonAttr)
macro optgroup*(e: expr): expr {.immediate.} =
macro optgroup*(e: expr): expr {.immediate.} =
## generates the HTML ``optgroup`` element.
let e = callsite()
result = xmlCheckedTag(e, "optgroup", "disabled" & commonAttr, "label", false)
macro option*(e: expr): expr {.immediate.} =
macro option*(e: expr): expr {.immediate.} =
## generates the HTML ``option`` element.
let e = callsite()
result = xmlCheckedTag(e, "option", "selected value" & commonAttr)
macro p*(e: expr): expr {.immediate.} =
macro p*(e: expr): expr {.immediate.} =
## generates the HTML ``p`` element.
let e = callsite()
result = xmlCheckedTag(e, "p", commonAttr)
macro param*(e: expr): expr {.immediate.} =
macro param*(e: expr): expr {.immediate.} =
## generates the HTML ``param`` element.
let e = callsite()
result = xmlCheckedTag(e, "param", "value id type valuetype", "name", true)
macro pre*(e: expr): expr {.immediate.} =
macro pre*(e: expr): expr {.immediate.} =
## generates the HTML ``pre`` element.
let e = callsite()
result = xmlCheckedTag(e, "pre", commonAttr)
macro q*(e: expr): expr {.immediate.} =
macro q*(e: expr): expr {.immediate.} =
## generates the HTML ``q`` element.
let e = callsite()
result = xmlCheckedTag(e, "q", "cite" & commonAttr)
macro samp*(e: expr): expr {.immediate.} =
macro samp*(e: expr): expr {.immediate.} =
## generates the HTML ``samp`` element.
let e = callsite()
result = xmlCheckedTag(e, "samp", commonAttr)
macro script*(e: expr): expr {.immediate.} =
macro script*(e: expr): expr {.immediate.} =
## generates the HTML ``script`` element.
let e = callsite()
result = xmlCheckedTag(e, "script", "src charset defer", "type", false)
macro select*(e: expr): expr {.immediate.} =
macro select*(e: expr): expr {.immediate.} =
## generates the HTML ``select`` element.
let e = callsite()
result = xmlCheckedTag(e, "select", "name size multiple disabled tabindex" &
result = xmlCheckedTag(e, "select", "name size multiple disabled tabindex" &
commonAttr)
macro small*(e: expr): expr {.immediate.} =
macro small*(e: expr): expr {.immediate.} =
## generates the HTML ``small`` element.
let e = callsite()
result = xmlCheckedTag(e, "small", commonAttr)
macro span*(e: expr): expr {.immediate.} =
macro span*(e: expr): expr {.immediate.} =
## generates the HTML ``span`` element.
let e = callsite()
result = xmlCheckedTag(e, "span", commonAttr)
macro strong*(e: expr): expr {.immediate.} =
macro strong*(e: expr): expr {.immediate.} =
## generates the HTML ``strong`` element.
let e = callsite()
result = xmlCheckedTag(e, "strong", commonAttr)
macro style*(e: expr): expr {.immediate.} =
macro style*(e: expr): expr {.immediate.} =
## generates the HTML ``style`` element.
let e = callsite()
result = xmlCheckedTag(e, "style", "media title", "type")
macro sub*(e: expr): expr {.immediate.} =
macro sub*(e: expr): expr {.immediate.} =
## generates the HTML ``sub`` element.
let e = callsite()
result = xmlCheckedTag(e, "sub", commonAttr)
macro sup*(e: expr): expr {.immediate.} =
macro sup*(e: expr): expr {.immediate.} =
## generates the HTML ``sup`` element.
let e = callsite()
result = xmlCheckedTag(e, "sup", commonAttr)
macro table*(e: expr): expr {.immediate.} =
macro table*(e: expr): expr {.immediate.} =
## generates the HTML ``table`` element.
let e = callsite()
result = xmlCheckedTag(e, "table", "summary border cellpadding cellspacing" &
" frame rules width" & commonAttr)
macro tbody*(e: expr): expr {.immediate.} =
macro tbody*(e: expr): expr {.immediate.} =
## generates the HTML ``tbody`` element.
let e = callsite()
result = xmlCheckedTag(e, "tbody", "align valign" & commonAttr)
macro td*(e: expr): expr {.immediate.} =
macro td*(e: expr): expr {.immediate.} =
## generates the HTML ``td`` element.
let e = callsite()
result = xmlCheckedTag(e, "td", "colspan rowspan abbr axis headers scope" &
" align valign" & commonAttr)
macro textarea*(e: expr): expr {.immediate.} =
macro textarea*(e: expr): expr {.immediate.} =
## generates the HTML ``textarea`` element.
let e = callsite()
result = xmlCheckedTag(e, "textarea", " name disabled readonly accesskey" &
" tabindex" & commonAttr, "rows cols", false)
macro tfoot*(e: expr): expr {.immediate.} =
macro tfoot*(e: expr): expr {.immediate.} =
## generates the HTML ``tfoot`` element.
let e = callsite()
result = xmlCheckedTag(e, "tfoot", "align valign" & commonAttr)
macro th*(e: expr): expr {.immediate.} =
macro th*(e: expr): expr {.immediate.} =
## generates the HTML ``th`` element.
let e = callsite()
result = xmlCheckedTag(e, "th", "colspan rowspan abbr axis headers scope" &
" align valign" & commonAttr)
macro thead*(e: expr): expr {.immediate.} =
macro thead*(e: expr): expr {.immediate.} =
## generates the HTML ``thead`` element.
let e = callsite()
result = xmlCheckedTag(e, "thead", "align valign" & commonAttr)
macro title*(e: expr): expr {.immediate.} =
macro title*(e: expr): expr {.immediate.} =
## generates the HTML ``title`` element.
let e = callsite()
result = xmlCheckedTag(e, "title")
macro tr*(e: expr): expr {.immediate.} =
macro tr*(e: expr): expr {.immediate.} =
## generates the HTML ``tr`` element.
let e = callsite()
result = xmlCheckedTag(e, "tr", "align valign" & commonAttr)
macro tt*(e: expr): expr {.immediate.} =
macro tt*(e: expr): expr {.immediate.} =
## generates the HTML ``tt`` element.
let e = callsite()
result = xmlCheckedTag(e, "tt", commonAttr)
macro ul*(e: expr): expr {.immediate.} =
macro ul*(e: expr): expr {.immediate.} =
## generates the HTML ``ul`` element.
let e = callsite()
result = xmlCheckedTag(e, "ul", commonAttr)
macro `var`*(e: expr): expr {.immediate.} =
macro `var`*(e: expr): expr {.immediate.} =
## generates the HTML ``var`` element.
let e = callsite()
result = xmlCheckedTag(e, "var", commonAttr)
when isMainModule:
var nim = "Nim"
echo h1(a(href="http://nim-lang.org", nim))
echo form(action="test", `accept-charset` = "Content-Type")
let nim = "Nim"
assert h1(a(href="http://nim-lang.org", nim)) ==
"""<h1><a href="http://nim-lang.org">Nim</a></h1>"""
assert form(action="test", `accept-charset` = "Content-Type") ==
"""<form action="test" accept-charset="Content-Type"></form>"""

View File

@@ -552,7 +552,7 @@ proc parse(x: var XmlParser, errors: var seq[string]): XmlNode =
proc parseHtml*(s: Stream, filename: string,
errors: var seq[string]): XmlNode =
## parses the XML from stream `s` and returns a ``PXmlNode``. Every
## occured parsing error is added to the `errors` sequence.
## occurred parsing error is added to the `errors` sequence.
var x: XmlParser
open(x, s, filename, {reportComments, reportWhitespace})
next(x)
@@ -581,7 +581,7 @@ proc parseHtml*(s: Stream): XmlNode =
proc loadHtml*(path: string, errors: var seq[string]): XmlNode =
## Loads and parses HTML from file specified by ``path``, and returns
## a ``PXmlNode``. Every occured parsing error is added to
## a ``PXmlNode``. Every occurred parsing error is added to
## the `errors` sequence.
var s = newFileStream(path, fmRead)
if s == nil: raise newException(IOError, "Unable to read file: " & path)
@@ -593,7 +593,7 @@ proc loadHtml*(path: string): XmlNode =
var errors: seq[string] = @[]
result = loadHtml(path, errors)
when isMainModule:
when not defined(testing) and isMainModule:
import os
var errors: seq[string] = @[]

View File

@@ -32,21 +32,12 @@
## the server.
##
## .. code-block:: Nim
## var headers: string = "Content-Type: multipart/form-data; boundary=xyz\c\L"
## var body: string = "--xyz\c\L"
## # soap 1.2 output
## body.add("Content-Disposition: form-data; name=\"output\"\c\L")
## body.add("\c\Lsoap12\c\L")
## var data = newMultipartData()
## data["output"] = "soap12"
## data["uploaded_file"] = ("test.html", "text/html",
## "<html><head></head><body><p>test</p></body></html>")
##
## # html
## body.add("--xyz\c\L")
## body.add("Content-Disposition: form-data; name=\"uploaded_file\";" &
## " filename=\"test.html\"\c\L")
## body.add("Content-Type: text/html\c\L")
## body.add("\c\L<html><head></head><body><p>test</p></body></html>\c\L")
## body.add("--xyz--")
##
## echo(postContent("http://validator.w3.org/check", headers, body))
## echo postContent("http://validator.w3.org/check", multipart=data)
##
## Asynchronous HTTP requests
## ==========================
@@ -88,7 +79,7 @@
## constructor should be used for this purpose. However,
## currently only basic authentication is supported.
import net, strutils, uri, parseutils, strtabs, base64, os
import net, strutils, uri, parseutils, strtabs, base64, os, mimetypes, math
import asyncnet, asyncdispatch
import rawsockets
@@ -103,6 +94,10 @@ type
url*: Uri
auth*: string
MultipartEntries* = openarray[tuple[name, content: string]]
MultipartData* = ref object
content: seq[string]
ProtocolError* = object of IOError ## exception that is raised when server
## does not conform to the implemented
## protocol
@@ -232,7 +227,7 @@ proc parseResponse(s: Socket, getBody: bool, timeout: int): Response =
inc(linei, le)
# Status code
linei.inc skipWhitespace(line, linei)
result.status = line[linei .. -1]
result.status = line[linei .. ^1]
parsedStatus = true
else:
# Parse headers
@@ -243,7 +238,7 @@ proc parseResponse(s: Socket, getBody: bool, timeout: int): Response =
if line[linei] != ':': httpError("invalid headers")
inc(linei) # Skip :
result.headers[name] = line[linei.. -1].strip()
result.headers[name] = line[linei.. ^1].strip()
if not fullyRead:
httpError("Connection was closed before full request has been made")
if getBody:
@@ -282,19 +277,126 @@ proc newProxy*(url: string, auth = ""): Proxy =
## Constructs a new ``TProxy`` object.
result = Proxy(url: parseUri(url), auth: auth)
proc request*(url: string, httpMethod = httpGET, extraHeaders = "",
body = "",
sslContext: SSLContext = defaultSSLContext,
timeout = -1, userAgent = defUserAgent,
proxy: Proxy = nil): Response =
## | Requests ``url`` with the specified ``httpMethod``.
## | Extra headers can be specified and must be seperated by ``\c\L``
proc newMultipartData*: MultipartData =
## Constructs a new ``MultipartData`` object.
MultipartData(content: @[])
proc add*(p: var MultipartData, name, content: string, filename: string = nil,
contentType: string = nil) =
## Add a value to the multipart data. Raises a `ValueError` exception if
## `name`, `filename` or `contentType` contain newline characters.
if {'\c','\L'} in name:
raise newException(ValueError, "name contains a newline character")
if filename != nil and {'\c','\L'} in filename:
raise newException(ValueError, "filename contains a newline character")
if contentType != nil and {'\c','\L'} in contentType:
raise newException(ValueError, "contentType contains a newline character")
var str = "Content-Disposition: form-data; name=\"" & name & "\""
if filename != nil:
str.add("; filename=\"" & filename & "\"")
str.add("\c\L")
if contentType != nil:
str.add("Content-Type: " & contentType & "\c\L")
str.add("\c\L" & content & "\c\L")
p.content.add(str)
proc add*(p: var MultipartData, xs: MultipartEntries): MultipartData
{.discardable.} =
## Add a list of multipart entries to the multipart data `p`. All values are
## added without a filename and without a content type.
##
## .. code-block:: Nim
## data.add({"action": "login", "format": "json"})
for name, content in xs.items:
p.add(name, content)
result = p
proc newMultipartData*(xs: MultipartEntries): MultipartData =
## Create a new multipart data object and fill it with the entries `xs`
## directly.
##
## .. code-block:: Nim
## var data = newMultipartData({"action": "login", "format": "json"})
result = MultipartData(content: @[])
result.add(xs)
proc addFiles*(p: var MultipartData, xs: openarray[tuple[name, file: string]]):
MultipartData {.discardable.} =
## Add files to a multipart data object. The file will be opened from your
## disk, read and sent with the automatically determined MIME type. Raises an
## `IOError` if the file cannot be opened or reading fails. To manually
## specify file content, filename and MIME type, use `[]=` instead.
##
## .. code-block:: Nim
## data.addFiles({"uploaded_file": "public/test.html"})
var m = newMimetypes()
for name, file in xs.items:
var contentType: string
let (dir, fName, ext) = splitFile(file)
if ext.len > 0:
contentType = m.getMimetype(ext[1..ext.high], nil)
p.add(name, readFile(file), fName & ext, contentType)
result = p
proc `[]=`*(p: var MultipartData, name, content: string) =
## Add a multipart entry to the multipart data `p`. The value is added
## without a filename and without a content type.
##
## .. code-block:: Nim
## data["username"] = "NimUser"
p.add(name, content)
proc `[]=`*(p: var MultipartData, name: string,
file: tuple[name, contentType, content: string]) =
## Add a file to the multipart data `p`, specifying filename, contentType and
## content manually.
##
## .. code-block:: Nim
## data["uploaded_file"] = ("test.html", "text/html",
## "<html><head></head><body><p>test</p></body></html>")
p.add(name, file.content, file.name, file.contentType)
proc format(p: MultipartData): tuple[header, body: string] =
if p == nil or p.content == nil or p.content.len == 0:
return ("", "")
# Create boundary that is not in the data to be formatted
var bound: string
while true:
bound = $random(int.high)
var found = false
for s in p.content:
if bound in s:
found = true
if not found:
break
result.header = "Content-Type: multipart/form-data; boundary=" & bound & "\c\L"
result.body = ""
for s in p.content:
result.body.add("--" & bound & "\c\L" & s)
result.body.add("--" & bound & "--\c\L")
proc request*(url: string, httpMethod: string, extraHeaders = "",
body = "", sslContext = defaultSSLContext, timeout = -1,
userAgent = defUserAgent, proxy: Proxy = nil): Response =
## | Requests ``url`` with the custom method string specified by the
## | ``httpMethod`` parameter.
## | Extra headers can be specified and must be separated by ``\c\L``
## | An optional timeout can be specified in miliseconds, if reading from the
## server takes longer than specified an ETimeout exception will be raised.
var r = if proxy == nil: parseUri(url) else: proxy.url
var headers = substr($httpMethod, len("http"))
var headers = substr(httpMethod, len("http"))
# TODO: Use generateHeaders further down once it supports proxies.
if proxy == nil:
headers.add(" /" & r.path & r.query)
headers.add ' '
if r.path[0] != '/': headers.add '/'
headers.add(r.path)
if r.query.len > 0:
headers.add("?" & r.query)
else:
headers.add(" " & url)
@@ -330,9 +432,19 @@ proc request*(url: string, httpMethod = httpGET, extraHeaders = "",
if body != "":
s.send(body)
result = parseResponse(s, httpMethod != httpHEAD, timeout)
result = parseResponse(s, httpMethod != "httpHEAD", timeout)
s.close()
proc request*(url: string, httpMethod = httpGET, extraHeaders = "",
body = "", sslContext = defaultSSLContext, timeout = -1,
userAgent = defUserAgent, proxy: Proxy = nil): Response =
## | Requests ``url`` with the specified ``httpMethod``.
## | Extra headers can be specified and must be separated by ``\c\L``
## | An optional timeout can be specified in miliseconds, if reading from the
## server takes longer than specified an ETimeout exception will be raised.
result = request(url, $httpMethod, extraHeaders, body, sslContext, timeout,
userAgent, proxy)
proc redirection(status: string): bool =
const redirectionNRs = ["301", "302", "303", "307"]
for i in items(redirectionNRs):
@@ -387,22 +499,37 @@ proc post*(url: string, extraHeaders = "", body = "",
maxRedirects = 5,
sslContext: SSLContext = defaultSSLContext,
timeout = -1, userAgent = defUserAgent,
proxy: Proxy = nil): Response =
proxy: Proxy = nil,
multipart: MultipartData = nil): Response =
## | POSTs ``body`` to the ``url`` and returns a ``Response`` object.
## | This proc adds the necessary Content-Length header.
## | This proc also handles redirection.
## | Extra headers can be specified and must be separated by ``\c\L``.
## | An optional timeout can be specified in miliseconds, if reading from the
## server takes longer than specified an ETimeout exception will be raised.
var xh = extraHeaders & "Content-Length: " & $len(body) & "\c\L"
result = request(url, httpPOST, xh, body, sslContext, timeout, userAgent,
## | The optional ``multipart`` parameter can be used to create
## ``multipart/form-data`` POSTs comfortably.
let (mpHeaders, mpBody) = format(multipart)
template withNewLine(x): expr =
if x.len > 0 and not x.endsWith("\c\L"):
x & "\c\L"
else:
x
var xb = mpBody.withNewLine() & body
var xh = extraHeaders.withNewLine() & mpHeaders.withNewLine() &
withNewLine("Content-Length: " & $len(xb))
result = request(url, httpPOST, xh, xb, sslContext, timeout, userAgent,
proxy)
var lastUrl = ""
for i in 1..maxRedirects:
if result.status.redirection():
let redirectTo = getNewLocation(lastURL, result.headers)
var meth = if result.status != "307": httpGet else: httpPost
result = request(redirectTo, meth, xh, body, sslContext, timeout,
result = request(redirectTo, meth, xh, xb, sslContext, timeout,
userAgent, proxy)
lastUrl = redirectTo
@@ -410,14 +537,17 @@ proc postContent*(url: string, extraHeaders = "", body = "",
maxRedirects = 5,
sslContext: SSLContext = defaultSSLContext,
timeout = -1, userAgent = defUserAgent,
proxy: Proxy = nil): string =
proxy: Proxy = nil,
multipart: MultipartData = nil): string =
## | POSTs ``body`` to ``url`` and returns the response's body as a string
## | Raises exceptions for the status codes ``4xx`` and ``5xx``
## | Extra headers can be specified and must be separated by ``\c\L``.
## | An optional timeout can be specified in miliseconds, if reading from the
## server takes longer than specified an ETimeout exception will be raised.
## | The optional ``multipart`` parameter can be used to create
## ``multipart/form-data`` POSTs comfortably.
var r = post(url, extraHeaders, body, maxRedirects, sslContext, timeout,
userAgent, proxy)
userAgent, proxy, multipart)
if r.status[0] in {'4','5'}:
raise newException(HttpRequestError, r.status)
else:
@@ -438,11 +568,16 @@ proc downloadFile*(url: string, outputFilename: string,
else:
fileError("Unable to open file")
proc generateHeaders(r: Uri, httpMethod: HttpMethod,
proc generateHeaders(r: Uri, httpMethod: string,
headers: StringTableRef): string =
result = substr($httpMethod, len("http"))
# TODO: Use this in the blocking HttpClient once it supports proxies.
result = substr(httpMethod, len("http"))
# TODO: Proxies
result.add(" /" & r.path & r.query)
result.add ' '
if r.path[0] != '/': result.add '/'
result.add(r.path)
if r.query.len > 0:
result.add("?" & r.query)
result.add(" HTTP/1.1\c\L")
add(result, "Host: " & r.hostname & "\c\L")
@@ -590,7 +725,7 @@ proc parseResponse(client: AsyncHttpClient,
inc(linei, le)
# Status code
linei.inc skipWhitespace(line, linei)
result.status = line[linei .. -1]
result.status = line[linei .. ^1]
parsedStatus = true
else:
# Parse headers
@@ -601,7 +736,7 @@ proc parseResponse(client: AsyncHttpClient,
if line[linei] != ':': httpError("invalid headers")
inc(linei) # Skip :
result.headers[name] = line[linei.. -1].strip()
result.headers[name] = line[linei.. ^1].strip()
if not fullyRead:
httpError("Connection was closed before full request has been made")
if getBody:
@@ -635,10 +770,10 @@ proc newConnection(client: AsyncHttpClient, url: Uri) {.async.} =
client.currentURL = url
client.connected = true
proc request*(client: AsyncHttpClient, url: string, httpMethod = httpGET,
proc request*(client: AsyncHttpClient, url: string, httpMethod: string,
body = ""): Future[Response] {.async.} =
## Connects to the hostname specified by the URL and performs a request
## using the method specified.
## using the custom method string specified by ``httpMethod``.
##
## Connection will kept alive. Further requests on the same ``client`` to
## the same hostname will not require a new connection to be made. The
@@ -651,13 +786,25 @@ proc request*(client: AsyncHttpClient, url: string, httpMethod = httpGET,
if not client.headers.hasKey("user-agent") and client.userAgent != "":
client.headers["User-Agent"] = client.userAgent
var headers = generateHeaders(r, httpMethod, client.headers)
var headers = generateHeaders(r, $httpMethod, client.headers)
await client.socket.send(headers)
if body != "":
await client.socket.send(body)
result = await parseResponse(client, httpMethod != httpHEAD)
result = await parseResponse(client, httpMethod != "httpHEAD")
proc request*(client: AsyncHttpClient, url: string, httpMethod = httpGET,
body = ""): Future[Response] =
## Connects to the hostname specified by the URL and performs a request
## using the method specified.
##
## Connection will kept alive. Further requests on the same ``client`` to
## the same hostname will not require a new connection to be made. The
## connection can be closed by using the ``close`` procedure.
##
## The returned future will complete once the request is completed.
result = request(client, url, $httpMethod, body)
proc get*(client: AsyncHttpClient, url: string): Future[Response] {.async.} =
## Connects to the hostname specified by the URL and performs a GET request.
@@ -672,7 +819,7 @@ proc get*(client: AsyncHttpClient, url: string): Future[Response] {.async.} =
result = await client.request(redirectTo, httpGET)
lastUrl = redirectTo
when isMainModule:
when not defined(testing) and isMainModule:
when true:
# Async
proc main() {.async.} =
@@ -706,18 +853,9 @@ when isMainModule:
#var r = get("http://validator.w3.org/check?uri=http%3A%2F%2Fgoogle.com&
# charset=%28detect+automatically%29&doctype=Inline&group=0")
var headers: string = "Content-Type: multipart/form-data; boundary=xyz\c\L"
var body: string = "--xyz\c\L"
# soap 1.2 output
body.add("Content-Disposition: form-data; name=\"output\"\c\L")
body.add("\c\Lsoap12\c\L")
var data = newMultipartData()
data["output"] = "soap12"
data["uploaded_file"] = ("test.html", "text/html",
"<html><head></head><body><p>test</p></body></html>")
# html
body.add("--xyz\c\L")
body.add("Content-Disposition: form-data; name=\"uploaded_file\";" &
" filename=\"test.html\"\c\L")
body.add("Content-Type: text/html\c\L")
body.add("\c\L<html><head></head><body><p>test</p></body></html>\c\L")
body.add("--xyz--")
echo(postContent("http://validator.w3.org/check", headers, body))
echo postContent("http://validator.w3.org/check", multipart=data)

View File

@@ -363,7 +363,7 @@ proc run*(handleRequest: proc (client: Socket,
port = Port(80)) =
## encapsulates the server object and main loop
var s: TServer
open(s, port)
open(s, port, reuseAddr = true)
#echo("httpserver running on port ", s.port)
while true:
next(s)
@@ -514,7 +514,7 @@ proc close*(h: PAsyncHTTPServer) =
## Closes the ``PAsyncHTTPServer``.
h.asyncSocket.close()
when isMainModule:
when not defined(testing) and isMainModule:
var counter = 0
var s: TServer

View File

@@ -1,15 +1,15 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2014 Andreas Rumpf, Dominik Picheta
# (c) Copyright 2015 Andreas Rumpf, Dominik Picheta
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module implements a simple high performance `JSON`:idx:
## parser. JSON (JavaScript Object Notation) is a lightweight
## data-interchange format that is easy for humans to read and write
## parser. JSON (JavaScript Object Notation) is a lightweight
## data-interchange format that is easy for humans to read and write
## (unlike XML). It is easy for machines to parse and generate.
## JSON is based on a subset of the JavaScript Programming Language,
## Standard ECMA-262 3rd Edition - December 1999.
@@ -30,13 +30,32 @@
##
## 1.3000000000000000e+00
## true
##
## This module can also be used to comfortably create JSON using the `%*`
## operator:
##
## .. code-block:: nim
##
## var hisName = "John"
## let herAge = 31
## var j = %*
## [
## {
## "name": hisName,
## "age": 30
## },
## {
## "name": "Susan",
## "age": herAge
## }
## ]
import
hashes, strutils, lexbase, streams, unicode
import
hashes, strutils, lexbase, streams, unicode, macros
type
type
JsonEventKind* = enum ## enumeration of all events that may occur when parsing
jsonError, ## an error ocurred during parsing
jsonError, ## an error occurred during parsing
jsonEof, ## end of file reached
jsonString, ## a string literal
jsonInt, ## an integer literal
@@ -48,7 +67,7 @@ type
jsonObjectEnd, ## end of an object: the ``}`` token
jsonArrayStart, ## start of an array: the ``[`` token
jsonArrayEnd ## start of an array: the ``]`` token
TTokKind = enum # must be synchronized with TJsonEventKind!
tkError,
tkEof,
@@ -64,7 +83,7 @@ type
tkBracketRi,
tkColon,
tkComma
JsonError* = enum ## enumeration that lists all errors that can occur
errNone, ## no error
errInvalidToken, ## invalid token
@@ -77,8 +96,8 @@ type
errEOC_Expected, ## ``*/`` expected
errEofExpected, ## EOF expected
errExprExpected ## expr expected
ParserState = enum
ParserState = enum
stateEof, stateStart, stateObject, stateArray, stateExpectArrayComma,
stateExpectObjectComma, stateExpectColon, stateExpectValue
@@ -92,7 +111,7 @@ type
{.deprecated: [TJsonEventKind: JsonEventKind, TJsonError: JsonError,
TJsonParser: JsonParser].}
const
errorMessages: array [JsonError, string] = [
"no error",
@@ -127,56 +146,56 @@ proc open*(my: var JsonParser, input: Stream, filename: string) =
my.state = @[stateStart]
my.kind = jsonError
my.a = ""
proc close*(my: var JsonParser) {.inline.} =
proc close*(my: var JsonParser) {.inline.} =
## closes the parser `my` and its associated input stream.
lexbase.close(my)
proc str*(my: JsonParser): string {.inline.} =
## returns the character data for the events: ``jsonInt``, ``jsonFloat``,
proc str*(my: JsonParser): string {.inline.} =
## returns the character data for the events: ``jsonInt``, ``jsonFloat``,
## ``jsonString``
assert(my.kind in {jsonInt, jsonFloat, jsonString})
return my.a
proc getInt*(my: JsonParser): BiggestInt {.inline.} =
proc getInt*(my: JsonParser): BiggestInt {.inline.} =
## returns the number for the event: ``jsonInt``
assert(my.kind == jsonInt)
return parseBiggestInt(my.a)
proc getFloat*(my: JsonParser): float {.inline.} =
proc getFloat*(my: JsonParser): float {.inline.} =
## returns the number for the event: ``jsonFloat``
assert(my.kind == jsonFloat)
return parseFloat(my.a)
proc kind*(my: JsonParser): JsonEventKind {.inline.} =
proc kind*(my: JsonParser): JsonEventKind {.inline.} =
## returns the current event type for the JSON parser
return my.kind
proc getColumn*(my: JsonParser): int {.inline.} =
proc getColumn*(my: JsonParser): int {.inline.} =
## get the current column the parser has arrived at.
result = getColNumber(my, my.bufpos)
proc getLine*(my: JsonParser): int {.inline.} =
proc getLine*(my: JsonParser): int {.inline.} =
## get the current line the parser has arrived at.
result = my.lineNumber
proc getFilename*(my: JsonParser): string {.inline.} =
proc getFilename*(my: JsonParser): string {.inline.} =
## get the filename of the file that the parser processes.
result = my.filename
proc errorMsg*(my: JsonParser): string =
proc errorMsg*(my: JsonParser): string =
## returns a helpful error message for the event ``jsonError``
assert(my.kind == jsonError)
result = "$1($2, $3) Error: $4" % [
my.filename, $getLine(my), $getColumn(my), errorMessages[my.err]]
proc errorMsgExpected*(my: JsonParser, e: string): string =
proc errorMsgExpected*(my: JsonParser, e: string): string =
## returns an error message "`e` expected" in the same format as the
## other error messages
## other error messages
result = "$1($2, $3) Error: $4" % [
my.filename, $getLine(my), $getColumn(my), e & " expected"]
proc handleHexChar(c: char, x: var int): bool =
proc handleHexChar(c: char, x: var int): bool =
result = true # Success
case c
of '0'..'9': x = (x shl 4) or (ord(c) - ord('0'))
@@ -189,8 +208,8 @@ proc parseString(my: var JsonParser): TTokKind =
var pos = my.bufpos + 1
var buf = my.buf
while true:
case buf[pos]
of '\0':
case buf[pos]
of '\0':
my.err = errQuoteExpected
result = tkError
break
@@ -199,21 +218,21 @@ proc parseString(my: var JsonParser): TTokKind =
break
of '\\':
case buf[pos+1]
of '\\', '"', '\'', '/':
of '\\', '"', '\'', '/':
add(my.a, buf[pos+1])
inc(pos, 2)
of 'b':
add(my.a, '\b')
inc(pos, 2)
inc(pos, 2)
of 'f':
add(my.a, '\f')
inc(pos, 2)
inc(pos, 2)
of 'n':
add(my.a, '\L')
inc(pos, 2)
inc(pos, 2)
of 'r':
add(my.a, '\C')
inc(pos, 2)
inc(pos, 2)
of 't':
add(my.a, '\t')
inc(pos, 2)
@@ -225,15 +244,15 @@ proc parseString(my: var JsonParser): TTokKind =
if handleHexChar(buf[pos], r): inc(pos)
if handleHexChar(buf[pos], r): inc(pos)
add(my.a, toUTF8(Rune(r)))
else:
else:
# don't bother with the error
add(my.a, buf[pos])
inc(pos)
of '\c':
of '\c':
pos = lexbase.handleCR(my, pos)
buf = my.buf
add(my.a, '\c')
of '\L':
of '\L':
pos = lexbase.handleLF(my, pos)
buf = my.buf
add(my.a, '\L')
@@ -241,25 +260,25 @@ proc parseString(my: var JsonParser): TTokKind =
add(my.a, buf[pos])
inc(pos)
my.bufpos = pos # store back
proc skip(my: var JsonParser) =
proc skip(my: var JsonParser) =
var pos = my.bufpos
var buf = my.buf
while true:
while true:
case buf[pos]
of '/':
if buf[pos+1] == '/':
of '/':
if buf[pos+1] == '/':
# skip line comment:
inc(pos, 2)
while true:
case buf[pos]
of '\0':
case buf[pos]
of '\0':
break
of '\c':
of '\c':
pos = lexbase.handleCR(my, pos)
buf = my.buf
break
of '\L':
of '\L':
pos = lexbase.handleLF(my, pos)
buf = my.buf
break
@@ -269,44 +288,44 @@ proc skip(my: var JsonParser) =
# skip long comment:
inc(pos, 2)
while true:
case buf[pos]
of '\0':
case buf[pos]
of '\0':
my.err = errEOC_Expected
break
of '\c':
of '\c':
pos = lexbase.handleCR(my, pos)
buf = my.buf
of '\L':
of '\L':
pos = lexbase.handleLF(my, pos)
buf = my.buf
of '*':
inc(pos)
if buf[pos] == '/':
if buf[pos] == '/':
inc(pos)
break
else:
inc(pos)
else:
else:
break
of ' ', '\t':
of ' ', '\t':
inc(pos)
of '\c':
of '\c':
pos = lexbase.handleCR(my, pos)
buf = my.buf
of '\L':
of '\L':
pos = lexbase.handleLF(my, pos)
buf = my.buf
else:
break
my.bufpos = pos
proc parseNumber(my: var JsonParser) =
proc parseNumber(my: var JsonParser) =
var pos = my.bufpos
var buf = my.buf
if buf[pos] == '-':
if buf[pos] == '-':
add(my.a, '-')
inc(pos)
if buf[pos] == '.':
if buf[pos] == '.':
add(my.a, "0.")
inc(pos)
else:
@@ -331,7 +350,7 @@ proc parseNumber(my: var JsonParser) =
inc(pos)
my.bufpos = pos
proc parseName(my: var JsonParser) =
proc parseName(my: var JsonParser) =
var pos = my.bufpos
var buf = my.buf
if buf[pos] in IdentStartChars:
@@ -340,11 +359,11 @@ proc parseName(my: var JsonParser) =
inc(pos)
my.bufpos = pos
proc getTok(my: var JsonParser): TTokKind =
proc getTok(my: var JsonParser): TTokKind =
setLen(my.a, 0)
skip(my) # skip whitespace, comments
case my.buf[my.bufpos]
of '-', '.', '0'..'9':
of '-', '.', '0'..'9':
parseNumber(my)
if {'.', 'e', 'E'} in my.a:
result = tkFloat
@@ -374,17 +393,17 @@ proc getTok(my: var JsonParser): TTokKind =
result = tkEof
of 'a'..'z', 'A'..'Z', '_':
parseName(my)
case my.a
case my.a
of "null": result = tkNull
of "true": result = tkTrue
of "false": result = tkFalse
else: result = tkError
else:
else:
inc(my.bufpos)
result = tkError
my.tok = result
proc next*(my: var JsonParser) =
proc next*(my: var JsonParser) =
## retrieves the first/next event. This controls the parser.
var tk = getTok(my)
var i = my.state.len-1
@@ -397,13 +416,13 @@ proc next*(my: var JsonParser) =
else:
my.kind = jsonError
my.err = errEofExpected
of stateStart:
# tokens allowed?
of stateStart:
# tokens allowed?
case tk
of tkString, tkInt, tkFloat, tkTrue, tkFalse, tkNull:
my.state[i] = stateEof # expect EOF next!
my.kind = JsonEventKind(ord(tk))
of tkBracketLe:
of tkBracketLe:
my.state.add(stateArray) # we expect any
my.kind = jsonArrayStart
of tkCurlyLe:
@@ -414,12 +433,12 @@ proc next*(my: var JsonParser) =
else:
my.kind = jsonError
my.err = errEofExpected
of stateObject:
of stateObject:
case tk
of tkString, tkInt, tkFloat, tkTrue, tkFalse, tkNull:
my.state.add(stateExpectColon)
my.kind = JsonEventKind(ord(tk))
of tkBracketLe:
of tkBracketLe:
my.state.add(stateExpectColon)
my.state.add(stateArray)
my.kind = jsonArrayStart
@@ -438,7 +457,7 @@ proc next*(my: var JsonParser) =
of tkString, tkInt, tkFloat, tkTrue, tkFalse, tkNull:
my.state.add(stateExpectArrayComma) # expect value next!
my.kind = JsonEventKind(ord(tk))
of tkBracketLe:
of tkBracketLe:
my.state.add(stateExpectArrayComma)
my.state.add(stateArray)
my.kind = jsonArrayStart
@@ -453,8 +472,8 @@ proc next*(my: var JsonParser) =
my.kind = jsonError
my.err = errBracketRiExpected
of stateExpectArrayComma:
case tk
of tkComma:
case tk
of tkComma:
discard my.state.pop()
next(my)
of tkBracketRi:
@@ -465,8 +484,8 @@ proc next*(my: var JsonParser) =
my.kind = jsonError
my.err = errBracketRiExpected
of stateExpectObjectComma:
case tk
of tkComma:
case tk
of tkComma:
discard my.state.pop()
next(my)
of tkCurlyRi:
@@ -476,9 +495,9 @@ proc next*(my: var JsonParser) =
else:
my.kind = jsonError
my.err = errCurlyRiExpected
of stateExpectColon:
case tk
of tkColon:
of stateExpectColon:
case tk
of tkColon:
my.state[i] = stateExpectValue
next(my)
else:
@@ -489,7 +508,7 @@ proc next*(my: var JsonParser) =
of tkString, tkInt, tkFloat, tkTrue, tkFalse, tkNull:
my.state[i] = stateExpectObjectComma
my.kind = JsonEventKind(ord(tk))
of tkBracketLe:
of tkBracketLe:
my.state[i] = stateExpectObjectComma
my.state.add(stateArray)
my.kind = jsonArrayStart
@@ -513,8 +532,8 @@ type
JString,
JObject,
JArray
JsonNode* = ref JsonNodeObj ## JSON node
JsonNode* = ref JsonNodeObj ## JSON node
JsonNodeObj* {.acyclic.} = object
case kind*: JsonNodeKind
of JString:
@@ -586,6 +605,49 @@ proc newJArray*(): JsonNode =
result.kind = JArray
result.elems = @[]
proc getStr*(n: JsonNode, default: string = ""): string =
## Retrieves the string value of a `JString JsonNode`.
##
## Returns ``default`` if ``n`` is not a ``JString``.
if n.kind != JString: return default
else: return n.str
proc getNum*(n: JsonNode, default: BiggestInt = 0): BiggestInt =
## Retrieves the int value of a `JInt JsonNode`.
##
## Returns ``default`` if ``n`` is not a ``JInt``.
if n.kind != JInt: return default
else: return n.num
proc getFNum*(n: JsonNode, default: float = 0.0): float =
## Retrieves the float value of a `JFloat JsonNode`.
##
## Returns ``default`` if ``n`` is not a ``JFloat``.
if n.kind != JFloat: return default
else: return n.fnum
proc getBVal*(n: JsonNode, default: bool = false): bool =
## Retrieves the bool value of a `JBool JsonNode`.
##
## Returns ``default`` if ``n`` is not a ``JBool``.
if n.kind != JBool: return default
else: return n.bval
proc getFields*(n: JsonNode,
default: seq[tuple[key: string, val: JsonNode]] = @[]):
seq[tuple[key: string, val: JsonNode]] =
## Retrieves the key, value pairs of a `JObject JsonNode`.
##
## Returns ``default`` if ``n`` is not a ``JObject``.
if n.kind != JObject: return default
else: return n.fields
proc getElems*(n: JsonNode, default: seq[JsonNode] = @[]): seq[JsonNode] =
## Retrieves the int value of a `JArray JsonNode`.
##
## Returns ``default`` if ``n`` is not a ``JArray``.
if n.kind != JArray: return default
else: return n.elems
proc `%`*(s: string): JsonNode =
## Generic constructor for JSON data. Creates a new `JString JsonNode`.
@@ -625,12 +687,35 @@ proc `%`*(elements: openArray[JsonNode]): JsonNode =
newSeq(result.elems, elements.len)
for i, p in pairs(elements): result.elems[i] = p
proc toJson(x: NimNode): NimNode {.compiletime.} =
case x.kind
of nnkBracket:
result = newNimNode(nnkBracket)
for i in 0 .. <x.len:
result.add(toJson(x[i]))
of nnkTableConstr:
result = newNimNode(nnkTableConstr)
for i in 0 .. <x.len:
assert x[i].kind == nnkExprColonExpr
result.add(newNimNode(nnkExprColonExpr).add(x[i][0]).add(toJson(x[i][1])))
else:
result = x
result = prefix(result, "%")
macro `%*`*(x: expr): expr =
## Convert an expression to a JsonNode directly, without having to specify
## `%` for every element.
result = toJson(x)
proc `==`* (a,b: JsonNode): bool =
## Check two nodes for equality
if a.isNil:
if b.isNil: return true
return false
elif b.isNil or a.kind != b.kind:
elif b.isNil or a.kind != b.kind:
return false
else:
return case a.kind
@@ -667,7 +752,7 @@ proc hash* (n:JsonNode): THash =
of JNull:
result = hash(0)
proc len*(n: JsonNode): int =
proc len*(n: JsonNode): int =
## If `n` is a `JArray`, it returns the number of elements.
## If `n` is a `JObject`, it returns the number of pairs.
## Else it returns 0.
@@ -685,7 +770,7 @@ proc `[]`*(node: JsonNode, name: string): JsonNode =
if key == name:
return item
return nil
proc `[]`*(node: JsonNode, index: int): JsonNode =
## Gets the node at `index` in an Array. Result is undefined if `index`
## is out of bounds
@@ -702,12 +787,12 @@ proc hasKey*(node: JsonNode, key: string): bool =
proc existsKey*(node: JsonNode, key: string): bool {.deprecated.} = node.hasKey(key)
## Deprecated for `hasKey`
proc add*(father, child: JsonNode) =
## Adds `child` to a JArray node `father`.
proc add*(father, child: JsonNode) =
## Adds `child` to a JArray node `father`.
assert father.kind == JArray
father.elems.add(child)
proc add*(obj: JsonNode, key: string, val: JsonNode) =
proc add*(obj: JsonNode, key: string, val: JsonNode) =
## Adds ``(key, val)`` pair to the JObject node `obj`. For speed
## reasons no check for duplicate keys is performed!
## But ``[]=`` performs the check.
@@ -718,27 +803,30 @@ proc `[]=`*(obj: JsonNode, key: string, val: JsonNode) =
## Sets a field from a `JObject`. Performs a check for duplicate keys.
assert(obj.kind == JObject)
for i in 0..obj.fields.len-1:
if obj.fields[i].key == key:
if obj.fields[i].key == key:
obj.fields[i].val = val
return
obj.fields.add((key, val))
proc `{}`*(node: JsonNode, key: string): JsonNode =
## Transverses the node and gets the given value. If any of the
## names does not exist, returns nil
proc `{}`*(node: JsonNode, keys: varargs[string]): JsonNode =
## Traverses the node and gets the given value. If any of the
## keys do not exist, returns nil. Also returns nil if one of the
## intermediate data structures is not an object
result = node
if isNil(node): return nil
result = result[key]
for key in keys:
if isNil(result) or result.kind!=JObject:
return nil
result=result[key]
proc `{}=`*(node: JsonNode, names: varargs[string], value: JsonNode) =
## Transverses the node and tries to set the value at the given location
## to `value` If any of the names are missing, they are added
proc `{}=`*(node: JsonNode, keys: varargs[string], value: JsonNode) =
## Traverses the node and tries to set the value at the given location
## to `value` If any of the keys are missing, they are added
var node = node
for i in 0..(names.len-2):
if isNil(node[names[i]]):
node[names[i]] = newJObject()
node = node[names[i]]
node[names[names.len-1]] = value
for i in 0..(keys.len-2):
if isNil(node[keys[i]]):
node[keys[i]] = newJObject()
node = node[keys[i]]
node[keys[keys.len-1]] = value
proc delete*(obj: JsonNode, key: string) =
## Deletes ``obj[key]`` preserving the order of the other (key, value)-pairs.
@@ -773,17 +861,17 @@ proc copy*(p: JsonNode): JsonNode =
# ------------- pretty printing ----------------------------------------------
proc indent(s: var string, i: int) =
s.add(repeatChar(i))
proc indent(s: var string, i: int) =
s.add(spaces(i))
proc newIndent(curr, indent: int, ml: bool): int =
if ml: return curr + indent
else: return indent
proc nl(s: var string, ml: bool) =
proc nl(s: var string, ml: bool) =
if ml: s.add("\n")
proc escapeJson*(s: string): string =
proc escapeJson*(s: string): string =
## Converts a string `s` to its JSON representation.
result = newStringOfCap(s.len + s.len shr 3)
result.add("\"")
@@ -800,13 +888,13 @@ proc escapeJson*(s: string): string =
result.add(toHex(r, 4))
result.add("\"")
proc toPretty(result: var string, node: JsonNode, indent = 2, ml = true,
proc toPretty(result: var string, node: JsonNode, indent = 2, ml = true,
lstArr = false, currIndent = 0) =
case node.kind
of JObject:
if currIndent != 0 and not lstArr: result.nl(ml)
result.indent(currIndent) # Indentation
if node.fields.len > 0:
if node.fields.len > 0:
result.add("{")
result.nl(ml) # New line
for i in 0..len(node.fields)-1:
@@ -814,17 +902,17 @@ proc toPretty(result: var string, node: JsonNode, indent = 2, ml = true,
result.add(", ")
result.nl(ml) # New Line
# Need to indent more than {
result.indent(newIndent(currIndent, indent, ml))
result.indent(newIndent(currIndent, indent, ml))
result.add(escapeJson(node.fields[i].key))
result.add(": ")
toPretty(result, node.fields[i].val, indent, ml, false,
toPretty(result, node.fields[i].val, indent, ml, false,
newIndent(currIndent, indent, ml))
result.nl(ml)
result.indent(currIndent) # indent the same as {
result.add("}")
else:
result.add("{}")
of JString:
of JString:
if lstArr: result.indent(currIndent)
result.add(escapeJson(node.str))
of JInt:
@@ -864,7 +952,7 @@ proc pretty*(node: JsonNode, indent = 2): string =
proc `$`*(node: JsonNode): string =
## Converts `node` to its JSON Representation on one line.
result = ""
toPretty(result, node, 1, false)
toPretty(result, node, 0, false)
iterator items*(node: JsonNode): JsonNode =
## Iterator for the items of `node`. `node` has to be a JArray.
@@ -872,17 +960,31 @@ iterator items*(node: JsonNode): JsonNode =
for i in items(node.elems):
yield i
iterator mitems*(node: var JsonNode): var JsonNode =
## Iterator for the items of `node`. `node` has to be a JArray. Items can be
## modified.
assert node.kind == JArray
for i in mitems(node.elems):
yield i
iterator pairs*(node: JsonNode): tuple[key: string, val: JsonNode] =
## Iterator for the child elements of `node`. `node` has to be a JObject.
assert node.kind == JObject
for key, val in items(node.fields):
yield (key, val)
proc eat(p: var JsonParser, tok: TTokKind) =
iterator mpairs*(node: var JsonNode): var tuple[key: string, val: JsonNode] =
## Iterator for the child elements of `node`. `node` has to be a JObject.
## Items can be modified
assert node.kind == JObject
for keyVal in mitems(node.fields):
yield keyVal
proc eat(p: var JsonParser, tok: TTokKind) =
if p.tok == tok: discard getTok(p)
else: raiseParseErr(p, tokToStr[tok])
proc parseJson(p: var JsonParser): JsonNode =
proc parseJson(p: var JsonParser): JsonNode =
## Parses JSON from a JSON Parser `p`.
case p.tok
of tkString:
@@ -899,17 +1001,17 @@ proc parseJson(p: var JsonParser): JsonNode =
of tkTrue:
result = newJBool(true)
discard getTok(p)
of tkFalse:
of tkFalse:
result = newJBool(false)
discard getTok(p)
of tkNull:
of tkNull:
result = newJNull()
discard getTok(p)
of tkCurlyLe:
of tkCurlyLe:
result = newJObject()
discard getTok(p)
while p.tok != tkCurlyRi:
if p.tok != tkString:
while p.tok != tkCurlyRi:
if p.tok != tkString:
raiseParseErr(p, "string literal as key expected")
var key = p.a
discard getTok(p)
@@ -922,7 +1024,7 @@ proc parseJson(p: var JsonParser): JsonNode =
of tkBracketLe:
result = newJArray()
discard getTok(p)
while p.tok != tkBracketRi:
while p.tok != tkBracketRi:
result.add(parseJson(p))
if p.tok != tkComma: break
discard getTok(p)
@@ -1042,28 +1144,31 @@ when false:
of jsonObjectEnd: echo("}")
of jsonArrayStart: echo("[")
of jsonArrayEnd: echo("]")
close(x)
# { "json": 5 }
# { "json": 5 }
# To get that we shall use, obj["json"]
when isMainModule:
#var node = parse("{ \"test\": null }")
#echo(node.existsKey("test56"))
var parsed = parseFile("tests/testdata/jsontest.json")
var parsed2 = parseFile("tests/testdata/jsontest2.json")
echo(parsed)
echo()
echo(pretty(parsed, 2))
echo()
echo(parsed["keyÄÖöoßß"])
echo()
echo(pretty(parsed2))
try:
echo(parsed["key2"][12123])
raise newException(ValueError, "That line was expected to fail")
except IndexError: echo()
when not defined(testing):
echo(parsed)
echo()
echo(pretty(parsed, 2))
echo()
echo(parsed["keyÄÖöoßß"])
echo()
echo(pretty(parsed2))
try:
echo(parsed["key2"][12123])
raise newException(ValueError, "That line was expected to fail")
except IndexError: echo()
let testJson = parseJson"""{ "a": [1, 2, 3, 4], "b": "asd" }"""
# nil passthrough
@@ -1087,11 +1192,51 @@ when isMainModule:
except:
assert(false, "EInvalidIndex thrown for valid index")
discard """
while true:
var json = stdin.readLine()
var node = parse(json)
echo(node)
echo()
echo()
"""
assert(testJson{"b"}.str=="asd", "Couldn't fetch a singly nested key with {}")
assert(isNil(testJson{"nonexistent"}), "Non-existent keys should return nil")
assert(parsed2{"repository", "description"}.str=="IRC Library for Haskell", "Couldn't fetch via multiply nested key using {}")
assert(isNil(testJson{"a", "b"}), "Indexing through a list should return nil")
assert(isNil(testJson{"a", "b"}), "Indexing through a list should return nil")
assert(testJson{"a"}==parseJson"[1, 2, 3, 4]", "Didn't return a non-JObject when there was one to be found")
assert(isNil(parseJson("[1, 2, 3]"){"foo"}), "Indexing directly into a list should return nil")
# Generator:
var j = %* [{"name": "John", "age": 30}, {"name": "Susan", "age": 31}]
assert j == %[%{"name": %"John", "age": %30}, %{"name": %"Susan", "age": %31}]
var j2 = %*
[
{
"name": "John",
"age": 30
},
{
"name": "Susan",
"age": 31
}
]
assert j2 == %[%{"name": %"John", "age": %30}, %{"name": %"Susan", "age": %31}]
var name = "John"
let herAge = 30
const hisAge = 31
var j3 = %*
[ { "name": "John"
, "age": herAge
}
, { "name": "Susan"
, "age": hisAge
}
]
assert j3 == %[%{"name": %"John", "age": %30}, %{"name": %"Susan", "age": %31}]
when not defined(testing):
discard """
while true:
var json = stdin.readLine()
var node = parse(json)
echo(node)
echo()
echo()
"""

View File

@@ -165,5 +165,5 @@ proc getCurrentLine(L: BaseLexer, marker: bool = true): string =
inc(i)
add(result, "\n")
if marker:
add(result, repeatChar(getColNumber(L, L.bufpos)) & "^\n")
add(result, spaces(getColNumber(L, L.bufpos)) & "^\n")

View File

@@ -1,16 +1,16 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2014 Andreas Rumpf, Dominik Picheta
# (c) Copyright 2015 Andreas Rumpf, Dominik Picheta
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module implements a simple logger. It has been designed to be as simple
## as possible to avoid bloat, if this library does not fullfill your needs,
## as possible to avoid bloat, if this library does not fulfill your needs,
## write your own.
##
##
## Format strings support the following variables which must be prefixed with
## the dollar operator (``$``):
##
@@ -21,23 +21,26 @@
## $time Current time
## $app ``os.getAppFilename()``
## ============ =======================
##
##
##
## The following example demonstrates logging to three different handlers
## simultaneously:
##
## .. code-block:: nim
##
##
## var L = newConsoleLogger()
## var fL = newFileLogger("test.log", fmtStr = verboseFmtStr)
## var rL = newRollingFileLogger("rolling.log", fmtStr = verboseFmtStr)
## handlers.add(L)
## handlers.add(fL)
## handlers.add(rL)
## addHandler(L)
## addHandler(fL)
## addHandler(rL)
## info("920410:52 accepted")
## warn("4 8 15 16 23 4-- Error")
## error("922044:16 SYSTEM FAILURE")
## fatal("SYSTEM FAILURE SYSTEM FAILURE")
##
## **Warning:** The global list of handlers is a thread var, this means that
## the handlers must be re-added in each thread.
import strutils, os, times
@@ -61,20 +64,20 @@ const
type
Logger* = ref object of RootObj ## abstract logger; the base type of all loggers
levelThreshold*: Level ## only messages of level >= levelThreshold
levelThreshold*: Level ## only messages of level >= levelThreshold
## should be processed
fmtStr: string ## = defaultFmtStr by default, see substituteLog for $date etc.
ConsoleLogger* = ref object of Logger ## logger that writes the messages to the
## console
FileLogger* = ref object of Logger ## logger that writes the messages to a file
f: File
RollingFileLogger* = ref object of FileLogger ## logger that writes the
RollingFileLogger* = ref object of FileLogger ## logger that writes the
## messages to a file and
## performs log rotation
maxLines: int # maximum number of lines
maxLines: int # maximum number of lines
curLine : int
baseName: string # initial filename
baseMode: FileMode # initial file mode
@@ -83,22 +86,22 @@ type
{.deprecated: [TLevel: Level, PLogger: Logger, PConsoleLogger: ConsoleLogger,
PFileLogger: FileLogger, PRollingFileLogger: RollingFileLogger].}
proc substituteLog(frmt: string): string =
proc substituteLog(frmt: string): string =
## converts $date to the current date
## converts $time to the current time
## converts $app to getAppFilename()
## converts
## converts
result = newStringOfCap(frmt.len + 20)
var i = 0
while i < frmt.len:
if frmt[i] != '$':
while i < frmt.len:
if frmt[i] != '$':
result.add(frmt[i])
inc(i)
else:
inc(i)
var v = ""
var app = getAppFilename()
while frmt[i] in IdentChars:
while frmt[i] in IdentChars:
v.add(toLower(frmt[i]))
inc(i)
case v
@@ -111,12 +114,12 @@ proc substituteLog(frmt: string): string =
method log*(logger: Logger, level: Level,
frmt: string, args: varargs[string, `$`]) {.
raises: [Exception],
raises: [Exception],
tags: [TimeEffect, WriteIOEffect, ReadIOEffect].} =
## Override this method in custom loggers. Default implementation does
## nothing.
discard
method log*(logger: ConsoleLogger, level: Level,
frmt: string, args: varargs[string, `$`]) =
## Logs to the console using ``logger`` only.
@@ -124,14 +127,14 @@ method log*(logger: ConsoleLogger, level: Level,
writeln(stdout, LevelNames[level], " ", substituteLog(logger.fmtStr),
frmt % args)
method log*(logger: FileLogger, level: Level,
method log*(logger: FileLogger, level: Level,
frmt: string, args: varargs[string, `$`]) =
## Logs to a file using ``logger`` only.
if level >= logger.levelThreshold:
writeln(logger.f, LevelNames[level], " ",
substituteLog(logger.fmtStr), frmt % args)
proc defaultFilename*(): string =
proc defaultFilename*(): string =
## Returns the default filename for a logger.
var (path, name, ext) = splitFile(getAppFilename())
result = changeFileExt(path / name, "log")
@@ -142,10 +145,10 @@ proc newConsoleLogger*(levelThreshold = lvlAll, fmtStr = defaultFmtStr): Console
result.fmtStr = fmtStr
result.levelThreshold = levelThreshold
proc newFileLogger*(filename = defaultFilename(),
proc newFileLogger*(filename = defaultFilename(),
mode: FileMode = fmAppend,
levelThreshold = lvlAll,
fmtStr = defaultFmtStr): FileLogger =
fmtStr = defaultFmtStr): FileLogger =
## Creates a new file logger. This logger logs to a file.
new(result)
result.levelThreshold = levelThreshold
@@ -167,14 +170,14 @@ proc countFiles(filename: string): int =
if kind == pcFile:
let llfn = name & ext & ExtSep
if path.extractFilename.startsWith(llfn):
let numS = path.extractFilename[llfn.len .. -1]
let numS = path.extractFilename[llfn.len .. ^1]
try:
let num = parseInt(numS)
if num > result:
result = num
except ValueError: discard
proc newRollingFileLogger*(filename = defaultFilename(),
proc newRollingFileLogger*(filename = defaultFilename(),
mode: FileMode = fmReadWrite,
levelThreshold = lvlAll,
fmtStr = defaultFmtStr,
@@ -183,15 +186,15 @@ proc newRollingFileLogger*(filename = defaultFilename(),
## a new log file will be started and the old will be renamed.
new(result)
result.levelThreshold = levelThreshold
result.fmtStr = defaultFmtStr
result.fmtStr = fmtStr
result.maxLines = maxLines
result.f = open(filename, mode)
result.curLine = 0
result.baseName = filename
result.baseMode = mode
result.logFiles = countFiles(filename)
if mode == fmAppend:
# We need to get a line count because we will be appending to the file.
result.curLine = countLogLines(result)
@@ -203,7 +206,7 @@ proc rotate(logger: RollingFileLogger) =
moveFile(dir / (name & ext & srcSuff),
dir / (name & ext & ExtSep & $(i+1)))
method log*(logger: RollingFileLogger, level: Level,
method log*(logger: RollingFileLogger, level: Level,
frmt: string, args: varargs[string, `$`]) =
## Logs to a file using rolling ``logger`` only.
if level >= logger.levelThreshold:
@@ -213,18 +216,17 @@ method log*(logger: RollingFileLogger, level: Level,
logger.logFiles.inc
logger.curLine = 0
logger.f = open(logger.baseName, logger.baseMode)
writeln(logger.f, LevelNames[level], " ", frmt % args)
writeln(logger.f, LevelNames[level], " ",substituteLog(logger.fmtStr), frmt % args)
logger.curLine.inc
# --------
var
level* = lvlAll ## global log filter
handlers*: seq[Logger] = @[] ## handlers with their own log levels
var level {.threadvar.}: Level ## global log filter
var handlers {.threadvar.}: seq[Logger] ## handlers with their own log levels
proc logLoop(level: Level, frmt: string, args: varargs[string, `$`]) =
for logger in items(handlers):
for logger in items(handlers):
if level >= logger.levelThreshold:
log(logger, level, frmt, args)
@@ -233,7 +235,7 @@ template log*(level: Level, frmt: string, args: varargs[string, `$`]) =
bind logLoop
bind `%`
bind logging.level
if level >= logging.level:
logLoop(level, frmt, args)
@@ -241,33 +243,49 @@ template debug*(frmt: string, args: varargs[string, `$`]) =
## Logs a debug message to all registered handlers.
log(lvlDebug, frmt, args)
template info*(frmt: string, args: varargs[string, `$`]) =
template info*(frmt: string, args: varargs[string, `$`]) =
## Logs an info message to all registered handlers.
log(lvlInfo, frmt, args)
template warn*(frmt: string, args: varargs[string, `$`]) =
template warn*(frmt: string, args: varargs[string, `$`]) =
## Logs a warning message to all registered handlers.
log(lvlWarn, frmt, args)
template error*(frmt: string, args: varargs[string, `$`]) =
template error*(frmt: string, args: varargs[string, `$`]) =
## Logs an error message to all registered handlers.
log(lvlError, frmt, args)
template fatal*(frmt: string, args: varargs[string, `$`]) =
template fatal*(frmt: string, args: varargs[string, `$`]) =
## Logs a fatal error message to all registered handlers.
log(lvlFatal, frmt, args)
proc addHandler*(handler: Logger) =
## Adds ``handler`` to the list of handlers.
if handlers.isNil: handlers = @[]
handlers.add(handler)
proc getHandlers*(): seq[Logger] =
## Returns a list of all the registered handlers.
return handlers
proc setLogFilter*(lvl: Level) =
## Sets the global log filter.
level = lvl
proc getLogFilter*(): Level =
## Gets the global log filter.
return level
# --------------
when isMainModule:
when not defined(testing) and isMainModule:
var L = newConsoleLogger()
var fL = newFileLogger("test.log", fmtStr = verboseFmtStr)
var rL = newRollingFileLogger("rolling.log", fmtStr = verboseFmtStr)
handlers.add(L)
handlers.add(fL)
handlers.add(rL)
addHandler(L)
addHandler(fL)
addHandler(rL)
for i in 0 .. 25:
info("hello" & $i, [])

View File

@@ -1,7 +1,7 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2014 Andreas Rumpf
# (c) Copyright 2015 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@@ -15,8 +15,8 @@
## type than its compiletime type:
##
## .. code-block:: nim
##
## type
##
## type
## TA = object
## TB = object of TA
## f: int
@@ -28,6 +28,8 @@
## new(b)
## a = b
## echo($$a[]) # produces "{}", not "{f: 0}"
##
## **Note**: The ``to`` and ``$$`` operations are available at compile-time!
import streams, typeinfo, json, intsets, tables
@@ -38,7 +40,12 @@ proc storeAny(s: Stream, a: TAny, stored: var IntSet) =
case a.kind
of akNone: assert false
of akBool: s.write($getBool(a))
of akChar: s.write(escapeJson($getChar(a)))
of akChar:
let ch = getChar(a)
if ch < '\128':
s.write(escapeJson($ch))
else:
s.write($int(ch))
of akArray, akSequence:
if a.kind == akSequence and isNil(a): s.write("null")
else:
@@ -92,7 +99,7 @@ proc storeAny(s: Stream, a: TAny, stored: var IntSet) =
proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) =
case a.kind
of akNone: assert false
of akBool:
of akBool:
case p.kind
of jsonFalse: setBiggestInt(a, 0)
of jsonTrue: setBiggestInt(a, 1)
@@ -105,8 +112,12 @@ proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) =
setBiggestInt(a, ord(x[0]))
next(p)
return
elif p.kind == jsonInt:
setBiggestInt(a, getInt(p))
next(p)
return
raiseParseErr(p, "string of length 1 expected for a char")
of akEnum:
of akEnum:
if p.kind == jsonString:
setBiggestInt(a, getEnumOrdinal(a, p.str))
next(p)
@@ -122,7 +133,7 @@ proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) =
if p.kind == jsonArrayEnd: next(p)
else: raiseParseErr(p, "']' end of array expected")
of akSequence:
case p.kind
case p.kind
of jsonNull:
setPointer(a, nil)
next(p)
@@ -143,7 +154,7 @@ proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) =
if p.kind != jsonObjectStart: raiseParseErr(p, "'{' expected for an object")
next(p)
while p.kind != jsonObjectEnd and p.kind != jsonEof:
if p.kind != jsonString:
if p.kind != jsonString:
raiseParseErr(p, "string expected for a field name")
var fieldName = p.str
next(p)
@@ -160,7 +171,7 @@ proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) =
if p.kind == jsonArrayEnd: next(p)
else: raiseParseErr(p, "']' end of array expected")
of akPtr, akRef:
case p.kind
case p.kind
of jsonNull:
setPointer(a, nil)
next(p)
@@ -170,7 +181,7 @@ proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) =
of jsonArrayStart:
next(p)
if a.kind == akRef: invokeNew(a)
else: setPointer(a, alloc0(a.baseTypeSize))
else: setPointer(a, alloc0(a.baseTypeSize))
if p.kind == jsonInt:
t[p.getInt] = getPointer(a)
next(p)
@@ -179,8 +190,8 @@ proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) =
if p.kind == jsonArrayEnd: next(p)
else: raiseParseErr(p, "']' end of ref-address pair expected")
else: raiseParseErr(p, "int for pointer type expected")
of akProc, akPointer, akCString:
case p.kind
of akProc, akPointer, akCString:
case p.kind
of jsonNull:
setPointer(a, nil)
next(p)
@@ -189,7 +200,7 @@ proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) =
next(p)
else: raiseParseErr(p, "int for pointer type expected")
of akString:
case p.kind
case p.kind
of jsonNull:
setPointer(a, nil)
next(p)
@@ -197,7 +208,7 @@ proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) =
setString(a, p.str)
next(p)
else: raiseParseErr(p, "string expected")
of akInt..akInt64, akUInt..akUInt64:
of akInt..akInt64, akUInt..akUInt64:
if p.kind == jsonInt:
setBiggestInt(a, getInt(p))
next(p)
@@ -243,22 +254,22 @@ proc to*[T](data: string): T =
## reads data and transforms it to a ``T``.
var tab = initTable[BiggestInt, pointer]()
loadAny(newStringStream(data), toAny(result), tab)
when isMainModule:
when not defined(testing) and isMainModule:
template testit(x: expr) = echo($$to[type(x)]($$x))
var x: array[0..4, array[0..4, string]] = [
["test", "1", "2", "3", "4"], ["test", "1", "2", "3", "4"],
["test", "1", "2", "3", "4"], ["test", "1", "2", "3", "4"],
["test", "1", "2", "3", "4"], ["test", "1", "2", "3", "4"],
["test", "1", "2", "3", "4"], ["test", "1", "2", "3", "4"],
["test", "1", "2", "3", "4"]]
testit(x)
var test2: tuple[name: string, s: uint] = ("tuple test", 56u)
testit(test2)
type
TE = enum
blah, blah2
TestObj = object
test, asd: int
case test2: TE
@@ -266,7 +277,7 @@ when isMainModule:
help: string
else:
nil
PNode = ref TNode
TNode = object
next, prev: PNode
@@ -294,7 +305,7 @@ when isMainModule:
test4.a = "ref string test: A"
test4.b = "ref string test: B"
testit(test4)
var test5 = @[(0,1),(2,3),(4,5)]
testit(test5)
@@ -305,7 +316,7 @@ when isMainModule:
echo($$test7)
testit(test7)
type
type
TA {.inheritable.} = object
TB = object of TA
f: int

View File

@@ -1,7 +1,7 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2014 Andreas Rumpf
# (c) Copyright 2015 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@@ -42,7 +42,7 @@ proc validEmailAddress*(s: string): bool {.noSideEffect,
case toLower(x)
of "com", "org", "net", "gov", "mil", "biz", "info", "mobi", "name",
"aero", "jobs", "museum": return true
return false
else: return false
proc parseInt*(s: string, value: var int, validRange: Slice[int]) {.
noSideEffect, rtl, extern: "nmatchParseInt".} =

View File

@@ -1,7 +1,7 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2014 Andreas Rumpf
# (c) Copyright 2015 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@@ -93,9 +93,9 @@ proc nextPowerOfTwo*(x: int): int {.noSideEffect.} =
result = x - 1
when defined(cpu64):
result = result or (result shr 32)
when sizeof(int) > 16:
when sizeof(int) > 2:
result = result or (result shr 16)
when sizeof(int) > 8:
when sizeof(int) > 1:
result = result or (result shr 8)
result = result or (result shr 4)
result = result or (result shr 2)
@@ -129,29 +129,30 @@ proc variance*(x: openArray[float]): float {.noSideEffect.} =
result = result + diff*diff
result = result / toFloat(len(x))
proc random*(max: int): int {.gcsafe.}
proc random*(max: int): int {.benign.}
## returns a random number in the range 0..max-1. The sequence of
## random number is always the same, unless `randomize` is called
## which initializes the random number generator with a "random"
## number, i.e. a tickcount.
proc random*(max: float): float {.gcsafe.}
proc random*(max: float): float {.benign.}
## returns a random number in the range 0..<max. The sequence of
## random number is always the same, unless `randomize` is called
## which initializes the random number generator with a "random"
## number, i.e. a tickcount. This has a 16-bit resolution on windows
## and a 48-bit resolution on other platforms.
proc randomize*() {.gcsafe.}
proc randomize*() {.benign.}
## initializes the random number generator with a "random"
## number, i.e. a tickcount. Note: Does nothing for the JavaScript target,
## as JavaScript does not support this.
proc randomize*(seed: int) {.gcsafe.}
proc randomize*(seed: int) {.benign.}
## initializes the random number generator with a specific seed.
## Note: Does nothing for the JavaScript target,
## as JavaScript does not support this.
{.push noSideEffect.}
when not defined(JS):
proc sqrt*(x: float): float {.importc: "sqrt", header: "<math.h>".}
## computes the square root of `x`.
@@ -273,6 +274,8 @@ else:
var y = exp(2.0*x)
return (y-1.0)/(y+1.0)
{.pop.}
proc `mod`*(x, y: float): float =
result = if y == 0.0: x else: x - y * (x/y).floor
@@ -280,7 +283,7 @@ proc random*[T](x: Slice[T]): T =
## For a slice `a .. b` returns a value in the range `a .. b-1`.
result = random(x.b - x.a) + x.a
proc random[T](a: openArray[T]): T =
proc random*[T](a: openArray[T]): T =
## returns a random element from the openarray `a`.
result = a[random(a.low..a.len)]
@@ -329,6 +332,31 @@ proc standardDeviation*(s: RunningStat): float =
{.pop.}
{.pop.}
proc `^`*[T](x, y: T): T =
## Computes ``x`` to the power ``y`. ``x`` must be non-negative, use
## `pow <#pow,float,float>` for negative exponents.
assert y >= 0
var (x, y) = (x, y)
result = 1
while y != 0:
if (y and 1) != 0:
result *= x
y = y shr 1
x *= x
proc gcd*[T](x, y: T): T =
## Computes the greatest common divisor of ``x`` and ``y``.
var (x,y) = (x,y)
while y != 0:
x = x mod y
swap x, y
abs x
proc lcm*[T](x, y: T): T =
## Computes the least common multiple of ``x`` and ``y``.
x div gcd(x, y) * y
when isMainModule and not defined(JS):
proc gettime(dummy: ptr cint): cint {.importc: "time", header: "<time.h>".}
@@ -344,4 +372,10 @@ when isMainModule and not defined(JS):
randomize(seed)
for i in 0..SIZE-1:
assert buf[i] == random(high(int)), "non deterministic random seeding"
echo "random values equal after reseeding"
when not defined(testing):
echo "random values equal after reseeding"
# Check for no side effect annotation
proc mySqrt(num: float): float {.noSideEffect.} =
return sqrt(num)

View File

@@ -9,18 +9,20 @@
## Module for computing MD5 checksums.
type
MD5State = array[0..3, int32]
MD5Block = array[0..15, int32]
MD5CBits = array[0..7, int8]
MD5Digest* = array[0..15, int8]
MD5Buffer = array[0..63, int8]
MD5Context* {.final.} = object
import unsigned
type
MD5State = array[0..3, uint32]
MD5Block = array[0..15, uint32]
MD5CBits = array[0..7, uint8]
MD5Digest* = array[0..15, uint8]
MD5Buffer = array[0..63, uint8]
MD5Context* {.final.} = object
state: MD5State
count: array[0..1, int32]
count: array[0..1, uint32]
buffer: MD5Buffer
const
const
padding: cstring = "\x80\0\0\0" &
"\0\0\0\0\0\0\0\0" &
"\0\0\0\0\0\0\0\0" &
@@ -31,60 +33,60 @@ const
"\0\0\0\0\0\0\0\0" &
"\0\0\0\0"
proc F(x, y, z: int32): int32 {.inline.} =
proc F(x, y, z: uint32): uint32 {.inline.} =
result = (x and y) or ((not x) and z)
proc G(x, y, z: int32): int32 {.inline.} =
proc G(x, y, z: uint32): uint32 {.inline.} =
result = (x and z) or (y and (not z))
proc H(x, y, z: int32): int32 {.inline.} =
proc H(x, y, z: uint32): uint32 {.inline.} =
result = x xor y xor z
proc I(x, y, z: int32): int32 {.inline.} =
proc I(x, y, z: uint32): uint32 {.inline.} =
result = y xor (x or (not z))
proc rot(x: var int32, n: int8) {.inline.} =
x = toU32(x shl ze(n)) or (x shr toU32(32 -% ze(n)))
proc rot(x: var uint32, n: uint8) {.inline.} =
x = (x shl n) or (x shr (32'u32 - n))
proc FF(a: var int32, b, c, d, x: int32, s: int8, ac: int32) =
a = a +% F(b, c, d) +% x +% ac
proc FF(a: var uint32, b, c, d, x: uint32, s: uint8, ac: uint32) =
a = a + F(b, c, d) + x + ac
rot(a, s)
a = a +% b
a = a + b
proc GG(a: var int32, b, c, d, x: int32, s: int8, ac: int32) =
a = a +% G(b, c, d) +% x +% ac
proc GG(a: var uint32, b, c, d, x: uint32, s: uint8, ac: uint32) =
a = a + G(b, c, d) + x + ac
rot(a, s)
a = a +% b
a = a + b
proc HH(a: var int32, b, c, d, x: int32, s: int8, ac: int32) =
a = a +% H(b, c, d) +% x +% ac
proc HH(a: var uint32, b, c, d, x: uint32, s: uint8, ac: uint32) =
a = a + H(b, c, d) + x + ac
rot(a, s)
a = a +% b
a = a + b
proc II(a: var int32, b, c, d, x: int32, s: int8, ac: int32) =
a = a +% I(b, c, d) +% x +% ac
proc II(a: var uint32, b, c, d, x: uint32, s: uint8, ac: uint32) =
a = a + I(b, c, d) + x + ac
rot(a, s)
a = a +% b
a = a + b
proc encode(dest: var MD5Block, src: cstring) =
proc encode(dest: var MD5Block, src: cstring) =
var j = 0
for i in 0..high(dest):
dest[i] = toU32(ord(src[j]) or
ord(src[j+1]) shl 8 or
ord(src[j+2]) shl 16 or
ord(src[j+3]) shl 24)
dest[i] = uint32(ord(src[j])) or
uint32(ord(src[j+1])) shl 8 or
uint32(ord(src[j+2])) shl 16 or
uint32(ord(src[j+3])) shl 24
inc(j, 4)
proc decode(dest: var openArray[int8], src: openArray[int32]) =
proc decode(dest: var openArray[uint8], src: openArray[uint32]) =
var i = 0
for j in 0..high(src):
dest[i] = toU8(src[j] and 0xff'i32)
dest[i+1] = toU8(src[j] shr 8'i32 and 0xff'i32)
dest[i+2] = toU8(src[j] shr 16'i32 and 0xff'i32)
dest[i+3] = toU8(src[j] shr 24'i32 and 0xff'i32)
dest[i] = src[j] and 0xff'u32
dest[i+1] = src[j] shr 8 and 0xff'u32
dest[i+2] = src[j] shr 16 and 0xff'u32
dest[i+3] = src[j] shr 24 and 0xff'u32
inc(i, 4)
proc transform(buffer: pointer, state: var MD5State) =
proc transform(buffer: pointer, state: var MD5State) =
var
myBlock: MD5Block
encode(myBlock, cast[cstring](buffer))
@@ -92,111 +94,111 @@ proc transform(buffer: pointer, state: var MD5State) =
var b = state[1]
var c = state[2]
var d = state[3]
FF(a, b, c, d, myBlock[0], 7'i8, 0xD76AA478'i32)
FF(d, a, b, c, myBlock[1], 12'i8, 0xE8C7B756'i32)
FF(c, d, a, b, myBlock[2], 17'i8, 0x242070DB'i32)
FF(b, c, d, a, myBlock[3], 22'i8, 0xC1BDCEEE'i32)
FF(a, b, c, d, myBlock[4], 7'i8, 0xF57C0FAF'i32)
FF(d, a, b, c, myBlock[5], 12'i8, 0x4787C62A'i32)
FF(c, d, a, b, myBlock[6], 17'i8, 0xA8304613'i32)
FF(b, c, d, a, myBlock[7], 22'i8, 0xFD469501'i32)
FF(a, b, c, d, myBlock[8], 7'i8, 0x698098D8'i32)
FF(d, a, b, c, myBlock[9], 12'i8, 0x8B44F7AF'i32)
FF(c, d, a, b, myBlock[10], 17'i8, 0xFFFF5BB1'i32)
FF(b, c, d, a, myBlock[11], 22'i8, 0x895CD7BE'i32)
FF(a, b, c, d, myBlock[12], 7'i8, 0x6B901122'i32)
FF(d, a, b, c, myBlock[13], 12'i8, 0xFD987193'i32)
FF(c, d, a, b, myBlock[14], 17'i8, 0xA679438E'i32)
FF(b, c, d, a, myBlock[15], 22'i8, 0x49B40821'i32)
GG(a, b, c, d, myBlock[1], 5'i8, 0xF61E2562'i32)
GG(d, a, b, c, myBlock[6], 9'i8, 0xC040B340'i32)
GG(c, d, a, b, myBlock[11], 14'i8, 0x265E5A51'i32)
GG(b, c, d, a, myBlock[0], 20'i8, 0xE9B6C7AA'i32)
GG(a, b, c, d, myBlock[5], 5'i8, 0xD62F105D'i32)
GG(d, a, b, c, myBlock[10], 9'i8, 0x02441453'i32)
GG(c, d, a, b, myBlock[15], 14'i8, 0xD8A1E681'i32)
GG(b, c, d, a, myBlock[4], 20'i8, 0xE7D3FBC8'i32)
GG(a, b, c, d, myBlock[9], 5'i8, 0x21E1CDE6'i32)
GG(d, a, b, c, myBlock[14], 9'i8, 0xC33707D6'i32)
GG(c, d, a, b, myBlock[3], 14'i8, 0xF4D50D87'i32)
GG(b, c, d, a, myBlock[8], 20'i8, 0x455A14ED'i32)
GG(a, b, c, d, myBlock[13], 5'i8, 0xA9E3E905'i32)
GG(d, a, b, c, myBlock[2], 9'i8, 0xFCEFA3F8'i32)
GG(c, d, a, b, myBlock[7], 14'i8, 0x676F02D9'i32)
GG(b, c, d, a, myBlock[12], 20'i8, 0x8D2A4C8A'i32)
HH(a, b, c, d, myBlock[5], 4'i8, 0xFFFA3942'i32)
HH(d, a, b, c, myBlock[8], 11'i8, 0x8771F681'i32)
HH(c, d, a, b, myBlock[11], 16'i8, 0x6D9D6122'i32)
HH(b, c, d, a, myBlock[14], 23'i8, 0xFDE5380C'i32)
HH(a, b, c, d, myBlock[1], 4'i8, 0xA4BEEA44'i32)
HH(d, a, b, c, myBlock[4], 11'i8, 0x4BDECFA9'i32)
HH(c, d, a, b, myBlock[7], 16'i8, 0xF6BB4B60'i32)
HH(b, c, d, a, myBlock[10], 23'i8, 0xBEBFBC70'i32)
HH(a, b, c, d, myBlock[13], 4'i8, 0x289B7EC6'i32)
HH(d, a, b, c, myBlock[0], 11'i8, 0xEAA127FA'i32)
HH(c, d, a, b, myBlock[3], 16'i8, 0xD4EF3085'i32)
HH(b, c, d, a, myBlock[6], 23'i8, 0x04881D05'i32)
HH(a, b, c, d, myBlock[9], 4'i8, 0xD9D4D039'i32)
HH(d, a, b, c, myBlock[12], 11'i8, 0xE6DB99E5'i32)
HH(c, d, a, b, myBlock[15], 16'i8, 0x1FA27CF8'i32)
HH(b, c, d, a, myBlock[2], 23'i8, 0xC4AC5665'i32)
II(a, b, c, d, myBlock[0], 6'i8, 0xF4292244'i32)
II(d, a, b, c, myBlock[7], 10'i8, 0x432AFF97'i32)
II(c, d, a, b, myBlock[14], 15'i8, 0xAB9423A7'i32)
II(b, c, d, a, myBlock[5], 21'i8, 0xFC93A039'i32)
II(a, b, c, d, myBlock[12], 6'i8, 0x655B59C3'i32)
II(d, a, b, c, myBlock[3], 10'i8, 0x8F0CCC92'i32)
II(c, d, a, b, myBlock[10], 15'i8, 0xFFEFF47D'i32)
II(b, c, d, a, myBlock[1], 21'i8, 0x85845DD1'i32)
II(a, b, c, d, myBlock[8], 6'i8, 0x6FA87E4F'i32)
II(d, a, b, c, myBlock[15], 10'i8, 0xFE2CE6E0'i32)
II(c, d, a, b, myBlock[6], 15'i8, 0xA3014314'i32)
II(b, c, d, a, myBlock[13], 21'i8, 0x4E0811A1'i32)
II(a, b, c, d, myBlock[4], 6'i8, 0xF7537E82'i32)
II(d, a, b, c, myBlock[11], 10'i8, 0xBD3AF235'i32)
II(c, d, a, b, myBlock[2], 15'i8, 0x2AD7D2BB'i32)
II(b, c, d, a, myBlock[9], 21'i8, 0xEB86D391'i32)
state[0] = state[0] +% a
state[1] = state[1] +% b
state[2] = state[2] +% c
state[3] = state[3] +% d
proc md5Init*(c: var MD5Context) =
## initializes a MD5Context
c.state[0] = 0x67452301'i32
c.state[1] = 0xEFCDAB89'i32
c.state[2] = 0x98BADCFE'i32
c.state[3] = 0x10325476'i32
c.count[0] = 0'i32
c.count[1] = 0'i32
FF(a, b, c, d, myBlock[0], 7'u8, 0xD76AA478'u32)
FF(d, a, b, c, myBlock[1], 12'u8, 0xE8C7B756'u32)
FF(c, d, a, b, myBlock[2], 17'u8, 0x242070DB'u32)
FF(b, c, d, a, myBlock[3], 22'u8, 0xC1BDCEEE'u32)
FF(a, b, c, d, myBlock[4], 7'u8, 0xF57C0FAF'u32)
FF(d, a, b, c, myBlock[5], 12'u8, 0x4787C62A'u32)
FF(c, d, a, b, myBlock[6], 17'u8, 0xA8304613'u32)
FF(b, c, d, a, myBlock[7], 22'u8, 0xFD469501'u32)
FF(a, b, c, d, myBlock[8], 7'u8, 0x698098D8'u32)
FF(d, a, b, c, myBlock[9], 12'u8, 0x8B44F7AF'u32)
FF(c, d, a, b, myBlock[10], 17'u8, 0xFFFF5BB1'u32)
FF(b, c, d, a, myBlock[11], 22'u8, 0x895CD7BE'u32)
FF(a, b, c, d, myBlock[12], 7'u8, 0x6B901122'u32)
FF(d, a, b, c, myBlock[13], 12'u8, 0xFD987193'u32)
FF(c, d, a, b, myBlock[14], 17'u8, 0xA679438E'u32)
FF(b, c, d, a, myBlock[15], 22'u8, 0x49B40821'u32)
GG(a, b, c, d, myBlock[1], 5'u8, 0xF61E2562'u32)
GG(d, a, b, c, myBlock[6], 9'u8, 0xC040B340'u32)
GG(c, d, a, b, myBlock[11], 14'u8, 0x265E5A51'u32)
GG(b, c, d, a, myBlock[0], 20'u8, 0xE9B6C7AA'u32)
GG(a, b, c, d, myBlock[5], 5'u8, 0xD62F105D'u32)
GG(d, a, b, c, myBlock[10], 9'u8, 0x02441453'u32)
GG(c, d, a, b, myBlock[15], 14'u8, 0xD8A1E681'u32)
GG(b, c, d, a, myBlock[4], 20'u8, 0xE7D3FBC8'u32)
GG(a, b, c, d, myBlock[9], 5'u8, 0x21E1CDE6'u32)
GG(d, a, b, c, myBlock[14], 9'u8, 0xC33707D6'u32)
GG(c, d, a, b, myBlock[3], 14'u8, 0xF4D50D87'u32)
GG(b, c, d, a, myBlock[8], 20'u8, 0x455A14ED'u32)
GG(a, b, c, d, myBlock[13], 5'u8, 0xA9E3E905'u32)
GG(d, a, b, c, myBlock[2], 9'u8, 0xFCEFA3F8'u32)
GG(c, d, a, b, myBlock[7], 14'u8, 0x676F02D9'u32)
GG(b, c, d, a, myBlock[12], 20'u8, 0x8D2A4C8A'u32)
HH(a, b, c, d, myBlock[5], 4'u8, 0xFFFA3942'u32)
HH(d, a, b, c, myBlock[8], 11'u8, 0x8771F681'u32)
HH(c, d, a, b, myBlock[11], 16'u8, 0x6D9D6122'u32)
HH(b, c, d, a, myBlock[14], 23'u8, 0xFDE5380C'u32)
HH(a, b, c, d, myBlock[1], 4'u8, 0xA4BEEA44'u32)
HH(d, a, b, c, myBlock[4], 11'u8, 0x4BDECFA9'u32)
HH(c, d, a, b, myBlock[7], 16'u8, 0xF6BB4B60'u32)
HH(b, c, d, a, myBlock[10], 23'u8, 0xBEBFBC70'u32)
HH(a, b, c, d, myBlock[13], 4'u8, 0x289B7EC6'u32)
HH(d, a, b, c, myBlock[0], 11'u8, 0xEAA127FA'u32)
HH(c, d, a, b, myBlock[3], 16'u8, 0xD4EF3085'u32)
HH(b, c, d, a, myBlock[6], 23'u8, 0x04881D05'u32)
HH(a, b, c, d, myBlock[9], 4'u8, 0xD9D4D039'u32)
HH(d, a, b, c, myBlock[12], 11'u8, 0xE6DB99E5'u32)
HH(c, d, a, b, myBlock[15], 16'u8, 0x1FA27CF8'u32)
HH(b, c, d, a, myBlock[2], 23'u8, 0xC4AC5665'u32)
II(a, b, c, d, myBlock[0], 6'u8, 0xF4292244'u32)
II(d, a, b, c, myBlock[7], 10'u8, 0x432AFF97'u32)
II(c, d, a, b, myBlock[14], 15'u8, 0xAB9423A7'u32)
II(b, c, d, a, myBlock[5], 21'u8, 0xFC93A039'u32)
II(a, b, c, d, myBlock[12], 6'u8, 0x655B59C3'u32)
II(d, a, b, c, myBlock[3], 10'u8, 0x8F0CCC92'u32)
II(c, d, a, b, myBlock[10], 15'u8, 0xFFEFF47D'u32)
II(b, c, d, a, myBlock[1], 21'u8, 0x85845DD1'u32)
II(a, b, c, d, myBlock[8], 6'u8, 0x6FA87E4F'u32)
II(d, a, b, c, myBlock[15], 10'u8, 0xFE2CE6E0'u32)
II(c, d, a, b, myBlock[6], 15'u8, 0xA3014314'u32)
II(b, c, d, a, myBlock[13], 21'u8, 0x4E0811A1'u32)
II(a, b, c, d, myBlock[4], 6'u8, 0xF7537E82'u32)
II(d, a, b, c, myBlock[11], 10'u8, 0xBD3AF235'u32)
II(c, d, a, b, myBlock[2], 15'u8, 0x2AD7D2BB'u32)
II(b, c, d, a, myBlock[9], 21'u8, 0xEB86D391'u32)
state[0] = state[0] + a
state[1] = state[1] + b
state[2] = state[2] + c
state[3] = state[3] + d
proc md5Init*(c: var MD5Context) =
## initializes a MD5Context
c.state[0] = 0x67452301'u32
c.state[1] = 0xEFCDAB89'u32
c.state[2] = 0x98BADCFE'u32
c.state[3] = 0x10325476'u32
c.count[0] = 0'u32
c.count[1] = 0'u32
zeroMem(addr(c.buffer), sizeof(MD5buffer))
proc md5Update*(c: var MD5Context, input: cstring, len: int) =
proc md5Update*(c: var MD5Context, input: cstring, len: int) =
## updates the MD5Context with the `input` data of length `len`
var input = input
var Index = (c.count[0] shr 3) and 0x3F
c.count[0] = c.count[0] +% toU32(len shl 3)
if c.count[0] < (len shl 3): c.count[1] = c.count[1] +% 1'i32
c.count[1] = c.count[1] +% toU32(len shr 29)
var Index = int((c.count[0] shr 3) and 0x3F)
c.count[0] = c.count[0] + (uint32(len) shl 3)
if c.count[0] < (uint32(len) shl 3): c.count[1] = c.count[1] + 1'u32
c.count[1] = c.count[1] + (uint32(len) shr 29)
var PartLen = 64 - Index
if len >= PartLen:
if len >= PartLen:
copyMem(addr(c.buffer[Index]), input, PartLen)
transform(addr(c.buffer), c.state)
var i = PartLen
while i + 63 < len:
while i + 63 < len:
transform(addr(input[i]), c.state)
inc(i, 64)
copyMem(addr(c.buffer[0]), addr(input[i]), len-i)
else:
copyMem(addr(c.buffer[Index]), addr(input[0]), len)
proc md5Final*(c: var MD5Context, digest: var MD5Digest) =
proc md5Final*(c: var MD5Context, digest: var MD5Digest) =
## finishes the MD5Context and stores the result in `digest`
var
Bits: MD5CBits
PadLen: int
decode(Bits, c.count)
var Index = (c.count[0] shr 3) and 0x3F
var Index = int((c.count[0] shr 3) and 0x3F)
if Index < 56: PadLen = 56 - Index
else: PadLen = 120 - Index
md5Update(c, padding, PadLen)
@@ -204,34 +206,34 @@ proc md5Final*(c: var MD5Context, digest: var MD5Digest) =
decode(digest, c.state)
zeroMem(addr(c), sizeof(MD5Context))
proc toMD5*(s: string): MD5Digest =
proc toMD5*(s: string): MD5Digest =
## computes the MD5Digest value for a string `s`
var c: MD5Context
md5Init(c)
md5Update(c, cstring(s), len(s))
md5Final(c, result)
proc `$`*(D: MD5Digest): string =
proc `$`*(d: MD5Digest): string =
## converts a MD5Digest value into its string representation
const digits = "0123456789abcdef"
result = ""
for i in 0..15:
add(result, digits[(D[i] shr 4) and 0xF])
add(result, digits[D[i] and 0xF])
for i in 0..15:
add(result, digits[(d[i] shr 4) and 0xF])
add(result, digits[d[i] and 0xF])
proc getMD5*(s: string): string =
proc getMD5*(s: string): string =
## computes an MD5 value of `s` and returns its string representation
var
var
c: MD5Context
d: MD5Digest
md5Init(c)
md5Update(c, cstring(s), len(s))
md5Final(c, d)
result = $d
proc `==`*(D1, D2: MD5Digest): bool =
proc `==`*(D1, D2: MD5Digest): bool =
## checks if two MD5Digest values are identical
for i in 0..15:
for i in 0..15:
if D1[i] != D2[i]: return false
return true
@@ -241,5 +243,3 @@ when isMainModule:
assert(getMD5("Frank jagt im komplett verwahrlosten Taxi quer durch Bayern") ==
"7e716d0e702df0505fc72e2b89467910")
assert($toMD5("") == "d41d8cd98f00b204e9800998ecf8427e")

View File

@@ -1,7 +1,7 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2014 Nim Contributors
# (c) Copyright 2015 Nim Contributors
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.

View File

@@ -32,7 +32,7 @@ proc getNum*(m: var MersenneTwister): int =
return int(y)
# Test
when isMainModule:
when not defined(testing) and isMainModule:
var mt = newMersenneTwister(2525)
for i in 0..99:

View File

@@ -518,5 +518,5 @@ proc register*(mimedb: var MimeDB, ext: string, mimetype: string) =
when isMainModule:
var m = newMimetypes()
echo m.getMimetype("mp4")
echo m.getExt("text/html")
assert m.getMimetype("mp4") == "video/mp4"
assert m.getExt("text/html") == "html"

View File

@@ -1,7 +1,7 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2014 Dominik Picheta
# (c) Copyright 2015 Dominik Picheta
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@@ -60,7 +60,8 @@ type
sslHasPeekChar: bool
sslPeekChar: char
of false: nil
lastError: OSErrorCode ## stores the last error on this socket
Socket* = ref SocketImpl
SOBool* = enum ## Boolean socket options.
@@ -80,6 +81,23 @@ type
TReadLineResult: ReadLineResult, TSOBool: SOBool, PSocket: Socket,
TSocketImpl: SocketImpl].}
type
IpAddressFamily* {.pure.} = enum ## Describes the type of an IP address
IPv6, ## IPv6 address
IPv4 ## IPv4 address
TIpAddress* = object ## stores an arbitrary IP address
case family*: IpAddressFamily ## the type of the IP address (IPv4 or IPv6)
of IpAddressFamily.IPv6:
address_v6*: array[0..15, uint8] ## Contains the IP address in bytes in
## case of IPv6
of IpAddressFamily.IPv4:
address_v4*: array[0..3, uint8] ## Contains the IP address in bytes in
## case of IPv4
proc isIpAddress*(address_str: string): bool {.tags: [].}
proc parseIpAddress*(address_str: string): TIpAddress
proc isDisconnectionError*(flags: set[SocketFlag],
lastError: OSErrorCode): bool =
## Determines whether ``lastError`` is a disconnection error. Only does this
@@ -119,7 +137,7 @@ proc newSocket*(domain, typ, protocol: cint, buffered = true): Socket =
result = newSocket(fd, buffered)
proc newSocket*(domain: Domain = AF_INET, typ: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP, buffered = true): Socket =
protocol: Protocol = IPPROTO_TCP, buffered = true): Socket =
## Creates a new socket.
##
## If an error occurs EOS will be raised.
@@ -231,6 +249,15 @@ when defined(ssl):
if SSLSetFd(socket.sslHandle, socket.fd) != 1:
raiseSSLError()
proc getSocketError*(socket: Socket): OSErrorCode =
## Checks ``osLastError`` for a valid error. If it has been reset it uses
## the last error stored in the socket object.
result = osLastError()
if result == 0.OSErrorCode:
result = socket.lastError
if result == 0.OSErrorCode:
raise newException(OSError, "No valid socket error code available")
proc socketError*(socket: Socket, err: int = -1, async = false,
lastError = (-1).OSErrorCode) =
## Raises an OSError based on the error code returned by ``SSLGetError``
@@ -258,7 +285,7 @@ proc socketError*(socket: Socket, err: int = -1, async = false,
of SSL_ERROR_WANT_X509_LOOKUP:
raiseSSLError("Function for x509 lookup has been called.")
of SSL_ERROR_SYSCALL:
var errStr = "IO error has occured "
var errStr = "IO error has occurred "
let sslErr = ErrPeekLastError()
if sslErr == 0 and err == 0:
errStr.add "because an EOF was observed that violates the protocol"
@@ -276,7 +303,7 @@ proc socketError*(socket: Socket, err: int = -1, async = false,
else: raiseSSLError("Unknown Error")
if err == -1 and not (when defined(ssl): socket.isSSL else: false):
let lastE = if lastError.int == -1: osLastError() else: lastError
var lastE = if lastError.int == -1: getSocketError(socket) else: lastError
if async:
when useWinVersion:
if lastE.int32 == WSAEWOULDBLOCK:
@@ -294,7 +321,8 @@ proc listen*(socket: Socket, backlog = SOMAXCONN) {.tags: [ReadIOEffect].} =
## queue of pending connections.
##
## Raises an EOS error upon failure.
if listen(socket.fd, backlog) < 0'i32: raiseOSError(osLastError())
if rawsockets.listen(socket.fd, backlog) < 0'i32:
raiseOSError(osLastError())
proc bindAddr*(socket: Socket, port = Port(0), address = "") {.
tags: [ReadIOEffect].} =
@@ -321,7 +349,8 @@ proc bindAddr*(socket: Socket, port = Port(0), address = "") {.
dealloc(aiList)
proc acceptAddr*(server: Socket, client: var Socket, address: var string,
flags = {SocketFlag.SafeDisconn}) {.tags: [ReadIOEffect].} =
flags = {SocketFlag.SafeDisconn}) {.
tags: [ReadIOEffect], gcsafe, locks: 0.} =
## Blocks until a connection is being made from a client. When a connection
## is made sets ``client`` to the client socket and ``address`` to the address
## of the connecting client.
@@ -442,6 +471,8 @@ proc close*(socket: Socket) =
# shutdown i.e not wait for the peers "close notify" alert with a second
# call to SSLShutdown
let res = SSLShutdown(socket.sslHandle)
SSLFree(socket.sslHandle)
socket.sslHandle = nil
if res == 0:
discard
elif res != 1:
@@ -497,6 +528,12 @@ proc connect*(socket: Socket, address: string, port = Port(0),
when defined(ssl):
if socket.isSSL:
# RFC3546 for SNI specifies that IP addresses are not allowed.
if not isIpAddress(address):
# Discard result in case OpenSSL version doesn't support SNI, or we're
# not using TLSv1+
discard SSL_set_tlsext_host_name(socket.sslHandle, address)
let ret = SSLConnect(socket.sslHandle)
socketError(socket, ret)
@@ -568,6 +605,10 @@ proc readIntoBuf(socket: Socket, flags: int32): int =
result = recv(socket.fd, addr(socket.buffer), cint(socket.buffer.high), flags)
else:
result = recv(socket.fd, addr(socket.buffer), cint(socket.buffer.high), flags)
if result < 0:
# Save it in case it gets reset (the Nim codegen occassionally may call
# Win API functions which reset it).
socket.lastError = osLastError()
if result <= 0:
socket.bufLen = 0
socket.currPos = 0
@@ -623,6 +664,9 @@ proc recv*(socket: Socket, data: pointer, size: int): int {.tags: [ReadIOEffect]
result = recv(socket.fd, data, size.cint, 0'i32)
else:
result = recv(socket.fd, data, size.cint, 0'i32)
if result < 0:
# Save the error in case it gets reset.
socket.lastError = osLastError()
proc waitFor(socket: Socket, waited: var float, timeout, size: int,
funcName: string): int {.tags: [TimeEffect].} =
@@ -695,7 +739,7 @@ proc recv*(socket: Socket, data: var string, size: int, timeout = -1,
result = recv(socket, cstring(data), size, timeout)
if result < 0:
data.setLen(0)
let lastError = osLastError()
let lastError = getSocketError(socket)
if flags.isDisconnectionError(lastError): return
socket.socketError(result, lastError = lastError)
data.setLen(result)
@@ -743,7 +787,7 @@ proc readLine*(socket: Socket, line: var TaintedString, timeout = -1,
line.add("\c\L")
template raiseSockError(): stmt {.dirty, immediate.} =
let lastError = osLastError()
let lastError = getSocketError(socket)
if flags.isDisconnectionError(lastError): setLen(line.string, 0); return
socket.socketError(n, lastError = lastError)
@@ -886,7 +930,7 @@ proc connectAsync(socket: Socket, name: string, port = Port(0),
af: Domain = AF_INET) {.tags: [ReadIOEffect].} =
## A variant of ``connect`` for non-blocking sockets.
##
## This procedure will immediatelly return, it will not block until a connection
## This procedure will immediately return, it will not block until a connection
## is made. It is up to the caller to make sure the connection has been established
## by checking (using ``select``) whether the socket is writeable.
##
@@ -938,26 +982,16 @@ proc connect*(socket: Socket, address: string, port = Port(0), timeout: int,
doAssert socket.handshake()
socket.fd.setBlocking(true)
proc isSsl*(socket: Socket): bool = return socket.isSSL
proc isSsl*(socket: Socket): bool =
## Determines whether ``socket`` is a SSL socket.
when defined(ssl):
result = socket.isSSL
else:
result = false
proc getFd*(socket: Socket): SocketHandle = return socket.fd
## Returns the socket's file descriptor
type
IpAddressFamily* {.pure.} = enum ## Describes the type of an IP address
IPv6, ## IPv6 address
IPv4 ## IPv4 address
TIpAddress* = object ## stores an arbitrary IP address
case family*: IpAddressFamily ## the type of the IP address (IPv4 or IPv6)
of IpAddressFamily.IPv6:
address_v6*: array[0..15, uint8] ## Contains the IP address in bytes in
## case of IPv6
of IpAddressFamily.IPv4:
address_v4*: array[0..3, uint8] ## Contains the IP address in bytes in
## case of IPv4
proc IPv4_any*(): TIpAddress =
## Returns the IPv4 any address, which can be used to listen on all available
## network adapters
@@ -1216,7 +1250,7 @@ proc parseIPv6Address(address_str: string): TIpAddress =
raise newException(ValueError,
"Invalid IP Address. The address consists of too many groups")
proc parseIpAddress*(address_str: string): TIpAddress =
proc parseIpAddress(address_str: string): TIpAddress =
## Parses an IP address
## Raises EInvalidValue on error
if address_str == nil:
@@ -1225,3 +1259,13 @@ proc parseIpAddress*(address_str: string): TIpAddress =
return parseIPv6Address(address_str)
else:
return parseIPv4Address(address_str)
proc isIpAddress(address_str: string): bool =
## Checks if a string is an IP address
## Returns true if it is, false otherwise
try:
discard parseIpAddress(address_str)
except ValueError:
return false
return true

View File

@@ -132,7 +132,7 @@ else:
proc hook(st: TStackTrace) {.nimcall.} =
if interval == 0:
hookAux(st, 1)
elif getTicks() - t0 > interval:
elif int64(t0) == 0 or getTicks() - t0 > interval:
hookAux(st, 1)
t0 = getTicks()

View File

@@ -88,6 +88,6 @@ proc generatedTime*(oid: Oid): Time =
bigEndian32(addr(tmp), addr(dummy))
result = Time(tmp)
when isMainModule:
when not defined(testing) and isMainModule:
let xo = genOid()
echo xo.generatedTime

View File

@@ -1,7 +1,7 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2014 Andreas Rumpf
# (c) Copyright 2015 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@@ -185,7 +185,7 @@ const
proc osErrorMsg*(): string {.rtl, extern: "nos$1", deprecated.} =
## Retrieves the operating system's error flag, ``errno``.
## On Windows ``GetLastError`` is checked before ``errno``.
## Returns "" if no error occured.
## Returns "" if no error occurred.
##
## **Deprecated since version 0.9.4**: use the other ``osErrorMsg`` proc.
@@ -599,7 +599,7 @@ proc splitPath*(path: string): tuple[head, tail: string] {.
proc parentDirPos(path: string): int =
var q = 1
if path[len(path)-1] in {DirSep, AltSep}: q = 2
if len(path) >= 1 and path[len(path)-1] in {DirSep, AltSep}: q = 2
for i in countdown(len(path)-q, 0):
if path[i] in {DirSep, AltSep}: return i
result = -1
@@ -1010,8 +1010,16 @@ proc copyFile*(source, dest: string) {.rtl, extern: "nos$1",
proc moveFile*(source, dest: string) {.rtl, extern: "nos$1",
tags: [ReadIOEffect, WriteIOEffect].} =
## Moves a file from `source` to `dest`. If this fails, `OSError` is raised.
if c_rename(source, dest) != 0'i32:
raise newException(OSError, $strerror(errno))
when defined(Windows):
when useWinUnicode:
let s = newWideCString(source)
let d = newWideCString(dest)
if moveFileW(s, d, 0'i32) == 0'i32: raiseOSError(osLastError())
else:
if moveFileA(source, dest, 0'i32) == 0'i32: raiseOSError(osLastError())
else:
if c_rename(source, dest) != 0'i32:
raise newException(OSError, $strerror(errno))
when not declared(ENOENT) and not defined(Windows):
when NoFakeVars:
@@ -1074,8 +1082,12 @@ when defined(windows):
# because we support Windows GUI applications, things get really
# messy here...
when useWinUnicode:
proc strEnd(cstr: WideCString, c = 0'i32): WideCString {.
importc: "wcschr", header: "<string.h>".}
when defined(cpp):
proc strEnd(cstr: WideCString, c = 0'i32): WideCString {.
importcpp: "(NI16*)wcschr((const wchar_t *)#, #)", header: "<string.h>".}
else:
proc strEnd(cstr: WideCString, c = 0'i32): WideCString {.
importc: "wcschr", header: "<string.h>".}
else:
proc strEnd(cstr: cstring, c = 0'i32): cstring {.
importc: "strchr", header: "<string.h>".}
@@ -1087,7 +1099,7 @@ when defined(windows):
var
env = getEnvironmentStringsW()
e = env
if e == nil: return # an error occured
if e == nil: return # an error occurred
while true:
var eend = strEnd(e)
add(environment, $e)
@@ -1098,7 +1110,7 @@ when defined(windows):
var
env = getEnvironmentStringsA()
e = env
if e == nil: return # an error occured
if e == nil: return # an error occurred
while true:
var eend = strEnd(e)
add(environment, $e)
@@ -1109,8 +1121,8 @@ when defined(windows):
else:
const
useNSGetEnviron = defined(macosx) and
(defined(createNimRtl) or defined(useNimRtl))
useNSGetEnviron = defined(macosx)
when useNSGetEnviron:
# From the manual:
# Shared libraries and bundles don't have direct access to environ,
@@ -1170,7 +1182,7 @@ proc putEnv*(key, val: string) {.tags: [WriteEnvEffect].} =
## If an error occurs, `EInvalidEnvVar` is raised.
# Note: by storing the string in the environment sequence,
# we gurantee that we don't free the memory before the program
# we guarantee that we don't free the memory before the program
# ends (this is needed for POSIX compliance). It is also needed so that
# the process itself may access its modified environment variables!
var indx = findEnvVar(key)
@@ -1287,8 +1299,16 @@ iterator walkDir*(dir: string): tuple[kind: PathComponent, path: string] {.
if y != "." and y != "..":
var s: TStat
y = dir / y
if lstat(y, s) < 0'i32: break
var k = pcFile
when defined(linux) or defined(macosx) or defined(bsd):
if x.d_type != DT_UNKNOWN:
if x.d_type == DT_DIR: k = pcDir
if x.d_type == DT_LNK: k = succ(k)
yield (k, y)
continue
if lstat(y, s) < 0'i32: break
if S_ISDIR(s.st_mode): k = pcDir
if S_ISLNK(s.st_mode): k = succ(k)
yield (k, y)
@@ -1335,7 +1355,7 @@ proc rawRemoveDir(dir: string) =
if rmdir(dir) != 0'i32 and errno != ENOENT: raiseOSError(osLastError())
proc removeDir*(dir: string) {.rtl, extern: "nos$1", tags: [
WriteDirEffect, ReadDirEffect].} =
WriteDirEffect, ReadDirEffect], benign.} =
## Removes the directory `dir` including all subdirectories and files
## in `dir` (recursively).
##
@@ -1381,7 +1401,7 @@ proc createDir*(dir: string) {.rtl, extern: "nos$1", tags: [WriteDirEffect].} =
rawCreateDir(dir)
proc copyDir*(source, dest: string) {.rtl, extern: "nos$1",
tags: [WriteIOEffect, ReadIOEffect].} =
tags: [WriteIOEffect, ReadIOEffect], benign.} =
## Copies a directory from `source` to `dest`.
##
## If this fails, `OSError` is raised. On the Windows platform this proc will
@@ -1442,7 +1462,7 @@ proc createHardlink*(src, dest: string) =
proc parseCmdLine*(c: string): seq[string] {.
noSideEffect, rtl, extern: "nos$1".} =
## Splits a command line into several components;
## This proc is only occassionally useful, better use the `parseopt` module.
## This proc is only occasionally useful, better use the `parseopt` module.
##
## On Windows, it uses the following parsing rules
## (see http://msdn.microsoft.com/en-us/library/17w5ykft.aspx ):
@@ -1554,7 +1574,7 @@ proc copyFileWithPermissions*(source, dest: string,
proc copyDirWithPermissions*(source, dest: string,
ignorePermissionErrors = true) {.rtl, extern: "nos$1",
tags: [WriteIOEffect, ReadIOEffect].} =
tags: [WriteIOEffect, ReadIOEffect], benign.} =
## Copies a directory from `source` to `dest` preserving file permissions.
##
## If this fails, `OSError` is raised. This is a wrapper proc around `copyDir()
@@ -1843,16 +1863,21 @@ proc getFileSize*(file: string): BiggestInt {.rtl, extern: "nos$1",
close(f)
else: raiseOSError(osLastError())
proc expandTilde*(path: string): string {.tags: [ReadEnvEffect].}
proc findExe*(exe: string): string {.tags: [ReadDirEffect, ReadEnvEffect].} =
## Searches for `exe` in the current working directory and then
## in directories listed in the ``PATH`` environment variable.
## Returns "" if the `exe` cannot be found. On DOS-like platforms, `exe`
## is added an ``.exe`` file extension if it has no extension.
## is added the `ExeExt <#ExeExt>`_ file extension if it has none.
result = addFileExt(exe, os.ExeExt)
if existsFile(result): return
var path = string(os.getEnv("PATH"))
for candidate in split(path, PathSep):
var x = candidate / result
when defined(windows):
var x = candidate / result
else:
var x = expandTilde(candidate) / result
if existsFile(x): return x
result = ""
@@ -1977,6 +2002,8 @@ proc getFileInfo*(handle: FileHandle): FileInfo =
rawToFormalFileInfo(rawInfo, result)
proc getFileInfo*(file: File): FileInfo =
if file.isNil:
raise newException(IOError, "File is nil")
result = getFileInfo(file.getFileHandle())
proc getFileInfo*(path: string, followSymlink = true): FileInfo =

View File

@@ -1,7 +1,7 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2014 Andreas Rumpf
# (c) Copyright 2015 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@@ -137,7 +137,7 @@ proc startProcess*(command: string,
## `env` is the environment that will be passed to the process.
## If ``env == nil`` the environment is inherited of
## the parent process. `options` are additional flags that may be passed
## to `startProcess`. See the documentation of ``TProcessOption`` for the
## to `startProcess`. See the documentation of ``ProcessOption`` for the
## meaning of these flags. You need to `close` the process when done.
##
## Note that you can't pass any `args` if you use the option
@@ -146,7 +146,7 @@ proc startProcess*(command: string,
## of `args` to `command` carefully escaping/quoting any special characters,
## since it will be passed *as is* to the system shell. Each system/shell may
## feature different escaping rules, so try to avoid this kind of shell
## invokation if possible as it leads to non portable software.
## invocation if possible as it leads to non portable software.
##
## Return value: The newly created process object. Nil is never returned,
## but ``EOS`` is raised in case of an error.
@@ -174,7 +174,7 @@ proc terminate*(p: Process) {.rtl, extern: "nosp$1", tags: [].}
proc kill*(p: Process) {.rtl, extern: "nosp$1", tags: [].}
## Kill the process `p`. On Posix OSes the procedure sends ``SIGKILL`` to
## the process. On Windows ``kill()`` is simply an alias for ``terminate()``.
proc running*(p: Process): bool {.rtl, extern: "nosp$1", tags: [].}
## Returns true iff the process `p` is still running. Returns immediately.
@@ -260,7 +260,7 @@ proc execProcesses*(cmds: openArray[string],
for i in 0..m-1:
if beforeRunEvent != nil:
beforeRunEvent(i)
q[i] = startCmd(cmds[i], options=options)
q[i] = startProcess(cmds[i], options=options + {poEvalCommand})
when defined(noBusyWaiting):
var r = 0
for i in m..high(cmds):
@@ -275,7 +275,7 @@ proc execProcesses*(cmds: openArray[string],
if q[r] != nil: close(q[r])
if beforeRunEvent != nil:
beforeRunEvent(i)
q[r] = startCmd(cmds[i], options=options)
q[r] = startProcess(cmds[i], options=options + {poEvalCommand})
r = (r + 1) mod n
else:
var i = m
@@ -288,7 +288,7 @@ proc execProcesses*(cmds: openArray[string],
if q[r] != nil: close(q[r])
if beforeRunEvent != nil:
beforeRunEvent(i)
q[r] = startCmd(cmds[i], options=options)
q[r] = startProcess(cmds[i], options=options + {poEvalCommand})
inc(i)
if i > high(cmds): break
for j in 0..m-1:
@@ -298,7 +298,7 @@ proc execProcesses*(cmds: openArray[string],
for i in 0..high(cmds):
if beforeRunEvent != nil:
beforeRunEvent(i)
var p = startCmd(cmds[i], options=options)
var p = startProcess(cmds[i], options=options + {poEvalCommand})
result = max(waitForExit(p), result)
close(p)
@@ -644,14 +644,14 @@ elif not defined(useNimRtl):
var pid: TPid
var sysArgs = allocCStringArray(sysArgsRaw)
finally: deallocCStringArray(sysArgs)
defer: deallocCStringArray(sysArgs)
var sysEnv = if env == nil:
envToCStringArray()
else:
envToCStringArray(env)
finally: deallocCStringArray(sysEnv)
defer: deallocCStringArray(sysEnv)
var data: TStartProcessData
data.sysCommand = sysCommand
@@ -666,7 +666,7 @@ elif not defined(useNimRtl):
data.workingDir = workingDir
when declared(posix_spawn) and not defined(useFork) and
when declared(posix_spawn) and not defined(useFork) and
not defined(useClone) and not defined(linux):
pid = startProcessAuxSpawn(data)
else:
@@ -748,7 +748,7 @@ elif not defined(useNimRtl):
if pipe(data.pErrorPipe) != 0:
raiseOSError(osLastError())
finally:
defer:
discard close(data.pErrorPipe[readIdx])
var pid: TPid
@@ -823,7 +823,7 @@ elif not defined(useNimRtl):
discard execvp(data.sysCommand, data.sysArgs)
else:
when defined(uClibc):
# uClibc environment (OpenWrt included) doesn't have the full execvpe
# uClibc environment (OpenWrt included) doesn't have the full execvpe
discard execve(data.sysCommand, data.sysArgs, data.sysEnv)
else:
discard execvpe(data.sysCommand, data.sysArgs, data.sysEnv)
@@ -848,7 +848,14 @@ elif not defined(useNimRtl):
if kill(p.id, SIGCONT) != 0'i32: raiseOsError(osLastError())
proc running(p: Process): bool =
var ret = waitpid(p.id, p.exitCode, WNOHANG)
var ret : int
when not defined(freebsd):
ret = waitpid(p.id, p.exitCode, WNOHANG)
else:
var status : cint = 1
ret = waitpid(p.id, status, WNOHANG)
if WIFEXITED(status):
p.exitCode = status
if ret == 0: return true # Can't establish status. Assume running.
result = ret == int(p.id)
@@ -857,9 +864,9 @@ elif not defined(useNimRtl):
raiseOsError(osLastError())
proc kill(p: Process) =
if kill(p.id, SIGKILL) != 0'i32:
if kill(p.id, SIGKILL) != 0'i32:
raiseOsError(osLastError())
proc waitForExit(p: Process, timeout: int = -1): int =
#if waitPid(p.id, p.exitCode, 0) == int(p.id):
# ``waitPid`` fails if the process is not running anymore. But then
@@ -876,7 +883,7 @@ elif not defined(useNimRtl):
var ret = waitpid(p.id, p.exitCode, WNOHANG)
var b = ret == int(p.id)
if b: result = -1
if p.exitCode == -3: result = -1
if not WIFEXITED(p.exitCode): result = -1
else: result = p.exitCode.int shr 8
proc createStream(stream: var Stream, handle: var FileHandle,
@@ -900,7 +907,7 @@ elif not defined(useNimRtl):
createStream(p.errStream, p.errHandle, fmRead)
return p.errStream
proc csystem(cmd: cstring): cint {.nodecl, importc: "system",
proc csystem(cmd: cstring): cint {.nodecl, importc: "system",
header: "<stdlib.h>".}
proc execCmd(command: string): int =
@@ -949,7 +956,7 @@ proc execCmdEx*(command: string, options: set[ProcessOption] = {
exitCode: int] {.tags: [ExecIOEffect, ReadIOEffect], gcsafe.} =
## a convenience proc that runs the `command`, grabs all its output and
## exit code and returns both.
var p = startCmd(command, options)
var p = startProcess(command, options=options + {poEvalCommand})
var outp = outputStream(p)
result = (TaintedString"", -1)
var line = newStringOfCap(120).TaintedString

View File

@@ -35,7 +35,7 @@ type
cfgSectionStart, ## a ``[section]`` has been parsed
cfgKeyValuePair, ## a ``key=value`` pair has been detected
cfgOption, ## a ``--key=value`` command line option
cfgError ## an error ocurred during parsing
cfgError ## an error occurred during parsing
CfgEvent* = object of RootObj ## describes a parsing event
case kind*: CfgEventKind ## the kind of the event

View File

@@ -166,7 +166,7 @@ proc close*(my: var CsvParser) {.inline.} =
## closes the parser `my` and its associated input stream.
lexbase.close(my)
when isMainModule:
when not defined(testing) and isMainModule:
import os
var s = newFileStream(paramStr(1), fmRead)
if s == nil: quit("cannot open the file" & paramStr(1))

View File

@@ -1,7 +1,7 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2014 Andreas Rumpf
# (c) Copyright 2015 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@@ -11,9 +11,12 @@
## It supports one convenience iterator over all command line options and some
## lower-level features.
##
## **Deprecated since version 0.9.3:** Use the `parseopt2 <parseopt2.html>`_
## module instead as this version has issues with spaces in arguments.
{.deprecated.}
## Supported syntax:
##
## 1. short options - ``-abcd``, where a, b, c, d are names
## 2. long option - ``--foo:bar``, ``--foo=bar`` or ``--foo``
## 3. argument - everything else
{.push debugger: off.}
include "system/inclrtl"

View File

@@ -1,7 +1,7 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2014 Andreas Rumpf
# (c) Copyright 2015 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@@ -60,7 +60,7 @@ proc initOptParser*(cmdline: string): OptParser {.rtl, deprecated.} =
## Initalizes option parses with cmdline. Splits cmdline in on spaces
## and calls initOptParser(openarray[string])
## Do not use.
if cmdline == "": # backward compatibilty
if cmdline == "": # backward compatibility
return initOptParser(seq[string](nil))
else:
return initOptParser(cmdline.split)

View File

@@ -1330,7 +1330,7 @@ proc renderSQL*(n: SqlNode): string =
result = ""
ra(n, result, 0)
when isMainModule:
when not defined(testing) and isMainModule:
echo(renderSQL(parseSQL(newStringStream("""
CREATE TYPE happiness AS ENUM ('happy', 'very happy', 'ecstatic');
CREATE TABLE holidays (

View File

@@ -1,7 +1,7 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2014 Dominik Picheta
# (c) Copyright 2015 Dominik Picheta
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.

View File

@@ -181,7 +181,7 @@ proc parseWhile*(s: string, token: var string, validChars: set[char],
token = substr(s, start, i-1)
proc captureBetween*(s: string, first: char, second = '\0', start = 0): string =
## Finds the first occurence of ``first``, then returns everything from there
## Finds the first occurrence of ``first``, then returns everything from there
## up to ``second``(if ``second`` is '\0', then ``first`` is used).
var i = skipUntil(s, first, start)+1+start
result = ""
@@ -228,7 +228,7 @@ proc parseInt*(s: string, number: var int, start = 0): int {.
if (sizeof(int) <= 4) and
((res < low(int)) or (res > high(int))):
raise newException(OverflowError, "overflow")
else:
elif result != 0:
number = int(res)
proc parseBiggestFloat*(s: string, number: var BiggestFloat, start = 0): int {.
@@ -240,11 +240,12 @@ proc parseBiggestFloat*(s: string, number: var BiggestFloat, start = 0): int {.
proc parseFloat*(s: string, number: var float, start = 0): int {.
rtl, extern: "npuParseFloat", noSideEffect.} =
## parses a float starting at `start` and stores the value into `number`.
## Result is the number of processed chars or 0 if there occured a parsing
## Result is the number of processed chars or 0 if there occurred a parsing
## error.
var bf: BiggestFloat
result = parseBiggestFloat(s, bf, start)
number = bf
if result != 0:
number = bf
type
InterpolatedKind* = enum ## describes for `interpolatedFragments`
@@ -294,7 +295,7 @@ iterator interpolatedFragments*(s: string): tuple[kind: InterpolatedKind,
dec nesting
of '\0':
raise newException(ValueError,
"Expected closing '}': " & s[i..s.len])
"Expected closing '}': " & substr(s, i, s.high))
else: discard
inc j
inc i, 2 # skip ${
@@ -310,7 +311,7 @@ iterator interpolatedFragments*(s: string): tuple[kind: InterpolatedKind,
kind = ikDollar
else:
raise newException(ValueError,
"Unable to parse a varible name at " & s[i..s.len])
"Unable to parse a varible name at " & substr(s, i, s.high))
else:
while j < s.len and s[j] != '$': inc j
kind = ikStr
@@ -322,8 +323,12 @@ iterator interpolatedFragments*(s: string): tuple[kind: InterpolatedKind,
i = j
when isMainModule:
for k, v in interpolatedFragments("$test{} $this is ${an{ example}} "):
echo "(", k, ", \"", v, "\")"
import sequtils
let input = "$test{} $this is ${an{ example}} "
let expected = @[(ikVar, "test"), (ikStr, "{} "), (ikVar, "this"),
(ikStr, " is "), (ikExpr, "an{ example}"), (ikStr, " ")]
assert toSeq(interpolatedFragments(input)) == expected
var value = 0
discard parseHex("0x38", value)
assert value == 56

View File

@@ -57,7 +57,7 @@ import
type
XmlEventKind* = enum ## enumation of all events that may occur when parsing
xmlError, ## an error ocurred during parsing
xmlError, ## an error occurred during parsing
xmlEof, ## end of file reached
xmlCharData, ## character data
xmlWhitespace, ## whitespace has been parsed
@@ -128,6 +128,7 @@ proc open*(my: var XmlParser, input: Stream, filename: string,
my.kind = xmlError
my.a = ""
my.b = ""
my.c = nil
my.options = options
proc close*(my: var XmlParser) {.inline.} =
@@ -138,43 +139,43 @@ proc kind*(my: XmlParser): XmlEventKind {.inline.} =
## returns the current event type for the XML parser
return my.kind
proc charData*(my: XmlParser): string {.inline.} =
template charData*(my: XmlParser): string =
## returns the character data for the events: ``xmlCharData``,
## ``xmlWhitespace``, ``xmlComment``, ``xmlCData``, ``xmlSpecial``
assert(my.kind in {xmlCharData, xmlWhitespace, xmlComment, xmlCData,
xmlSpecial})
return my.a
my.a
proc elementName*(my: XmlParser): string {.inline.} =
template elementName*(my: XmlParser): string =
## returns the element name for the events: ``xmlElementStart``,
## ``xmlElementEnd``, ``xmlElementOpen``
assert(my.kind in {xmlElementStart, xmlElementEnd, xmlElementOpen})
return my.a
my.a
proc entityName*(my: XmlParser): string {.inline.} =
template entityName*(my: XmlParser): string =
## returns the entity name for the event: ``xmlEntity``
assert(my.kind == xmlEntity)
return my.a
my.a
proc attrKey*(my: XmlParser): string {.inline.} =
template attrKey*(my: XmlParser): string =
## returns the attribute key for the event ``xmlAttribute``
assert(my.kind == xmlAttribute)
return my.a
my.a
proc attrValue*(my: XmlParser): string {.inline.} =
template attrValue*(my: XmlParser): string =
## returns the attribute value for the event ``xmlAttribute``
assert(my.kind == xmlAttribute)
return my.b
my.b
proc piName*(my: XmlParser): string {.inline.} =
template piName*(my: XmlParser): string =
## returns the processing instruction name for the event ``xmlPI``
assert(my.kind == xmlPI)
return my.a
my.a
proc piRest*(my: XmlParser): string {.inline.} =
template piRest*(my: XmlParser): string =
## returns the rest of the processing instruction for the event ``xmlPI``
assert(my.kind == xmlPI)
return my.b
my.b
proc rawData*(my: XmlParser): string {.inline.} =
## returns the underlying 'data' string by reference.
@@ -621,13 +622,13 @@ proc next*(my: var XmlParser) =
of stateEmptyElementTag:
my.state = stateNormal
my.kind = xmlElementEnd
if not isNil(my.c):
if not my.c.isNil:
my.a = my.c
of stateError:
my.kind = xmlError
my.state = stateNormal
when isMainModule:
when not defined(testing) and isMainModule:
import os
var s = newFileStream(paramStr(1), fmRead)
if s == nil: quit("cannot open the file" & paramStr(1))

View File

@@ -29,7 +29,7 @@ when useUnicode:
const
InlineThreshold = 5 ## number of leaves; -1 to disable inlining
MaxSubpatterns* = 20 ## defines the maximum number of subpatterns that
## can be captured. More subpatterns cannot be captured!
## can be captured. More subpatterns cannot be captured!
type
PegKind = enum
@@ -85,14 +85,14 @@ type
of pkBackRef..pkBackRefIgnoreStyle: index: range[0..MaxSubpatterns]
else: sons: seq[TNode]
NonTerminal* = ref NonTerminalObj
Peg* = TNode ## type that represents a PEG
{.deprecated: [TPeg: Peg].}
proc term*(t: string): Peg {.nosideEffect, rtl, extern: "npegs$1Str".} =
## constructs a PEG from a terminal string
if t.len != 1:
if t.len != 1:
result.kind = pkTerminal
result.term = t
else:
@@ -116,7 +116,7 @@ proc term*(t: char): Peg {.nosideEffect, rtl, extern: "npegs$1Char".} =
assert t != '\0'
result.kind = pkChar
result.ch = t
proc charSet*(s: set[char]): Peg {.nosideEffect, rtl, extern: "npegs$1".} =
## constructs a PEG from a character set `s`
assert '\0' notin s
@@ -129,12 +129,12 @@ proc add(d: var Peg, s: Peg) {.inline.} = add(d.sons, s)
proc addChoice(dest: var Peg, elem: Peg) =
var L = dest.len-1
if L >= 0 and dest.sons[L].kind == pkCharChoice:
if L >= 0 and dest.sons[L].kind == pkCharChoice:
# caution! Do not introduce false aliasing here!
case elem.kind
of pkCharChoice:
dest.sons[L] = charSet(dest.sons[L].charChoice[] + elem.charChoice[])
of pkChar:
of pkChar:
dest.sons[L] = charSet(dest.sons[L].charChoice[] + {elem.ch})
else: add(dest, elem)
else: add(dest, elem)
@@ -158,12 +158,12 @@ proc `/`*(a: varargs[Peg]): Peg {.
proc addSequence(dest: var Peg, elem: Peg) =
var L = dest.len-1
if L >= 0 and dest.sons[L].kind == pkTerminal:
if L >= 0 and dest.sons[L].kind == pkTerminal:
# caution! Do not introduce false aliasing here!
case elem.kind
of pkTerminal:
of pkTerminal:
dest.sons[L] = term(dest.sons[L].term & elem.term)
of pkChar:
of pkChar:
dest.sons[L] = term(dest.sons[L].term & elem.ch)
else: add(dest, elem)
else: add(dest, elem)
@@ -172,7 +172,7 @@ proc sequence*(a: varargs[Peg]): Peg {.
nosideEffect, rtl, extern: "npegs$1".} =
## constructs a sequence with all the PEGs from `a`
multipleOp(pkSequence, addSequence)
proc `?`*(a: Peg): Peg {.nosideEffect, rtl, extern: "npegsOptional".} =
## constructs an optional for the PEG `a`
if a.kind in {pkOption, pkGreedyRep, pkGreedyAny, pkGreedyRepChar,
@@ -207,7 +207,7 @@ proc `!*`*(a: Peg): Peg {.nosideEffect, rtl, extern: "npegsSearch".} =
result.kind = pkSearch
result.sons = @[a]
proc `!*\`*(a: Peg): Peg {.noSideEffect, rtl,
proc `!*\`*(a: Peg): Peg {.noSideEffect, rtl,
extern: "npgegsCapturedSearch".} =
## constructs a "captured search" for the PEG `a`
result.kind = pkCapturedSearch
@@ -216,7 +216,7 @@ proc `!*\`*(a: Peg): Peg {.noSideEffect, rtl,
proc `+`*(a: Peg): Peg {.nosideEffect, rtl, extern: "npegsGreedyPosRep".} =
## constructs a "greedy positive repetition" with the PEG `a`
return sequence(a, *a)
proc `&`*(a: Peg): Peg {.nosideEffect, rtl, extern: "npegsAndPredicate".} =
## constructs an "and predicate" with the PEG `a`
result.kind = pkAndPredicate
@@ -239,33 +239,33 @@ proc newLine*: Peg {.inline.} =
## constructs the PEG `newline`:idx: (``\n``)
result.kind = pkNewLine
proc unicodeLetter*: Peg {.inline.} =
proc unicodeLetter*: Peg {.inline.} =
## constructs the PEG ``\letter`` which matches any Unicode letter.
result.kind = pkLetter
proc unicodeLower*: Peg {.inline.} =
## constructs the PEG ``\lower`` which matches any Unicode lowercase letter.
result.kind = pkLower
proc unicodeUpper*: Peg {.inline.} =
proc unicodeLower*: Peg {.inline.} =
## constructs the PEG ``\lower`` which matches any Unicode lowercase letter.
result.kind = pkLower
proc unicodeUpper*: Peg {.inline.} =
## constructs the PEG ``\upper`` which matches any Unicode uppercase letter.
result.kind = pkUpper
proc unicodeTitle*: Peg {.inline.} =
proc unicodeTitle*: Peg {.inline.} =
## constructs the PEG ``\title`` which matches any Unicode title letter.
result.kind = pkTitle
proc unicodeWhitespace*: Peg {.inline.} =
## constructs the PEG ``\white`` which matches any Unicode
proc unicodeWhitespace*: Peg {.inline.} =
## constructs the PEG ``\white`` which matches any Unicode
## whitespace character.
result.kind = pkWhitespace
proc startAnchor*: Peg {.inline.} =
## constructs the PEG ``^`` which matches the start of the input.
proc startAnchor*: Peg {.inline.} =
## constructs the PEG ``^`` which matches the start of the input.
result.kind = pkStartAnchor
proc endAnchor*: Peg {.inline.} =
## constructs the PEG ``$`` which matches the end of the input.
proc endAnchor*: Peg {.inline.} =
## constructs the PEG ``$`` which matches the end of the input.
result = !any()
proc capture*(a: Peg): Peg {.nosideEffect, rtl, extern: "npegsCapture".} =
@@ -274,21 +274,21 @@ proc capture*(a: Peg): Peg {.nosideEffect, rtl, extern: "npegsCapture".} =
result.sons = @[a]
proc backref*(index: range[1..MaxSubpatterns]): Peg {.
nosideEffect, rtl, extern: "npegs$1".} =
nosideEffect, rtl, extern: "npegs$1".} =
## constructs a back reference of the given `index`. `index` starts counting
## from 1.
result.kind = pkBackRef
result.index = index-1
proc backrefIgnoreCase*(index: range[1..MaxSubpatterns]): Peg {.
nosideEffect, rtl, extern: "npegs$1".} =
nosideEffect, rtl, extern: "npegs$1".} =
## constructs a back reference of the given `index`. `index` starts counting
## from 1. Ignores case for matching.
result.kind = pkBackRefIgnoreCase
result.index = index-1
proc backrefIgnoreStyle*(index: range[1..MaxSubpatterns]): Peg {.
nosideEffect, rtl, extern: "npegs$1".}=
nosideEffect, rtl, extern: "npegs$1".}=
## constructs a back reference of the given `index`. `index` starts counting
## from 1. Ignores style for matching.
result.kind = pkBackRefIgnoreStyle
@@ -298,7 +298,7 @@ proc spaceCost(n: Peg): int =
case n.kind
of pkEmpty: discard
of pkTerminal, pkTerminalIgnoreCase, pkTerminalIgnoreStyle, pkChar,
pkGreedyRepChar, pkCharChoice, pkGreedyRepSet,
pkGreedyRepChar, pkCharChoice, pkGreedyRepSet,
pkAny..pkWhitespace, pkGreedyAny:
result = 1
of pkNonTerminal:
@@ -310,7 +310,7 @@ proc spaceCost(n: Peg): int =
if result >= InlineThreshold: break
proc nonterminal*(n: NonTerminal): Peg {.
nosideEffect, rtl, extern: "npegs$1".} =
nosideEffect, rtl, extern: "npegs$1".} =
## constructs a PEG that consists of the nonterminal symbol
assert n != nil
if ntDeclared in n.flags and spaceCost(n.rule) < InlineThreshold:
@@ -331,7 +331,7 @@ proc newNonTerminal*(name: string, line, column: int): NonTerminal {.
template letters*: expr =
## expands to ``charset({'A'..'Z', 'a'..'z'})``
charSet({'A'..'Z', 'a'..'z'})
template digits*: expr =
## expands to ``charset({'0'..'9'})``
charSet({'0'..'9'})
@@ -339,11 +339,11 @@ template digits*: expr =
template whitespace*: expr =
## expands to ``charset({' ', '\9'..'\13'})``
charSet({' ', '\9'..'\13'})
template identChars*: expr =
## expands to ``charset({'a'..'z', 'A'..'Z', '0'..'9', '_'})``
charSet({'a'..'z', 'A'..'Z', '0'..'9', '_'})
template identStartChars*: expr =
## expands to ``charset({'A'..'Z', 'a'..'z', '_'})``
charSet({'a'..'z', 'A'..'Z', '_'})
@@ -352,14 +352,14 @@ template ident*: expr =
## same as ``[a-zA-Z_][a-zA-z_0-9]*``; standard identifier
sequence(charSet({'a'..'z', 'A'..'Z', '_'}),
*charSet({'a'..'z', 'A'..'Z', '0'..'9', '_'}))
template natural*: expr =
## same as ``\d+``
+digits
# ------------------------- debugging -----------------------------------------
proc esc(c: char, reserved = {'\0'..'\255'}): string =
proc esc(c: char, reserved = {'\0'..'\255'}): string =
case c
of '\b': result = "\\b"
of '\t': result = "\\t"
@@ -374,38 +374,38 @@ proc esc(c: char, reserved = {'\0'..'\255'}): string =
elif c < ' ' or c >= '\128': result = '\\' & $ord(c)
elif c in reserved: result = '\\' & c
else: result = $c
proc singleQuoteEsc(c: char): string = return "'" & esc(c, {'\''}) & "'"
proc singleQuoteEsc(str: string): string =
proc singleQuoteEsc(str: string): string =
result = "'"
for c in items(str): add result, esc(c, {'\''})
add result, '\''
proc charSetEscAux(cc: set[char]): string =
proc charSetEscAux(cc: set[char]): string =
const reserved = {'^', '-', ']'}
result = ""
var c1 = 0
while c1 <= 0xff:
if chr(c1) in cc:
while c1 <= 0xff:
if chr(c1) in cc:
var c2 = c1
while c2 < 0xff and chr(succ(c2)) in cc: inc(c2)
if c1 == c2:
if c1 == c2:
add result, esc(chr(c1), reserved)
elif c2 == succ(c1):
elif c2 == succ(c1):
add result, esc(chr(c1), reserved) & esc(chr(c2), reserved)
else:
else:
add result, esc(chr(c1), reserved) & '-' & esc(chr(c2), reserved)
c1 = c2
inc(c1)
proc charSetEsc(cc: set[char]): string =
if card(cc) >= 128+64:
if card(cc) >= 128+64:
result = "[^" & charSetEscAux({'\1'..'\xFF'} - cc) & ']'
else:
else:
result = '[' & charSetEscAux(cc) & ']'
proc toStrAux(r: Peg, res: var string) =
proc toStrAux(r: Peg, res: var string) =
case r.kind
of pkEmpty: add(res, "()")
of pkAny: add(res, '.')
@@ -469,25 +469,25 @@ proc toStrAux(r: Peg, res: var string) =
toStrAux(r.sons[0], res)
of pkCapture:
add(res, '{')
toStrAux(r.sons[0], res)
toStrAux(r.sons[0], res)
add(res, '}')
of pkBackRef:
of pkBackRef:
add(res, '$')
add(res, $r.index)
of pkBackRefIgnoreCase:
of pkBackRefIgnoreCase:
add(res, "i$")
add(res, $r.index)
of pkBackRefIgnoreStyle:
of pkBackRefIgnoreStyle:
add(res, "y$")
add(res, $r.index)
of pkRule:
toStrAux(r.sons[0], res)
toStrAux(r.sons[0], res)
add(res, " <- ")
toStrAux(r.sons[1], res)
of pkList:
for i in 0 .. high(r.sons):
toStrAux(r.sons[i], res)
add(res, "\n")
add(res, "\n")
of pkStartAnchor:
add(res, '^')
@@ -506,14 +506,14 @@ type
{.deprecated: [TCaptures: Captures].}
proc bounds*(c: Captures,
i: range[0..MaxSubpatterns-1]): tuple[first, last: int] =
proc bounds*(c: Captures,
i: range[0..MaxSubpatterns-1]): tuple[first, last: int] =
## returns the bounds ``[first..last]`` of the `i`'th capture.
result = c.matches[i]
when not useUnicode:
type
TRune = char
Rune = char
template fastRuneAt(s, i, ch: expr) =
ch = s[i]
inc(i)
@@ -527,7 +527,7 @@ when not useUnicode:
proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int {.
nosideEffect, rtl, extern: "npegs$1".} =
## low-level matching proc that implements the PEG interpreter. Use this
## low-level matching proc that implements the PEG interpreter. Use this
## for maximum efficiency (every other PEG operation ends up calling this
## proc).
## Returns -1 if it does not match, else the length of the match
@@ -541,7 +541,7 @@ proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int {.
result = runeLenAt(s, start)
else:
result = -1
of pkLetter:
of pkLetter:
if s[start] != '\0':
var a: Rune
result = start
@@ -550,7 +550,7 @@ proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int {.
else: result = -1
else:
result = -1
of pkLower:
of pkLower:
if s[start] != '\0':
var a: Rune
result = start
@@ -559,7 +559,7 @@ proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int {.
else: result = -1
else:
result = -1
of pkUpper:
of pkUpper:
if s[start] != '\0':
var a: Rune
result = start
@@ -568,16 +568,16 @@ proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int {.
else: result = -1
else:
result = -1
of pkTitle:
of pkTitle:
if s[start] != '\0':
var a: Rune
result = start
fastRuneAt(s, result, a)
if isTitle(a): dec(result, start)
if isTitle(a): dec(result, start)
else: result = -1
else:
result = -1
of pkWhitespace:
of pkWhitespace:
if s[start] != '\0':
var a: Rune
result = start
@@ -641,7 +641,7 @@ proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int {.
when false: echo "leave: ", p.nt.name
if result < 0: c.ml = oldMl
of pkSequence:
var oldMl = c.ml
var oldMl = c.ml
result = 0
for i in 0..high(p.sons):
var x = rawMatch(s, p.sons[i], start+result, c)
@@ -723,11 +723,11 @@ proc rawMatch*(s: string, p: Peg, start: int, c: var Captures): int {.
#else: silently ignore the capture
else:
c.ml = idx
of pkBackRef..pkBackRefIgnoreStyle:
of pkBackRef..pkBackRefIgnoreStyle:
if p.index >= c.ml: return -1
var (a, b) = c.matches[p.index]
var n: Peg
n.kind = succ(pkTerminal, ord(p.kind)-ord(pkBackRef))
n.kind = succ(pkTerminal, ord(p.kind)-ord(pkBackRef))
n.term = s.substr(a, b)
result = rawMatch(s, n, start, c)
of pkStartAnchor:
@@ -744,24 +744,6 @@ template fillMatches(s, caps, c: expr) =
else:
caps[k] = nil
proc match*(s: string, pattern: Peg, matches: var openArray[string],
start = 0): bool {.nosideEffect, rtl, extern: "npegs$1Capture".} =
## returns ``true`` if ``s[start..]`` matches the ``pattern`` and
## the captured substrings in the array ``matches``. If it does not
## match, nothing is written into ``matches`` and ``false`` is
## returned.
var c: Captures
c.origStart = start
result = rawMatch(s, pattern, start, c) == len(s) - start
if result: fillMatches(s, matches, c)
proc match*(s: string, pattern: Peg,
start = 0): bool {.nosideEffect, rtl, extern: "npegs$1".} =
## returns ``true`` if ``s`` matches the ``pattern`` beginning from ``start``.
var c: Captures
c.origStart = start
result = rawMatch(s, pattern, start, c) == len(s)-start
proc matchLen*(s: string, pattern: Peg, matches: var openArray[string],
start = 0): int {.nosideEffect, rtl, extern: "npegs$1Capture".} =
## the same as ``match``, but it returns the length of the match,
@@ -773,7 +755,7 @@ proc matchLen*(s: string, pattern: Peg, matches: var openArray[string],
result = rawMatch(s, pattern, start, c)
if result >= 0: fillMatches(s, matches, c)
proc matchLen*(s: string, pattern: Peg,
proc matchLen*(s: string, pattern: Peg,
start = 0): int {.nosideEffect, rtl, extern: "npegs$1".} =
## the same as ``match``, but it returns the length of the match,
## if there is no match, -1 is returned. Note that a match length
@@ -783,6 +765,20 @@ proc matchLen*(s: string, pattern: Peg,
c.origStart = start
result = rawMatch(s, pattern, start, c)
proc match*(s: string, pattern: Peg, matches: var openArray[string],
start = 0): bool {.nosideEffect, rtl, extern: "npegs$1Capture".} =
## returns ``true`` if ``s[start..]`` matches the ``pattern`` and
## the captured substrings in the array ``matches``. If it does not
## match, nothing is written into ``matches`` and ``false`` is
## returned.
result = matchLen(s, pattern, matches, start) != -1
proc match*(s: string, pattern: Peg,
start = 0): bool {.nosideEffect, rtl, extern: "npegs$1".} =
## returns ``true`` if ``s`` matches the ``pattern`` beginning from ``start``.
result = matchLen(s, pattern, start) != -1
proc find*(s: string, pattern: Peg, matches: var openArray[string],
start = 0): int {.nosideEffect, rtl, extern: "npegs$1Capture".} =
## returns the starting position of ``pattern`` in ``s`` and the captured
@@ -797,11 +793,11 @@ proc find*(s: string, pattern: Peg, matches: var openArray[string],
return i
return -1
# could also use the pattern here: (!P .)* P
proc findBounds*(s: string, pattern: Peg, matches: var openArray[string],
start = 0): tuple[first, last: int] {.
nosideEffect, rtl, extern: "npegs$1Capture".} =
## returns the starting position and end position of ``pattern`` in ``s``
## returns the starting position and end position of ``pattern`` in ``s``
## and the captured
## substrings in the array ``matches``. If it does not match, nothing
## is written into ``matches`` and (-1,0) is returned.
@@ -814,8 +810,8 @@ proc findBounds*(s: string, pattern: Peg, matches: var openArray[string],
fillMatches(s, matches, c)
return (i, i+L-1)
return (-1, 0)
proc find*(s: string, pattern: Peg,
proc find*(s: string, pattern: Peg,
start = 0): int {.nosideEffect, rtl, extern: "npegs$1".} =
## returns the starting position of ``pattern`` in ``s``. If it does not
## match, -1 is returned.
@@ -824,8 +820,8 @@ proc find*(s: string, pattern: Peg,
for i in start .. s.len-1:
if rawMatch(s, pattern, i, c) >= 0: return i
return -1
iterator findAll*(s: string, pattern: Peg, start = 0): string =
iterator findAll*(s: string, pattern: Peg, start = 0): string =
## yields all matching *substrings* of `s` that match `pattern`.
var c: Captures
c.origStart = start
@@ -838,23 +834,23 @@ iterator findAll*(s: string, pattern: Peg, start = 0): string =
else:
yield substr(s, i, i+L-1)
inc(i, L)
proc findAll*(s: string, pattern: Peg, start = 0): seq[string] {.
nosideEffect, rtl, extern: "npegs$1".} =
nosideEffect, rtl, extern: "npegs$1".} =
## returns all matching *substrings* of `s` that match `pattern`.
## If it does not match, @[] is returned.
accumulateResult(findAll(s, pattern, start))
when not defined(nimhygiene):
{.pragma: inject.}
template `=~`*(s: string, pattern: Peg): bool =
## This calls ``match`` with an implicit declared ``matches`` array that
## can be used in the scope of the ``=~`` call:
##
## This calls ``match`` with an implicit declared ``matches`` array that
## can be used in the scope of the ``=~`` call:
##
## .. code-block:: nim
##
## if line =~ peg"\s* {\w+} \s* '=' \s* {\w+}":
## if line =~ peg"\s* {\w+} \s* '=' \s* {\w+}":
## # matches a key=value pair:
## echo("Key: ", matches[0])
## echo("Value: ", matches[1])
@@ -865,7 +861,7 @@ template `=~`*(s: string, pattern: Peg): bool =
## echo("comment: ", matches[0])
## else:
## echo("syntax error")
##
##
bind MaxSubpatterns
when not declaredInScope(matches):
var matches {.inject.}: array[0..MaxSubpatterns-1, string]
@@ -902,7 +898,7 @@ proc replacef*(s: string, sub: Peg, by: string): string {.
## with the notation ``$i`` and ``$#`` (see strutils.`%`). Examples:
##
## .. code-block:: nim
## "var1=key; var2=key2".replace(peg"{\ident}'='{\ident}", "$1<-$2$2")
## "var1=key; var2=key2".replacef(peg"{\ident}'='{\ident}", "$1<-$2$2")
##
## Results in:
##
@@ -941,10 +937,10 @@ proc replace*(s: string, sub: Peg, by = ""): string {.
add(result, by)
inc(i, x)
add(result, substr(s, i))
proc parallelReplace*(s: string, subs: varargs[
tuple[pattern: Peg, repl: string]]): string {.
nosideEffect, rtl, extern: "npegs$1".} =
nosideEffect, rtl, extern: "npegs$1".} =
## Returns a modified copy of `s` with the substitutions in `subs`
## applied in parallel.
result = ""
@@ -964,8 +960,8 @@ proc parallelReplace*(s: string, subs: varargs[
add(result, s[i])
inc(i)
# copy the rest:
add(result, substr(s, i))
add(result, substr(s, i))
proc transformFile*(infile, outfile: string,
subs: varargs[tuple[pattern: Peg, repl: string]]) {.
rtl, extern: "npegs$1".} =
@@ -974,7 +970,7 @@ proc transformFile*(infile, outfile: string,
## error occurs. This is supposed to be used for quick scripting.
var x = readFile(infile).string
writeFile(outfile, x.parallelReplace(subs))
iterator split*(s: string, sep: Peg): string =
## Splits the string `s` into substrings.
##
@@ -1049,14 +1045,14 @@ type
tkBackref, ## '$'
tkDollar, ## '$'
tkHat ## '^'
TToken {.final.} = object ## a token
kind: TTokKind ## the type of the token
modifier: TModifier
literal: string ## the parsed (string) literal
charset: set[char] ## if kind == tkCharSet
index: int ## if kind == tkBackref
PegLexer {.inheritable.} = object ## the lexer object.
bufpos: int ## the current position within the buffer
buf: cstring ## the buffer itself
@@ -1086,7 +1082,7 @@ proc handleLF(L: var PegLexer, pos: int): int =
result = pos+1
L.lineStart = result
proc init(L: var PegLexer, input, filename: string, line = 1, col = 0) =
proc init(L: var PegLexer, input, filename: string, line = 1, col = 0) =
L.buf = input
L.bufpos = 0
L.lineNumber = line
@@ -1094,69 +1090,69 @@ proc init(L: var PegLexer, input, filename: string, line = 1, col = 0) =
L.lineStart = 0
L.filename = filename
proc getColumn(L: PegLexer): int {.inline.} =
proc getColumn(L: PegLexer): int {.inline.} =
result = abs(L.bufpos - L.lineStart) + L.colOffset
proc getLine(L: PegLexer): int {.inline.} =
proc getLine(L: PegLexer): int {.inline.} =
result = L.lineNumber
proc errorStr(L: PegLexer, msg: string, line = -1, col = -1): string =
var line = if line < 0: getLine(L) else: line
var col = if col < 0: getColumn(L) else: col
result = "$1($2, $3) Error: $4" % [L.filename, $line, $col, msg]
proc handleHexChar(c: var PegLexer, xi: var int) =
proc handleHexChar(c: var PegLexer, xi: var int) =
case c.buf[c.bufpos]
of '0'..'9':
of '0'..'9':
xi = (xi shl 4) or (ord(c.buf[c.bufpos]) - ord('0'))
inc(c.bufpos)
of 'a'..'f':
of 'a'..'f':
xi = (xi shl 4) or (ord(c.buf[c.bufpos]) - ord('a') + 10)
inc(c.bufpos)
of 'A'..'F':
of 'A'..'F':
xi = (xi shl 4) or (ord(c.buf[c.bufpos]) - ord('A') + 10)
inc(c.bufpos)
else: discard
proc getEscapedChar(c: var PegLexer, tok: var TToken) =
proc getEscapedChar(c: var PegLexer, tok: var TToken) =
inc(c.bufpos)
case c.buf[c.bufpos]
of 'r', 'R', 'c', 'C':
of 'r', 'R', 'c', 'C':
add(tok.literal, '\c')
inc(c.bufpos)
of 'l', 'L':
of 'l', 'L':
add(tok.literal, '\L')
inc(c.bufpos)
of 'f', 'F':
of 'f', 'F':
add(tok.literal, '\f')
inc(c.bufpos)
of 'e', 'E':
of 'e', 'E':
add(tok.literal, '\e')
inc(c.bufpos)
of 'a', 'A':
of 'a', 'A':
add(tok.literal, '\a')
inc(c.bufpos)
of 'b', 'B':
of 'b', 'B':
add(tok.literal, '\b')
inc(c.bufpos)
of 'v', 'V':
of 'v', 'V':
add(tok.literal, '\v')
inc(c.bufpos)
of 't', 'T':
of 't', 'T':
add(tok.literal, '\t')
inc(c.bufpos)
of 'x', 'X':
of 'x', 'X':
inc(c.bufpos)
var xi = 0
handleHexChar(c, xi)
handleHexChar(c, xi)
if xi == 0: tok.kind = tkInvalid
else: add(tok.literal, chr(xi))
of '0'..'9':
of '0'..'9':
var val = ord(c.buf[c.bufpos]) - ord('0')
inc(c.bufpos)
var i = 1
while (i <= 3) and (c.buf[c.bufpos] in {'0'..'9'}):
while (i <= 3) and (c.buf[c.bufpos] in {'0'..'9'}):
val = val * 10 + ord(c.buf[c.bufpos]) - ord('0')
inc(c.bufpos)
inc(i)
@@ -1169,32 +1165,32 @@ proc getEscapedChar(c: var PegLexer, tok: var TToken) =
else:
add(tok.literal, c.buf[c.bufpos])
inc(c.bufpos)
proc skip(c: var PegLexer) =
proc skip(c: var PegLexer) =
var pos = c.bufpos
var buf = c.buf
while true:
while true:
case buf[pos]
of ' ', '\t':
of ' ', '\t':
inc(pos)
of '#':
while not (buf[pos] in {'\c', '\L', '\0'}): inc(pos)
of '\c':
pos = handleCR(c, pos)
buf = c.buf
of '\L':
of '\L':
pos = handleLF(c, pos)
buf = c.buf
else:
else:
break # EndOfFile also leaves the loop
c.bufpos = pos
proc getString(c: var PegLexer, tok: var TToken) =
proc getString(c: var PegLexer, tok: var TToken) =
tok.kind = tkStringLit
var pos = c.bufpos + 1
var buf = c.buf
var quote = buf[pos-1]
while true:
while true:
case buf[pos]
of '\\':
c.bufpos = pos
@@ -1205,13 +1201,13 @@ proc getString(c: var PegLexer, tok: var TToken) =
break
elif buf[pos] == quote:
inc(pos)
break
break
else:
add(tok.literal, buf[pos])
inc(pos)
c.bufpos = pos
proc getDollar(c: var PegLexer, tok: var TToken) =
proc getDollar(c: var PegLexer, tok: var TToken) =
var pos = c.bufpos + 1
var buf = c.buf
if buf[pos] in {'0'..'9'}:
@@ -1223,8 +1219,8 @@ proc getDollar(c: var PegLexer, tok: var TToken) =
else:
tok.kind = tkDollar
c.bufpos = pos
proc getCharSet(c: var PegLexer, tok: var TToken) =
proc getCharSet(c: var PegLexer, tok: var TToken) =
tok.kind = tkCharSet
tok.charset = {}
var pos = c.bufpos + 1
@@ -1247,7 +1243,7 @@ proc getCharSet(c: var PegLexer, tok: var TToken) =
of '\C', '\L', '\0':
tok.kind = tkInvalid
break
else:
else:
ch = buf[pos]
inc(pos)
incl(tok.charset, ch)
@@ -1267,18 +1263,18 @@ proc getCharSet(c: var PegLexer, tok: var TToken) =
of '\C', '\L', '\0':
tok.kind = tkInvalid
break
else:
else:
ch2 = buf[pos]
inc(pos)
for i in ord(ch)+1 .. ord(ch2):
incl(tok.charset, chr(i))
c.bufpos = pos
if caret: tok.charset = {'\1'..'\xFF'} - tok.charset
proc getSymbol(c: var PegLexer, tok: var TToken) =
proc getSymbol(c: var PegLexer, tok: var TToken) =
var pos = c.bufpos
var buf = c.buf
while true:
while true:
add(tok.literal, buf[pos])
inc(pos)
if buf[pos] notin strutils.IdentChars: break
@@ -1294,7 +1290,7 @@ proc getBuiltin(c: var PegLexer, tok: var TToken) =
tok.kind = tkEscaped
getEscapedChar(c, tok) # may set tok.kind to tkInvalid
proc getTok(c: var PegLexer, tok: var TToken) =
proc getTok(c: var PegLexer, tok: var TToken) =
tok.kind = tkInvalid
tok.modifier = modNone
setLen(tok.literal, 0)
@@ -1309,11 +1305,11 @@ proc getTok(c: var PegLexer, tok: var TToken) =
else:
tok.kind = tkCurlyLe
add(tok.literal, '{')
of '}':
of '}':
tok.kind = tkCurlyRi
inc(c.bufpos)
add(tok.literal, '}')
of '[':
of '[':
getCharSet(c, tok)
of '(':
tok.kind = tkParLe
@@ -1323,7 +1319,7 @@ proc getTok(c: var PegLexer, tok: var TToken) =
tok.kind = tkParRi
inc(c.bufpos)
add(tok.literal, ')')
of '.':
of '.':
tok.kind = tkAny
inc(c.bufpos)
add(tok.literal, '.')
@@ -1331,16 +1327,16 @@ proc getTok(c: var PegLexer, tok: var TToken) =
tok.kind = tkAnyRune
inc(c.bufpos)
add(tok.literal, '_')
of '\\':
of '\\':
getBuiltin(c, tok)
of '\'', '"': getString(c, tok)
of '$': getDollar(c, tok)
of '\0':
of '\0':
tok.kind = tkEof
tok.literal = "[EOF]"
of 'a'..'z', 'A'..'Z', '\128'..'\255':
getSymbol(c, tok)
if c.buf[c.bufpos] in {'\'', '"'} or
if c.buf[c.bufpos] in {'\'', '"'} or
c.buf[c.bufpos] == '$' and c.buf[c.bufpos+1] in {'0'..'9'}:
case tok.literal
of "i": tok.modifier = modIgnoreCase
@@ -1388,7 +1384,7 @@ proc getTok(c: var PegLexer, tok: var TToken) =
tok.kind = tkAt
inc(c.bufpos)
add(tok.literal, '@')
if c.buf[c.bufpos] == '@':
if c.buf[c.bufpos] == '@':
tok.kind = tkCurlyAt
inc(c.bufpos)
add(tok.literal, '@')
@@ -1407,7 +1403,7 @@ proc arrowIsNextTok(c: PegLexer): bool =
result = c.buf[pos] == '<' and c.buf[pos+1] == '-'
# ----------------------------- parser ----------------------------------------
type
EInvalidPeg* = object of ValueError ## raised if an invalid
## PEG has been detected
@@ -1425,7 +1421,7 @@ proc pegError(p: PegParser, msg: string, line = -1, col = -1) =
e.msg = errorStr(p, msg, line, col)
raise e
proc getTok(p: var PegParser) =
proc getTok(p: var PegParser) =
getTok(p, p.tok)
if p.tok.kind == tkInvalid: pegError(p, "invalid token")
@@ -1475,7 +1471,7 @@ proc builtin(p: var PegParser): Peg =
of "white": result = unicodeWhitespace()
else: pegError(p, "unknown built-in: " & p.tok.literal)
proc token(terminal: Peg, p: PegParser): Peg =
proc token(terminal: Peg, p: PegParser): Peg =
if p.skip.kind == pkEmpty: result = terminal
else: result = sequence(p.skip, terminal)
@@ -1496,7 +1492,7 @@ proc primary(p: var PegParser): Peg =
else: discard
case p.tok.kind
of tkIdentifier:
if p.identIsVerbatim:
if p.identIsVerbatim:
var m = p.tok.modifier
if m == modNone: m = p.modifier
result = modifiedTerm(p.tok.literal, m).token(p)
@@ -1539,17 +1535,17 @@ proc primary(p: var PegParser): Peg =
of tkEscaped:
result = term(p.tok.literal[0]).token(p)
getTok(p)
of tkDollar:
of tkDollar:
result = endAnchor()
getTok(p)
of tkHat:
of tkHat:
result = startAnchor()
getTok(p)
of tkBackref:
var m = p.tok.modifier
if m == modNone: m = p.modifier
result = modifiedBackref(p.tok.index, m).token(p)
if p.tok.index < 0 or p.tok.index > p.captures:
if p.tok.index < 0 or p.tok.index > p.captures:
pegError(p, "invalid back reference index: " & $p.tok.index)
getTok(p)
else:
@@ -1573,7 +1569,7 @@ proc seqExpr(p: var PegParser): Peg =
while true:
case p.tok.kind
of tkAmp, tkNot, tkAt, tkStringLit, tkCharSet, tkParLe, tkCurlyLe,
tkAny, tkAnyRune, tkBuiltin, tkEscaped, tkDollar, tkBackref,
tkAny, tkAnyRune, tkBuiltin, tkEscaped, tkDollar, tkBackref,
tkHat, tkCurlyAt:
result = sequence(result, primary(p))
of tkIdentifier:
@@ -1587,7 +1583,7 @@ proc parseExpr(p: var PegParser): Peg =
while p.tok.kind == tkBar:
getTok(p)
result = result / seqExpr(p)
proc parseRule(p: var PegParser): NonTerminal =
if p.tok.kind == tkIdentifier and arrowIsNextTok(p):
result = getNonTerminal(p, p.tok.literal)
@@ -1601,7 +1597,7 @@ proc parseRule(p: var PegParser): NonTerminal =
incl(result.flags, ntDeclared) # NOW inlining may be attempted
else:
pegError(p, "rule expected, but found: " & p.tok.literal)
proc rawParse(p: var PegParser): Peg =
## parses a rule or a PEG expression
while p.tok.kind == tkBuiltin:
@@ -1680,27 +1676,29 @@ when isMainModule:
assert(not match("W_HI_L", peg"\y 'while'"))
assert(not match("W_HI_Le", peg"\y v'while'"))
assert match("W_HI_Le", peg"y'while'")
assert($ +digits == $peg"\d+")
assert "0158787".match(peg"\d+")
assert "ABC 0232".match(peg"\w+\s+\d+")
assert "ABC".match(peg"\d+ / \w+")
var accum: seq[string] = @[]
for word in split("00232this02939is39an22example111", peg"\d+"):
writeln(stdout, word)
accum.add(word)
assert(accum == @["this", "is", "an", "example"])
assert matchLen("key", ident) == 3
var pattern = sequence(ident, *whitespace, term('='), *whitespace, ident)
assert matchLen("key1= cal9", pattern) == 11
var ws = newNonTerminal("ws", 1, 1)
ws.rule = *whitespace
var expr = newNonTerminal("expr", 1, 1)
expr.rule = sequence(capture(ident), *sequence(
nonterminal(ws), term('+'), nonterminal(ws), nonterminal(expr)))
var c: Captures
var s = "a+b + c +d+e+f"
assert rawMatch(s, expr.rule, 0, c) == len(s)
@@ -1722,7 +1720,7 @@ when isMainModule:
assert matches[0] == "abc"
else:
assert false
var g2 = peg"""S <- A B / C D
A <- 'a'+
B <- 'b'+
@@ -1748,18 +1746,20 @@ when isMainModule:
else:
assert false
accum = @[]
for x in findAll("abcdef", peg".", 3):
echo x
accum.add(x)
assert(accum == @["d", "e", "f"])
for x in findAll("abcdef", peg"^{.}", 3):
assert x == "d"
if "f(a, b)" =~ peg"{[0-9]+} / ({\ident} '(' {@} ')')":
assert matches[0] == "f"
assert matches[1] == "a, b"
else:
assert false
assert match("eine übersicht und außerdem", peg"(\letter \white*)+")
# ß is not a lower cased letter?!
assert match("eine übersicht und auerdem", peg"(\lower \white*)+")
@@ -1783,3 +1783,9 @@ when isMainModule:
if "foo" =~ peg"{'foo'}":
assert matches[0] == "foo"
else: assert false
let empty_test = peg"^\d*"
let str = "XYZ"
assert(str.find(empty_test) == 0)
assert(str.match(empty_test))

File diff suppressed because it is too large Load Diff

View File

@@ -58,7 +58,7 @@ proc `[]=` *(p:var Poly;idx:int,v:float)=
iterator items*(p:Poly):float=
## Iterates through the corfficients of the polynomial.
## Iterates through the coefficients of the polynomial.
var i=p.degree
while i>=0:
yield p[i]

289
lib/pure/rationals.nim Normal file
View File

@@ -0,0 +1,289 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2015 Dennis Felsing
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module implements rational numbers, consisting of a numerator `num` and
## a denominator `den`, both of type int. The denominator can not be 0.
import math
import hashes
type Rational*[T] = object
## a rational number, consisting of a numerator and denominator
num*, den*: T
proc initRational*[T](num, den: T): Rational[T] =
## Create a new rational number.
result.num = num
result.den = den
proc `//`*[T](num, den: T): Rational[T] = initRational[T](num, den)
## A friendlier version of `initRational`. Example usage:
##
## .. code-block:: nim
## var x = 1//3 + 1//5
proc `$`*[T](x: Rational[T]): string =
## Turn a rational number into a string.
result = $x.num & "/" & $x.den
proc toRational*[T](x: T): Rational[T] =
## Convert some integer `x` to a rational number.
result.num = x
result.den = 1
proc toFloat*[T](x: Rational[T]): float =
## Convert a rational number `x` to a float.
x.num / x.den
proc toInt*[T](x: Rational[T]): int =
## Convert a rational number `x` to an int. Conversion rounds towards 0 if
## `x` does not contain an integer value.
x.num div x.den
proc reduce*[T](x: var Rational[T]) =
## Reduce rational `x`.
let common = gcd(x.num, x.den)
if x.den > 0:
x.num = x.num div common
x.den = x.den div common
elif x.den < 0:
x.num = -x.num div common
x.den = -x.den div common
else:
raise newException(DivByZeroError, "division by zero")
proc `+` *[T](x, y: Rational[T]): Rational[T] =
## Add two rational numbers.
let common = lcm(x.den, y.den)
result.num = common div x.den * x.num + common div y.den * y.num
result.den = common
reduce(result)
proc `+` *[T](x: Rational[T], y: T): Rational[T] =
## Add rational `x` to int `y`.
result.num = x.num + y * x.den
result.den = x.den
proc `+` *[T](x: T, y: Rational[T]): Rational[T] =
## Add int `x` to rational `y`.
result.num = x * y.den + y.num
result.den = y.den
proc `+=` *[T](x: var Rational[T], y: Rational[T]) =
## Add rational `y` to rational `x`.
let common = lcm(x.den, y.den)
x.num = common div x.den * x.num + common div y.den * y.num
x.den = common
reduce(x)
proc `+=` *[T](x: var Rational[T], y: T) =
## Add int `y` to rational `x`.
x.num += y * x.den
proc `-` *[T](x: Rational[T]): Rational[T] =
## Unary minus for rational numbers.
result.num = -x.num
result.den = x.den
proc `-` *[T](x, y: Rational[T]): Rational[T] =
## Subtract two rational numbers.
let common = lcm(x.den, y.den)
result.num = common div x.den * x.num - common div y.den * y.num
result.den = common
reduce(result)
proc `-` *[T](x: Rational[T], y: T): Rational[T] =
## Subtract int `y` from rational `x`.
result.num = x.num - y * x.den
result.den = x.den
proc `-` *[T](x: T, y: Rational[T]): Rational[T] =
## Subtract rational `y` from int `x`.
result.num = - x * y.den + y.num
result.den = y.den
proc `-=` *[T](x: var Rational[T], y: Rational[T]) =
## Subtract rational `y` from rational `x`.
let common = lcm(x.den, y.den)
x.num = common div x.den * x.num - common div y.den * y.num
x.den = common
reduce(x)
proc `-=` *[T](x: var Rational[T], y: T) =
## Subtract int `y` from rational `x`.
x.num -= y * x.den
proc `*` *[T](x, y: Rational[T]): Rational[T] =
## Multiply two rational numbers.
result.num = x.num * y.num
result.den = x.den * y.den
reduce(result)
proc `*` *[T](x: Rational[T], y: T): Rational[T] =
## Multiply rational `x` with int `y`.
result.num = x.num * y
result.den = x.den
reduce(result)
proc `*` *[T](x: T, y: Rational[T]): Rational[T] =
## Multiply int `x` with rational `y`.
result.num = x * y.num
result.den = y.den
reduce(result)
proc `*=` *[T](x: var Rational[T], y: Rational[T]) =
## Multiply rationals `y` to `x`.
x.num *= y.num
x.den *= y.den
reduce(x)
proc `*=` *[T](x: var Rational[T], y: T) =
## Multiply int `y` to rational `x`.
x.num *= y
reduce(x)
proc reciprocal*[T](x: Rational[T]): Rational[T] =
## Calculate the reciprocal of `x`. (1/x)
if x.num > 0:
result.num = x.den
result.den = x.num
elif x.num < 0:
result.num = -x.den
result.den = -x.num
else:
raise newException(DivByZeroError, "division by zero")
proc `/`*[T](x, y: Rational[T]): Rational[T] =
## Divide rationals `x` by `y`.
result.num = x.num * y.den
result.den = x.den * y.num
reduce(result)
proc `/`*[T](x: Rational[T], y: T): Rational[T] =
## Divide rational `x` by int `y`.
result.num = x.num
result.den = x.den * y
reduce(result)
proc `/`*[T](x: T, y: Rational[T]): Rational[T] =
## Divide int `x` by Rational `y`.
result.num = x * y.den
result.den = y.num
reduce(result)
proc `/=`*[T](x: var Rational[T], y: Rational[T]) =
## Divide rationals `x` by `y` in place.
x.num *= y.den
x.den *= y.num
reduce(x)
proc `/=`*[T](x: var Rational[T], y: T) =
## Divide rational `x` by int `y` in place.
x.den *= y
reduce(x)
proc cmp*(x, y: Rational): int {.procvar.} =
## Compares two rationals.
(x - y).num
proc `<` *(x, y: Rational): bool =
(x - y).num < 0
proc `<=` *(x, y: Rational): bool =
(x - y).num <= 0
proc `==` *(x, y: Rational): bool =
(x - y).num == 0
proc abs*[T](x: Rational[T]): Rational[T] =
result.num = abs x.num
result.den = abs x.den
proc hash*[T](x: Rational[T]): THash =
## Computes hash for rational `x`
# reduce first so that hash(x) == hash(y) for x == y
var copy = x
reduce(copy)
var h: THash = 0
h = h !& hash(copy.num)
h = h !& hash(copy.den)
result = !$h
when isMainModule:
var
z = Rational[int](num: 0, den: 1)
o = initRational(num=1, den=1)
a = initRational(1, 2)
b = -1 // -2
m1 = -1 // 1
tt = 10 // 2
assert( a == a )
assert( (a-a) == z )
assert( (a+b) == o )
assert( (a/b) == o )
assert( (a*b) == 1 // 4 )
assert( (3/a) == 6 // 1 )
assert( (a/3) == 1 // 6 )
assert( a*b == 1 // 4 )
assert( tt*z == z )
assert( 10*a == tt )
assert( a*10 == tt )
assert( tt/10 == a )
assert( a-m1 == 3 // 2 )
assert( a+m1 == -1 // 2 )
assert( m1+tt == 16 // 4 )
assert( m1-tt == 6 // -1 )
assert( z < o )
assert( z <= o )
assert( z == z )
assert( cmp(z, o) < 0 )
assert( cmp(o, z) > 0 )
assert( o == o )
assert( o >= o )
assert( not(o > o) )
assert( cmp(o, o) == 0 )
assert( cmp(z, z) == 0 )
assert( hash(o) == hash(o) )
assert( a == b )
assert( a >= b )
assert( not(b > a) )
assert( cmp(a, b) == 0 )
assert( hash(a) == hash(b) )
var x = 1//3
x *= 5//1
assert( x == 5//3 )
x += 2 // 9
assert( x == 17//9 )
x -= 9//18
assert( x == 25//18 )
x /= 1//2
assert( x == 50//18 )
var y = 1//3
y *= 4
assert( y == 4//3 )
y += 5
assert( y == 19//3 )
y -= 2
assert( y == 13//3 )
y /= 9
assert( y == 13//27 )
assert toRational(5) == 5//1
assert abs(toFloat(y) - 0.4814814814814815) < 1.0e-7
assert toInt(z) == 0

View File

@@ -1,7 +1,7 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2014 Dominik Picheta
# (c) Copyright 2015 Dominik Picheta
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@@ -39,6 +39,9 @@ export
SO_KEEPALIVE, SO_OOBINLINE, SO_REUSEADDR,
MSG_PEEK
when defined(macosx):
export SO_NOSIGPIPE
type
Port* = distinct uint16 ## port type

View File

@@ -1,426 +0,0 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2014 Dominik Picheta
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module implements a low-level cross-platform sockets interface. Look
## at the ``net`` module for the higher-level version.
# TODO: Clean up the exports a bit and everything else in general.
import unsigned, os
when hostOS == "solaris":
{.passl: "-lsocket -lnsl".}
const useWinVersion = defined(Windows) or defined(nimdoc)
when useWinVersion:
import winlean
export WSAEWOULDBLOCK, WSAECONNRESET, WSAECONNABORTED, WSAENETRESET,
WSAEDISCON, ERROR_NETNAME_DELETED
else:
import posix
export fcntl, F_GETFL, O_NONBLOCK, F_SETFL, EAGAIN, EWOULDBLOCK, MSG_NOSIGNAL,
EINTR, EINPROGRESS, ECONNRESET, EPIPE, ENETRESET
export SocketHandle, Sockaddr_in, Addrinfo, INADDR_ANY, SockAddr, SockLen,
inet_ntoa, recv, `==`, connect, send, accept, recvfrom, sendto
export
SO_ERROR,
SOL_SOCKET,
SOMAXCONN,
SO_ACCEPTCONN, SO_BROADCAST, SO_DEBUG, SO_DONTROUTE,
SO_KEEPALIVE, SO_OOBINLINE, SO_REUSEADDR,
MSG_PEEK
type
Port* = distinct uint16 ## port type
Domain* = enum ## domain, which specifies the protocol family of the
## created socket. Other domains than those that are listed
## here are unsupported.
AF_UNIX, ## for local socket (using a file). Unsupported on Windows.
AF_INET = 2, ## for network protocol IPv4 or
AF_INET6 = 23 ## for network protocol IPv6.
SockType* = enum ## second argument to `socket` proc
SOCK_STREAM = 1, ## reliable stream-oriented service or Stream Sockets
SOCK_DGRAM = 2, ## datagram service or Datagram Sockets
SOCK_RAW = 3, ## raw protocols atop the network layer.
SOCK_SEQPACKET = 5 ## reliable sequenced packet service
Protocol* = enum ## third argument to `socket` proc
IPPROTO_TCP = 6, ## Transmission control protocol.
IPPROTO_UDP = 17, ## User datagram protocol.
IPPROTO_IP, ## Internet protocol. Unsupported on Windows.
IPPROTO_IPV6, ## Internet Protocol Version 6. Unsupported on Windows.
IPPROTO_RAW, ## Raw IP Packets Protocol. Unsupported on Windows.
IPPROTO_ICMP ## Control message protocol. Unsupported on Windows.
Servent* = object ## information about a service
name*: string
aliases*: seq[string]
port*: Port
proto*: string
Hostent* = object ## information about a given host
name*: string
aliases*: seq[string]
addrtype*: Domain
length*: int
addrList*: seq[string]
{.deprecated: [TPort: Port, TDomain: Domain, TType: SockType,
TProtocol: Protocol, TServent: Servent, THostent: Hostent].}
when useWinVersion:
let
osInvalidSocket* = winlean.INVALID_SOCKET
const
IOCPARM_MASK* = 127
IOC_IN* = int(-2147483648)
FIONBIO* = IOC_IN.int32 or ((sizeof(int32) and IOCPARM_MASK) shl 16) or
(102 shl 8) or 126
proc ioctlsocket*(s: SocketHandle, cmd: clong,
argptr: ptr clong): cint {.
stdcall, importc: "ioctlsocket", dynlib: "ws2_32.dll".}
else:
let
osInvalidSocket* = posix.INVALID_SOCKET
proc `==`*(a, b: Port): bool {.borrow.}
## ``==`` for ports.
proc `$`*(p: Port): string {.borrow.}
## returns the port number as a string
proc toInt*(domain: Domain): cint
## Converts the TDomain enum to a platform-dependent ``cint``.
proc toInt*(typ: SockType): cint
## Converts the TType enum to a platform-dependent ``cint``.
proc toInt*(p: Protocol): cint
## Converts the TProtocol enum to a platform-dependent ``cint``.
when not useWinVersion:
proc toInt(domain: Domain): cint =
case domain
of AF_UNIX: result = posix.AF_UNIX
of AF_INET: result = posix.AF_INET
of AF_INET6: result = posix.AF_INET6
else: discard
proc toInt(typ: SockType): cint =
case typ
of SOCK_STREAM: result = posix.SOCK_STREAM
of SOCK_DGRAM: result = posix.SOCK_DGRAM
of SOCK_SEQPACKET: result = posix.SOCK_SEQPACKET
of SOCK_RAW: result = posix.SOCK_RAW
else: discard
proc toInt(p: Protocol): cint =
case p
of IPPROTO_TCP: result = posix.IPPROTO_TCP
of IPPROTO_UDP: result = posix.IPPROTO_UDP
of IPPROTO_IP: result = posix.IPPROTO_IP
of IPPROTO_IPV6: result = posix.IPPROTO_IPV6
of IPPROTO_RAW: result = posix.IPPROTO_RAW
of IPPROTO_ICMP: result = posix.IPPROTO_ICMP
else: discard
else:
proc toInt(domain: Domain): cint =
result = toU16(ord(domain))
proc toInt(typ: SockType): cint =
result = cint(ord(typ))
proc toInt(p: Protocol): cint =
result = cint(ord(p))
proc newRawSocket*(domain: Domain = AF_INET, typ: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP): SocketHandle =
## Creates a new socket; returns `InvalidSocket` if an error occurs.
socket(toInt(domain), toInt(typ), toInt(protocol))
proc close*(socket: SocketHandle) =
## closes a socket.
when useWinVersion:
discard winlean.closesocket(socket)
else:
discard posix.close(socket)
# TODO: These values should not be discarded. An EOS should be raised.
# http://stackoverflow.com/questions/12463473/what-happens-if-you-call-close-on-a-bsd-socket-multiple-times
proc bindAddr*(socket: SocketHandle, name: ptr SockAddr, namelen: SockLen): cint =
result = bindSocket(socket, name, namelen)
proc listen*(socket: SocketHandle, backlog = SOMAXCONN): cint {.tags: [ReadIOEffect].} =
## Marks ``socket`` as accepting connections.
## ``Backlog`` specifies the maximum length of the
## queue of pending connections.
when useWinVersion:
result = winlean.listen(socket, cint(backlog))
else:
result = posix.listen(socket, cint(backlog))
proc getAddrInfo*(address: string, port: Port, af: Domain = AF_INET, typ: SockType = SOCK_STREAM,
prot: Protocol = IPPROTO_TCP): ptr AddrInfo =
##
##
## **Warning**: The resulting ``ptr TAddrInfo`` must be freed using ``dealloc``!
var hints: AddrInfo
result = nil
hints.ai_family = toInt(af)
hints.ai_socktype = toInt(typ)
hints.ai_protocol = toInt(prot)
var gaiResult = getaddrinfo(address, $port, addr(hints), result)
if gaiResult != 0'i32:
when useWinVersion:
raiseOSError(osLastError())
else:
raise newException(OSError, $gai_strerror(gaiResult))
proc dealloc*(ai: ptr AddrInfo) =
freeaddrinfo(ai)
proc ntohl*(x: int32): int32 =
## Converts 32-bit integers from network to host byte order.
## On machines where the host byte order is the same as network byte order,
## this is a no-op; otherwise, it performs a 4-byte swap operation.
when cpuEndian == bigEndian: result = x
else: result = (x shr 24'i32) or
(x shr 8'i32 and 0xff00'i32) or
(x shl 8'i32 and 0xff0000'i32) or
(x shl 24'i32)
proc ntohs*(x: int16): int16 =
## Converts 16-bit integers from network to host byte order. On machines
## where the host byte order is the same as network byte order, this is
## a no-op; otherwise, it performs a 2-byte swap operation.
when cpuEndian == bigEndian: result = x
else: result = (x shr 8'i16) or (x shl 8'i16)
proc htonl*(x: int32): int32 =
## Converts 32-bit integers from host to network byte order. On machines
## where the host byte order is the same as network byte order, this is
## a no-op; otherwise, it performs a 4-byte swap operation.
result = rawsockets.ntohl(x)
proc htons*(x: int16): int16 =
## Converts 16-bit positive integers from host to network byte order.
## On machines where the host byte order is the same as network byte
## order, this is a no-op; otherwise, it performs a 2-byte swap operation.
result = rawsockets.ntohs(x)
proc getServByName*(name, proto: string): Servent {.tags: [ReadIOEffect].} =
## Searches the database from the beginning and finds the first entry for
## which the service name specified by ``name`` matches the s_name member
## and the protocol name specified by ``proto`` matches the s_proto member.
##
## On posix this will search through the ``/etc/services`` file.
when useWinVersion:
var s = winlean.getservbyname(name, proto)
else:
var s = posix.getservbyname(name, proto)
if s == nil: raise newException(OSError, "Service not found.")
result.name = $s.s_name
result.aliases = cstringArrayToSeq(s.s_aliases)
result.port = Port(s.s_port)
result.proto = $s.s_proto
proc getServByPort*(port: Port, proto: string): Servent {.tags: [ReadIOEffect].} =
## Searches the database from the beginning and finds the first entry for
## which the port specified by ``port`` matches the s_port member and the
## protocol name specified by ``proto`` matches the s_proto member.
##
## On posix this will search through the ``/etc/services`` file.
when useWinVersion:
var s = winlean.getservbyport(ze(int16(port)).cint, proto)
else:
var s = posix.getservbyport(ze(int16(port)).cint, proto)
if s == nil: raise newException(OSError, "Service not found.")
result.name = $s.s_name
result.aliases = cstringArrayToSeq(s.s_aliases)
result.port = Port(s.s_port)
result.proto = $s.s_proto
proc getHostByAddr*(ip: string): Hostent {.tags: [ReadIOEffect].} =
## This function will lookup the hostname of an IP Address.
var myaddr: InAddr
myaddr.s_addr = inet_addr(ip)
when useWinVersion:
var s = winlean.gethostbyaddr(addr(myaddr), sizeof(myaddr).cuint,
cint(rawsockets.AF_INET))
if s == nil: raiseOSError(osLastError())
else:
var s = posix.gethostbyaddr(addr(myaddr), sizeof(myaddr).Socklen,
cint(posix.AF_INET))
if s == nil:
raise newException(OSError, $hstrerror(h_errno))
result.name = $s.h_name
result.aliases = cstringArrayToSeq(s.h_aliases)
when useWinVersion:
result.addrtype = Domain(s.h_addrtype)
else:
if s.h_addrtype == posix.AF_INET:
result.addrtype = AF_INET
elif s.h_addrtype == posix.AF_INET6:
result.addrtype = AF_INET6
else:
raise newException(OSError, "unknown h_addrtype")
result.addrList = cstringArrayToSeq(s.h_addr_list)
result.length = int(s.h_length)
proc getHostByName*(name: string): Hostent {.tags: [ReadIOEffect].} =
## This function will lookup the IP address of a hostname.
when useWinVersion:
var s = winlean.gethostbyname(name)
else:
var s = posix.gethostbyname(name)
if s == nil: raiseOSError(osLastError())
result.name = $s.h_name
result.aliases = cstringArrayToSeq(s.h_aliases)
when useWinVersion:
result.addrtype = Domain(s.h_addrtype)
else:
if s.h_addrtype == posix.AF_INET:
result.addrtype = AF_INET
elif s.h_addrtype == posix.AF_INET6:
result.addrtype = AF_INET6
else:
raise newException(OSError, "unknown h_addrtype")
result.addrList = cstringArrayToSeq(s.h_addr_list)
result.length = int(s.h_length)
proc getSockName*(socket: SocketHandle): Port =
## returns the socket's associated port number.
var name: Sockaddr_in
when useWinVersion:
name.sin_family = int16(ord(AF_INET))
else:
name.sin_family = posix.AF_INET
#name.sin_port = htons(cint16(port))
#name.sin_addr.s_addr = htonl(INADDR_ANY)
var namelen = sizeof(name).SockLen
if getsockname(socket, cast[ptr SockAddr](addr(name)),
addr(namelen)) == -1'i32:
raiseOSError(osLastError())
result = Port(rawsockets.ntohs(name.sin_port))
proc getSockOptInt*(socket: SocketHandle, level, optname: int): int {.
tags: [ReadIOEffect].} =
## getsockopt for integer options.
var res: cint
var size = sizeof(res).SockLen
if getsockopt(socket, cint(level), cint(optname),
addr(res), addr(size)) < 0'i32:
raiseOSError(osLastError())
result = int(res)
proc setSockOptInt*(socket: SocketHandle, level, optname, optval: int) {.
tags: [WriteIOEffect].} =
## setsockopt for integer options.
var value = cint(optval)
if setsockopt(socket, cint(level), cint(optname), addr(value),
sizeof(value).SockLen) < 0'i32:
raiseOSError(osLastError())
proc setBlocking*(s: SocketHandle, blocking: bool) =
## Sets blocking mode on socket.
##
## Raises EOS on error.
when useWinVersion:
var mode = clong(ord(not blocking)) # 1 for non-blocking, 0 for blocking
if ioctlsocket(s, FIONBIO, addr(mode)) == -1:
raiseOSError(osLastError())
else: # BSD sockets
var x: int = fcntl(s, F_GETFL, 0)
if x == -1:
raiseOSError(osLastError())
else:
var mode = if blocking: x and not O_NONBLOCK else: x or O_NONBLOCK
if fcntl(s, F_SETFL, mode) == -1:
raiseOSError(osLastError())
proc timeValFromMilliseconds(timeout = 500): Timeval =
if timeout != -1:
var seconds = timeout div 1000
result.tv_sec = seconds.int32
result.tv_usec = ((timeout - seconds * 1000) * 1000).int32
proc createFdSet(fd: var FdSet, s: seq[SocketHandle], m: var int) =
FD_ZERO(fd)
for i in items(s):
m = max(m, int(i))
fdSet(i, fd)
proc pruneSocketSet(s: var seq[SocketHandle], fd: var FdSet) =
var i = 0
var L = s.len
while i < L:
if FD_ISSET(s[i], fd) == 0'i32:
s[i] = s[L-1]
dec(L)
else:
inc(i)
setLen(s, L)
proc select*(readfds: var seq[SocketHandle], timeout = 500): int =
## Traditional select function. This function will return the number of
## sockets that are ready to be read from, written to, or which have errors.
## If there are none; 0 is returned.
## ``Timeout`` is in miliseconds and -1 can be specified for no timeout.
##
## A socket is removed from the specific ``seq`` when it has data waiting to
## be read/written to or has errors (``exceptfds``).
var tv {.noInit.}: Timeval = timeValFromMilliseconds(timeout)
var rd: FdSet
var m = 0
createFdSet((rd), readfds, m)
if timeout != -1:
result = int(select(cint(m+1), addr(rd), nil, nil, addr(tv)))
else:
result = int(select(cint(m+1), addr(rd), nil, nil, nil))
pruneSocketSet(readfds, (rd))
proc selectWrite*(writefds: var seq[SocketHandle],
timeout = 500): int {.tags: [ReadIOEffect].} =
## When a socket in ``writefds`` is ready to be written to then a non-zero
## value will be returned specifying the count of the sockets which can be
## written to. The sockets which can be written to will also be removed
## from ``writefds``.
##
## ``timeout`` is specified in miliseconds and ``-1`` can be specified for
## an unlimited time.
var tv {.noInit.}: Timeval = timeValFromMilliseconds(timeout)
var wr: FdSet
var m = 0
createFdSet((wr), writefds, m)
if timeout != -1:
result = int(select(cint(m+1), nil, addr(wr), nil, addr(tv)))
else:
result = int(select(cint(m+1), nil, addr(wr), nil, nil))
pruneSocketSet(writefds, (wr))
when defined(Windows):
var wsa: WSAData
if wsaStartup(0x0101'i16, addr wsa) != 0: raiseOSError(osLastError())

View File

@@ -285,6 +285,30 @@ proc keys*(r: Redis, pattern: string): RedisList =
r.sendCommand("KEYS", pattern)
return r.readArray()
proc scan*(r: Redis, cursor: var BiggestInt): RedisList =
## Find all keys matching the given pattern and yield it to client in portions
## using default Redis values for MATCH and COUNT parameters
r.sendCommand("SCAN", $cursor)
let reply = r.readArray()
cursor = strutils.parseBiggestInt(reply[0])
return reply[1..high(reply)]
proc scan*(r: Redis, cursor: var BiggestInt, pattern: string): RedisList =
## Find all keys matching the given pattern and yield it to client in portions
## using cursor as a client query identifier. Using default Redis value for COUNT argument
r.sendCommand("SCAN", $cursor, ["MATCH", pattern])
let reply = r.readArray()
cursor = strutils.parseBiggestInt(reply[0])
return reply[1..high(reply)]
proc scan*(r: Redis, cursor: var BiggestInt, pattern: string, count: int): RedisList =
## Find all keys matching the given pattern and yield it to client in portions
## using cursor as a client query identifier.
r.sendCommand("SCAN", $cursor, ["MATCH", pattern, "COUNT", $count])
let reply = r.readArray()
cursor = strutils.parseBiggestInt(reply[0])
return reply[1..high(reply)]
proc move*(r: Redis, key: string, db: int): bool =
## Move a key to another database. Returns `true` on a successful move.
r.sendCommand("MOVE", key, $db)
@@ -798,6 +822,22 @@ proc zunionstore*(r: Redis, destination: string, numkeys: string,
return r.readInteger()
# HyperLogLog
proc pfadd*(r: Redis, key: string, elements: varargs[string]): RedisInteger =
## Add variable number of elements into special 'HyperLogLog' set type
r.sendCommand("PFADD", key, elements)
return r.readInteger()
proc pfcount*(r: Redis, key: string): RedisInteger =
## Count approximate number of elements in 'HyperLogLog'
r.sendCommand("PFCOUNT", key)
return r.readInteger()
proc pfmerge*(r: Redis, destination: string, sources: varargs[string]) =
## Merge several source HyperLogLog's into one specified by destKey
r.sendCommand("PFMERGE", destination, sources)
raiseNoOK(r.readStatus(), r.pipeline.enabled)
# Pub/Sub
@@ -1040,7 +1080,7 @@ proc assertListsIdentical(listA, listB: seq[string]) =
assert(item == listB[i])
i = i + 1
when isMainModule:
when not defined(testing) and isMainModule:
when false:
var r = open()

View File

@@ -44,16 +44,13 @@ proc decimalToRoman*(number: range[1..3_999]): string =
("XC", 90), ("L", 50), ("XL", 40), ("X", 10), ("IX", 9),
("V", 5), ("IV", 4), ("I", 1)]
result = ""
var decVal = number
var decVal: int = number
for key, val in items(romanComposites):
while decVal >= val:
dec(decVal, val)
result.add(key)
when isMainModule:
import math
randomize()
for i in 1 .. 100:
var rnd = 1 + random(3990)
assert rnd == rnd.decimalToRoman.romanToDecimal
for i in 1 .. 3_999:
assert i == i.decimalToRoman.romanToDecimal

View File

@@ -43,7 +43,7 @@ proc isConc(r: Rope): bool {.inline.} = return isNil(r.data)
# Note that the left and right pointers are not needed for leafs.
# Leaves have relatively high memory overhead (~30 bytes on a 32
# bit machine) and we produce many of them. This is why we cache and
# share leafs accross different rope trees.
# share leafs across different rope trees.
# To cache them they are inserted in another tree, a splay tree for best
# performance. But for the caching tree we use the leaf's left and right
# pointers.
@@ -52,9 +52,9 @@ proc len*(a: Rope): int {.rtl, extern: "nro$1".} =
## the rope's length
if a == nil: result = 0
else: result = a.length
proc newRope(): Rope = new(result)
proc newRope(data: string): Rope =
proc newRope(data: string): Rope =
new(result)
result.length = len(data)
result.data = data
@@ -65,18 +65,18 @@ var
when countCacheMisses:
var misses, hits: int
proc splay(s: string, tree: Rope, cmpres: var int): Rope =
proc splay(s: string, tree: Rope, cmpres: var int): Rope =
var c: int
var t = tree
N.left = nil
N.right = nil # reset to nil
var le = N
var r = N
while true:
while true:
c = cmp(s, t.data)
if c < 0:
if (t.left != nil) and (s < t.left.data):
if c < 0:
if (t.left != nil) and (s < t.left.data):
var y = t.left
t.left = y.right
y.right = t
@@ -85,8 +85,8 @@ proc splay(s: string, tree: Rope, cmpres: var int): Rope =
r.left = t
r = t
t = t.left
elif c > 0:
if (t.right != nil) and (s > t.right.data):
elif c > 0:
if (t.right != nil) and (s > t.right.data):
var y = t.right
t.right = y.left
y.left = t
@@ -95,8 +95,8 @@ proc splay(s: string, tree: Rope, cmpres: var int): Rope =
le.right = t
le = t
t = t.right
else:
break
else:
break
cmpres = c
le.right = t.left
r.left = t.right
@@ -104,50 +104,50 @@ proc splay(s: string, tree: Rope, cmpres: var int): Rope =
t.right = N.left
result = t
proc insertInCache(s: string, tree: Rope): Rope =
proc insertInCache(s: string, tree: Rope): Rope =
var t = tree
if t == nil:
if t == nil:
result = newRope(s)
when countCacheMisses: inc(misses)
return
var cmp: int
t = splay(s, t, cmp)
if cmp == 0:
if cmp == 0:
# We get here if it's already in the Tree
# Don't add it again
result = t
when countCacheMisses: inc(hits)
else:
else:
when countCacheMisses: inc(misses)
result = newRope(s)
if cmp < 0:
if cmp < 0:
result.left = t.left
result.right = t
t.left = nil
else:
else:
# i > t.item:
result.right = t.right
result.left = t
t.right = nil
proc rope*(s: string): Rope {.rtl, extern: "nro$1Str".} =
## Converts a string to a rope.
if s.len == 0:
## Converts a string to a rope.
if s.len == 0:
result = nil
elif cacheEnabled:
elif cacheEnabled:
result = insertInCache(s, cache)
cache = result
else:
else:
result = newRope(s)
proc rope*(i: BiggestInt): Rope {.rtl, extern: "nro$1BiggestInt".} =
## Converts an int to a rope.
proc rope*(i: BiggestInt): Rope {.rtl, extern: "nro$1BiggestInt".} =
## Converts an int to a rope.
result = rope($i)
proc rope*(f: BiggestFloat): Rope {.rtl, extern: "nro$1BiggestFloat".} =
## Converts a float to a rope.
## Converts a float to a rope.
result = rope($f)
proc enableCache*() {.rtl, extern: "nro$1".} =
## Enables the caching of leaves. This reduces the memory footprint at
## the cost of runtime efficiency.
@@ -160,9 +160,9 @@ proc disableCache*() {.rtl, extern: "nro$1".} =
proc `&`*(a, b: Rope): Rope {.rtl, extern: "nroConcRopeRope".} =
## the concatenation operator for ropes.
if a == nil:
if a == nil:
result = b
elif b == nil:
elif b == nil:
result = a
else:
result = newRope()
@@ -177,16 +177,16 @@ proc `&`*(a, b: Rope): Rope {.rtl, extern: "nroConcRopeRope".} =
else:
result.left = a
result.right = b
proc `&`*(a: Rope, b: string): Rope {.rtl, extern: "nroConcRopeStr".} =
proc `&`*(a: Rope, b: string): Rope {.rtl, extern: "nroConcRopeStr".} =
## the concatenation operator for ropes.
result = a & rope(b)
proc `&`*(a: string, b: Rope): Rope {.rtl, extern: "nroConcStrRope".} =
proc `&`*(a: string, b: Rope): Rope {.rtl, extern: "nroConcStrRope".} =
## the concatenation operator for ropes.
result = rope(a) & b
proc `&`*(a: openArray[Rope]): Rope {.rtl, extern: "nroConcOpenArray".} =
proc `&`*(a: openArray[Rope]): Rope {.rtl, extern: "nroConcOpenArray".} =
## the concatenation operator for an openarray of ropes.
for i in countup(0, high(a)): result = result & a[i]
@@ -219,7 +219,7 @@ iterator leaves*(r: Rope): string =
## iterates over any leaf string in the rope `r`.
if r != nil:
var stack = @[r]
while stack.len > 0:
while stack.len > 0:
var it = stack.pop
while isConc(it):
stack.add(it.right)
@@ -227,7 +227,7 @@ iterator leaves*(r: Rope): string =
assert(it != nil)
assert(it.data != nil)
yield it.data
iterator items*(r: Rope): char =
## iterates over any character in the rope `r`.
for s in leaves(r):
@@ -237,7 +237,7 @@ proc write*(f: File, r: Rope) {.rtl, extern: "nro$1".} =
## writes a rope to a file.
for s in leaves(r): write(f, s)
proc `$`*(r: Rope): string {.rtl, extern: "nroToString".}=
proc `$`*(r: Rope): string {.rtl, extern: "nroToString".}=
## converts a rope back to a string.
result = newString(r.len)
setLen(result, 0)
@@ -251,25 +251,25 @@ when false:
new(result)
result.length = -idx
proc compileFrmt(frmt: string): Rope =
proc compileFrmt(frmt: string): Rope =
var i = 0
var length = len(frmt)
result = nil
var num = 0
while i < length:
if frmt[i] == '$':
while i < length:
if frmt[i] == '$':
inc(i)
case frmt[i]
of '$':
of '$':
add(result, "$")
inc(i)
of '#':
of '#':
inc(i)
add(result, compiledArg(num+1))
inc(num)
of '0'..'9':
of '0'..'9':
var j = 0
while true:
while true:
j = j * 10 + ord(frmt[i]) - ord('0')
inc(i)
if frmt[i] notin {'0'..'9'}: break
@@ -285,13 +285,13 @@ when false:
add(s, compiledArg(j))
else: raise newException(EInvalidValue, "invalid format string")
var start = i
while i < length:
while i < length:
if frmt[i] != '$': inc(i)
else: break
if i - 1 >= start:
else: break
if i - 1 >= start:
add(result, substr(frmt, start, i-1))
proc `%`*(frmt: string, args: openArray[Rope]): Rope {.
proc `%`*(frmt: string, args: openArray[Rope]): Rope {.
rtl, extern: "nroFormat".} =
## `%` substitution operator for ropes. Does not support the ``$identifier``
## nor ``${identifier}`` notations.
@@ -299,23 +299,23 @@ proc `%`*(frmt: string, args: openArray[Rope]): Rope {.
var length = len(frmt)
result = nil
var num = 0
while i < length:
if frmt[i] == '$':
while i < length:
if frmt[i] == '$':
inc(i)
case frmt[i]
of '$':
of '$':
add(result, "$")
inc(i)
of '#':
of '#':
inc(i)
add(result, args[num])
inc(num)
of '0'..'9':
of '0'..'9':
var j = 0
while true:
while true:
j = j * 10 + ord(frmt[i]) - ord('0')
inc(i)
if frmt[i] notin {'0'..'9'}: break
if frmt[i] notin {'0'..'9'}: break
add(result, args[j-1])
of '{':
inc(i)
@@ -325,13 +325,14 @@ proc `%`*(frmt: string, args: openArray[Rope]): Rope {.
inc(i)
if frmt[i] == '}': inc(i)
else: raise newException(ValueError, "invalid format string")
add(result, args[j-1])
else: raise newException(ValueError, "invalid format string")
var start = i
while i < length:
while i < length:
if frmt[i] != '$': inc(i)
else: break
if i - 1 >= start:
else: break
if i - 1 >= start:
add(result, substr(frmt, start, i - 1))
proc addf*(c: var Rope, frmt: string, args: openArray[Rope]) {.
@@ -339,29 +340,46 @@ proc addf*(c: var Rope, frmt: string, args: openArray[Rope]) {.
## shortcut for ``add(c, frmt % args)``.
add(c, frmt % args)
const
bufSize = 1024 # 1 KB is reasonable
proc equalsFile*(r: Rope, f: File): bool {.rtl, extern: "nro$1File".} =
## returns true if the contents of the file `f` equal `r`.
var bufSize = 1024 # reasonable start value
var buf = alloc(bufSize)
for s in leaves(r):
if s.len > bufSize:
bufSize = max(bufSize * 2, s.len)
buf = realloc(buf, bufSize)
var readBytes = readBuffer(f, buf, s.len)
result = readBytes == s.len and equalMem(buf, cstring(s), s.len)
if not result: break
if result:
result = readBuffer(f, buf, 1) == 0 # really at the end of file?
dealloc(buf)
var
buf: array[bufSize, char]
bpos = buf.len
blen = buf.len
proc equalsFile*(r: Rope, f: string): bool {.rtl, extern: "nro$1Str".} =
for s in leaves(r):
var spos = 0
let slen = s.len
while spos < slen:
if bpos == blen:
# Read more data
bpos = 0
blen = readBuffer(f, addr(buf[0]), buf.len)
if blen == 0: # no more data in file
result = false
return
let n = min(blen - bpos, slen - spos)
# TODO There's gotta be a better way of comparing here...
if not equalMem(addr(buf[bpos]),
cast[pointer](cast[int](cstring(s))+spos), n):
result = false
return
spos += n
bpos += n
result = readBuffer(f, addr(buf[0]), 1) == 0 # check that we've read all
proc equalsFile*(r: Rope, filename: string): bool {.rtl, extern: "nro$1Str".} =
## returns true if the contents of the file `f` equal `r`. If `f` does not
## exist, false is returned.
var bin: File
result = open(bin, f)
var f: File
result = open(f, filename)
if result:
result = equalsFile(r, bin)
close(bin)
result = equalsFile(r, f)
close(f)
new(N) # init dummy node for splay algorithm

View File

@@ -1,7 +1,7 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2014 Dominik Picheta
# (c) Copyright 2015 Dominik Picheta
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@@ -23,7 +23,7 @@ proc `$`*(x: SocketHandle): string {.borrow.}
type
Event* = enum
EvRead, EvWrite
EvRead, EvWrite, EvError
SelectorKey* = ref object
fd*: SocketHandle
@@ -35,7 +35,7 @@ type
when defined(nimdoc):
type
Selector* = ref object
## An object which holds file descripters to be checked for read/write
## An object which holds file descriptors to be checked for read/write
## status.
fds: Table[SocketHandle, SelectorKey]
@@ -48,12 +48,21 @@ when defined(nimdoc):
events: set[Event]): SelectorKey {.discardable.} =
## Updates the events which ``fd`` wants notifications for.
proc unregister*(s: Selector, fd: SocketHandle): SelectorKey {.discardable.} =
## Unregisters file descriptor ``fd`` from selector ``s``.
proc close*(s: Selector) =
## Closes the selector
proc select*(s: Selector, timeout: int): seq[ReadyInfo] =
## The ``events`` field of the returned ``key`` contains the original events
## for which the ``fd`` was bound. This is contrary to the ``events`` field
## of the ``TReadyInfo`` tuple which determines which events are ready
## on the ``fd``.
proc newSelector*(): Selector =
## Creates a new selector
proc contains*(s: Selector, fd: SocketHandle): bool =
## Determines whether selector contains a file descriptor.
@@ -78,8 +87,6 @@ elif defined(linux):
proc register*(s: Selector, fd: SocketHandle, events: set[Event],
data: RootRef): SelectorKey {.discardable.} =
## Registers file descriptor ``fd`` to selector ``s`` with a set of TEvent
## ``events``.
var event = createEventStruct(events, fd)
if events != {}:
if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fd, addr(event)) != 0:
@@ -92,7 +99,6 @@ elif defined(linux):
proc update*(s: Selector, fd: SocketHandle,
events: set[Event]): SelectorKey {.discardable.} =
## Updates the events which ``fd`` wants notifications for.
if s.fds[fd].events != events:
if events == {}:
# This fd is idle -- it should not be registered to epoll.
@@ -146,12 +152,17 @@ elif defined(linux):
## on the ``fd``.
result = @[]
let evNum = epoll_wait(s.epollFD, addr s.events[0], 64.cint, timeout.cint)
if evNum < 0: raiseOSError(osLastError())
if evNum < 0:
let err = osLastError()
if err.cint == EINTR:
return @[]
raiseOSError(osLastError())
if evNum == 0: return @[]
for i in 0 .. <evNum:
let fd = s.events[i].data.fd.SocketHandle
var evSet: set[Event] = {}
if (s.events[i].events and EPOLLERR) != 0 or (s.events[i].events and EPOLLHUP) != 0: evSet = evSet + {EvError}
if (s.events[i].events and EPOLLIN) != 0: evSet = evSet + {EvRead}
if (s.events[i].events and EPOLLOUT) != 0: evSet = evSet + {EvWrite}
let selectorKey = s.fds[fd]
@@ -199,7 +210,6 @@ elif not defined(nimdoc):
proc update*(s: Selector, fd: SocketHandle,
events: set[Event]): SelectorKey {.discardable.} =
## Updates the events which ``fd`` wants notifications for.
if not s.fds.hasKey(fd):
raise newException(ValueError, "File descriptor not found.")
@@ -286,7 +296,7 @@ proc contains*(s: Selector, key: SelectorKey): bool =
TReadyInfo: ReadyInfo, PSelector: Selector].}
when isMainModule and not defined(nimdoc):
when not defined(testing) and isMainModule and not defined(nimdoc):
# Select()
import sockets
type
@@ -294,7 +304,7 @@ when isMainModule and not defined(nimdoc):
sock: Socket
var sock = socket()
if sock == sockets.InvalidSocket: raiseOSError(osLastError())
if sock == sockets.invalidSocket: raiseOSError(osLastError())
#sock.setBlocking(false)
sock.connect("irc.freenode.net", Port(6667))

View File

@@ -238,7 +238,7 @@ proc sendMail*(smtp: AsyncSmtp, fromAddr: string,
await smtp.sock.send("MAIL FROM:<" & fromAddr & ">\c\L")
await smtp.checkReply("250")
for address in items(toAddrs):
await smtp.sock.send("RCPT TO:<" & smtp.address & ">\c\L")
await smtp.sock.send("RCPT TO:<" & address & ">\c\L")
await smtp.checkReply("250")
# Send the message
@@ -253,7 +253,7 @@ proc close*(smtp: AsyncSmtp) {.async.} =
await smtp.sock.send("QUIT\c\L")
smtp.sock.close()
when isMainModule:
when not defined(testing) and isMainModule:
#var msg = createMessage("Test subject!",
# "Hello, my name is dom96.\n What\'s yours?", @["dominik@localhost"])
#echo(msg)

View File

@@ -657,6 +657,8 @@ proc close*(socket: Socket) =
when defined(ssl):
if socket.isSSL:
discard SSLShutdown(socket.sslHandle)
SSLFree(socket.sslHandle)
socket.sslHandle = nil
proc getServByName*(name, proto: string): Servent {.tags: [ReadIOEffect].} =
## Searches the database from the beginning and finds the first entry for
@@ -851,7 +853,7 @@ proc connectAsync*(socket: Socket, name: string, port = Port(0),
af: Domain = AF_INET) {.tags: [ReadIOEffect].} =
## A variant of ``connect`` for non-blocking sockets.
##
## This procedure will immediatelly return, it will not block until a connection
## This procedure will immediately return, it will not block until a connection
## is made. It is up to the caller to make sure the connection has been established
## by checking (using ``select``) whether the socket is writeable.
##
@@ -1467,7 +1469,7 @@ proc recvAsync*(socket: Socket, s: var TaintedString): bool {.
of SSL_ERROR_ZERO_RETURN:
raiseSslError("TLS/SSL connection failed to initiate, socket closed prematurely.")
of SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT:
raiseSslError("Unexpected error occured.") # This should just not happen.
raiseSslError("Unexpected error occurred.") # This should just not happen.
of SSL_ERROR_WANT_WRITE, SSL_ERROR_WANT_READ:
return false
of SSL_ERROR_WANT_X509_LOOKUP:
@@ -1610,7 +1612,7 @@ proc sendAsync*(socket: Socket, data: string): int {.tags: [WriteIOEffect].} =
of SSL_ERROR_ZERO_RETURN:
raiseSslError("TLS/SSL connection failed to initiate, socket closed prematurely.")
of SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT:
raiseSslError("Unexpected error occured.") # This should just not happen.
raiseSslError("Unexpected error occurred.") # This should just not happen.
of SSL_ERROR_WANT_WRITE, SSL_ERROR_WANT_READ:
return 0
of SSL_ERROR_WANT_X509_LOOKUP:

View File

@@ -122,41 +122,41 @@ proc read[T](s: Stream, result: var T) =
raise newEIO("cannot read from stream")
proc readChar*(s: Stream): char =
## reads a char from the stream `s`. Raises `EIO` if an error occured.
## reads a char from the stream `s`. Raises `EIO` if an error occurred.
## Returns '\0' as an EOF marker.
if readData(s, addr(result), sizeof(result)) != 1: result = '\0'
proc readBool*(s: Stream): bool =
## reads a bool from the stream `s`. Raises `EIO` if an error occured.
## reads a bool from the stream `s`. Raises `EIO` if an error occurred.
read(s, result)
proc readInt8*(s: Stream): int8 =
## reads an int8 from the stream `s`. Raises `EIO` if an error occured.
## reads an int8 from the stream `s`. Raises `EIO` if an error occurred.
read(s, result)
proc readInt16*(s: Stream): int16 =
## reads an int16 from the stream `s`. Raises `EIO` if an error occured.
## reads an int16 from the stream `s`. Raises `EIO` if an error occurred.
read(s, result)
proc readInt32*(s: Stream): int32 =
## reads an int32 from the stream `s`. Raises `EIO` if an error occured.
## reads an int32 from the stream `s`. Raises `EIO` if an error occurred.
read(s, result)
proc readInt64*(s: Stream): int64 =
## reads an int64 from the stream `s`. Raises `EIO` if an error occured.
## reads an int64 from the stream `s`. Raises `EIO` if an error occurred.
read(s, result)
proc readFloat32*(s: Stream): float32 =
## reads a float32 from the stream `s`. Raises `EIO` if an error occured.
## reads a float32 from the stream `s`. Raises `EIO` if an error occurred.
read(s, result)
proc readFloat64*(s: Stream): float64 =
## reads a float64 from the stream `s`. Raises `EIO` if an error occured.
## reads a float64 from the stream `s`. Raises `EIO` if an error occurred.
read(s, result)
proc readStr*(s: Stream, length: int): TaintedString =
## reads a string of length `length` from the stream `s`. Raises `EIO` if
## an error occured.
## an error occurred.
result = newString(length).TaintedString
var L = readData(s, addr(string(result)[0]), length)
if L != length: setLen(result.string, L)
@@ -183,7 +183,7 @@ proc readLine*(s: Stream, line: var TaintedString): bool =
proc readLine*(s: Stream): TaintedString =
## Reads a line from a stream `s`. Note: This is not very efficient. Raises
## `EIO` if an error occured.
## `EIO` if an error occurred.
result = TaintedString""
while true:
var c = readChar(s)
@@ -224,10 +224,12 @@ proc ssReadData(s: Stream, buffer: pointer, bufLen: int): int =
proc ssWriteData(s: Stream, buffer: pointer, bufLen: int) =
var s = StringStream(s)
if bufLen > 0:
setLen(s.data, s.data.len + bufLen)
copyMem(addr(s.data[s.pos]), buffer, bufLen)
inc(s.pos, bufLen)
if bufLen <= 0:
return
if s.pos + bufLen > s.data.len:
setLen(s.data, s.pos + bufLen)
copyMem(addr(s.data[s.pos]), buffer, bufLen)
inc(s.pos, bufLen)
proc ssClose(s: Stream) =
var s = StringStream(s)
@@ -284,7 +286,7 @@ when not defined(js):
proc newFileStream*(filename: string, mode: FileMode): FileStream =
## creates a new stream from the file named `filename` with the mode `mode`.
## If the file cannot be opened, nil is returned. See the `system
## <system.html>`_ module for a list of available TFileMode enums.
## <system.html>`_ module for a list of available FileMode enums.
var f: File
if open(f, filename, mode): result = newFileStream(f)

View File

@@ -112,7 +112,7 @@ proc `[]`*(t: StringTableRef, key: string): string {.rtl, extern: "nstGet".} =
proc mget*(t: StringTableRef, key: string): var string {.
rtl, extern: "nstTake".} =
## retrieves the location at ``t[key]``. If `key` is not in `t`, the
## ``EInvalidKey`` exception is raised.
## ``KeyError`` exception is raised.
var index = rawGet(t, key)
if index >= 0: result = t.data[index].val
else: raise newException(KeyError, "key does not exist: " & key)
@@ -158,7 +158,7 @@ proc getValue(t: StringTableRef, flags: set[FormatFlag], key: string): string =
else: result = ""
if result.len == 0:
if useKey in flags: result = '$' & key
elif not (useEmpty in flags): raiseFormatException(key)
elif useEmpty notin flags: raiseFormatException(key)
proc newStringTable*(mode: StringTableMode): StringTableRef {.
rtl, extern: "nst$1".} =
@@ -168,6 +168,12 @@ proc newStringTable*(mode: StringTableMode): StringTableRef {.
result.counter = 0
newSeq(result.data, startSize)
proc clear*(s: StringTableRef, mode: StringTableMode) =
## resets a string table to be empty again.
s.mode = mode
s.counter = 0
s.data.setLen(startSize)
proc newStringTable*(keyValuePairs: varargs[string],
mode: StringTableMode): StringTableRef {.
rtl, extern: "nst$1WithPairs".} =
@@ -227,7 +233,7 @@ proc `$`*(t: StringTableRef): string {.rtl, extern: "nstDollar".} =
result = "{:}"
else:
result = "{"
for key, val in pairs(t):
for key, val in pairs(t):
if result.len > 1: result.add(", ")
result.add(key)
result.add(": ")

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