Merge remote-tracking branch 'upstream/master' into standalone

This commit is contained in:
Simon Hafner 2013-06-29 20:29:37 -05:00
commit 7dd9dbe4dd
119 changed files with 4593 additions and 1580 deletions

11
.gitignore vendored
View file

@ -167,4 +167,15 @@ examples/cross_calculator/android/tags
/run.json /run.json
/testresults.html /testresults.html
/testresults.json /testresults.json
/tests/caas/SymbolProcRun.*/
/tests/caas/absurd_nesting
/tests/caas/forward_declarations
/tests/caas/idetools_api
/tests/caas/imported
/tests/caas/issue_416_template_shift
/tests/caas/issue_452_export_shift
/tests/caas/issue_477_dynamic_dispatch
/tests/caas/its_full_of_procs
/tests/caas/main
/tests/caasdriver
/tools/nimgrep /tools/nimgrep

View file

@ -278,11 +278,12 @@ const
# the compiler will avoid printing such names # the compiler will avoid printing such names
# in user messages. # in user messages.
sfHoist* = sfVolatile ## proc return value can be hoisted
sfNoForward* = sfRegister sfNoForward* = sfRegister
# forward declarations are not required (per module) # forward declarations are not required (per module)
sfNoRoot* = sfBorrow # a local variable is provably no root so it doesn't
# require RC ops
const const
# getting ready for the future expr/stmt merge # getting ready for the future expr/stmt merge
nkWhen* = nkWhenStmt nkWhen* = nkWhenStmt
@ -353,7 +354,7 @@ type
nfSem # node has been checked for semantics nfSem # node has been checked for semantics
TNodeFlags* = set[TNodeFlag] TNodeFlags* = set[TNodeFlag]
TTypeFlag* = enum # keep below 32 for efficiency reasons (now: 19) TTypeFlag* = enum # keep below 32 for efficiency reasons (now: 23)
tfVarargs, # procedure has C styled varargs tfVarargs, # procedure has C styled varargs
tfNoSideEffect, # procedure type does not allow side effects tfNoSideEffect, # procedure type does not allow side effects
tfFinal, # is the object final? tfFinal, # is the object final?
@ -380,7 +381,13 @@ type
tfByRef, # pass object/tuple by reference (C backend) tfByRef, # pass object/tuple by reference (C backend)
tfIterator, # type is really an iterator, not a tyProc tfIterator, # type is really an iterator, not a tyProc
tfShared, # type is 'shared' tfShared, # type is 'shared'
tfNotNil # type cannot be 'nil' tfNotNil, # type cannot be 'nil'
tfNeedsInit, # type constains a "not nil" constraint somewhere or some
# other type so that it requires inititalization
tfHasShared, # type constains a "shared" constraint modifier somewhere
tfHasMeta, # type has "typedesc" or "expr" somewhere
tfHasGCedMem, # type contains GC'ed memory
TTypeFlags* = set[TTypeFlag] TTypeFlags* = set[TTypeFlag]
@ -659,12 +666,15 @@ type
# (or not in symbol table) # (or not in symbol table)
# for modules, an unique index corresponding # for modules, an unique index corresponding
# to the module's fileIdx # to the module's fileIdx
# for variables a slot index for the evaluator
offset*: int # offset of record field offset*: int # offset of record field
loc*: TLoc loc*: TLoc
annex*: PLib # additional fields (seldom used, so we use a annex*: PLib # additional fields (seldom used, so we use a
# reference to another object to safe space) # reference to another object to safe space)
constraint*: PNode # additional constraints like 'lit|result' constraint*: PNode # additional constraints like 'lit|result'; also
# misused for the codegenDecl pragma in the hope
# it won't cause problems
TTypeSeq* = seq[PType] TTypeSeq* = seq[PType]
TType* {.acyclic.} = object of TIdObj # \ TType* {.acyclic.} = object of TIdObj # \
@ -920,7 +930,10 @@ proc discardSons(father: PNode) =
father.sons = nil father.sons = nil
when defined(useNodeIds): when defined(useNodeIds):
const nodeIdToDebug = 140600 const nodeIdToDebug = 612777 # 612794
#612840 # 612905 # 614635 # 614637 # 614641
# 423408
#429107 # 430443 # 441048 # 441090 # 441153
var gNodeId: int var gNodeId: int
proc newNode(kind: TNodeKind): PNode = proc newNode(kind: TNodeKind): PNode =
@ -933,6 +946,7 @@ proc newNode(kind: TNodeKind): PNode =
when defined(useNodeIds): when defined(useNodeIds):
result.id = gNodeId result.id = gNodeId
if result.id == nodeIdToDebug: if result.id == nodeIdToDebug:
echo "KIND ", result.kind
writeStackTrace() writeStackTrace()
inc gNodeId inc gNodeId
@ -973,6 +987,12 @@ proc newNodeI(kind: TNodeKind, info: TLineInfo): PNode =
new(result) new(result)
result.kind = kind result.kind = kind
result.info = info result.info = info
when defined(useNodeIds):
result.id = gNodeId
if result.id == nodeIdToDebug:
echo "KIND ", result.kind
writeStackTrace()
inc gNodeId
proc newNodeI*(kind: TNodeKind, info: TLineInfo, children: int): PNode = proc newNodeI*(kind: TNodeKind, info: TLineInfo, children: int): PNode =
new(result) new(result)
@ -980,6 +1000,12 @@ proc newNodeI*(kind: TNodeKind, info: TLineInfo, children: int): PNode =
result.info = info result.info = info
if children > 0: if children > 0:
newSeq(result.sons, children) newSeq(result.sons, children)
when defined(useNodeIds):
result.id = gNodeId
if result.id == nodeIdToDebug:
echo "KIND ", result.kind
writeStackTrace()
inc gNodeId
proc newNode*(kind: TNodeKind, info: TLineInfo, sons: TNodeSeq = @[], proc newNode*(kind: TNodeKind, info: TLineInfo, sons: TNodeSeq = @[],
typ: PType = nil): PNode = typ: PType = nil): PNode =
@ -989,6 +1015,12 @@ proc newNode*(kind: TNodeKind, info: TLineInfo, sons: TNodeSeq = @[],
result.typ = typ result.typ = typ
# XXX use shallowCopy here for ownership transfer: # XXX use shallowCopy here for ownership transfer:
result.sons = sons result.sons = sons
when defined(useNodeIds):
result.id = gNodeId
if result.id == nodeIdToDebug:
echo "KIND ", result.kind
writeStackTrace()
inc gNodeId
proc newNodeIT(kind: TNodeKind, info: TLineInfo, typ: PType): PNode = proc newNodeIT(kind: TNodeKind, info: TLineInfo, typ: PType): PNode =
result = newNode(kind) result = newNode(kind)
@ -1146,14 +1178,33 @@ proc newSons(father: PNode, length: int) =
else: else:
setlen(father.sons, length) setlen(father.sons, length)
proc addSon*(father, son: PType) {.deprecated.} = proc propagateToOwner*(owner, elem: PType) =
if isNil(father.sons): father.sons = @[] const HaveTheirOwnEmpty = {tySequence, tySet}
add(father.sons, son) owner.flags = owner.flags + (elem.flags * {tfHasShared, tfHasMeta,
#assert((father.kind != tyGenericInvokation) or (son.kind != tyGenericInst)) tfHasGCedMem})
if tfNotNil in elem.flags:
if owner.kind in {tyGenericInst, tyGenericBody, tyGenericInvokation}:
owner.flags.incl tfNotNil
elif owner.kind notin HaveTheirOwnEmpty:
owner.flags.incl tfNeedsInit
if tfNeedsInit in elem.flags:
if owner.kind in HaveTheirOwnEmpty: nil
else: owner.flags.incl tfNeedsInit
if tfShared in elem.flags:
owner.flags.incl tfHasShared
if elem.kind in {tyExpr, tyTypeDesc}:
owner.flags.incl tfHasMeta
elif elem.kind in {tyString, tyRef, tySequence} or
elem.kind == tyProc and elem.callConv == ccClosure:
owner.flags.incl tfHasGCedMem
proc rawAddSon*(father, son: PType) = proc rawAddSon*(father, son: PType) =
if isNil(father.sons): father.sons = @[] if isNil(father.sons): father.sons = @[]
add(father.sons, son) add(father.sons, son)
if not son.isNil: propagateToOwner(father, son)
proc addSon(father, son: PNode) = proc addSon(father, son: PNode) =
assert son != nil assert son != nil
@ -1178,6 +1229,9 @@ proc copyNode(src: PNode): PNode =
result.info = src.info result.info = src.info
result.typ = src.typ result.typ = src.typ
result.flags = src.flags * PersistentNodeFlags result.flags = src.flags * PersistentNodeFlags
when defined(useNodeIds):
if result.id == nodeIdToDebug:
echo "COMES FROM ", src.id
case src.Kind case src.Kind
of nkCharLit..nkUInt64Lit: result.intVal = src.intVal of nkCharLit..nkUInt64Lit: result.intVal = src.intVal
of nkFloatLit..nkFloat128Lit: result.floatVal = src.floatVal of nkFloatLit..nkFloat128Lit: result.floatVal = src.floatVal
@ -1193,6 +1247,9 @@ proc shallowCopy*(src: PNode): PNode =
result.info = src.info result.info = src.info
result.typ = src.typ result.typ = src.typ
result.flags = src.flags * PersistentNodeFlags result.flags = src.flags * PersistentNodeFlags
when defined(useNodeIds):
if result.id == nodeIdToDebug:
echo "COMES FROM ", src.id
case src.Kind case src.Kind
of nkCharLit..nkUInt64Lit: result.intVal = src.intVal of nkCharLit..nkUInt64Lit: result.intVal = src.intVal
of nkFloatLit..nkFloat128Lit: result.floatVal = src.floatVal of nkFloatLit..nkFloat128Lit: result.floatVal = src.floatVal
@ -1209,6 +1266,9 @@ proc copyTree(src: PNode): PNode =
result.info = src.info result.info = src.info
result.typ = src.typ result.typ = src.typ
result.flags = src.flags * PersistentNodeFlags result.flags = src.flags * PersistentNodeFlags
when defined(useNodeIds):
if result.id == nodeIdToDebug:
echo "COMES FROM ", src.id
case src.Kind case src.Kind
of nkCharLit..nkUInt64Lit: result.intVal = src.intVal of nkCharLit..nkUInt64Lit: result.intVal = src.intVal
of nkFloatLit..nkFloat128Lit: result.floatVal = src.floatVal of nkFloatLit..nkFloat128Lit: result.floatVal = src.floatVal

View file

@ -652,6 +652,8 @@ proc finishTypeDescriptions(m: BModule) =
discard getTypeDesc(m, m.typeStack[i]) discard getTypeDesc(m, m.typeStack[i])
inc(i) inc(i)
template cgDeclFrmt*(s: PSym): string = s.constraint.strVal
proc genProcHeader(m: BModule, prc: PSym): PRope = proc genProcHeader(m: BModule, prc: PSym): PRope =
var var
rettype, params: PRope rettype, params: PRope
@ -669,12 +671,16 @@ proc genProcHeader(m: BModule, prc: PSym): PRope =
genProcParams(m, prc.typ, rettype, params, check) genProcParams(m, prc.typ, rettype, params, check)
# careful here! don't access ``prc.ast`` as that could reload large parts of # careful here! don't access ``prc.ast`` as that could reload large parts of
# the object graph! # the object graph!
if prc.constraint.isNil:
appf(result, "$1($2, $3)$4", appf(result, "$1($2, $3)$4",
[toRope(CallingConvToStr[prc.typ.callConv]), rettype, prc.loc.r, params]) [toRope(CallingConvToStr[prc.typ.callConv]), rettype, prc.loc.r,
params])
else:
result = ropef(prc.cgDeclFrmt, [rettype, prc.loc.r, params])
# ------------------ type info generation ------------------------------------- # ------------------ type info generation -------------------------------------
proc genTypeInfo(m: BModule, typ: PType): PRope proc genTypeInfo(m: BModule, t: PType): PRope
proc getNimNode(m: BModule): PRope = proc getNimNode(m: BModule): PRope =
result = ropef("$1[$2]", [m.typeNodesName, toRope(m.typeNodes)]) result = ropef("$1[$2]", [m.typeNodesName, toRope(m.typeNodes)])
inc(m.typeNodes) inc(m.typeNodes)
@ -901,21 +907,21 @@ type
include ccgtrav include ccgtrav
proc genTypeInfo(m: BModule, typ: PType): PRope = proc genTypeInfo(m: BModule, t: PType): PRope =
var t = getUniqueType(typ) var t = getUniqueType(t)
result = ropef("NTI$1", [toRope(t.id)]) result = ropef("NTI$1", [toRope(t.id)])
let owner = typ.skipTypes(typedescPtrs).owner.getModule if ContainsOrIncl(m.typeInfoMarker, t.id):
return con("(&".toRope, result, ")".toRope)
let owner = t.skipTypes(typedescPtrs).owner.getModule
if owner != m.module: if owner != m.module:
# make sure the type info is created in the owner module # make sure the type info is created in the owner module
discard genTypeInfo(owner.bmod, typ) discard genTypeInfo(owner.bmod, t)
# refenrece the type info as extern here # reference the type info as extern here
discard cgsym(m, "TNimType") discard cgsym(m, "TNimType")
discard cgsym(m, "TNimNode") discard cgsym(m, "TNimNode")
appf(m.s[cfsVars], "extern TNimType $1; /* $2 */$n", appf(m.s[cfsVars], "extern TNimType $1; /* $2 */$n",
[result, toRope(typeToString(t))]) [result, toRope(typeToString(t))])
return con("(&".toRope, result, ")".toRope) return con("(&".toRope, result, ")".toRope)
if ContainsOrIncl(m.typeInfoMarker, t.id):
return con("(&".toRope, result, ")".toRope)
case t.kind case t.kind
of tyEmpty: result = toRope"0" of tyEmpty: result = toRope"0"
of tyPointer, tyBool, tyChar, tyCString, tyString, tyInt..tyUInt64, tyVar: of tyPointer, tyBool, tyChar, tyCString, tyString, tyInt..tyUInt64, tyVar:

View file

@ -488,12 +488,15 @@ proc assignLocalVar(p: BProc, s: PSym) =
fillLoc(s.loc, locLocalVar, s.typ, mangleName(s), OnStack) fillLoc(s.loc, locLocalVar, s.typ, mangleName(s), OnStack)
if s.kind == skLet: incl(s.loc.flags, lfNoDeepCopy) if s.kind == skLet: incl(s.loc.flags, lfNoDeepCopy)
var decl = getTypeDesc(p.module, s.loc.t) var decl = getTypeDesc(p.module, s.loc.t)
if s.constraint.isNil:
if sfRegister in s.flags: app(decl, " register") if sfRegister in s.flags: app(decl, " register")
#elif skipTypes(s.typ, abstractInst).kind in GcTypeKinds: #elif skipTypes(s.typ, abstractInst).kind in GcTypeKinds:
# app(decl, " GC_GUARD") # app(decl, " GC_GUARD")
if sfVolatile in s.flags or p.nestedTryStmts.len > 0: if sfVolatile in s.flags or p.nestedTryStmts.len > 0:
app(decl, " volatile") app(decl, " volatile")
appf(decl, " $1;$n", [s.loc.r]) appf(decl, " $1;$n", [s.loc.r])
else:
decl = ropef(s.cgDeclFrmt & ";$n", decl, s.loc.r)
line(p, cpsLocals, decl) line(p, cpsLocals, decl)
localDebugInfo(p, s) localDebugInfo(p, s)
@ -518,11 +521,17 @@ proc assignGlobalVar(p: BProc, s: PSym) =
if sfThread in s.flags: if sfThread in s.flags:
declareThreadVar(p.module, s, sfImportc in s.flags) declareThreadVar(p.module, s, sfImportc in s.flags)
else: else:
if sfImportc in s.flags: app(p.module.s[cfsVars], "extern ") var decl: PRope = nil
app(p.module.s[cfsVars], getTypeDesc(p.module, s.loc.t)) var td = getTypeDesc(p.module, s.loc.t)
if sfRegister in s.flags: app(p.module.s[cfsVars], " register") if s.constraint.isNil:
if sfVolatile in s.flags: app(p.module.s[cfsVars], " volatile") if sfImportc in s.flags: app(decl, "extern ")
appf(p.module.s[cfsVars], " $1;$n", [s.loc.r]) app(decl, td)
if sfRegister in s.flags: app(decl, " register")
if sfVolatile in s.flags: app(decl, " volatile")
appf(decl, " $1;$n", [s.loc.r])
else:
decl = ropef(s.cgDeclFrmt & ";$n", td, s.loc.r)
app(p.module.s[cfsVars], decl)
if p.withinLoop > 0: if p.withinLoop > 0:
# fixes tests/run/tzeroarray: # fixes tests/run/tzeroarray:
resetLoc(p, s.loc) resetLoc(p, s.loc)
@ -1031,7 +1040,7 @@ proc genInitCode(m: BModule) =
var procname = CStringLit(m.initProc, prc, m.module.name.s) var procname = CStringLit(m.initProc, prc, m.module.name.s)
app(prc, initFrame(m.initProc, procname, m.module.info.quotedFilename)) app(prc, initFrame(m.initProc, procname, m.module.info.quotedFilename))
else: else:
app(prc, ~"\tvolatile TFrame F; F.len = 0;$N") app(prc, ~"\tTFrame F; F.len = 0;$N")
app(prc, genSectionStart(cpsInit)) app(prc, genSectionStart(cpsInit))
app(prc, m.preInitProc.s(cpsInit)) app(prc, m.preInitProc.s(cpsInit))

View file

@ -23,12 +23,12 @@ when hasFFI:
type type
PStackFrame* = ref TStackFrame PStackFrame* = ref TStackFrame
TStackFrame*{.final.} = object TStackFrame* = object
mapping*: TIdNodeTable # mapping from symbols to nodes prc: PSym # current prc; proc that is evaluated
prc*: PSym # current prc; proc that is evaluated slots: TNodeSeq # parameters passed to the proc + locals;
call*: PNode # parameters come first
next*: PStackFrame # for stacking call: PNode
params*: TNodeSeq # parameters passed to the proc next: PStackFrame # for stacking
TEvalMode* = enum ## reason for evaluation TEvalMode* = enum ## reason for evaluation
emRepl, ## evaluate because in REPL mode emRepl, ## evaluate because in REPL mode
@ -69,8 +69,7 @@ const
proc newStackFrame*(): PStackFrame = proc newStackFrame*(): PStackFrame =
new(result) new(result)
initIdNodeTable(result.mapping) result.slots = @[]
result.params = @[]
proc newEvalContext*(module: PSym, mode: TEvalMode): PEvalContext = proc newEvalContext*(module: PSym, mode: TEvalMode): PEvalContext =
new(result) new(result)
@ -287,23 +286,48 @@ proc getNullValue(typ: PType, info: TLineInfo): PNode =
result = newNodeIT(nkCurly, info, t) result = newNodeIT(nkCurly, info, t)
else: InternalError("getNullValue: " & $t.kind) else: InternalError("getNullValue: " & $t.kind)
proc evalVarValue(c: PEvalContext, n: PNode): PNode =
result = evalAux(c, n, {})
if result.kind in {nkType..nkNilLit}: result = result.copyNode
proc setSlot(c: PStackFrame, sym: PSym, val: PNode) =
assert sym.owner == c.prc
var idx = sym.position
if idx == 0:
idx = c.slots.len
if idx == 0: idx = 1
sym.position = idx
setLen(c.slots, max(idx+1, c.slots.len))
c.slots[idx] = val
proc setVar(c: PEvalContext, v: PSym, n: PNode) =
if sfGlobal notin v.flags: setSlot(c.tos, v, n)
else: IdNodeTablePut(c.globals, v, n)
proc evalVar(c: PEvalContext, n: PNode): PNode = proc evalVar(c: PEvalContext, n: PNode): PNode =
for i in countup(0, sonsLen(n) - 1): for i in countup(0, sonsLen(n) - 1):
var a = n.sons[i] let a = n.sons[i]
if a.kind == nkCommentStmt: continue if a.kind == nkCommentStmt: continue
if a.kind != nkIdentDefs: return raiseCannotEval(c, n.info)
# XXX var (x, y) = z support?
#assert(a.sons[0].kind == nkSym) can happen for transformed vars #assert(a.sons[0].kind == nkSym) can happen for transformed vars
if a.sons[2].kind != nkEmpty: if a.kind == nkVarTuple:
result = evalAux(c, a.sons[2], {}) result = evalVarValue(c, a.lastSon)
if result.kind in {nkType..nkNilLit}: if result.kind in {nkType..nkNilLit}:
result = result.copyNode result = result.copyNode
if isSpecial(result): return if isSpecial(result): return
if result.kind != nkPar:
return raiseCannotEval(c, n.info)
for i in 0 .. a.len-3:
var v = a.sons[i].sym
setVar(c, v, result.sons[i])
else:
if a.sons[2].kind != nkEmpty:
result = evalVarValue(c, a.sons[2])
if isSpecial(result): return
else: else:
result = getNullValue(a.sons[0].typ, a.sons[0].info) result = getNullValue(a.sons[0].typ, a.sons[0].info)
if a.sons[0].kind == nkSym: if a.sons[0].kind == nkSym:
var v = a.sons[0].sym var v = a.sons[0].sym
IdNodeTablePut(c.tos.mapping, v, result) setVar(c, v, result)
else: else:
# assign to a.sons[0]: # assign to a.sons[0]:
var x = result var x = result
@ -331,18 +355,17 @@ proc aliasNeeded(n: PNode, flags: TEvalFlags): bool =
proc evalVariable(c: PStackFrame, sym: PSym, flags: TEvalFlags): PNode = proc evalVariable(c: PStackFrame, sym: PSym, flags: TEvalFlags): PNode =
# We need to return a node to the actual value, # We need to return a node to the actual value,
# which can be modified. # which can be modified.
assert sym.position != 0 or skResult == sym.kind
var x = c var x = c
while x != nil: while x != nil:
if sym.kind == skResult and x.params.len > 0: if sym.owner == x.prc:
result = x.params[0] result = x.slots[sym.position]
if result == nil: result = emptyNode assert result != nil
return if not aliasNeeded(result, flags):
result = IdNodeTableGet(x.mapping, sym)
if result != nil and not aliasNeeded(result, flags):
result = copyTree(result) result = copyTree(result)
if result != nil: return return
x = x.next x = x.next
#internalError(sym.info, "cannot eval " & sym.name.s) #internalError(sym.info, "cannot eval " & sym.name.s & " " & $sym.position)
result = raiseCannotEval(nil, sym.info) result = raiseCannotEval(nil, sym.info)
#result = emptyNode #result = emptyNode
@ -374,12 +397,12 @@ proc evalCall(c: PEvalContext, n: PNode): PNode =
d.call = n d.call = n
var prc = n.sons[0] var prc = n.sons[0]
let isClosure = prc.kind == nkClosure let isClosure = prc.kind == nkClosure
setlen(d.params, sonsLen(n) + ord(isClosure)) setlen(d.slots, sonsLen(n) + ord(isClosure))
if isClosure: if isClosure:
#debug prc #debug prc
result = evalAux(c, prc.sons[1], {efLValue}) result = evalAux(c, prc.sons[1], {efLValue})
if isSpecial(result): return if isSpecial(result): return
d.params[sonsLen(n)] = result d.slots[sonsLen(n)] = result
result = evalAux(c, prc.sons[0], {}) result = evalAux(c, prc.sons[0], {})
else: else:
result = evalAux(c, prc, {}) result = evalAux(c, prc, {})
@ -397,21 +420,21 @@ proc evalCall(c: PEvalContext, n: PNode): PNode =
for i in countup(1, sonsLen(n) - 1): for i in countup(1, sonsLen(n) - 1):
result = evalAux(c, n.sons[i], {}) result = evalAux(c, n.sons[i], {})
if isSpecial(result): return if isSpecial(result): return
d.params[i] = result d.slots[i] = result
if n.typ != nil: d.params[0] = getNullValue(n.typ, n.info) if n.typ != nil: d.slots[0] = getNullValue(n.typ, n.info)
when hasFFI: when hasFFI:
if sfImportc in prc.sym.flags and allowFFI in c.features: if sfImportc in prc.sym.flags and allowFFI in c.features:
var newCall = newNodeI(nkCall, n.info, n.len) var newCall = newNodeI(nkCall, n.info, n.len)
newCall.sons[0] = evalGlobalVar(c, prc.sym, {}) newCall.sons[0] = evalGlobalVar(c, prc.sym, {})
for i in 1 .. <n.len: for i in 1 .. <n.len:
newCall.sons[i] = d.params[i] newCall.sons[i] = d.slots[i]
return callForeignFunction(newCall) return callForeignFunction(newCall)
pushStackFrame(c, d) pushStackFrame(c, d)
result = evalAux(c, prc.sym.getBody, {}) result = evalAux(c, prc.sym.getBody, {})
if result.kind == nkExceptBranch: return if result.kind == nkExceptBranch: return
if n.typ != nil: result = d.params[0] if n.typ != nil: result = d.slots[0]
popStackFrame(c) popStackFrame(c)
proc evalArrayAccess(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode = proc evalArrayAccess(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode =
@ -553,8 +576,8 @@ proc evalSym(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode =
result = evalGlobalVar(c, s, flags) result = evalGlobalVar(c, s, flags)
of skParam: of skParam:
# XXX what about LValue? # XXX what about LValue?
if s.position + 1 <% c.tos.params.len: if s.position + 1 <% c.tos.slots.len:
result = c.tos.params[s.position + 1] result = c.tos.slots[s.position + 1]
of skConst: result = s.ast of skConst: result = s.ast
of skEnumField: result = newIntNodeT(s.position, n) of skEnumField: result = newIntNodeT(s.position, n)
else: result = nil else: result = nil
@ -739,10 +762,12 @@ proc evalProc(c: PEvalContext, n: PNode): PNode =
if (resultPos < sonsLen(n)) and (n.sons[resultPos].kind != nkEmpty): if (resultPos < sonsLen(n)) and (n.sons[resultPos].kind != nkEmpty):
var v = n.sons[resultPos].sym var v = n.sons[resultPos].sym
result = getNullValue(v.typ, n.info) result = getNullValue(v.typ, n.info)
IdNodeTablePut(c.tos.mapping, v, result) if c.tos.slots.len == 0: setLen(c.tos.slots, 1)
c.tos.slots[0] = result
#IdNodeTablePut(c.tos.mapping, v, result)
result = evalAux(c, s.getBody, {}) result = evalAux(c, s.getBody, {})
if result.kind == nkReturnToken: if result.kind == nkReturnToken:
result = IdNodeTableGet(c.tos.mapping, v) result = c.tos.slots[0]
else: else:
result = evalAux(c, s.getBody, {}) result = evalAux(c, s.getBody, {})
if result.kind == nkReturnToken: if result.kind == nkReturnToken:
@ -922,7 +947,7 @@ proc evalTypeTrait*(n: PNode, context: PSym): PNode =
let typ = n.sons[1].sym.typ.skipTypes({tyTypeDesc}) let typ = n.sons[1].sym.typ.skipTypes({tyTypeDesc})
case n.sons[0].sym.name.s.normalize case n.sons[0].sym.name.s.normalize
of "name": of "name":
result = newStrNode(nkStrLit, typ.typeToString(preferName)) result = newStrNode(nkStrLit, typ.typeToString(preferExported))
result.typ = newType(tyString, context) result.typ = newType(tyString, context)
result.info = n.info result.info = n.info
else: else:
@ -1468,20 +1493,21 @@ proc eval*(c: PEvalContext, n: PNode): PNode =
else: else:
stackTrace(c, result, errCannotInterpretNodeX, renderTree(n)) stackTrace(c, result, errCannotInterpretNodeX, renderTree(n))
proc evalConstExprAux(module: PSym, e: PNode, mode: TEvalMode): PNode = proc evalConstExprAux(module, prc: PSym, e: PNode, mode: TEvalMode): PNode =
var p = newEvalContext(module, mode) var p = newEvalContext(module, mode)
var s = newStackFrame() var s = newStackFrame()
s.call = e s.call = e
s.prc = prc
pushStackFrame(p, s) pushStackFrame(p, s)
result = tryEval(p, e) result = tryEval(p, e)
if result != nil and result.kind == nkExceptBranch: result = nil if result != nil and result.kind == nkExceptBranch: result = nil
popStackFrame(p) popStackFrame(p)
proc evalConstExpr*(module: PSym, e: PNode): PNode = proc evalConstExpr*(module: PSym, e: PNode): PNode =
result = evalConstExprAux(module, e, emConst) result = evalConstExprAux(module, nil, e, emConst)
proc evalStaticExpr*(module: PSym, e: PNode): PNode = proc evalStaticExpr*(module: PSym, e: PNode, prc: PSym): PNode =
result = evalConstExprAux(module, e, emStatic) result = evalConstExprAux(module, prc, e, emStatic)
proc setupMacroParam(x: PNode): PNode = proc setupMacroParam(x: PNode): PNode =
result = x result = x
@ -1496,16 +1522,17 @@ proc evalMacroCall(c: PEvalContext, n, nOrig: PNode, sym: PSym): PNode =
c.callsite = nOrig c.callsite = nOrig
var s = newStackFrame() var s = newStackFrame()
s.call = n s.call = n
s.prc = sym
var L = n.safeLen var L = n.safeLen
if L == 0: L = 1 if L == 0: L = 1
setlen(s.params, L) setlen(s.slots, L)
# return value: # return value:
s.params[0] = newNodeIT(nkNilLit, n.info, sym.typ.sons[0]) s.slots[0] = newNodeIT(nkNilLit, n.info, sym.typ.sons[0])
# setup parameters: # setup parameters:
for i in 1 .. < L: s.params[i] = setupMacroParam(n.sons[i]) for i in 1 .. < L: s.slots[i] = setupMacroParam(n.sons[i])
pushStackFrame(c, s) pushStackFrame(c, s)
discard eval(c, sym.getBody) discard eval(c, sym.getBody)
result = s.params[0] result = s.slots[0]
popStackFrame(c) popStackFrame(c)
if cyclicTree(result): GlobalError(n.info, errCyclicTree) if cyclicTree(result): GlobalError(n.info, errCyclicTree)
dec(evalTemplateCounter) dec(evalTemplateCounter)
@ -1521,10 +1548,11 @@ var oldErrorCount: int
proc myProcess(c: PPassContext, n: PNode): PNode = proc myProcess(c: PPassContext, n: PNode): PNode =
# don't eval errornous code: # don't eval errornous code:
if oldErrorCount != msgs.gErrorCounter: if oldErrorCount == msgs.gErrorCounter:
oldErrorCount = msgs.gErrorCounter
return n
result = eval(PEvalContext(c), n) result = eval(PEvalContext(c), n)
else:
result = n
oldErrorCount = msgs.gErrorCounter
const evalPass* = makePass(myOpen, nil, myProcess, myProcess) const evalPass* = makePass(myOpen, nil, myProcess, myProcess)

584
compiler/guards.nim Normal file
View file

@ -0,0 +1,584 @@
#
#
# The Nimrod Compiler
# (c) Copyright 2013 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module implements the 'implies' relation for guards.
import ast, astalgo, msgs, magicsys, nimsets, trees, types, renderer
const
someEq = {mEqI, mEqI64, mEqF64, mEqEnum, mEqCh, mEqB, mEqRef, mEqProc,
mEqUntracedRef, mEqStr, mEqSet, mEqCString}
# set excluded here as the semantics are vastly different:
someLe = {mLeI, mLeI64, mLeF64, mLeU, mLeU64, mLeEnum,
mLeCh, mLeB, mLePtr, mLeStr}
someLt = {mLtI, mLtI64, mLtF64, mLtU, mLtU64, mLtEnum,
mLtCh, mLtB, mLtPtr, mLtStr}
someLen = {mLengthOpenArray, mLengthStr, mLengthArray, mLengthSeq}
someIn = {mInRange, mInSet}
proc isValue(n: PNode): bool = n.kind in {nkCharLit..nkNilLit}
proc isLocation(n: PNode): bool = not n.isValue
proc isLet(n: PNode): bool =
if n.kind == nkSym:
if n.sym.kind in {skLet, skTemp, skForVar}:
result = true
elif n.sym.kind == skParam and skipTypes(n.sym.typ,
abstractInst).kind != tyVar:
result = true
proc isVar(n: PNode): bool =
n.kind == nkSym and n.sym.kind in {skResult, skVar} and
{sfGlobal, sfAddrTaken} * n.sym.flags == {}
proc isLetLocation(m: PNode, isApprox: bool): bool =
# consider: 'n[].kind' --> we really need to support 1 deref op even if this
# is technically wrong due to aliasing :-( We could introduce "soft" facts
# for this; this would still be very useful for warnings and also nicely
# solves the 'var' problems. For now we fix this by requiring much more
# restrictive expressions for the 'not nil' checking.
var n = m
var derefs = 0
while true:
case n.kind
of nkDotExpr, nkCheckedFieldExpr, nkObjUpConv, nkObjDownConv:
n = n.sons[0]
of nkDerefExpr, nkHiddenDeref:
n = n.sons[0]
inc derefs
of nkBracketExpr:
if isConstExpr(n.sons[1]) or isLet(n.sons[1]):
n = n.sons[0]
else: return
of nkHiddenStdConv, nkHiddenSubConv, nkConv:
n = n.sons[1]
else:
break
result = n.isLet and derefs <= ord(isApprox)
if not result and isApprox:
result = isVar(n)
proc interestingCaseExpr*(m: PNode): bool = isLetLocation(m, true)
proc swapArgs(fact: PNode, newOp: string, m: TMagic): PNode =
result = newNodeI(nkCall, fact.info, 3)
result.sons[0] = newSymNode(getSysMagic(newOp, m))
result.sons[1] = fact.sons[2]
result.sons[2] = fact.sons[1]
proc neg(n: PNode): PNode =
if n == nil: return nil
case n.getMagic
of mNot:
result = n.sons[1]
of someLt:
# not (a < b) == a >= b == b <= a
result = swapArgs(n, "<=", mLeI)
of someLe:
result = swapArgs(n, "<", mLtI)
of mInSet:
if n.sons[1].kind != nkCurly: return nil
let t = n.sons[2].typ.skipTypes(abstractInst)
result = newNodeI(nkCall, n.info, 3)
result.sons[0] = n.sons[0]
result.sons[2] = n.sons[2]
if t.kind == tyEnum:
var s = newNodeIT(nkCurly, n.info, n.sons[1].typ)
for e in t.n:
let eAsNode = newIntNode(nkIntLit, e.sym.position)
if not inSet(n.sons[1], eAsNode): s.add eAsNode
result.sons[1] = s
elif lengthOrd(t) < 1000:
result.sons[1] = complement(n.sons[1])
else:
# not ({2, 3, 4}.contains(x)) x != 2 and x != 3 and x != 4
# XXX todo
result = nil
of mOr:
# not (a or b) --> not a and not b
let
a = n.sons[1].neg
b = n.sons[2].neg
if a != nil and b != nil:
result = newNodeI(nkCall, n.info, 3)
result.sons[0] = newSymNode(getSysMagic("and", mAnd))
result.sons[1] = a
result.sons[2] = b
elif a != nil:
result = a
elif b != nil:
result = b
else:
# leave not (a == 4) as it is
result = newNodeI(nkCall, n.info, 2)
result.sons[0] = newSymNode(getSysMagic("not", mNot))
result.sons[1] = n
proc buildIsNil(arg: PNode): PNode =
result = newNodeI(nkCall, arg.info, 2)
result.sons[0] = newSymNode(getSysMagic("isNil", mIsNil))
result.sons[1] = arg
proc usefulFact(n: PNode): PNode =
case n.getMagic
of someEq:
if skipConv(n.sons[2]).kind == nkNilLit and (
isLetLocation(n.sons[1], false) or isVar(n.sons[1])):
result = buildIsNil(n.sons[1])
else:
if isLetLocation(n.sons[1], true) or isLetLocation(n.sons[2], true):
# XXX algebraic simplifications! 'i-1 < a.len' --> 'i < a.len+1'
result = n
of someLe+someLt:
if isLetLocation(n.sons[1], true) or isLetLocation(n.sons[2], true):
# XXX algebraic simplifications! 'i-1 < a.len' --> 'i < a.len+1'
result = n
of mIsNil:
if isLetLocation(n.sons[1], false) or isVar(n.sons[1]):
result = n
of someIn:
if isLetLocation(n.sons[1], true):
result = n
of mAnd:
let
a = usefulFact(n.sons[1])
b = usefulFact(n.sons[2])
if a != nil and b != nil:
result = newNodeI(nkCall, n.info, 3)
result.sons[0] = newSymNode(getSysMagic("and", mAnd))
result.sons[1] = a
result.sons[2] = b
elif a != nil:
result = a
elif b != nil:
result = b
of mNot:
let a = usefulFact(n.sons[1])
if a != nil:
result = a.neg
of mOr:
# 'or' sucks! (p.isNil or q.isNil) --> hard to do anything
# with that knowledge...
# DeMorgan helps a little though:
# not a or not b --> not (a and b)
# (x == 3) or (y == 2) ---> not ( not (x==3) and not (y == 2))
# not (x != 3 and y != 2)
let
a = usefulFact(n.sons[1]).neg
b = usefulFact(n.sons[2]).neg
if a != nil and b != nil:
result = newNodeI(nkCall, n.info, 3)
result.sons[0] = newSymNode(getSysMagic("and", mAnd))
result.sons[1] = a
result.sons[2] = b
result = result.neg
elif n.kind == nkSym and n.sym.kind == skLet:
# consider:
# let a = 2 < x
# if a:
# ...
# We make can easily replace 'a' by '2 < x' here:
result = usefulFact(n.sym.ast)
elif n.kind == nkStmtListExpr:
result = usefulFact(n.lastSon)
type
TModel* = seq[PNode] # the "knowledge base"
proc addFact*(m: var TModel, n: PNode) =
let n = usefulFact(n)
if n != nil: m.add n
proc addFactNeg*(m: var TModel, n: PNode) =
let n = n.neg
if n != nil: addFact(m, n)
proc sameTree(a, b: PNode): bool =
result = false
if a == b:
result = true
elif (a != nil) and (b != nil) and (a.kind == b.kind):
case a.kind
of nkSym: result = a.sym == b.sym
of nkIdent: result = a.ident.id == b.ident.id
of nkCharLit..nkInt64Lit: result = a.intVal == b.intVal
of nkFloatLit..nkFloat64Lit: result = a.floatVal == b.floatVal
of nkStrLit..nkTripleStrLit: result = a.strVal == b.strVal
of nkType: result = a.typ == b.typ
of nkEmpty, nkNilLit: result = true
else:
if sonsLen(a) == sonsLen(b):
for i in countup(0, sonsLen(a) - 1):
if not sameTree(a.sons[i], b.sons[i]): return
result = true
proc hasSubTree(n, x: PNode): bool =
if n.sameTree(x): result = true
else:
for i in 0..safeLen(n)-1:
if hasSubTree(n.sons[i], x): return true
proc invalidateFacts*(m: var TModel, n: PNode) =
# We are able to guard local vars (as opposed to 'let' variables)!
# 'while p != nil: f(p); p = p.next'
# This is actually quite easy to do:
# Re-assignments (incl. pass to a 'var' param) trigger an invalidation
# of every fact that contains 'v'.
#
# if x < 4:
# if y < 5
# x = unknown()
# # we invalidate 'x' here but it's known that x >= 4
# # for the else anyway
# else:
# echo x
#
# The same mechanism could be used for more complex data stored on the heap;
# procs that 'write: []' cannot invalidate 'n.kind' for instance. In fact, we
# could CSE these expressions then and help C's optimizer.
for i in 0..high(m):
if m[i] != nil and m[i].hasSubTree(n): m[i] = nil
proc valuesUnequal(a, b: PNode): bool =
if a.isValue and b.isValue:
result = not SameValue(a, b)
proc pred(n: PNode): PNode =
if n.kind in {nkCharLit..nkUInt64Lit} and n.intVal != low(biggestInt):
result = copyNode(n)
dec result.intVal
else:
result = n
type
TImplication* = enum
impUnknown, impNo, impYes
proc impliesEq(fact, eq: PNode): TImplication =
let (loc, val) = if isLocation(eq.sons[1]): (1, 2) else: (2, 1)
case fact.sons[0].sym.magic
of someEq:
if sameTree(fact.sons[1], eq.sons[loc]):
# this is not correct; consider: a == b; a == 1 --> unknown!
if sameTree(fact.sons[2], eq.sons[val]): result = impYes
elif valuesUnequal(fact.sons[2], eq.sons[val]): result = impNo
elif sameTree(fact.sons[2], eq.sons[loc]):
if sameTree(fact.sons[1], eq.sons[val]): result = impYes
elif valuesUnequal(fact.sons[1], eq.sons[val]): result = impNo
of mInSet:
# remember: mInSet is 'contains' so the set comes first!
if sameTree(fact.sons[2], eq.sons[loc]) and isValue(eq.sons[val]):
if inSet(fact.sons[1], eq.sons[val]): result = impYes
else: result = impNo
of mNot, mOr, mAnd: internalError(eq.info, "impliesEq")
else: discard
proc leImpliesIn(x, c, aSet: PNode): TImplication =
if c.kind in {nkCharLit..nkUInt64Lit}:
# fact: x <= 4; question x in {56}?
# --> true if every value <= 4 is in the set {56}
#
var value = newIntNode(c.kind, firstOrd(x.typ))
# don't iterate too often:
if c.intVal - value.intVal < 1000:
var i, pos, neg: int
while value.intVal <= c.intVal:
if inSet(aSet, value): inc pos
else: inc neg
inc i; inc value.intVal
if pos == i: result = impYes
elif neg == i: result = impNo
proc geImpliesIn(x, c, aSet: PNode): TImplication =
if c.kind in {nkCharLit..nkUInt64Lit}:
# fact: x >= 4; question x in {56}?
# --> true iff every value >= 4 is in the set {56}
#
var value = newIntNode(c.kind, c.intVal)
let max = lastOrd(x.typ)
# don't iterate too often:
if max - value.intVal < 1000:
var i, pos, neg: int
while value.intVal <= max:
if inSet(aSet, value): inc pos
else: inc neg
inc i; inc value.intVal
if pos == i: result = impYes
elif neg == i: result = impNo
proc compareSets(a, b: PNode): TImplication =
if equalSets(a, b): result = impYes
elif intersectSets(a, b).len == 0: result = impNo
proc impliesIn(fact, loc, aSet: PNode): TImplication =
case fact.sons[0].sym.magic
of someEq:
if sameTree(fact.sons[1], loc):
if inSet(aSet, fact.sons[2]): result = impYes
else: result = impNo
elif sameTree(fact.sons[2], loc):
if inSet(aSet, fact.sons[1]): result = impYes
else: result = impNo
of mInSet:
if sameTree(fact.sons[2], loc):
result = compareSets(fact.sons[1], aSet)
of someLe:
if sameTree(fact.sons[1], loc):
result = leImpliesIn(fact.sons[1], fact.sons[2], aSet)
elif sameTree(fact.sons[2], loc):
result = geImpliesIn(fact.sons[2], fact.sons[1], aSet)
of someLt:
if sameTree(fact.sons[1], loc):
result = leImpliesIn(fact.sons[1], fact.sons[2].pred, aSet)
elif sameTree(fact.sons[2], loc):
# 4 < x --> 3 <= x
result = geImpliesIn(fact.sons[2], fact.sons[1].pred, aSet)
of mNot, mOr, mAnd: internalError(loc.info, "impliesIn")
else: discard
proc valueIsNil(n: PNode): TImplication =
if n.kind == nkNilLit: impYes
elif n.kind in {nkStrLit..nkTripleStrLit, nkBracket, nkObjConstr}: impNo
else: impUnknown
proc impliesIsNil(fact, eq: PNode): TImplication =
case fact.sons[0].sym.magic
of mIsNil:
if sameTree(fact.sons[1], eq.sons[1]):
result = impYes
of someEq:
if sameTree(fact.sons[1], eq.sons[1]):
result = valueIsNil(fact.sons[2].skipConv)
elif sameTree(fact.sons[2], eq.sons[1]):
result = valueIsNil(fact.sons[1].skipConv)
of mNot, mOr, mAnd: internalError(eq.info, "impliesIsNil")
else: discard
proc impliesGe(fact, x, c: PNode): TImplication =
InternalAssert isLocation(x)
case fact.sons[0].sym.magic
of someEq:
if sameTree(fact.sons[1], x):
if isValue(fact.sons[2]) and isValue(c):
# fact: x = 4; question x >= 56? --> true iff 4 >= 56
if leValue(c, fact.sons[2]): result = impYes
else: result = impNo
elif sameTree(fact.sons[2], x):
if isValue(fact.sons[1]) and isValue(c):
if leValue(c, fact.sons[1]): result = impYes
else: result = impNo
of someLt:
if sameTree(fact.sons[1], x):
if isValue(fact.sons[2]) and isValue(c):
# fact: x < 4; question N <= x? --> false iff N <= 4
if leValue(fact.sons[2], c): result = impNo
# fact: x < 4; question 2 <= x? --> we don't know
elif sameTree(fact.sons[2], x):
# fact: 3 < x; question: N-1 < x ? --> true iff N-1 <= 3
if isValue(fact.sons[1]) and isValue(c):
if leValue(c.pred, fact.sons[1]): result = impYes
of someLe:
if sameTree(fact.sons[1], x):
if isValue(fact.sons[2]) and isValue(c):
# fact: x <= 4; question x >= 56? --> false iff 4 <= 56
if leValue(fact.sons[2], c): result = impNo
# fact: x <= 4; question x >= 2? --> we don't know
elif sameTree(fact.sons[2], x):
# fact: 3 <= x; question: x >= 2 ? --> true iff 2 <= 3
if isValue(fact.sons[1]) and isValue(c):
if leValue(c, fact.sons[1]): result = impYes
of mNot, mOr, mAnd: internalError(x.info, "impliesGe")
else: discard
proc impliesLe(fact, x, c: PNode): TImplication =
if not isLocation(x):
return impliesGe(fact, c, x)
case fact.sons[0].sym.magic
of someEq:
if sameTree(fact.sons[1], x):
if isValue(fact.sons[2]) and isValue(c):
# fact: x = 4; question x <= 56? --> true iff 4 <= 56
if leValue(fact.sons[2], c): result = impYes
else: result = impNo
elif sameTree(fact.sons[2], x):
if isValue(fact.sons[1]) and isValue(c):
if leValue(fact.sons[1], c): result = impYes
else: result = impNo
of someLt:
if sameTree(fact.sons[1], x):
if isValue(fact.sons[2]) and isValue(c):
# fact: x < 4; question x <= N? --> true iff N-1 <= 4
if leValue(fact.sons[2], c.pred): result = impYes
# fact: x < 4; question x <= 2? --> we don't know
elif sameTree(fact.sons[2], x):
# fact: 3 < x; question: x <= 1 ? --> false iff 1 <= 3
if isValue(fact.sons[1]) and isValue(c):
if leValue(c, fact.sons[1]): result = impNo
of someLe:
if sameTree(fact.sons[1], x):
if isValue(fact.sons[2]) and isValue(c):
# fact: x <= 4; question x <= 56? --> true iff 4 <= 56
if leValue(fact.sons[2], c): result = impYes
# fact: x <= 4; question x <= 2? --> we don't know
elif sameTree(fact.sons[2], x):
# fact: 3 <= x; question: x <= 2 ? --> false iff 2 < 3
if isValue(fact.sons[1]) and isValue(c):
if leValue(c, fact.sons[1].pred): result = impNo
of mNot, mOr, mAnd: internalError(x.info, "impliesLe")
else: nil
proc impliesLt(fact, x, c: PNode): TImplication =
# x < 3 same as x <= 2:
let p = c.pred
if p != c:
result = impliesLe(fact, x, p)
else:
# 4 < x same as 3 <= x
let q = x.pred
if q != x:
result = impliesLe(fact, q, c)
proc `~`(x: TImplication): TImplication =
case x
of impUnknown: impUnknown
of impNo: impYes
of impYes: impNo
proc factImplies(fact, prop: PNode): TImplication =
case fact.getMagic
of mNot:
# Consider:
# enum nkBinary, nkTernary, nkStr
# fact: not (k <= nkBinary)
# question: k in {nkStr}
# --> 'not' for facts is entirely different than 'not' for questions!
# it's provably wrong if every value > 4 is in the set {56}
# That's because we compute the implication and 'a -> not b' cannot
# be treated the same as 'not a -> b'
# (not a) -> b compute as not (a -> b) ???
# == not a or not b == not (a and b)
let arg = fact.sons[1]
case arg.getMagic
of mIsNil:
return ~factImplies(arg, prop)
of mAnd:
# not (a and b) means not a or not b:
# a or b --> both need to imply 'prop'
let a = factImplies(arg.sons[1], prop)
let b = factImplies(arg.sons[2], prop)
if a == b: return ~a
return impUnknown
else:
InternalError(fact.info, "invalid fact")
of mAnd:
result = factImplies(fact.sons[1], prop)
if result != impUnknown: return result
return factImplies(fact.sons[2], prop)
else: discard
case prop.sons[0].sym.magic
of mNot:
result = ~fact.factImplies(prop.sons[1])
of mIsNil:
result = impliesIsNil(fact, prop)
of someEq:
result = impliesEq(fact, prop)
of someLe:
result = impliesLe(fact, prop.sons[1], prop.sons[2])
of someLt:
result = impliesLt(fact, prop.sons[1], prop.sons[2])
of mInSet:
result = impliesIn(fact, prop.sons[2], prop.sons[1])
else:
internalError(prop.info, "invalid proposition")
proc doesImply*(facts: TModel, prop: PNode): TImplication =
assert prop.kind in nkCallKinds
for f in facts:
# facts can be invalidated, in which case they are 'nil':
if not f.isNil:
result = f.factImplies(prop)
if result != impUnknown: return
proc impliesNotNil*(facts: TModel, arg: PNode): TImplication =
result = doesImply(facts, buildIsNil(arg).neg)
proc settype(n: PNode): PType =
result = newType(tySet, n.typ.owner)
addSonSkipIntLit(result, n.typ)
proc buildOf(it, loc: PNode): PNode =
var s = newNodeI(nkCurly, it.info, it.len-1)
s.typ = settype(loc)
for i in 0..it.len-2: s.sons[i] = it.sons[i]
result = newNodeI(nkCall, it.info, 3)
result.sons[0] = newSymNode(getSysMagic("contains", mInSet))
result.sons[1] = s
result.sons[2] = loc
proc buildElse(n: PNode): PNode =
var s = newNodeIT(nkCurly, n.info, settype(n.sons[0]))
for i in 1..n.len-2:
let branch = n.sons[i]
assert branch.kind == nkOfBranch
for j in 0..branch.len-2:
s.add(branch.sons[j])
result = newNodeI(nkCall, n.info, 3)
result.sons[0] = newSymNode(getSysMagic("contains", mInSet))
result.sons[1] = s
result.sons[2] = n.sons[0]
proc addDiscriminantFact*(m: var TModel, n: PNode) =
var fact = newNodeI(nkCall, n.info, 3)
fact.sons[0] = newSymNode(getSysMagic("==", mEqI))
fact.sons[1] = n.sons[0]
fact.sons[2] = n.sons[1]
m.add fact
proc addAsgnFact*(m: var TModel, key, value: PNode) =
var fact = newNodeI(nkCall, key.info, 3)
fact.sons[0] = newSymNode(getSysMagic("==", mEqI))
fact.sons[1] = key
fact.sons[2] = value
m.add fact
proc addCaseBranchFacts*(m: var TModel, n: PNode, i: int) =
let branch = n.sons[i]
if branch.kind == nkOfBranch:
m.add buildOf(branch, n.sons[0])
else:
m.add n.buildElse.neg
proc buildProperFieldCheck(access, check: PNode): PNode =
if check.sons[1].kind == nkCurly:
result = copyTree(check)
if access.kind == nkDotExpr:
var a = copyTree(access)
a.sons[1] = check.sons[2]
result.sons[2] = a
# 'access.kind != nkDotExpr' can happen for object constructors
# which we don't check yet
else:
# it is some 'not'
assert check.getMagic == mNot
result = buildProperFieldCheck(access, check.sons[1]).neg
proc checkFieldAccess*(m: TModel, n: PNode) =
for i in 1..n.len-1:
let check = buildProperFieldCheck(n.sons[0], n.sons[i])
if m.doesImply(check) != impYes:
Message(n.info, warnProveField, renderTree(n.sons[0])); break

View file

@ -236,7 +236,7 @@ proc matchTwoChars(L: TLexer, first: Char, second: TCharSet): bool =
result = (L.buf[L.bufpos] == first) and (L.buf[L.bufpos + 1] in Second) result = (L.buf[L.bufpos] == first) and (L.buf[L.bufpos + 1] in Second)
proc isFloatLiteral(s: string): bool = proc isFloatLiteral(s: string): bool =
for i in countup(0, len(s) + 0 - 1): for i in countup(0, len(s) - 1):
if s[i] in {'.', 'e', 'E'}: if s[i] in {'.', 'e', 'E'}:
return true return true
result = false result = false
@ -250,16 +250,18 @@ proc GetNumber(L: var TLexer): TToken =
result.literal = "" result.literal = ""
result.base = base10 # BUGFIX result.base = base10 # BUGFIX
pos = L.bufpos # make sure the literal is correct for error messages: pos = L.bufpos # make sure the literal is correct for error messages:
var eallowed = false
if L.buf[pos] == '0' and L.buf[pos+1] in {'X', 'x'}: if L.buf[pos] == '0' and L.buf[pos+1] in {'X', 'x'}:
matchUnderscoreChars(L, result, {'A'..'F', 'a'..'f', '0'..'9', 'X', 'x'}) matchUnderscoreChars(L, result, {'A'..'F', 'a'..'f', '0'..'9', 'X', 'x'})
else: else:
matchUnderscoreChars(L, result, {'0'..'9', 'b', 'B', 'o', 'c', 'C'}) matchUnderscoreChars(L, result, {'0'..'9', 'b', 'B', 'o', 'c', 'C'})
eallowed = true
if (L.buf[L.bufpos] == '.') and (L.buf[L.bufpos + 1] in {'0'..'9'}): if (L.buf[L.bufpos] == '.') and (L.buf[L.bufpos + 1] in {'0'..'9'}):
add(result.literal, '.') add(result.literal, '.')
inc(L.bufpos) inc(L.bufpos)
#matchUnderscoreChars(L, result, ['A'..'Z', 'a'..'z', '0'..'9'])
matchUnderscoreChars(L, result, {'0'..'9'}) matchUnderscoreChars(L, result, {'0'..'9'})
if L.buf[L.bufpos] in {'e', 'E'}: eallowed = true
if eallowed and L.buf[L.bufpos] in {'e', 'E'}:
add(result.literal, 'e') add(result.literal, 'e')
inc(L.bufpos) inc(L.bufpos)
if L.buf[L.bufpos] in {'+', '-'}: if L.buf[L.bufpos] in {'+', '-'}:
@ -268,7 +270,6 @@ proc GetNumber(L: var TLexer): TToken =
matchUnderscoreChars(L, result, {'0'..'9'}) matchUnderscoreChars(L, result, {'0'..'9'})
endpos = L.bufpos endpos = L.bufpos
if L.buf[endpos] in {'\'', 'f', 'F', 'i', 'I', 'u', 'U'}: if L.buf[endpos] in {'\'', 'f', 'F', 'i', 'I', 'u', 'U'}:
#matchUnderscoreChars(L, result, ['''', 'f', 'F', 'i', 'I', '0'..'9']);
if L.buf[endpos] == '\'': inc(endpos) if L.buf[endpos] == '\'': inc(endpos)
L.bufpos = pos # restore position L.bufpos = pos # restore position
case L.buf[endpos] case L.buf[endpos]

View file

@ -91,6 +91,7 @@ proc errorSym*(c: PContext, n: PNode): PSym =
result.typ = errorType(c) result.typ = errorType(c)
incl(result.flags, sfDiscardable) incl(result.flags, sfDiscardable)
# pretend it's imported from some unknown module to prevent cascading errors: # pretend it's imported from some unknown module to prevent cascading errors:
if gCmd != cmdInteractive:
c.importTable.addSym(result) c.importTable.addSym(result)
type type

View file

@ -43,6 +43,18 @@ proc getSysSym(name: string): PSym =
result.typ = newType(tyError, systemModule) result.typ = newType(tyError, systemModule)
if result.kind == skStub: loadStub(result) if result.kind == skStub: loadStub(result)
proc getSysMagic*(name: string, m: TMagic): PSym =
var ti: TIdentIter
let id = getIdent(name)
result = InitIdentIter(ti, systemModule.tab, id)
while result != nil:
if result.kind == skStub: loadStub(result)
if result.magic == m: return result
result = NextIdentIter(ti, systemModule.tab)
rawMessage(errSystemNeeds, name)
result = newSym(skError, id, systemModule, systemModule.info)
result.typ = newType(tyError, systemModule)
proc sysTypeFromName*(name: string): PType = proc sysTypeFromName*(name: string): PType =
result = getSysSym(name).typ result = getSysSym(name).typ
@ -111,7 +123,9 @@ proc skipIntLit*(t: PType): PType {.inline.} =
proc AddSonSkipIntLit*(father, son: PType) = proc AddSonSkipIntLit*(father, son: PType) =
if isNil(father.sons): father.sons = @[] if isNil(father.sons): father.sons = @[]
add(father.sons, son.skipIntLit) let s = son.skipIntLit
add(father.sons, s)
propagateToOwner(father, s)
proc setIntLitType*(result: PNode) = proc setIntLitType*(result: PNode) =
let i = result.intVal let i = result.intVal

View file

@ -106,7 +106,9 @@ type
warnUnknownSubstitutionX, warnLanguageXNotSupported, warnCommentXIgnored, warnUnknownSubstitutionX, warnLanguageXNotSupported, warnCommentXIgnored,
warnNilStatement, warnAnalysisLoophole, warnNilStatement, warnAnalysisLoophole,
warnDifferentHeaps, warnWriteToForeignHeap, warnImplicitClosure, warnDifferentHeaps, warnWriteToForeignHeap, warnImplicitClosure,
warnEachIdentIsTuple, warnShadowIdent, warnUser, warnEachIdentIsTuple, warnShadowIdent,
warnProveInit, warnProveField, warnProveIndex,
warnUninit, warnUser,
hintSuccess, hintSuccessX, hintSuccess, hintSuccessX,
hintLineTooLong, hintXDeclaredButNotUsed, hintConvToBaseNotNeeded, hintLineTooLong, hintXDeclaredButNotUsed, hintConvToBaseNotNeeded,
hintConvFromXtoItselfNotNeeded, hintExprAlwaysX, hintQuitCalled, hintConvFromXtoItselfNotNeeded, hintExprAlwaysX, hintQuitCalled,
@ -355,6 +357,10 @@ const
warnImplicitClosure: "implicit closure convention: '$1' [ImplicitClosure]", warnImplicitClosure: "implicit closure convention: '$1' [ImplicitClosure]",
warnEachIdentIsTuple: "each identifier is a tuple [EachIdentIsTuple]", warnEachIdentIsTuple: "each identifier is a tuple [EachIdentIsTuple]",
warnShadowIdent: "shadowed identifier: '$1' [ShadowIdent]", warnShadowIdent: "shadowed identifier: '$1' [ShadowIdent]",
warnProveInit: "Cannot prove that '$1' is initialized. This will become a compile time error in the future. [ProveInit]",
warnProveField: "cannot prove that field '$1' is accessible [ProveField]",
warnProveIndex: "cannot prove index '$1' is valid [ProveIndex]",
warnUninit: "'$1' might not have been initialized [Uninit]",
warnUser: "$1 [User]", warnUser: "$1 [User]",
hintSuccess: "operation successful [Success]", hintSuccess: "operation successful [Success]",
hintSuccessX: "operation successful ($# lines compiled; $# sec total; $#) [SuccessX]", hintSuccessX: "operation successful ($# lines compiled; $# sec total; $#) [SuccessX]",
@ -374,14 +380,15 @@ const
hintUser: "$1 [User]"] hintUser: "$1 [User]"]
const const
WarningsToStr*: array[0..19, string] = ["CannotOpenFile", "OctalEscape", WarningsToStr*: array[0..23, string] = ["CannotOpenFile", "OctalEscape",
"XIsNeverRead", "XmightNotBeenInit", "XIsNeverRead", "XmightNotBeenInit",
"Deprecated", "ConfigDeprecated", "Deprecated", "ConfigDeprecated",
"SmallLshouldNotBeUsed", "UnknownMagic", "SmallLshouldNotBeUsed", "UnknownMagic",
"RedefinitionOfLabel", "UnknownSubstitutionX", "LanguageXNotSupported", "RedefinitionOfLabel", "UnknownSubstitutionX", "LanguageXNotSupported",
"CommentXIgnored", "NilStmt", "CommentXIgnored", "NilStmt",
"AnalysisLoophole", "DifferentHeaps", "WriteToForeignHeap", "AnalysisLoophole", "DifferentHeaps", "WriteToForeignHeap",
"ImplicitClosure", "EachIdentIsTuple", "ShadowIdent", "User"] "ImplicitClosure", "EachIdentIsTuple", "ShadowIdent",
"ProveInit", "ProveField", "ProveIndex", "Uninit", "User"]
HintsToStr*: array[0..15, string] = ["Success", "SuccessX", "LineTooLong", HintsToStr*: array[0..15, string] = ["Success", "SuccessX", "LineTooLong",
"XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded", "XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded",
@ -512,7 +519,9 @@ proc raiseRecoverableError*(msg: string) {.noinline, noreturn.} =
proc sourceLine*(i: TLineInfo): PRope proc sourceLine*(i: TLineInfo): PRope
var var
gNotes*: TNoteKinds = {low(TNoteKind)..high(TNoteKind)} - {warnShadowIdent} gNotes*: TNoteKinds = {low(TNoteKind)..high(TNoteKind)} -
{warnShadowIdent, warnUninit,
warnProveField, warnProveIndex}
gErrorCounter*: int = 0 # counts the number of errors gErrorCounter*: int = 0 # counts the number of errors
gHintCounter*: int = 0 gHintCounter*: int = 0
gWarnCounter*: int = 0 gWarnCounter*: int = 0
@ -789,11 +798,12 @@ proc addSourceLine*(fileIdx: int32, line: string) =
proc sourceLine*(i: TLineInfo): PRope = proc sourceLine*(i: TLineInfo): PRope =
if i.fileIndex < 0: return nil if i.fileIndex < 0: return nil
if not optPreserveOrigSource and if not optPreserveOrigSource and fileInfos[i.fileIndex].lines.len == 0:
fileInfos[i.fileIndex].lines.len == 0: try:
for line in lines(i.toFullPath): for line in lines(i.toFullPath):
addSourceLine i.fileIndex, line.string addSourceLine i.fileIndex, line.string
except EIO:
discard
InternalAssert i.fileIndex < fileInfos.len InternalAssert i.fileIndex < fileInfos.len
# can happen if the error points to EOF: # can happen if the error points to EOF:
if i.line > fileInfos[i.fileIndex].lines.len: return nil if i.line > fileInfos[i.fileIndex].lines.len: return nil

View file

@ -64,7 +64,6 @@ Files: "bin/empty.txt"
[Lib] [Lib]
Files: "lib/nimbase.h" Files: "lib/nimbase.h"
Files: "lib/copying.txt"
Files: "lib/*.nim" Files: "lib/*.nim"
Files: "lib/*.cfg" Files: "lib/*.cfg"

View file

@ -156,6 +156,12 @@ proc equalSets(a, b: PNode): bool =
toBitSet(b, y) toBitSet(b, y)
result = bitSetEquals(x, y) result = bitSetEquals(x, y)
proc complement*(a: PNode): PNode =
var x: TBitSet
toBitSet(a, x)
for i in countup(0, high(x)): x[i] = not x[i]
result = toTreeSet(x, a.typ, a.info)
proc cardSet(s: PNode): BiggestInt = proc cardSet(s: PNode): BiggestInt =
# here we can do better than converting it into a compact set # here we can do better than converting it into a compact set
# we just count the elements directly # we just count the elements directly

View file

@ -14,7 +14,7 @@ const
hasTinyCBackend* = defined(tinyc) hasTinyCBackend* = defined(tinyc)
useEffectSystem* = true useEffectSystem* = true
hasFFI* = defined(useFFI) hasFFI* = defined(useFFI)
newScopeForIf* = false # XXX activate for 0.9.4 newScopeForIf* = true
type # please make sure we have under 32 options type # please make sure we have under 32 options
# (improves code efficiency a lot!) # (improves code efficiency a lot!)

View file

@ -50,6 +50,8 @@ proc add(code: var TPatternCode, op: TOpcode) {.inline.} =
proc whichAlias*(p: PSym): TAliasRequest = proc whichAlias*(p: PSym): TAliasRequest =
if p.constraint != nil: if p.constraint != nil:
result = TAliasRequest(p.constraint.strVal[0].ord) result = TAliasRequest(p.constraint.strVal[0].ord)
else:
result = aqNone
proc compileConstraints(p: PNode, result: var TPatternCode) = proc compileConstraints(p: PNode, result: var TPatternCode) =
case p.kind case p.kind

View file

@ -1748,7 +1748,7 @@ proc complexOrSimpleStmt(p: var TParser): PNode =
proc parseStmt(p: var TParser): PNode = proc parseStmt(p: var TParser): PNode =
#| stmt = (IND{>} complexOrSimpleStmt^+(IND{=} / ';') DED) #| stmt = (IND{>} complexOrSimpleStmt^+(IND{=} / ';') DED)
#| / simpleStmt #| / simpleStmt ^+ ';'
if p.tok.indent > p.currInd: if p.tok.indent > p.currInd:
result = newNodeP(nkStmtList, p) result = newNodeP(nkStmtList, p)
withInd(p): withInd(p):
@ -1779,10 +1779,14 @@ proc parseStmt(p: var TParser): PNode =
parMessage(p, errComplexStmtRequiresInd) parMessage(p, errComplexStmtRequiresInd)
result = ast.emptyNode result = ast.emptyNode
else: else:
result = newNodeP(nkStmtList, p)
while true:
if p.tok.indent >= 0: parMessage(p, errInvalidIndentation) if p.tok.indent >= 0: parMessage(p, errInvalidIndentation)
result = simpleStmt(p) let a = simpleStmt(p)
if result.kind == nkEmpty: parMessage(p, errExprExpected, p.tok) if a.kind == nkEmpty: parMessage(p, errExprExpected, p.tok)
#while p.tok.tokType == tkSemicolon: getTok(p) result.add(a)
if p.tok.tokType != tkSemicolon: break
getTok(p)
proc parseAll(p: var TParser): PNode = proc parseAll(p: var TParser): PNode =
result = newNodeP(nkStmtList, p) result = newNodeP(nkStmtList, p)

View file

@ -23,7 +23,7 @@ const
wMagic, wNosideEffect, wSideEffect, wNoreturn, wDynLib, wHeader, wMagic, wNosideEffect, wSideEffect, wNoreturn, wDynLib, wHeader,
wCompilerProc, wProcVar, wDeprecated, wVarargs, wCompileTime, wMerge, wCompilerProc, wProcVar, wDeprecated, wVarargs, wCompileTime, wMerge,
wBorrow, wExtern, wImportCompilerProc, wThread, wImportCpp, wImportObjC, wBorrow, wExtern, wImportCompilerProc, wThread, wImportCpp, wImportObjC,
wNoStackFrame, wError, wDiscardable, wNoInit, wDestructor, wHoist, wNoStackFrame, wError, wDiscardable, wNoInit, wDestructor, wCodegenDecl,
wGenSym, wInject, wRaises, wTags} wGenSym, wInject, wRaises, wTags}
converterPragmas* = procPragmas converterPragmas* = procPragmas
methodPragmas* = procPragmas methodPragmas* = procPragmas
@ -50,13 +50,13 @@ const
typePragmas* = {wImportc, wExportc, wDeprecated, wMagic, wAcyclic, wNodecl, typePragmas* = {wImportc, wExportc, wDeprecated, wMagic, wAcyclic, wNodecl,
wPure, wHeader, wCompilerProc, wFinal, wSize, wExtern, wShallow, wPure, wHeader, wCompilerProc, wFinal, wSize, wExtern, wShallow,
wImportcpp, wImportobjc, wError, wIncompleteStruct, wByCopy, wByRef, wImportcpp, wImportobjc, wError, wIncompleteStruct, wByCopy, wByRef,
wInheritable, wGenSym, wInject} wInheritable, wGenSym, wInject, wRequiresInit}
fieldPragmas* = {wImportc, wExportc, wDeprecated, wExtern, fieldPragmas* = {wImportc, wExportc, wDeprecated, wExtern,
wImportcpp, wImportobjc, wError} wImportcpp, wImportobjc, wError}
varPragmas* = {wImportc, wExportc, wVolatile, wRegister, wThreadVar, wNodecl, varPragmas* = {wImportc, wExportc, wVolatile, wRegister, wThreadVar, wNodecl,
wMagic, wHeader, wDeprecated, wCompilerProc, wDynLib, wExtern, wMagic, wHeader, wDeprecated, wCompilerProc, wDynLib, wExtern,
wImportcpp, wImportobjc, wError, wNoInit, wCompileTime, wGlobal, wImportcpp, wImportobjc, wError, wNoInit, wCompileTime, wGlobal,
wGenSym, wInject} wGenSym, wInject, wCodegenDecl}
constPragmas* = {wImportc, wExportc, wHeader, wDeprecated, wMagic, wNodecl, constPragmas* = {wImportc, wExportc, wHeader, wDeprecated, wMagic, wNodecl,
wExtern, wImportcpp, wImportobjc, wError, wGenSym, wInject} wExtern, wImportcpp, wImportobjc, wError, wGenSym, wInject}
letPragmas* = varPragmas letPragmas* = varPragmas
@ -148,6 +148,9 @@ proc getOptionalStr(c: PContext, n: PNode, defaultStr: string): string =
if n.kind == nkExprColonExpr: result = expectStrLit(c, n) if n.kind == nkExprColonExpr: result = expectStrLit(c, n)
else: result = defaultStr else: result = defaultStr
proc processCodegenDecl(c: PContext, n: PNode, sym: PSym) =
sym.constraint = getStrLitNode(c, n)
proc processMagic(c: PContext, n: PNode, s: PSym) = proc processMagic(c: PContext, n: PNode, s: PSym) =
#if sfSystemModule notin c.module.flags: #if sfSystemModule notin c.module.flags:
# liMessage(n.info, errMagicOnlyInSystem) # liMessage(n.info, errMagicOnlyInSystem)
@ -253,11 +256,11 @@ proc processNote(c: PContext, n: PNode) =
of wHint: of wHint:
var x = findStr(msgs.HintsToStr, n.sons[0].sons[1].ident.s) var x = findStr(msgs.HintsToStr, n.sons[0].sons[1].ident.s)
if x >= 0: nk = TNoteKind(x + ord(hintMin)) if x >= 0: nk = TNoteKind(x + ord(hintMin))
else: invalidPragma(n) else: invalidPragma(n); return
of wWarning: of wWarning:
var x = findStr(msgs.WarningsToStr, n.sons[0].sons[1].ident.s) var x = findStr(msgs.WarningsToStr, n.sons[0].sons[1].ident.s)
if x >= 0: nk = TNoteKind(x + ord(warnMin)) if x >= 0: nk = TNoteKind(x + ord(warnMin))
else: InvalidPragma(n) else: InvalidPragma(n); return
else: else:
invalidPragma(n) invalidPragma(n)
return return
@ -669,9 +672,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int,
of wNoInit: of wNoInit:
noVal(it) noVal(it)
if sym != nil: incl(sym.flags, sfNoInit) if sym != nil: incl(sym.flags, sfNoInit)
of wHoist: of wCodegenDecl: processCodegenDecl(c, it, sym)
noVal(it)
if sym != nil: incl(sym.flags, sfHoist)
of wChecks, wObjChecks, wFieldChecks, wRangechecks, wBoundchecks, of wChecks, wObjChecks, wFieldChecks, wRangechecks, wBoundchecks,
wOverflowchecks, wNilchecks, wAssertions, wWarnings, wHints, wOverflowchecks, wNilchecks, wAssertions, wWarnings, wHints,
wLinedir, wStacktrace, wLinetrace, wOptimization, wLinedir, wStacktrace, wLinetrace, wOptimization,
@ -695,6 +696,10 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int,
noVal(it) noVal(it)
if sym.typ == nil: invalidPragma(it) if sym.typ == nil: invalidPragma(it)
else: incl(sym.typ.flags, tfIncompleteStruct) else: incl(sym.typ.flags, tfIncompleteStruct)
of wRequiresInit:
noVal(it)
if sym.typ == nil: invalidPragma(it)
else: incl(sym.typ.flags, tfNeedsInit)
of wByRef: of wByRef:
noVal(it) noVal(it)
if sym == nil or sym.typ == nil: if sym == nil or sym.typ == nil:

View file

@ -33,6 +33,7 @@ type
# indentation value # indentation value
comStack*: seq[PNode] # comment stack comStack*: seq[PNode] # comment stack
flags*: TRenderFlags flags*: TRenderFlags
checkAnon: bool # we're in a context that can contain sfAnon
proc renderModule*(n: PNode, filename: string, renderFlags: TRenderFlags = {}) proc renderModule*(n: PNode, filename: string, renderFlags: TRenderFlags = {})
@ -71,6 +72,7 @@ proc InitSrcGen(g: var TSrcGen, renderFlags: TRenderFlags) =
g.buf = "" g.buf = ""
g.flags = renderFlags g.flags = renderFlags
g.pendingNL = -1 g.pendingNL = -1
g.checkAnon = false
proc addTok(g: var TSrcGen, kind: TTokType, s: string) = proc addTok(g: var TSrcGen, kind: TTokType, s: string) =
var length = len(g.tokens) var length = len(g.tokens)
@ -497,8 +499,10 @@ proc gcommaAux(g: var TSrcGen, n: PNode, ind: int, start: int = 0,
var c = i < sonsLen(n) + theEnd var c = i < sonsLen(n) + theEnd
var sublen = lsub(n.sons[i]) + ord(c) var sublen = lsub(n.sons[i]) + ord(c)
if not fits(g, sublen) and (ind + sublen < maxLineLen): optNL(g, ind) if not fits(g, sublen) and (ind + sublen < maxLineLen): optNL(g, ind)
let oldLen = g.tokens.len
gsub(g, n.sons[i]) gsub(g, n.sons[i])
if c: if c:
if g.tokens.len > oldLen:
putWithSpace(g, separator, TokTypeToStr[separator]) putWithSpace(g, separator, TokTypeToStr[separator])
if hasCom(n.sons[i]): if hasCom(n.sons[i]):
gcoms(g) gcoms(g)
@ -673,7 +677,10 @@ proc gproc(g: var TSrcGen, n: PNode) =
if n.sons[patternPos].kind != nkEmpty: if n.sons[patternPos].kind != nkEmpty:
gpattern(g, n.sons[patternPos]) gpattern(g, n.sons[patternPos])
let oldCheckAnon = g.checkAnon
g.checkAnon = true
gsub(g, n.sons[genericParamsPos]) gsub(g, n.sons[genericParamsPos])
g.checkAnon = oldCheckAnon
gsub(g, n.sons[paramsPos]) gsub(g, n.sons[paramsPos])
gsub(g, n.sons[pragmasPos]) gsub(g, n.sons[pragmasPos])
if renderNoBody notin g.flags: if renderNoBody notin g.flags:
@ -725,6 +732,7 @@ proc gasm(g: var TSrcGen, n: PNode) =
gsub(g, n.sons[1]) gsub(g, n.sons[1])
proc gident(g: var TSrcGen, n: PNode) = proc gident(g: var TSrcGen, n: PNode) =
if g.checkAnon and n.kind == nkSym and sfAnon in n.sym.flags: return
var t: TTokType var t: TTokType
var s = atom(n) var s = atom(n)
if (s[0] in lexer.SymChars): if (s[0] in lexer.SymChars):

View file

@ -590,7 +590,7 @@ proc evalAtCompileTime(c: PContext, n: PNode): PNode =
call.add(a) call.add(a)
#echo "NOW evaluating at compile time: ", call.renderTree #echo "NOW evaluating at compile time: ", call.renderTree
if sfCompileTime in callee.flags: if sfCompileTime in callee.flags:
result = evalStaticExpr(c.module, call) result = evalStaticExpr(c.module, call, c.p.owner)
if result.isNil: if result.isNil:
LocalError(n.info, errCannotInterpretNodeX, renderTree(call)) LocalError(n.info, errCannotInterpretNodeX, renderTree(call))
else: else:
@ -601,9 +601,10 @@ proc evalAtCompileTime(c: PContext, n: PNode): PNode =
proc semStaticExpr(c: PContext, n: PNode): PNode = proc semStaticExpr(c: PContext, n: PNode): PNode =
let a = semExpr(c, n.sons[0]) let a = semExpr(c, n.sons[0])
result = evalStaticExpr(c.module, a) result = evalStaticExpr(c.module, a, c.p.owner)
if result.isNil: if result.isNil:
LocalError(n.info, errCannotInterpretNodeX, renderTree(n)) LocalError(n.info, errCannotInterpretNodeX, renderTree(n))
result = emptyNode
proc semOverloadedCallAnalyseEffects(c: PContext, n: PNode, nOrig: PNode, proc semOverloadedCallAnalyseEffects(c: PContext, n: PNode, nOrig: PNode,
flags: TExprFlags): PNode = flags: TExprFlags): PNode =
@ -1290,6 +1291,7 @@ proc newAnonSym(kind: TSymKind, info: TLineInfo,
proc semExpandToAst(c: PContext, n: PNode): PNode = proc semExpandToAst(c: PContext, n: PNode): PNode =
var macroCall = n[1] var macroCall = n[1]
var expandedSym = expectMacroOrTemplateCall(c, macroCall) var expandedSym = expectMacroOrTemplateCall(c, macroCall)
if expandedSym.kind == skError: return n
macroCall.sons[0] = newSymNode(expandedSym, macroCall.info) macroCall.sons[0] = newSymNode(expandedSym, macroCall.info)
markUsed(n, expandedSym) markUsed(n, expandedSym)
@ -1601,6 +1603,25 @@ proc semTuplePositionsConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =
addSonSkipIntLit(typ, n.sons[i].typ) addSonSkipIntLit(typ, n.sons[i].typ)
result.typ = typ result.typ = typ
proc checkInitialized(n: PNode, ids: TIntSet, info: TLineInfo) =
case n.kind
of nkRecList:
for i in countup(0, sonsLen(n) - 1):
checkInitialized(n.sons[i], ids, info)
of nkRecCase:
if (n.sons[0].kind != nkSym): InternalError(info, "checkInitialized")
checkInitialized(n.sons[0], ids, info)
when false:
# XXX we cannot check here, as we don't know the branch!
for i in countup(1, sonsLen(n) - 1):
case n.sons[i].kind
of nkOfBranch, nkElse: checkInitialized(lastSon(n.sons[i]), ids, info)
else: internalError(info, "checkInitialized")
of nkSym:
if tfNeedsInit in n.sym.typ.flags and n.sym.name.id notin ids:
Message(info, errGenerated, "field not initialized: " & n.sym.name.s)
else: internalError(info, "checkInitialized")
proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode = proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =
var t = semTypeNode(c, n.sons[0], nil) var t = semTypeNode(c, n.sons[0], nil)
result = n result = n
@ -1611,6 +1632,7 @@ proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =
if t.kind != tyObject: if t.kind != tyObject:
localError(n.info, errGenerated, "object constructor needs an object type") localError(n.info, errGenerated, "object constructor needs an object type")
return return
var objType = t
var ids = initIntSet() var ids = initIntSet()
for i in 1.. <n.len: for i in 1.. <n.len:
let it = n.sons[i] let it = n.sons[i]
@ -1637,11 +1659,18 @@ proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =
e = fitNode(c, f.typ, e) e = fitNode(c, f.typ, e)
# small hack here in a nkObjConstr the ``nkExprColonExpr`` node can have # small hack here in a nkObjConstr the ``nkExprColonExpr`` node can have
# 3 childen the last being the field check # 3 childen the last being the field check
if check != nil: it.add(check) if check != nil:
check.sons[0] = it.sons[0]
it.add(check)
else: else:
localError(it.info, errUndeclaredFieldX, id.s) localError(it.info, errUndeclaredFieldX, id.s)
it.sons[1] = e it.sons[1] = e
# XXX object field name check for 'case objects' if the kind is static? # XXX object field name check for 'case objects' if the kind is static?
if tfNeedsInit in objType.flags:
while true:
checkInitialized(objType.n, ids, n.info)
if objType.sons[0] == nil: break
objType = skipTypes(objType.sons[0], {tyGenericInst})
proc semBlock(c: PContext, n: PNode): PNode = proc semBlock(c: PContext, n: PNode): PNode =
result = n result = n

View file

@ -9,12 +9,13 @@
import import
intsets, ast, astalgo, msgs, renderer, magicsys, types, idents, trees, intsets, ast, astalgo, msgs, renderer, magicsys, types, idents, trees,
wordrecg, strutils, options wordrecg, strutils, options, guards
# Second semantic checking pass over the AST. Necessary because the old # Second semantic checking pass over the AST. Necessary because the old
# way had some inherent problems. Performs: # way had some inherent problems. Performs:
# #
# * effect+exception tracking # * effect+exception tracking
# * "usage before definition" checking
# * checks for invalid usages of compiletime magics (not implemented) # * checks for invalid usages of compiletime magics (not implemented)
# * checks for invalid usages of PNimNode (not implemented) # * checks for invalid usages of PNimNode (not implemented)
# * later: will do an escape analysis for closures at least # * later: will do an escape analysis for closures at least
@ -43,14 +44,6 @@ import
# done: It essentially requires a built-in 'indexSplit' operation and dependent # done: It essentially requires a built-in 'indexSplit' operation and dependent
# typing. # typing.
when false:
proc sem2call(c: PContext, n: PNode): PNode =
assert n.kind in nkCallKinds
proc sem2sym(c: PContext, n: PNode): PNode =
assert n.kind == nkSym
# ------------------------ exception and tag tracking ------------------------- # ------------------------ exception and tag tracking -------------------------
discard """ discard """
@ -64,14 +57,6 @@ discard """
c() c()
--> we need a stack of scopes for this analysis --> we need a stack of scopes for this analysis
Effect tracking:
We track the effects per proc; forward declarations and indirect calls cause
problems: Forward declarations are computed lazily (we do this pass after
a whole module) and indirect calls are assumed the worst, unless they have
an effect annotation.
""" """
type type
@ -80,9 +65,55 @@ type
tags: PNode # list of tags tags: PNode # list of tags
bottom: int bottom: int
owner: PSym owner: PSym
init: seq[int] # list of initialized variables
guards: TModel # nested guards
locked: seq[PNode] # locked locations
PEffects = var TEffects PEffects = var TEffects
proc isLocalVar(a: PEffects, s: PSym): bool =
s.kind in {skVar, skResult} and sfGlobal notin s.flags and s.owner == a.owner
proc initVar(a: PEffects, n: PNode) =
if n.kind != nkSym: return
let s = n.sym
if isLocalVar(a, s):
for x in a.init:
if x == s.id: return
a.init.add s.id
proc initVarViaNew(a: PEffects, n: PNode) =
if n.kind != nkSym: return
let s = n.sym
if {tfNeedsInit, tfNotNil} * s.typ.flags == {tfNotNil}:
# 'x' is not nil, but that doesn't mean it's not nil children
# are initialized:
initVarViaNew(a, n)
proc useVar(a: PEffects, n: PNode) =
let s = n.sym
if isLocalVar(a, s):
if s.id notin a.init:
if {tfNeedsInit, tfNotNil} * s.typ.flags != {}:
when true:
Message(n.info, warnProveInit, s.name.s)
else:
Message(n.info, errGenerated,
"'$1' might not have been initialized" % s.name.s)
else:
Message(n.info, warnUninit, s.name.s)
# prevent superfluous warnings about the same variable:
a.init.add s.id
type
TIntersection = seq[tuple[id, count: int]] # a simple count table
proc addToIntersection(inter: var TIntersection, s: int) =
for j in 0.. <inter.len:
if s == inter[j].id:
inc inter[j].count
return
inter.add((id: s, count: 1))
proc throws(tracked, n: PNode) = proc throws(tracked, n: PNode) =
if n.typ == nil or n.typ.kind != tyError: tracked.add n if n.typ == nil or n.typ.kind != tyError: tracked.add n
@ -158,21 +189,43 @@ proc track(tracked: PEffects, n: PNode)
proc trackTryStmt(tracked: PEffects, n: PNode) = proc trackTryStmt(tracked: PEffects, n: PNode) =
let oldBottom = tracked.bottom let oldBottom = tracked.bottom
tracked.bottom = tracked.exc.len tracked.bottom = tracked.exc.len
let oldState = tracked.init.len
var inter: TIntersection = @[]
track(tracked, n.sons[0]) track(tracked, n.sons[0])
for i in oldState.. <tracked.init.len:
addToIntersection(inter, tracked.init[i])
var branches = 1
var hasFinally = false
for i in 1 .. < n.len: for i in 1 .. < n.len:
let b = n.sons[i] let b = n.sons[i]
let blen = sonsLen(b) let blen = sonsLen(b)
if b.kind == nkExceptBranch: if b.kind == nkExceptBranch:
inc branches
if blen == 1: if blen == 1:
catchesAll(tracked) catchesAll(tracked)
else: else:
for j in countup(0, blen - 2): for j in countup(0, blen - 2):
assert(b.sons[j].kind == nkType) assert(b.sons[j].kind == nkType)
catches(tracked, b.sons[j].typ) catches(tracked, b.sons[j].typ)
setLen(tracked.init, oldState)
track(tracked, b.sons[blen-1])
for i in oldState.. <tracked.init.len:
addToIntersection(inter, tracked.init[i])
else: else:
assert b.kind == nkFinally assert b.kind == nkFinally
setLen(tracked.init, oldState)
track(tracked, b.sons[blen-1]) track(tracked, b.sons[blen-1])
hasFinally = true
tracked.bottom = oldBottom tracked.bottom = oldBottom
if not hasFinally:
setLen(tracked.init, oldState)
for id, count in items(inter):
if count == branches: tracked.init.add id
proc isIndirectCall(n: PNode, owner: PSym): bool = proc isIndirectCall(n: PNode, owner: PSym): bool =
# we don't count f(...) as an indirect call if 'f' is an parameter. # we don't count f(...) as an indirect call if 'f' is an parameter.
@ -245,7 +298,25 @@ proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) =
let tagSpec = effectSpec(pragma, wTags) let tagSpec = effectSpec(pragma, wTags)
mergeTags(tracked, tagSpec, n) mergeTags(tracked, tagSpec, n)
proc trackOperand(tracked: PEffects, n: PNode) = proc notNilCheck(tracked: PEffects, n: PNode, paramType: PType) =
let n = n.skipConv
if paramType != nil and tfNotNil in paramType.flags and
n.typ != nil and tfNotNil notin n.typ.flags:
if n.kind == nkAddr:
# addr(x[]) can't be proven, but addr(x) can:
if not containsNode(n, {nkDerefExpr, nkHiddenDeref}): return
elif n.kind == nkSym and n.sym.kind in RoutineKinds:
# 'p' is not nil obviously:
return
case impliesNotNil(tracked.guards, n)
of impUnknown:
Message(n.info, errGenerated,
"cannot prove '$1' is not nil" % n.renderTree)
of impNo:
Message(n.info, errGenerated, "'$1' is provably nil" % n.renderTree)
of impYes: discard
proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) =
let op = n.typ let op = n.typ
if op != nil and op.kind == tyProc and n.kind != nkNilLit: if op != nil and op.kind == tyProc and n.kind != nkNilLit:
InternalAssert op.n.sons[0].kind == nkEffectList InternalAssert op.n.sons[0].kind == nkEffectList
@ -262,12 +333,117 @@ proc trackOperand(tracked: PEffects, n: PNode) =
else: else:
mergeEffects(tracked, effectList.sons[exceptionEffects], n) mergeEffects(tracked, effectList.sons[exceptionEffects], n)
mergeTags(tracked, effectList.sons[tagEffects], n) mergeTags(tracked, effectList.sons[tagEffects], n)
notNilCheck(tracked, n, paramType)
proc breaksBlock(n: PNode): bool =
case n.kind
of nkStmtList, nkStmtListExpr:
for c in n:
if breaksBlock(c): return true
of nkBreakStmt, nkReturnStmt, nkRaiseStmt:
return true
of nkCallKinds:
if n.sons[0].kind == nkSym and sfNoReturn in n.sons[0].sym.flags:
return true
else:
discard
proc trackCase(tracked: PEffects, n: PNode) =
track(tracked, n.sons[0])
let oldState = tracked.init.len
let oldFacts = tracked.guards.len
let interesting = interestingCaseExpr(n.sons[0]) and warnProveField in gNotes
var inter: TIntersection = @[]
var toCover = 0
for i in 1.. <n.len:
let branch = n.sons[i]
setLen(tracked.init, oldState)
if interesting:
setLen(tracked.guards, oldFacts)
addCaseBranchFacts(tracked.guards, n, i)
for i in 0 .. <branch.len:
track(tracked, branch.sons[i])
if not breaksBlock(branch.lastSon): inc toCover
for i in oldState.. <tracked.init.len:
addToIntersection(inter, tracked.init[i])
let exh = case skipTypes(n.sons[0].Typ, abstractVarRange-{tyTypeDesc}).Kind
of tyFloat..tyFloat128, tyString:
lastSon(n).kind == nkElse
else:
true
setLen(tracked.init, oldState)
if exh:
for id, count in items(inter):
if count >= toCover: tracked.init.add id
# else we can't merge
setLen(tracked.guards, oldFacts)
proc trackIf(tracked: PEffects, n: PNode) =
track(tracked, n.sons[0].sons[0])
let oldFacts = tracked.guards.len
addFact(tracked.guards, n.sons[0].sons[0])
let oldState = tracked.init.len
var inter: TIntersection = @[]
var toCover = 0
track(tracked, n.sons[0].sons[1])
if not breaksBlock(n.sons[0].sons[1]): inc toCover
for i in oldState.. <tracked.init.len:
addToIntersection(inter, tracked.init[i])
for i in 1.. <n.len:
let branch = n.sons[i]
setLen(tracked.guards, oldFacts)
for j in 0..i-1:
addFactNeg(tracked.guards, n.sons[j].sons[0])
if branch.len > 1:
addFact(tracked.guards, branch.sons[0])
setLen(tracked.init, oldState)
for i in 0 .. <branch.len:
track(tracked, branch.sons[i])
if not breaksBlock(branch.lastSon): inc toCover
for i in oldState.. <tracked.init.len:
addToIntersection(inter, tracked.init[i])
setLen(tracked.init, oldState)
if lastSon(n).len == 1:
for id, count in items(inter):
if count >= toCover: tracked.init.add id
# else we can't merge as it is not exhaustive
setLen(tracked.guards, oldFacts)
proc trackBlock(tracked: PEffects, n: PNode) =
if n.kind in {nkStmtList, nkStmtListExpr}:
var oldState = -1
for i in 0.. <n.len:
if hasSubnodeWith(n.sons[i], nkBreakStmt):
# block:
# x = def
# if ...: ... break # some nested break
# y = def
# --> 'y' not defined after block!
if oldState < 0: oldState = tracked.init.len
track(tracked, n.sons[i])
if oldState > 0: setLen(tracked.init, oldState)
else:
track(tracked, n)
proc isTrue(n: PNode): bool =
n.kind == nkSym and n.sym.kind == skEnumField and n.sym.position != 0 or
n.kind == nkIntLit and n.intVal != 0
proc paramType(op: PType, i: int): PType =
if op != nil and i < op.len: result = op.sons[i]
proc track(tracked: PEffects, n: PNode) = proc track(tracked: PEffects, n: PNode) =
case n.kind case n.kind
of nkSym:
useVar(tracked, n)
of nkRaiseStmt: of nkRaiseStmt:
n.sons[0].info = n.info n.sons[0].info = n.info
throws(tracked.exc, n.sons[0]) throws(tracked.exc, n.sons[0])
for i in 0 .. <safeLen(n):
track(tracked, n.sons[i])
of nkCallKinds: of nkCallKinds:
# p's effects are ours too: # p's effects are ours too:
let a = n.sons[0] let a = n.sons[0]
@ -286,17 +462,71 @@ proc track(tracked: PEffects, n: PNode) =
else: else:
mergeEffects(tracked, effectList.sons[exceptionEffects], n) mergeEffects(tracked, effectList.sons[exceptionEffects], n)
mergeTags(tracked, effectList.sons[tagEffects], n) mergeTags(tracked, effectList.sons[tagEffects], n)
for i in 1 .. <len(n): trackOperand(tracked, n.sons[i]) for i in 1 .. <len(n): trackOperand(tracked, n.sons[i], paramType(op, i))
of nkTryStmt: if a.kind == nkSym and a.sym.magic in {mNew, mNewFinalize,
trackTryStmt(tracked, n) mNewSeq, mShallowCopy}:
return # may not look like an assignment, but it is:
of nkPragma: initVarViaNew(tracked, n.sons[1])
trackPragmaStmt(tracked, n)
return
of nkMacroDef, nkTemplateDef: return
else: nil
for i in 0 .. <safeLen(n): for i in 0 .. <safeLen(n):
track(tracked, n.sons[i]) track(tracked, n.sons[i])
of nkCheckedFieldExpr:
track(tracked, n.sons[0])
if warnProveField in gNotes: checkFieldAccess(tracked.guards, n)
of nkTryStmt: trackTryStmt(tracked, n)
of nkPragma: trackPragmaStmt(tracked, n)
of nkMacroDef, nkTemplateDef: discard
of nkAsgn, nkFastAsgn:
track(tracked, n.sons[1])
initVar(tracked, n.sons[0])
invalidateFacts(tracked.guards, n.sons[0])
track(tracked, n.sons[0])
addAsgnFact(tracked.guards, n.sons[0], n.sons[1])
notNilCheck(tracked, n.sons[1], n.sons[0].typ)
of nkVarSection:
for child in n:
let last = lastSon(child)
if child.kind == nkIdentDefs and last.kind != nkEmpty:
track(tracked, last)
for i in 0 .. child.len-3:
initVar(tracked, child.sons[i])
addAsgnFact(tracked.guards, child.sons[i], last)
notNilCheck(tracked, last, child.sons[i].typ)
# since 'var (a, b): T = ()' is not even allowed, there is always type
# inference for (a, b) and thus no nil checking is necessary.
of nkCaseStmt: trackCase(tracked, n)
of nkIfStmt, nkIfExpr: trackIf(tracked, n)
of nkBlockStmt, nkBlockExpr: trackBlock(tracked, n.sons[1])
of nkWhileStmt:
track(tracked, n.sons[0])
# 'while true' loop?
if isTrue(n.sons[0]):
trackBlock(tracked, n.sons[1])
else:
# loop may never execute:
let oldState = tracked.init.len
let oldFacts = tracked.guards.len
addFact(tracked.guards, n.sons[0])
track(tracked, n.sons[1])
setLen(tracked.init, oldState)
setLen(tracked.guards, oldFacts)
of nkForStmt, nkParForStmt:
# we are very conservative here and assume the loop is never executed:
let oldState = tracked.init.len
for i in 0 .. <len(n):
track(tracked, n.sons[i])
setLen(tracked.init, oldState)
of nkObjConstr:
track(tracked, n.sons[0])
let oldFacts = tracked.guards.len
for i in 1 .. <len(n):
let x = n.sons[i]
track(tracked, x)
if sfDiscriminant in x.sons[0].sym.flags:
addDiscriminantFact(tracked.guards, x)
setLen(tracked.guards, oldFacts)
of nkTypeSection: discard
else:
for i in 0 .. <safeLen(n): track(tracked, n.sons[i])
proc checkRaisesSpec(spec, real: PNode, msg: string, hints: bool) = proc checkRaisesSpec(spec, real: PNode, msg: string, hints: bool) =
# check that any real exception is listed in 'spec'; mark those as used; # check that any real exception is listed in 'spec'; mark those as used;
@ -362,8 +592,15 @@ proc trackProc*(s: PSym, body: PNode) =
t.exc = effects.sons[exceptionEffects] t.exc = effects.sons[exceptionEffects]
t.tags = effects.sons[tagEffects] t.tags = effects.sons[tagEffects]
t.owner = s t.owner = s
t.init = @[]
t.guards = @[]
track(t, body) track(t, body)
if not isEmptyType(s.typ.sons[0]) and tfNeedsInit in s.typ.sons[0].flags and
s.kind in {skProc, skConverter, skMethod}:
var res = s.ast.sons[resultPos].sym # get result symbol
if res.id notin t.init:
Message(body.info, warnProveInit, "result")
let p = s.ast.sons[pragmasPos] let p = s.ast.sons[pragmasPos]
let raisesSpec = effectSpec(p, wRaises) let raisesSpec = effectSpec(p, wRaises)
if not isNil(raisesSpec): if not isNil(raisesSpec):

View file

@ -146,7 +146,9 @@ proc discardCheck(result: PNode) =
if result.typ.kind == tyNil: if result.typ.kind == tyNil:
fixNilType(result) fixNilType(result)
else: else:
localError(result.info, errDiscardValue) var n = result
while n.kind in skipForDiscardable: n = n.lastSon
localError(n.info, errDiscardValue)
proc semIf(c: PContext, n: PNode): PNode = proc semIf(c: PContext, n: PNode): PNode =
result = n result = n
@ -188,7 +190,7 @@ proc semCase(c: PContext, n: PNode): PNode =
var typ = CommonTypeBegin var typ = CommonTypeBegin
var hasElse = false var hasElse = false
case skipTypes(n.sons[0].Typ, abstractVarRange-{tyTypeDesc}).Kind case skipTypes(n.sons[0].Typ, abstractVarRange-{tyTypeDesc}).Kind
of tyInt..tyInt64, tyChar, tyEnum: of tyInt..tyInt64, tyChar, tyEnum, tyUInt..tyUInt32:
chckCovered = true chckCovered = true
of tyFloat..tyFloat128, tyString, tyError: of tyFloat..tyFloat128, tyString, tyError:
nil nil
@ -313,6 +315,13 @@ proc semIdentDef(c: PContext, n: PNode, kind: TSymKind): PSym =
result = semIdentWithPragma(c, kind, n, {}) result = semIdentWithPragma(c, kind, n, {})
suggestSym(n, result) suggestSym(n, result)
proc checkNilable(v: PSym) =
if sfGlobal in v.flags and {tfNotNil, tfNeedsInit} * v.typ.flags != {}:
if v.ast.isNil:
Message(v.info, warnProveInit, v.name.s)
elif tfNotNil in v.typ.flags and tfNotNil notin v.ast.typ.flags:
Message(v.info, warnProveInit, v.name.s)
proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode = proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
var b: PNode var b: PNode
result = copyNode(n) result = copyNode(n)
@ -371,11 +380,11 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
# side of the '=': # side of the '=':
if warnShadowIdent in gNotes and not identWithin(def, v.name): if warnShadowIdent in gNotes and not identWithin(def, v.name):
Message(a.info, warnShadowIdent, v.name.s) Message(a.info, warnShadowIdent, v.name.s)
if a.kind != nkVarTuple:
if def != nil and def.kind != nkEmpty: if def != nil and def.kind != nkEmpty:
# this is only needed for the evaluation pass: # this is needed for the evaluation pass and for the guard checking:
v.ast = def v.ast = def
if sfThread in v.flags: LocalError(def.info, errThreadvarCannotInit) if sfThread in v.flags: LocalError(def.info, errThreadvarCannotInit)
if a.kind != nkVarTuple:
v.typ = typ v.typ = typ
b = newNodeI(nkIdentDefs, a.info) b = newNodeI(nkIdentDefs, a.info)
if importantComments(): if importantComments():
@ -386,8 +395,10 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
addSon(b, copyTree(def)) addSon(b, copyTree(def))
addSon(result, b) addSon(result, b)
else: else:
if def.kind == nkPar: v.ast = def[j]
v.typ = tup.sons[j] v.typ = tup.sons[j]
b.sons[j] = newSymNode(v) b.sons[j] = newSymNode(v)
checkNilable(v)
proc semConst(c: PContext, n: PNode): PNode = proc semConst(c: PContext, n: PNode): PNode =
result = copyNode(n) result = copyNode(n)
@ -1128,9 +1139,10 @@ proc semPragmaBlock(c: PContext, n: PNode): PNode =
proc semStaticStmt(c: PContext, n: PNode): PNode = proc semStaticStmt(c: PContext, n: PNode): PNode =
let a = semStmt(c, n.sons[0]) let a = semStmt(c, n.sons[0])
result = evalStaticExpr(c.module, a) result = evalStaticExpr(c.module, a, c.p.owner)
if result.isNil: if result.isNil:
LocalError(n.info, errCannotInterpretNodeX, renderTree(n)) LocalError(n.info, errCannotInterpretNodeX, renderTree(n))
result = emptyNode
elif result.kind == nkEmpty: elif result.kind == nkEmpty:
result = newNodeI(nkDiscardStmt, n.info, 1) result = newNodeI(nkDiscardStmt, n.info, 1)
result.sons[0] = emptyNode result.sons[0] = emptyNode

View file

@ -39,6 +39,7 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType =
counter = lastOrd(base) + 1 counter = lastOrd(base) + 1
rawAddSon(result, base) rawAddSon(result, base)
let isPure = result.sym != nil and sfPure in result.sym.flags let isPure = result.sym != nil and sfPure in result.sym.flags
var hasNull = false
for i in countup(1, sonsLen(n) - 1): for i in countup(1, sonsLen(n) - 1):
case n.sons[i].kind case n.sons[i].kind
of nkEnumFieldDef: of nkEnumFieldDef:
@ -74,6 +75,7 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType =
else: illFormedAst(n) else: illFormedAst(n)
e.typ = result e.typ = result
e.position = int(counter) e.position = int(counter)
if e.position == 0: hasNull = true
if result.sym != nil and sfExported in result.sym.flags: if result.sym != nil and sfExported in result.sym.flags:
incl(e.flags, sfUsed) incl(e.flags, sfUsed)
incl(e.flags, sfExported) incl(e.flags, sfExported)
@ -81,6 +83,7 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType =
addSon(result.n, newSymNode(e)) addSon(result.n, newSymNode(e))
if sfGenSym notin e.flags and not isPure: addDecl(c, e) if sfGenSym notin e.flags and not isPure: addDecl(c, e)
inc(counter) inc(counter)
if not hasNull: incl(result.flags, tfNeedsInit)
proc semSet(c: PContext, n: PNode, prev: PType): PType = proc semSet(c: PContext, n: PNode, prev: PType): PType =
result = newOrPrevType(tySet, prev, c) result = newOrPrevType(tySet, prev, c)
@ -168,7 +171,14 @@ proc semRangeAux(c: PContext, n: PNode, prev: PType): PType =
proc semRange(c: PContext, n: PNode, prev: PType): PType = proc semRange(c: PContext, n: PNode, prev: PType): PType =
result = nil result = nil
if sonsLen(n) == 2: if sonsLen(n) == 2:
if isRange(n[1]): result = semRangeAux(c, n[1], prev) if isRange(n[1]):
result = semRangeAux(c, n[1], prev)
let n = result.n
if n.sons[0].kind in {nkCharLit..nkUInt64Lit}:
if n.sons[0].intVal > 0 or n.sons[1].intVal < 0:
incl(result.flags, tfNeedsInit)
elif n.sons[0].floatVal > 0.0 or n.sons[1].floatVal < 0.0:
incl(result.flags, tfNeedsInit)
else: else:
LocalError(n.sons[0].info, errRangeExpected) LocalError(n.sons[0].info, errRangeExpected)
result = newOrPrevType(tyError, prev, c) result = newOrPrevType(tyError, prev, c)
@ -387,9 +397,9 @@ proc semCaseBranch(c: PContext, t, branch: PNode, branchIndex: int,
checkForOverlap(c, t, i, branchIndex) checkForOverlap(c, t, i, branchIndex)
proc semRecordNodeAux(c: PContext, n: PNode, check: var TIntSet, pos: var int, proc semRecordNodeAux(c: PContext, n: PNode, check: var TIntSet, pos: var int,
father: PNode, rectype: PSym) father: PNode, rectype: PType)
proc semRecordCase(c: PContext, n: PNode, check: var TIntSet, pos: var int, proc semRecordCase(c: PContext, n: PNode, check: var TIntSet, pos: var int,
father: PNode, rectype: PSym) = father: PNode, rectype: PType) =
var a = copyNode(n) var a = copyNode(n)
checkMinSonsLen(n, 2) checkMinSonsLen(n, 2)
semRecordNodeAux(c, n.sons[0], check, pos, a, rectype) semRecordNodeAux(c, n.sons[0], check, pos, a, rectype)
@ -424,7 +434,7 @@ proc semRecordCase(c: PContext, n: PNode, check: var TIntSet, pos: var int,
addSon(father, a) addSon(father, a)
proc semRecordNodeAux(c: PContext, n: PNode, check: var TIntSet, pos: var int, proc semRecordNodeAux(c: PContext, n: PNode, check: var TIntSet, pos: var int,
father: PNode, rectype: PSym) = father: PNode, rectype: PType) =
if n == nil: return if n == nil: return
case n.kind case n.kind
of nkRecWhen: of nkRecWhen:
@ -483,15 +493,17 @@ proc semRecordNodeAux(c: PContext, n: PNode, check: var TIntSet, pos: var int,
typ = errorType(c) typ = errorType(c)
else: else:
typ = semTypeNode(c, n.sons[length-2], nil) typ = semTypeNode(c, n.sons[length-2], nil)
propagateToOwner(rectype, typ)
let rec = rectype.sym
for i in countup(0, sonsLen(n)-3): for i in countup(0, sonsLen(n)-3):
var f = semIdentWithPragma(c, skField, n.sons[i], {sfExported}) var f = semIdentWithPragma(c, skField, n.sons[i], {sfExported})
suggestSym(n.sons[i], f) suggestSym(n.sons[i], f)
f.typ = typ f.typ = typ
f.position = pos f.position = pos
if (rectype != nil) and ({sfImportc, sfExportc} * rectype.flags != {}) and if (rec != nil) and ({sfImportc, sfExportc} * rec.flags != {}) and
(f.loc.r == nil): (f.loc.r == nil):
f.loc.r = toRope(f.name.s) f.loc.r = toRope(f.name.s)
f.flags = f.flags + ({sfImportc, sfExportc} * rectype.flags) f.flags = f.flags + ({sfImportc, sfExportc} * rec.flags)
inc(pos) inc(pos)
if ContainsOrIncl(check, f.name.id): if ContainsOrIncl(check, f.name.id):
localError(n.sons[i].info, errAttemptToRedefine, f.name.s) localError(n.sons[i].info, errAttemptToRedefine, f.name.s)
@ -553,7 +565,7 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType): PType =
result = newOrPrevType(tyObject, prev, c) result = newOrPrevType(tyObject, prev, c)
rawAddSon(result, base) rawAddSon(result, base)
result.n = newNodeI(nkRecList, n.info) result.n = newNodeI(nkRecList, n.info)
semRecordNodeAux(c, n.sons[2], check, pos, result.n, result.sym) semRecordNodeAux(c, n.sons[2], check, pos, result.n, result)
if n.sons[0].kind != nkEmpty: if n.sons[0].kind != nkEmpty:
# dummy symbol for `pragma`: # dummy symbol for `pragma`:
var s = newSymS(skType, newIdentNode(getIdent("dummy"), n.info), c) var s = newSymS(skType, newIdentNode(getIdent("dummy"), n.info), c)

View file

@ -152,6 +152,7 @@ proc handleGenericInvokation(cl: var TReplTypeVars, t: PType): PType =
x = lookupTypeVar(cl, x) x = lookupTypeVar(cl, x)
if header == nil: header = copyType(t, t.owner, false) if header == nil: header = copyType(t, t.owner, false)
header.sons[i] = x header.sons[i] = x
propagateToOwner(header, x)
#idTablePut(cl.typeMap, body.sons[i-1], x) #idTablePut(cl.typeMap, body.sons[i-1], x)
if header != nil: if header != nil:
# search again after first pass: # search again after first pass:
@ -170,6 +171,7 @@ proc handleGenericInvokation(cl: var TReplTypeVars, t: PType): PType =
var x = replaceTypeVarsT(cl, t.sons[i]) var x = replaceTypeVarsT(cl, t.sons[i])
assert x.kind != tyGenericInvokation assert x.kind != tyGenericInvokation
header.sons[i] = x header.sons[i] = x
propagateToOwner(header, x)
idTablePut(cl.typeMap, body.sons[i-1], x) idTablePut(cl.typeMap, body.sons[i-1], x)
for i in countup(1, sonsLen(t) - 1): for i in countup(1, sonsLen(t) - 1):

View file

@ -48,6 +48,9 @@ type
isFromIntLit, # conversion *from* int literal; proven safe isFromIntLit, # conversion *from* int literal; proven safe
isEqual isEqual
const
isNilConversion = isConvertible # maybe 'isIntConv' fits better?
proc markUsed*(n: PNode, s: PSym) proc markUsed*(n: PNode, s: PSym)
proc initCandidateAux(c: var TCandidate, callee: PType) {.inline.} = proc initCandidateAux(c: var TCandidate, callee: PType) {.inline.} =
@ -163,6 +166,15 @@ proc writeMatches*(c: TCandidate) =
Writeln(stdout, "intconv matches: " & $c.intConvMatches) Writeln(stdout, "intconv matches: " & $c.intConvMatches)
Writeln(stdout, "generic matches: " & $c.genericMatches) Writeln(stdout, "generic matches: " & $c.genericMatches)
proc argTypeToString(arg: PNode): string =
if arg.kind in nkSymChoices:
result = typeToString(arg[0].typ)
for i in 1 .. <arg.len:
result.add(" | ")
result.add typeToString(arg[i].typ)
else:
result = arg.typ.typeToString
proc NotFoundError*(c: PContext, n: PNode) = proc NotFoundError*(c: PContext, n: PNode) =
# Gives a detailed error message; this is separated from semOverloadedCall, # Gives a detailed error message; this is separated from semOverloadedCall,
# as semOverlodedCall is already pretty slow (and we need this information # as semOverlodedCall is already pretty slow (and we need this information
@ -172,20 +184,20 @@ proc NotFoundError*(c: PContext, n: PNode) =
GlobalError(n.info, errTypeMismatch, "") GlobalError(n.info, errTypeMismatch, "")
var result = msgKindToString(errTypeMismatch) var result = msgKindToString(errTypeMismatch)
for i in countup(1, sonsLen(n) - 1): for i in countup(1, sonsLen(n) - 1):
var nt = n.sons[i].typ var arg = n.sons[i]
if n.sons[i].kind == nkExprEqExpr: if n.sons[i].kind == nkExprEqExpr:
add(result, renderTree(n.sons[i].sons[0])) add(result, renderTree(n.sons[i].sons[0]))
add(result, ": ") add(result, ": ")
if nt.isNil: if arg.typ.isNil:
n.sons[i].sons[1] = c.semOperand(c, n.sons[i].sons[1]) arg = c.semOperand(c, n.sons[i].sons[1])
nt = n.sons[i].sons[1].typ n.sons[i].typ = arg.typ
n.sons[i].typ = nt n.sons[i].sons[1] = arg
else: else:
if nt.isNil: if arg.typ.isNil:
n.sons[i] = c.semOperand(c, n.sons[i]) arg = c.semOperand(c, n.sons[i])
nt = n.sons[i].typ n.sons[i] = arg
if nt.kind == tyError: return if arg.typ.kind == tyError: return
add(result, typeToString(nt)) add(result, argTypeToString(arg))
if i != sonsLen(n) - 1: add(result, ", ") if i != sonsLen(n) - 1: add(result, ", ")
add(result, ')') add(result, ')')
var candidates = "" var candidates = ""
@ -471,6 +483,8 @@ proc typeRel(c: var TCandidate, f, a: PType): TTypeRelation =
else: else:
result = typeRel(c, f.sons[0], a.sons[0]) result = typeRel(c, f.sons[0], a.sons[0])
if result < isGeneric: result = isNone if result < isGeneric: result = isNone
elif tfNotNil in f.flags and tfNotNil notin a.flags:
result = isNilConversion
of tyNil: result = f.allowsNil of tyNil: result = f.allowsNil
else: nil else: nil
of tyOrdinal: of tyOrdinal:
@ -506,6 +520,8 @@ proc typeRel(c: var TCandidate, f, a: PType): TTypeRelation =
of tyPtr: of tyPtr:
result = typeRel(c, base(f), base(a)) result = typeRel(c, base(f), base(a))
if result <= isConvertible: result = isNone if result <= isConvertible: result = isNone
elif tfNotNil in f.flags and tfNotNil notin a.flags:
result = isNilConversion
of tyNil: result = f.allowsNil of tyNil: result = f.allowsNil
else: nil else: nil
of tyRef: of tyRef:
@ -513,13 +529,21 @@ proc typeRel(c: var TCandidate, f, a: PType): TTypeRelation =
of tyRef: of tyRef:
result = typeRel(c, base(f), base(a)) result = typeRel(c, base(f), base(a))
if result <= isConvertible: result = isNone if result <= isConvertible: result = isNone
elif tfNotNil in f.flags and tfNotNil notin a.flags:
result = isNilConversion
of tyNil: result = f.allowsNil of tyNil: result = f.allowsNil
else: nil else: nil
of tyProc: of tyProc:
result = procTypeRel(c, f, a) result = procTypeRel(c, f, a)
if result != isNone and tfNotNil in f.flags and tfNotNil notin a.flags:
result = isNilConversion
of tyPointer: of tyPointer:
case a.kind case a.kind
of tyPointer: result = isEqual of tyPointer:
if tfNotNil in f.flags and tfNotNil notin a.flags:
result = isNilConversion
else:
result = isEqual
of tyNil: result = f.allowsNil of tyNil: result = f.allowsNil
of tyProc: of tyProc:
if a.callConv != ccClosure: result = isConvertible if a.callConv != ccClosure: result = isConvertible
@ -527,13 +551,21 @@ proc typeRel(c: var TCandidate, f, a: PType): TTypeRelation =
else: nil else: nil
of tyString: of tyString:
case a.kind case a.kind
of tyString: result = isEqual of tyString:
if tfNotNil in f.flags and tfNotNil notin a.flags:
result = isNilConversion
else:
result = isEqual
of tyNil: result = f.allowsNil of tyNil: result = f.allowsNil
else: nil else: nil
of tyCString: of tyCString:
# conversion from string to cstring is automatic: # conversion from string to cstring is automatic:
case a.Kind case a.Kind
of tyCString: result = isEqual of tyCString:
if tfNotNil in f.flags and tfNotNil notin a.flags:
result = isNilConversion
else:
result = isEqual
of tyNil: result = f.allowsNil of tyNil: result = f.allowsNil
of tyString: result = isConvertible of tyString: result = isConvertible
of tyPtr: of tyPtr:
@ -851,6 +883,20 @@ proc prepareNamedParam(a: PNode) =
var info = a.sons[0].info var info = a.sons[0].info
a.sons[0] = newIdentNode(considerAcc(a.sons[0]), info) a.sons[0] = newIdentNode(considerAcc(a.sons[0]), info)
proc arrayConstr(c: PContext, n: PNode): PType =
result = newTypeS(tyArrayConstr, c)
rawAddSon(result, makeRangeType(c, 0, 0, n.info))
addSonSkipIntLit(result, skipTypes(n.typ, {tyGenericInst, tyVar, tyOrdinal}))
proc arrayConstr(c: PContext, info: TLineInfo): PType =
result = newTypeS(tyArrayConstr, c)
rawAddSon(result, makeRangeType(c, 0, -1, info))
rawAddSon(result, newTypeS(tyEmpty, c)) # needs an empty basetype!
proc incrIndexType(t: PType) =
assert t.kind == tyArrayConstr
inc t.sons[0].n.sons[1].intVal
proc matchesAux(c: PContext, n, nOrig: PNode, proc matchesAux(c: PContext, n, nOrig: PNode,
m: var TCandidate, marker: var TIntSet) = m: var TCandidate, marker: var TIntSet) =
template checkConstraint(n: expr) {.immediate, dirty.} = template checkConstraint(n: expr) {.immediate, dirty.} =
@ -901,7 +947,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode,
checkConstraint(n.sons[a].sons[1]) checkConstraint(n.sons[a].sons[1])
if m.baseTypeMatch: if m.baseTypeMatch:
assert(container == nil) assert(container == nil)
container = newNodeI(nkBracket, n.sons[a].info) container = newNodeIT(nkBracket, n.sons[a].info, arrayConstr(c, arg))
addSon(container, arg) addSon(container, arg)
setSon(m.call, formal.position + 1, container) setSon(m.call, formal.position + 1, container)
if f != formalLen - 1: container = nil if f != formalLen - 1: container = nil
@ -927,6 +973,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode,
n.sons[a], nOrig.sons[a]) n.sons[a], nOrig.sons[a])
if (arg != nil) and m.baseTypeMatch and (container != nil): if (arg != nil) and m.baseTypeMatch and (container != nil):
addSon(container, arg) addSon(container, arg)
incrIndexType(container.typ)
else: else:
m.state = csNoMatch m.state = csNoMatch
return return
@ -952,7 +999,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode,
return return
if m.baseTypeMatch: if m.baseTypeMatch:
assert(container == nil) assert(container == nil)
container = newNodeI(nkBracket, n.sons[a].info) container = newNodeIT(nkBracket, n.sons[a].info, arrayConstr(c, arg))
addSon(container, arg) addSon(container, arg)
setSon(m.call, formal.position + 1, setSon(m.call, formal.position + 1,
implicitConv(nkHiddenStdConv, formal.typ, container, m, c)) implicitConv(nkHiddenStdConv, formal.typ, container, m, c))
@ -985,7 +1032,7 @@ proc matches*(c: PContext, n, nOrig: PNode, m: var TCandidate) =
if not ContainsOrIncl(marker, formal.position): if not ContainsOrIncl(marker, formal.position):
if formal.ast == nil: if formal.ast == nil:
if formal.typ.kind == tyVarargs: if formal.typ.kind == tyVarargs:
var container = newNodeI(nkBracket, n.info) var container = newNodeIT(nkBracket, n.info, arrayConstr(c, n.info))
addSon(m.call, implicitConv(nkHiddenStdConv, formal.typ, addSon(m.call, implicitConv(nkHiddenStdConv, formal.typ,
container, m, c)) container, m, c))
else: else:

View file

@ -417,6 +417,15 @@ proc putArgInto(arg: PNode, formal: PType): TPutArgInto =
if skipTypes(formal, abstractInst).kind == tyVar: result = paVarAsgn if skipTypes(formal, abstractInst).kind == tyVar: result = paVarAsgn
else: result = paFastAsgn else: result = paFastAsgn
proc findWrongOwners(c: PTransf, n: PNode) =
if n.kind == nkVarSection:
let x = n.sons[0].sons[0]
if x.kind == nkSym and x.sym.owner != getCurrOwner(c):
internalError(x.info, "bah " & x.sym.name.s & " " &
x.sym.owner.name.s & " " & getCurrOwner(c).name.s)
else:
for i in 0 .. <safeLen(n): findWrongOwners(c, n.sons[i])
proc transformFor(c: PTransf, n: PNode): PTransNode = proc transformFor(c: PTransf, n: PNode): PTransNode =
# generate access statements for the parameters (unless they are constant) # generate access statements for the parameters (unless they are constant)
# put mapping from formal parameters to actual parameters # put mapping from formal parameters to actual parameters
@ -468,6 +477,7 @@ proc transformFor(c: PTransf, n: PNode): PTransNode =
pushInfoContext(n.info) pushInfoContext(n.info)
inc(c.inlining) inc(c.inlining)
add(result, transform(c, body)) add(result, transform(c, body))
#findWrongOwners(c, result.pnode)
dec(c.inlining) dec(c.inlining)
popInfoContext() popInfoContext()
popTransCon(c) popTransCon(c)
@ -504,7 +514,7 @@ proc transformCase(c: PTransf, n: PNode): PTransNode =
result.add(elseBranch) result.add(elseBranch)
elif result.Pnode.lastSon.kind != nkElse and not ( elif result.Pnode.lastSon.kind != nkElse and not (
skipTypes(n.sons[0].Typ, abstractVarRange).Kind in skipTypes(n.sons[0].Typ, abstractVarRange).Kind in
{tyInt..tyInt64, tyChar, tyEnum}): {tyInt..tyInt64, tyChar, tyEnum, tyUInt..tyUInt32}):
# fix a stupid code gen bug by normalizing: # fix a stupid code gen bug by normalizing:
var elseBranch = newTransNode(nkElse, n.info, 1) var elseBranch = newTransNode(nkElse, n.info, 1)
elseBranch[0] = newTransNode(nkNilLit, n.info, 0) elseBranch[0] = newTransNode(nkNilLit, n.info, 0)
@ -675,12 +685,12 @@ proc transform(c: PTransf, n: PNode): PTransNode =
if cnst != nil and not dontInlineConstant(n, cnst): if cnst != nil and not dontInlineConstant(n, cnst):
result = PTransNode(cnst) # do not miss an optimization result = PTransNode(cnst) # do not miss an optimization
proc processTransf(c: PTransf, n: PNode): PNode = proc processTransf(c: PTransf, n: PNode, owner: PSym): PNode =
# Note: For interactive mode we cannot call 'passes.skipCodegen' and skip # Note: For interactive mode we cannot call 'passes.skipCodegen' and skip
# this step! We have to rely that the semantic pass transforms too errornous # this step! We have to rely that the semantic pass transforms too errornous
# nodes into an empty node. # nodes into an empty node.
if passes.skipCodegen(n) or c.fromCache or nfTransf in n.flags: return n if passes.skipCodegen(n) or c.fromCache or nfTransf in n.flags: return n
pushTransCon(c, newTransCon(getCurrOwner(c))) pushTransCon(c, newTransCon(owner))
result = PNode(transform(c, n)) result = PNode(transform(c, n))
popTransCon(c) popTransCon(c)
incl(result.flags, nfTransf) incl(result.flags, nfTransf)
@ -691,35 +701,27 @@ proc openTransf(module: PSym, filename: string): PTransf =
result.breakSyms = @[] result.breakSyms = @[]
result.module = module result.module = module
when false:
proc openTransfCached(module: PSym, filename: string,
rd: PRodReader): PPassContext =
result = openTransf(module, filename)
for m in items(rd.methods): methodDef(m, true)
const transfPass* = makePass(openTransf, openTransfCached,
processTransf, processTransf) # we need to process generics too!
proc transformBody*(module: PSym, n: PNode, prc: PSym): PNode = proc transformBody*(module: PSym, n: PNode, prc: PSym): PNode =
if nfTransf in n.flags or prc.kind in {skTemplate, skMacro}: if nfTransf in n.flags or prc.kind in {skTemplate}:
result = n result = n
else: else:
when useEffectSystem: trackProc(prc, n) #when useEffectSystem: trackProc(prc, n)
var c = openTransf(module, "") var c = openTransf(module, "")
result = processTransf(c, n) result = processTransf(c, n, prc)
if prc.kind != skMacro: if prc.kind != skMacro:
# XXX no closures yet for macros: # XXX no closures yet for macros:
result = liftLambdas(prc, result) result = liftLambdas(prc, result)
if prc.kind == skIterator and prc.typ.callConv == ccClosure: if prc.kind == skIterator and prc.typ.callConv == ccClosure:
result = lambdalifting.liftIterator(prc, result) result = lambdalifting.liftIterator(prc, result)
incl(result.flags, nfTransf) incl(result.flags, nfTransf)
when useEffectSystem: trackProc(prc, result)
proc transformStmt*(module: PSym, n: PNode): PNode = proc transformStmt*(module: PSym, n: PNode): PNode =
if nfTransf in n.flags: if nfTransf in n.flags:
result = n result = n
else: else:
var c = openTransf(module, "") var c = openTransf(module, "")
result = processTransf(c, n) result = processTransf(c, n, module)
result = liftLambdasForTopLevel(module, result) result = liftLambdasForTopLevel(module, result)
incl(result.flags, nfTransf) incl(result.flags, nfTransf)
@ -728,5 +730,5 @@ proc transformExpr*(module: PSym, n: PNode): PNode =
result = n result = n
else: else:
var c = openTransf(module, "") var c = openTransf(module, "")
result = processTransf(c, n) result = processTransf(c, n, module)
incl(result.flags, nfTransf) incl(result.flags, nfTransf)

View file

@ -93,8 +93,7 @@ proc getOpSym*(op: PNode): PSym =
proc getMagic*(op: PNode): TMagic = proc getMagic*(op: PNode): TMagic =
case op.kind case op.kind
of nkCall, nkHiddenCallConv, nkCommand, nkCallStrLit, nkPrefix, nkPostfix, of nkCallKinds:
nkInfix:
case op.sons[0].Kind case op.sons[0].Kind
of nkSym: result = op.sons[0].sym.magic of nkSym: result = op.sons[0].sym.magic
else: result = mNone else: result = mNone

View file

@ -41,7 +41,7 @@ type
wImmediate, wDestructor, wImportCpp, wImportObjC, wImmediate, wDestructor, wImportCpp, wImportObjC,
wImportCompilerProc, wImportCompilerProc,
wImportc, wExportc, wIncompleteStruct, wImportc, wExportc, wIncompleteStruct, wRequiresInit,
wAlign, wNodecl, wPure, wSideeffect, wHeader, wAlign, wNodecl, wPure, wSideeffect, wHeader,
wNosideeffect, wNoreturn, wMerge, wLib, wDynlib, wCompilerproc, wProcVar, wNosideeffect, wNoreturn, wMerge, wLib, wDynlib, wCompilerproc, wProcVar,
wFatal, wError, wWarning, wHint, wLine, wPush, wPop, wDefine, wUndef, wFatal, wError, wWarning, wHint, wLine, wPush, wPop, wDefine, wUndef,
@ -62,7 +62,7 @@ type
wAcyclic, wShallow, wUnroll, wLinearScanEnd, wAcyclic, wShallow, wUnroll, wLinearScanEnd,
wWrite, wGensym, wInject, wDirty, wInheritable, wThreadVar, wEmit, wWrite, wGensym, wInject, wDirty, wInheritable, wThreadVar, wEmit,
wNoStackFrame, wNoStackFrame,
wImplicitStatic, wGlobal, wHoist, wImplicitStatic, wGlobal, wCodegenDecl,
wAuto, wBool, wCatch, wChar, wClass, wAuto, wBool, wCatch, wChar, wClass,
wConst_cast, wDefault, wDelete, wDouble, wDynamic_cast, wConst_cast, wDefault, wDelete, wDouble, wDynamic_cast,
@ -122,7 +122,7 @@ const
"immediate", "destructor", "importcpp", "importobjc", "immediate", "destructor", "importcpp", "importobjc",
"importcompilerproc", "importc", "exportc", "incompletestruct", "importcompilerproc", "importc", "exportc", "incompletestruct",
"align", "nodecl", "pure", "sideeffect", "requiresinit", "align", "nodecl", "pure", "sideeffect",
"header", "nosideeffect", "noreturn", "merge", "lib", "dynlib", "header", "nosideeffect", "noreturn", "merge", "lib", "dynlib",
"compilerproc", "procvar", "fatal", "error", "warning", "hint", "line", "compilerproc", "procvar", "fatal", "error", "warning", "hint", "line",
"push", "pop", "define", "undef", "linedir", "stacktrace", "linetrace", "push", "pop", "define", "undef", "linedir", "stacktrace", "linetrace",
@ -142,7 +142,7 @@ const
"watchpoint", "watchpoint",
"subschar", "acyclic", "shallow", "unroll", "linearscanend", "subschar", "acyclic", "shallow", "unroll", "linearscanend",
"write", "gensym", "inject", "dirty", "inheritable", "threadvar", "emit", "write", "gensym", "inject", "dirty", "inheritable", "threadvar", "emit",
"nostackframe", "implicitstatic", "global", "hoist", "nostackframe", "implicitstatic", "global", "codegendecl",
"auto", "bool", "catch", "char", "class", "auto", "bool", "catch", "char", "class",
"const_cast", "default", "delete", "double", "const_cast", "default", "delete", "double",

View file

@ -93,10 +93,10 @@ hint[LineTooLong]=off
cc = clang cc = clang
tlsEmulation:on tlsEmulation:on
gcc.options.always = "-w -fasm-blocks" gcc.options.always = "-w -fasm-blocks"
gpp.options.always = "-w -fasm-blocks" gpp.options.always = "-w -fasm-blocks -fpermissive"
@else: @else:
gcc.options.always = "-w" gcc.options.always = "-w"
gpp.options.always = "-w" gpp.options.always = "-w -fpermissive"
@end @end
gcc.options.speed = "-O3 -fno-strict-aliasing" gcc.options.speed = "-O3 -fno-strict-aliasing"

View file

@ -12,11 +12,17 @@ Advanced commands:
//check checks the project for syntax and semantic //check checks the project for syntax and semantic
//idetools compiler support for IDEs: possible options: //idetools compiler support for IDEs: possible options:
--track:FILE,LINE,COL track a file/cursor position --track:FILE,LINE,COL track a file/cursor position
--trackDirty:DIRTY_FILE,ORIG_FILE,LINE,COL
track a file, currently not saved to disk
--suggest suggest all possible symbols at position --suggest suggest all possible symbols at position
--def list all possible definitions at position --def list all possible definitions at position
--context list possible invokation context --context list possible invokation context
--usages list all usages of the symbol at position --usages list all usages of the symbol at position
--eval evaluates an expression --eval evaluates an expression
//serve start the compiler as a service mode (CAAS)
--server.type:TYPE either stdin or tcp
--server.port:PORT port for tcp mode, by default 6000
--server.address:HOST binds to that address, by default ""
Advanced options: Advanced options:
-m, --mainmodule:FILE set the project main module -m, --mainmodule:FILE set the project main module

View file

@ -186,4 +186,4 @@ complexOrSimpleStmt = (ifStmt | whenStmt | whileStmt
| bindStmt | mixinStmt) | bindStmt | mixinStmt)
/ simpleStmt / simpleStmt
stmt = (IND{>} complexOrSimpleStmt^+(IND{=} / ';') DED) stmt = (IND{>} complexOrSimpleStmt^+(IND{=} / ';') DED)
/ simpleStmt / simpleStmt ^+ ';'

467
doc/idetools.txt Normal file
View file

@ -0,0 +1,467 @@
================================
Nimrod IDE Integration Guide
================================
:Author: Britney Spears
:Version: |nimrodversion|
.. contents::
Nimrod differs from many other compilers in that it is really fast,
and being so fast makes it suited to provide external queries for
text editors about the source code being written. Through the
``idetools`` command of `the compiler <nimrodc.html>`_, any IDE can
query a ``.nim`` source file and obtain useful information like
definition of symbols or suggestions for completion.
This document will guide you through the available options. If you
want to look at practical examples of idetools support you can look
at the test files found in ``tests/caas/*.txt`` or `various editor
integrations <https://github.com/Araq/Nimrod/wiki/Editor-Support>`_
already available.
Idetools invokation
===================
Specifying the location of the query
------------------------------------
All of the available idetools commands require you to specify a
query location through the ``--track`` or ``--trackDirty`` switches.
The general idetools invokations are::
nimrod idetools --track:FILE,LINE,COL <switches> proj.nim
Or::
nimrod idetools --trackDirty:DIRTY_FILE,FILE,LINE,COL <switches> proj.nim
``proj.nim``
This is the main *project* filename. Most of the time you will
pass in the same as **FILE**, but for bigger projects this is
the file which is used as main entry point for the program, the
one which users compile to generate a final binary.
``<switches>``
This would be any of the other idetools available options, like
``--def`` or ``--suggest`` explained in the following sections.
``COL``
An integer with the column you are going to query. For the
compiler columns start at zero, so the first column will be
**0** and the last in an 80 column terminal will be **79**.
``LINE``
An integer with the line you are going to query. For the compiler
lines start at **1**.
``FILE``
The file you want to perform the query on. Usually you will
pass in the same value as **proj.nim**.
``DIRTY_FILE``
The **FILE** paramater is enough for static analysis, but IDEs
tend to have *unsaved buffers* where the user may still be in
the middle of typing a line. In such situations the IDE can
save the current contents to a temporary file and then use the
``--trackDirty`` switch.
Dirty files are likely to contain errors and they are usually
compiled partially only to the point needed to service the
idetool request. The compiler discriminates them to ensure that
**a)** they won't be cached and **b)** they won't invalidate
the cached contents of the original module.
The other reason is that the dirty file can appear anywhere on
disk (e.g. in tmpfs), but it must be treated as having a path
matching the original module when it comes to usage of relative
paths, etc. Queries, however, will refer to the dirty module
name in their answers instead of the normal filename.
Definitions
-----------
The ``--def`` idetools switch performs a query about the definition
of a specific symbol. If available, idetools will answer with the
type, source file, line/column information and other accessory data
if available like a docstring. With this information an IDE can
provide the typical *Jump to definition* where a user puts the
cursor on a symbol or uses the mouse to select it and is redirected
to the place where the symbol is located.
Since Nimrod is implemented in Nimrod, one of the nice things of
this feature is that any user with an IDE supporting it can quickly
jump around the standard library implementation and see exactly
what a proc does, learning about the language and seeing real life
examples of how to write/implement specific features.
Idetools will always answer with a single definition or none if it
can't find any valid symbol matching the position of the query.
Suggestions
-----------
The ``--suggest`` idetools switch performs a query about possible
completion symbols at some point in the file. IDEs can easily provide
an autocompletion feature where the IDE scans the current file (and
related ones, if it knows about the language being edited and follows
includes/imports) and when the user starts typing something a
completion box with different options appears.
However such features are not context sensitive and work simply on
string matching, which can be problematic in Nimrod especially due
to the case insensitiveness of the language (plus underscores as
separators!).
The typical usage scenario for this option is to call it after the
user has typed the dot character for `the object oriented call
syntax <tut2.html#method-call-syntax>`_.
Invokation context
------------------
The ``--context`` idetools switch is very similar to the suggestions
switch, but instead of being used after the user has typed a dot
character, this one is meant to be used after the user has typed
an opening brace to start typing parameters.
Symbol usages
-------------
The ``--usages`` idetools switch lists all usages of the symbol at
a position. IDEs can use this to find all the places in the file
where the symbol is used and offer the user to rename it in all
places at the same time. Again, a pure string based search and
replace may catch symbols out of the scope of a funcion/loop.
For this kind of query the IDE will most likely ignore all the
type/signature info provided by idetools and concentrate on the
filename, line and column position of the multiple returned answers.
Expression evaluation
---------------------
This feature is still under development. In the future it will allow
an IDE to evaluate an expression in the context of the currently
running/debugged user project.
Compiler as a service (CAAS)
============================
The ocasional use of idetools is acceptable for things like
definitions, where the user puts the cursor on a symbol or double
clicks it and after a second or two the IDE displays where that
symbol is defined. Such latencies would be terrible for features
like symbol suggestion, plus why wait at all if we can avoid it?
The idetools command can be run as a compiler service, where you
first launch the compiler and it will stay online as a server,
accepting queries in a telnet like fashion. The advantage of staying
on is that for many queries the compiler can cache the results of
the compilation, and subsequent queries should be fast in the
millisecond range, thus being responsive enough for IDEs.
If you want to start the server using stdin/stdout as communication
you need to type::
nimrod serve --server.type:stdin proj.nim
If you want to start the server using tcp and a port, you need to type::
nimrod serve --server.type:tcp --server.port:6000 \
--server.address:hostname proj.nim
In both cases the server will start up and await further commands.
The syntax of the commands you can now send to the server is
practically the same as running the nimrod compiler on the commandline,
you only need to remove the name of the compiler since you are
already talking to it. The server will answer with as many lines
of text it thinks necessary plus an empty line to indicate the end
of the answer.
You can find examples of client/server communication in the idetools
tests found in ``tests/caas/*.txt``.
Parsing idetools output
=======================
Idetools outputs is always returned on single lines separated by
tab characters (``\t``). The values of each column are:
1. Three characters indicating the type of returned answer (e.g.
def for definition, ``sug`` for suggestion, etc).
2. Type of the symbol. This can be ``skProc``, ``skLet``, and just
about any of the enums defined in the module ``compiler/ast.nim``.
3. Full qualitifed path of the symbol. If you are querying a symbol
defined in the ``proj.nim`` file, this would have the form
``proj.symbolName``.
4. Type/signature. For variables and enums this will contain the
type of the symbol, for procs, methods and templates this will
contain the full unique signature (e.g. ``proc (TFile)``).
5. Full path to the file containing the symbol.
6. Line where the symbol is located in the file. Lines start to
count at **1**.
7. Column where the symbol is located in the file. Columns start
to count at **0**.
8. Docstring for the symbol if available or the empty string. To
differentiate the docstring from end of answer in server mode,
the docstring is always provided enclosed in double quotes, and
if the docstring spans multiple lines, all following lines of the
docstring will start with a blank space to align visually with
the starting quote.
Also, you won't find raw ``\n`` characters breaking the one
answer per line format. Instead you will need to parse sequences
in the form ``\xHH``, where *HH* is a hexadecimal value (e.g.
newlines generate the sequence ``\x0A``).
The following sections define the expected output for each kind of
symbol for which idetools returns valid output.
skConst
-------
| **Third column**: module + [n scope nesting] + const name.
| **Fourth column**: the type of the const value.
| **Docstring**: always the empty string.
.. code-block:: nimrod
const SOME_SEQUENCE = @[1, 2]
--> col 2: $MODULE.SOME_SEQUENCE
col 3: seq[int]
col 7: ""
skEnumField
-----------
| **Third column**: module + [n scope nesting] + enum type + enum field name.
| **Fourth column**: enum type grouping other enum fields.
| **Docstring**: always the empty string.
.. code-block:: nimrod
Open(filename, fmWrite)
--> col 2: system.TFileMode.fmWrite
col 3: TFileMode
col 7: ""
skForVar
--------
| **Third column**: module + [n scope nesting] + var name.
| **Fourth column**: type of the var.
| **Docstring**: always the empty string.
.. code-block:: nimrod
proc looper(filename = "tests.nim") =
for letter in filename:
echo letter
--> col 2: $MODULE.looper.letter
col 3: char
col 7: ""
skIterator
----------
The fourth column will be the empty string if the iterator is being
defined, since at that point in the file the parser hasn't processed
the full line yet. The signature will be returned complete in
posterior instances of the iterator.
| **Third column**: module + [n scope nesting] + iterator name.
| **Fourth column**: signature of the iterator including return type.
| **Docstring**: docstring if available.
.. code-block:: nimrod
let
text = "some text"
letters = toSeq(runes(text))
--> col 2: unicode.runes
col 3: iterator (string): TRune
col 7: "iterates over any unicode character of the string `s`."
skLet
-----
| **Third column**: module + [n scope nesting] + let name.
| **Fourth column**: the type of the let variable.
| **Docstring**: always the empty string.
.. code-block:: nimrod
let
text = "some text"
--> col 2: $MODULE.text
col 3: TaintedString
col 7: ""
skMethod
--------
The fourth column will be the empty string if the method is being
defined, since at that point in the file the parser hasn't processed
the full line yet. The signature will be returned complete in
posterior instances of the method.
Methods imply `dynamic dispatch <tut2.html#dynamic-dispatch>`_ and
idetools performs a static analysis on the code. For this reason
idetools may not return the definition of the correct method you
are querying because it may be impossible to know until the code
is executed. It will try to return the method which covers the most
possible cases (i.e. for variations of different classes in a
hierarchy it will prefer methods using the base class).
While at the language level a method is differentiated from others
by the parameters and return value, the signature of the method
returned by idetools returns also the pragmas for the method.
Note that at the moment the word ``proc`` is returned for the
signature of the found method instead of the expected ``method``.
This may change in the future.
| **Third column**: module + [n scope nesting] + method name.
| **Fourth column**: signature of the method including return type.
| **Docstring**: docstring if available.
.. code-block:: nimrod
method eval(e: PExpr): int = quit "to override!"
method eval(e: PLiteral): int = e.x
method eval(e: PPlusExpr): int = eval(e.a) + eval(e.b)
echo eval(newPlus(newPlus(newLit(1), newLit(2)), newLit(4)))
--> col 2: $MODULE.eval
col 3: proc (PPlusExpr): int
col 7: ""
skParam
-------
| **Third column**: module + [n scope nesting] + param name.
| **Fourth column**: the type of the parameter.
| **Docstring**: always the empty string.
.. code-block:: nimrod
proc reader(filename = "tests.nim") =
let text = readFile(filename)
--> col 2: $MODULE.reader.filename
col 3: string
col 7: ""
skProc
------
The fourth column will be the empty string if the proc is being
defined, since at that point in the file the parser hasn't processed
the full line yet. The signature will be returned complete in
posterior instances of the proc.
While at the language level a proc is differentiated from others
by the parameters and return value, the signature of the proc
returned by idetools returns also the pragmas for the proc.
| **Third column**: module + [n scope nesting] + proc name.
| **Fourth column**: signature of the proc including return type.
| **Docstring**: docstring if available.
.. code-block:: nimrod
Open(filename, fmWrite)
--> col 2: system.Open
col 3: proc (var TFile, string, TFileMode, int): bool
col 7:
"Opens a file named `filename` with given `mode`.
Default mode is readonly. Returns true iff the file could be opened.
This throws no exception if the file could not be opened."
skResult
--------
| **Third column**: module + [n scope nesting] + result.
| **Fourth column**: the type of the result.
| **Docstring**: always the empty string.
.. code-block:: nimrod
proc getRandomValue() : int =
return 4
--> col 2: $MODULE.getRandomValue.result
col 3: int
col 7: ""
skTemplate
----------
The fourth column will be the empty string if the template is being
defined, since at that point in the file the parser hasn't processed
the full line yet. The signature will be returned complete in
posterior instances of the template.
| **Third column**: module + [n scope nesting] + template name.
| **Fourth column**: signature of the template including return type.
| **Docstring**: docstring if available.
.. code-block:: nimrod
let
text = "some text"
letters = toSeq(runes(text))
--> col 2: sequtils.toSeq
col 3: proc (expr): expr
col 7:
"Transforms any iterator into a sequence.
Example:
.. code-block:: nimrod
let
numeric = @[1, 2, 3, 4, 5, 6, 7, 8, 9]
odd_numbers = toSeq(filter(numeric) do (x: int) -> bool:
if x mod 2 == 1:
result = true)
assert odd_numbers == @[1, 3, 5, 7, 9]"
skType
------
| **Third column**: module + [n scope nesting] + type name.
| **Fourth column**: the type.
| **Docstring**: always the empty string.
.. code-block:: nimrod
proc writeTempFile() =
var output: TFile
--> col 2: system.TFile
col 3: TFile
col 7: ""
skVar
-----
| **Third column**: module + [n scope nesting] + var name.
| **Fourth column**: the type of the var.
| **Docstring**: always the empty string.
.. code-block:: nimrod
proc writeTempFile() =
var output: TFile
output.open("/tmp/somefile", fmWrite)
output.write("test")
--> col 2: $MODULE.writeTempFile.output
col 3: TFile
col 7: ""

View file

@ -255,6 +255,22 @@ Parsers
This is a low level module that implements an extremely efficient buffering This is a low level module that implements an extremely efficient buffering
scheme for lexers and parsers. This is used by the diverse parsing modules. scheme for lexers and parsers. This is used by the diverse parsing modules.
* `highlite <highlite.html>`_
Source highlighter for programming or markup languages. Currently
only few languages are supported, other languages may be added.
The interface supports one language nested in another.
* `rst <rst.html>`_
This module implements a reStructuredText parser. A large subset
is implemented. Some features of the markdown wiki syntax are
also supported.
* `rstast <rstast.html>`_
This module implements an AST for the reStructuredText parser.
* `rstgen <rstgen.html>`_
This module implements a generator of HTML/Latex from reStructuredText.
XML Processing XML Processing
-------------- --------------

View file

@ -327,35 +327,35 @@ Numerical constants
`Numerical constants`:idx: are of a single type and have the form:: `Numerical constants`:idx: are of a single type and have the form::
hexdigit ::= digit | 'A'..'F' | 'a'..'f' hexdigit = digit | 'A'..'F' | 'a'..'f'
octdigit ::= '0'..'7' octdigit = '0'..'7'
bindigit ::= '0'..'1' bindigit = '0'..'1'
HEX_LIT ::= '0' ('x' | 'X' ) hexdigit ( ['_'] hexdigit )* HEX_LIT = '0' ('x' | 'X' ) hexdigit ( ['_'] hexdigit )*
DEC_LIT ::= digit ( ['_'] digit )* DEC_LIT = digit ( ['_'] digit )*
OCT_LIT ::= '0o' octdigit ( ['_'] octdigit )* OCT_LIT = '0o' octdigit ( ['_'] octdigit )*
BIN_LIT ::= '0' ('b' | 'B' ) bindigit ( ['_'] bindigit )* BIN_LIT = '0' ('b' | 'B' ) bindigit ( ['_'] bindigit )*
INT_LIT ::= HEX_LIT INT_LIT = HEX_LIT
| DEC_LIT | DEC_LIT
| OCT_LIT | OCT_LIT
| BIN_LIT | BIN_LIT
INT8_LIT ::= INT_LIT ['\''] ('i' | 'I') '8' INT8_LIT = INT_LIT ['\''] ('i' | 'I') '8'
INT16_LIT ::= INT_LIT ['\''] ('i' | 'I') '16' INT16_LIT = INT_LIT ['\''] ('i' | 'I') '16'
INT32_LIT ::= INT_LIT ['\''] ('i' | 'I') '32' INT32_LIT = INT_LIT ['\''] ('i' | 'I') '32'
INT64_LIT ::= INT_LIT ['\''] ('i' | 'I') '64' INT64_LIT = INT_LIT ['\''] ('i' | 'I') '64'
UINT8_LIT ::= INT_LIT ['\''] ('u' | 'U') UINT8_LIT = INT_LIT ['\''] ('u' | 'U')
UINT8_LIT ::= INT_LIT ['\''] ('u' | 'U') '8' UINT8_LIT = INT_LIT ['\''] ('u' | 'U') '8'
UINT16_LIT ::= INT_LIT ['\''] ('u' | 'U') '16' UINT16_LIT = INT_LIT ['\''] ('u' | 'U') '16'
UINT32_LIT ::= INT_LIT ['\''] ('u' | 'U') '32' UINT32_LIT = INT_LIT ['\''] ('u' | 'U') '32'
UINT64_LIT ::= INT_LIT ['\''] ('u' | 'U') '64' UINT64_LIT = INT_LIT ['\''] ('u' | 'U') '64'
exponent ::= ('e' | 'E' ) ['+' | '-'] digit ( ['_'] digit )* exponent = ('e' | 'E' ) ['+' | '-'] digit ( ['_'] digit )*
FLOAT_LIT ::= digit (['_'] digit)* ('.' (['_'] digit)* [exponent] |exponent) FLOAT_LIT = digit (['_'] digit)* (('.' (['_'] digit)* [exponent]) |exponent)
FLOAT32_LIT ::= HEX_LIT '\'' ('f'|'F') '32' FLOAT32_LIT = HEX_LIT '\'' ('f'|'F') '32'
| (FLOAT_LIT | DEC_LIT | OCT_LIT | BIN_LIT) ['\''] ('f'|'F') '32' | (FLOAT_LIT | DEC_LIT | OCT_LIT | BIN_LIT) ['\''] ('f'|'F') '32'
FLOAT64_LIT ::= HEX_LIT '\'' ('f'|'F') '64' FLOAT64_LIT = HEX_LIT '\'' ('f'|'F') '64'
| (FLOAT_LIT | DEC_LIT | OCT_LIT | BIN_LIT) ['\''] ('f'|'F') '64' | (FLOAT_LIT | DEC_LIT | OCT_LIT | BIN_LIT) ['\''] ('f'|'F') '64'
@ -1388,7 +1388,7 @@ accesses its environment. If it does so, it has the calling convention
Distinct type Distinct type
------------- -------------
A distinct type is new type derived from a `base type`:idx: that is A `distinct type`:idx: is new type derived from a `base type`:idx: that is
incompatible with its base type. In particular, it is an essential property incompatible with its base type. In particular, it is an essential property
of a distinct type that it **does not** imply a subtype relation between it of a distinct type that it **does not** imply a subtype relation between it
and its base type. Explicit type conversions from a distinct type to its and its base type. Explicit type conversions from a distinct type to its
@ -1435,7 +1435,7 @@ number without unit; and the same holds for division:
This quickly gets tedious. The implementations are trivial and the compiler This quickly gets tedious. The implementations are trivial and the compiler
should not generate all this code only to optimize it away later - after all should not generate all this code only to optimize it away later - after all
``+`` for dollars should produce the same binary code as ``+`` for ints. ``+`` for dollars should produce the same binary code as ``+`` for ints.
The pragma ``borrow`` has been designed to solve this problem; in principle The pragma `borrow`:idx: has been designed to solve this problem; in principle
it generates the above trivial implementations: it generates the above trivial implementations:
.. code-block:: nimrod .. code-block:: nimrod
@ -1818,6 +1818,24 @@ If a proc is annotated with the ``noinit`` pragma this refers to its implicit
proc returnUndefinedValue: int {.noinit.} = nil proc returnUndefinedValue: int {.noinit.} = nil
The implicit initialization can be also prevented by the `requiresInit`:idx:
type pragma. The compiler requires an explicit initialization then. However
it does a `control flow analysis`:idx: to prove the variable has been
initialized and does not rely on syntactic properties:
.. code-block:: nimrod
type
TMyObject = object {.requiresInit.}
proc p() =
# the following is valid:
var x: TMyObject
if someCondition():
x = a()
else:
x = a()
use x
let statement let statement
------------- -------------
@ -1920,8 +1938,6 @@ the corresponding *then* block:
In the example the scopes have been enclosed in ``{| |}``. In the example the scopes have been enclosed in ``{| |}``.
**Note**: These scoping rules will be active in 0.9.4.
Case statement Case statement
-------------- --------------

View file

@ -20,7 +20,7 @@ on the different supported platforms. It is not a definition of the Nimrod
programming language (therefore is the `manual <manual.html>`_). programming language (therefore is the `manual <manual.html>`_).
Nimrod is free software; it is licensed under the Nimrod is free software; it is licensed under the
`GNU General Public License <gpl.html>`_. `MIT License <http://www.opensource.org/licenses/mit-license.php>`_.
Compiler Usage Compiler Usage
@ -401,6 +401,21 @@ this to work. The conditional symbol ``objc`` is defined when the compiler
emits Objective C code. emits Objective C code.
CodegenDecl pragma
------------------
The `codegenDecl`:idx: pragma can be used to directly influence Nimrod's code
generator. It receives a format string that determines how the variable or
proc is declared in the generated code:
.. code-block:: nimrod
var
a {.codegenDecl: "$# progmem $#".}: int
proc myinterrupt() {.codegenDecl: "__interrupt $# $#$#".} =
echo "realistic interrupt handler"
LineDir option LineDir option
-------------- --------------
The `lineDir`:idx: option can be turned on or off. If turned on the The `lineDir`:idx: option can be turned on or off. If turned on the
@ -443,6 +458,14 @@ in C/C++).
**Note**: This pragma will not exist for the LLVM backend. **Note**: This pragma will not exist for the LLVM backend.
Nimrod idetools integration
=======================
Nimrod provides language integration with external IDEs through the
idetools command. See the documentation of `idetools <idetools.html>`_
for further information.
Nimrod interactive mode Nimrod interactive mode
======================= =======================

View file

@ -1182,6 +1182,21 @@ type and instead write it embedded directly as the type of the first dimension:
type type
TLightTower = array[1..10, array[north..west, TBlinkLights]] TLightTower = array[1..10, array[north..west, TBlinkLights]]
It is quite frequent to have arrays start at zero, so there's a shortcut syntax
to specify a range from zero to the specified index minus one:
.. code-block:: nimrod
type
TIntArray = array[0..5, int] # an array that is indexed with 0..5
TQuickArray = array[6, int] # an array that is indexed with 0..5
var
x: TIntArray
y: TQuickArray
x = [1, 2, 3, 4, 5, 6]
y = x
for i in low(x)..high(x):
echo(x[i], y[i])
Sequences Sequences
--------- ---------

View file

@ -64,13 +64,16 @@ proc tryExec(cmd: string): bool =
echo(cmd) echo(cmd)
result = execShellCmd(cmd) == 0 result = execShellCmd(cmd) == 0
const
compileNimInst = "-d:useLibzipSrc tools/niminst/niminst"
proc csource(args: string) = proc csource(args: string) =
exec("nimrod cc $1 -r tools/niminst/niminst --var:version=$2 csource compiler/nimrod.ini $1" % exec("nimrod cc $1 -r $3 --var:version=$2 csource compiler/nimrod.ini $1" %
[args, NimrodVersion]) [args, NimrodVersion, compileNimInst])
proc zip(args: string) = proc zip(args: string) =
exec("nimrod cc -r tools/niminst/niminst --var:version=$# zip compiler/nimrod.ini" % exec("nimrod cc -r $2 --var:version=$1 zip compiler/nimrod.ini" %
NimrodVersion) [NimrodVersion, compileNimInst])
proc buildTool(toolname, args: string) = proc buildTool(toolname, args: string) =
exec("nimrod cc $# $#" % [args, toolname]) exec("nimrod cc $# $#" % [args, toolname])
@ -85,8 +88,8 @@ proc inno(args: string) =
NimrodVersion) NimrodVersion)
proc install(args: string) = proc install(args: string) =
exec("nimrod cc -r tools/niminst/niminst --var:version=$# scripts compiler/nimrod.ini" % exec("nimrod cc -r $# --var:version=$# scripts compiler/nimrod.ini" %
NimrodVersion) [compileNimInst, NimrodVersion])
exec("sh ./install.sh $#" % args) exec("sh ./install.sh $#" % args)
proc web(args: string) = proc web(args: string) =

View file

@ -1,24 +0,0 @@
===============================================================================
Nimrod -- a Compiler for Nimrod. http://nimrod-code.org/
Copyright (C) 2004-2013 Andreas Rumpf. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
[ MIT license: http://www.opensource.org/licenses/mit-license.php ]

View file

@ -390,7 +390,7 @@ proc lispRepr*(n: PNimrodNode): string {.compileTime.} =
add(result, ")") add(result, ")")
macro dumpTree*(s: stmt): stmt = echo s.treeRepr macro dumpTree*(s: stmt): stmt {.immediate.} = echo s.treeRepr
## Accepts a block of nimrod code and prints the parsed abstract syntax ## Accepts a block of nimrod code and prints the parsed abstract syntax
## tree using the `toTree` function. Printing is done *at compile time*. ## tree using the `toTree` function. Printing is done *at compile time*.
## ##
@ -398,16 +398,16 @@ macro dumpTree*(s: stmt): stmt = echo s.treeRepr
## tree and to discover what kind of nodes must be created to represent ## tree and to discover what kind of nodes must be created to represent
## a certain expression/statement. ## a certain expression/statement.
macro dumpLisp*(s: stmt): stmt = echo s.lispRepr macro dumpLisp*(s: stmt): stmt {.immediate.} = echo s.lispRepr
## Accepts a block of nimrod code and prints the parsed abstract syntax ## Accepts a block of nimrod code and prints the parsed abstract syntax
## tree using the `toLisp` function. Printing is done *at compile time*. ## tree using the `toLisp` function. Printing is done *at compile time*.
## ##
## See `dumpTree`. ## See `dumpTree`.
macro dumpTreeImm*(s: stmt): stmt {.immediate.} = echo s.treeRepr macro dumpTreeImm*(s: stmt): stmt {.immediate, deprecated.} = echo s.treeRepr
## The ``immediate`` version of `dumpTree`. ## The ``immediate`` version of `dumpTree`.
macro dumpLispImm*(s: stmt): stmt {.immediate.} = echo s.lispRepr macro dumpLispImm*(s: stmt): stmt {.immediate, deprecated.} = echo s.lispRepr
## The ``immediate`` version of `dumpLisp`. ## The ``immediate`` version of `dumpLisp`.

View file

@ -379,7 +379,7 @@ proc getBiggestInt*(x: TAny): biggestInt =
of tyInt8: result = biggestInt(cast[ptr int8](x.value)[]) of tyInt8: result = biggestInt(cast[ptr int8](x.value)[])
of tyInt16: result = biggestInt(cast[ptr int16](x.value)[]) of tyInt16: result = biggestInt(cast[ptr int16](x.value)[])
of tyInt32: result = biggestInt(cast[ptr int32](x.value)[]) of tyInt32: result = biggestInt(cast[ptr int32](x.value)[])
of tyInt64: result = biggestInt(cast[ptr int64](x.value)[]) of tyInt64, tyUInt64: result = biggestInt(cast[ptr int64](x.value)[])
of tyBool: result = biggestInt(cast[ptr bool](x.value)[]) of tyBool: result = biggestInt(cast[ptr bool](x.value)[])
of tyChar: result = biggestInt(cast[ptr char](x.value)[]) of tyChar: result = biggestInt(cast[ptr char](x.value)[])
of tyEnum, tySet: of tyEnum, tySet:
@ -389,6 +389,10 @@ proc getBiggestInt*(x: TAny): biggestInt =
of 4: result = biggestInt(cast[ptr int32](x.value)[]) of 4: result = biggestInt(cast[ptr int32](x.value)[])
of 8: result = biggestInt(cast[ptr int64](x.value)[]) of 8: result = biggestInt(cast[ptr int64](x.value)[])
else: assert false else: assert false
of tyUInt: result = biggestInt(cast[ptr uint](x.value)[])
of tyUInt8: result = biggestInt(cast[ptr uint8](x.value)[])
of tyUInt16: result = biggestInt(cast[ptr uint16](x.value)[])
of tyUInt32: result = biggestInt(cast[ptr uint32](x.value)[])
else: assert false else: assert false
proc setBiggestInt*(x: TAny, y: biggestInt) = proc setBiggestInt*(x: TAny, y: biggestInt) =
@ -400,7 +404,7 @@ proc setBiggestInt*(x: TAny, y: biggestInt) =
of tyInt8: cast[ptr int8](x.value)[] = int8(y) of tyInt8: cast[ptr int8](x.value)[] = int8(y)
of tyInt16: cast[ptr int16](x.value)[] = int16(y) of tyInt16: cast[ptr int16](x.value)[] = int16(y)
of tyInt32: cast[ptr int32](x.value)[] = int32(y) of tyInt32: cast[ptr int32](x.value)[] = int32(y)
of tyInt64: cast[ptr int64](x.value)[] = int64(y) of tyInt64, tyUInt64: cast[ptr int64](x.value)[] = int64(y)
of tyBool: cast[ptr bool](x.value)[] = y != 0 of tyBool: cast[ptr bool](x.value)[] = y != 0
of tyChar: cast[ptr char](x.value)[] = chr(y.int) of tyChar: cast[ptr char](x.value)[] = chr(y.int)
of tyEnum, tySet: of tyEnum, tySet:
@ -410,6 +414,10 @@ proc setBiggestInt*(x: TAny, y: biggestInt) =
of 4: cast[ptr int32](x.value)[] = int32(y) of 4: cast[ptr int32](x.value)[] = int32(y)
of 8: cast[ptr int64](x.value)[] = y of 8: cast[ptr int64](x.value)[] = y
else: assert false else: assert false
of tyUInt: cast[ptr uint](x.value)[] = uint(y)
of tyUInt8: cast[ptr uint8](x.value)[] = uint8(y)
of tyUInt16: cast[ptr uint16](x.value)[] = uint16(y)
of tyUInt32: cast[ptr uint32](x.value)[] = uint32(y)
else: assert false else: assert false
proc getChar*(x: TAny): char = proc getChar*(x: TAny): char =

View file

@ -242,7 +242,6 @@ template `=~` *(s: string, pattern: TRegEx): expr =
bind maxSubPatterns bind maxSubPatterns
when not definedInScope(matches): when not definedInScope(matches):
var matches {.inject.}: array[0..maxSubPatterns-1, string] var matches {.inject.}: array[0..maxSubPatterns-1, string]
{.warning: "injected 'matches' might be affected by new scoping rules in 0.9.4".}
match(s, pattern, matches) match(s, pattern, matches)
# ------------------------- more string handling ------------------------------ # ------------------------- more string handling ------------------------------

View file

@ -443,11 +443,11 @@ struct TFrame {
}; };
#define nimfr(proc, file) \ #define nimfr(proc, file) \
volatile TFrame F; \ TFrame F; \
F.procname = proc; F.filename = file; F.line = 0; F.len = 0; nimFrame(&F); F.procname = proc; F.filename = file; F.line = 0; F.len = 0; nimFrame(&F);
#define nimfrs(proc, file, slots, length) \ #define nimfrs(proc, file, slots, length) \
volatile struct {TFrame* prev;NCSTRING procname;NI line;NCSTRING filename; NI len; TVarSlot s[slots];} F; \ 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); F.procname = proc; F.filename = file; F.line = 0; F.len = length; nimFrame((TFrame*)&F);
#define nimln(n, file) \ #define nimln(n, file) \

View file

@ -44,7 +44,17 @@ const
"Assembler", "Preprocessor", "Directive", "Command", "Rule", "Hyperlink", "Assembler", "Preprocessor", "Directive", "Command", "Rule", "Hyperlink",
"Label", "Reference", "Other"] "Label", "Reference", "Other"]
nimrodKeywords = slurp("doc/keywords.txt").split # 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.
nimrodKeywords = ["addr", "and", "as", "asm", "atomic", "bind", "block",
"break", "case", "cast", "const", "continue", "converter", "discard",
"distinct", "div", "do", "elif", "else", "end", "enum", "except", "export",
"finally", "for", "from", "generic", "if", "import", "in", "include",
"interface", "is", "isnot", "iterator", "lambda", "let", "macro", "method",
"mixin", "mod", "nil", "not", "notin", "object", "of", "or", "out", "proc",
"ptr", "raise", "ref", "return", "shared", "shl", "shr", "static",
"template", "try", "tuple", "type", "var", "when", "while", "with",
"without", "xor", "yield"]
proc getSourceLanguage*(name: string): TSourceLanguage = proc getSourceLanguage*(name: string): TSourceLanguage =
for i in countup(succ(low(TSourceLanguage)), high(TSourceLanguage)): for i in countup(succ(low(TSourceLanguage)), high(TSourceLanguage)):
@ -535,3 +545,16 @@ proc getNextToken*(g: var TGeneralTokenizer, lang: TSourceLanguage) =
of langC: cNextToken(g) of langC: cNextToken(g)
of langJava: javaNextToken(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
doAssert (not keywords.isNil, "Couldn't read any keywords.txt file!")
doAssert keywords.len == nimrodKeywords.len, "No matching lengths"
for i in 0..keywords.len-1:
#echo keywords[i], " == ", nimrodKeywords[i]
doAssert keywords[i] == nimrodKeywords[i], "Unexpected keyword"

View file

@ -7,7 +7,7 @@
# distribution, for details about the copyright. # distribution, for details about the copyright.
# #
## This module implements a `reStructuredText`:idx parser. A large ## This module implements a `reStructuredText`:idx: parser. A large
## subset is implemented. Some features of the `markdown`:idx: wiki syntax are ## subset is implemented. Some features of the `markdown`:idx: wiki syntax are
## also supported. ## also supported.

View file

@ -7,7 +7,7 @@
# distribution, for details about the copyright. # distribution, for details about the copyright.
# #
## This module implements an AST for the `reStructuredText`:idx parser. ## This module implements an AST for the `reStructuredText`:idx: parser.
import strutils import strutils

View file

@ -7,7 +7,7 @@
# distribution, for details about the copyright. # distribution, for details about the copyright.
# #
## This module implements a generator of HTML/Latex from `reStructuredText`:idx. ## This module implements a generator of HTML/Latex from `reStructuredText`:idx:.
import strutils, os, hashes, strtabs, rstast, rst, highlite import strutils, os, hashes, strtabs, rstast, rst, highlite

View file

@ -167,7 +167,7 @@ proc AsyncSocket*(domain: TDomain = AF_INET, typ: TType = SOCK_STREAM,
result = newAsyncSocket() result = newAsyncSocket()
result.socket = socket(domain, typ, protocol, buffered) result.socket = socket(domain, typ, protocol, buffered)
result.proto = protocol result.proto = protocol
if result.socket == InvalidSocket: OSError() if result.socket == InvalidSocket: OSError(OSLastError())
result.socket.setBlocking(false) result.socket.setBlocking(false)
proc toAsyncSocket*(sock: TSocket, state: TInfo = SockConnected): PAsyncSocket = proc toAsyncSocket*(sock: TSocket, state: TInfo = SockConnected): PAsyncSocket =
@ -349,7 +349,7 @@ proc acceptAddr*(server: PAsyncSocket, client: var PAsyncSocket,
client.sslNeedAccept = false client.sslNeedAccept = false
client.info = SockConnected client.info = SockConnected
if c == InvalidSocket: OSError() if c == InvalidSocket: SocketError(server.socket)
c.setBlocking(false) # TODO: Needs to be tested. c.setBlocking(false) # TODO: Needs to be tested.
# deleg.open is set in ``toDelegate``. # deleg.open is set in ``toDelegate``.
@ -423,6 +423,10 @@ proc isConnecting*(s: PAsyncSocket): bool =
proc isClosed*(s: PAsyncSocket): bool = proc isClosed*(s: PAsyncSocket): bool =
## Determines whether ``s`` has been closed. ## Determines whether ``s`` has been closed.
return s.info == SockClosed return s.info == SockClosed
proc isSendDataBuffered*(s: PAsyncSocket): bool =
## Determines whether ``s`` has data waiting to be sent, i.e. whether this
## socket's sendBuffer contains data.
return s.sendBuffer.len != 0
proc setHandleWrite*(s: PAsyncSocket, proc setHandleWrite*(s: PAsyncSocket,
handleWrite: proc (s: PAsyncSocket) {.closure.}) = handleWrite: proc (s: PAsyncSocket) {.closure.}) =
@ -638,8 +642,7 @@ when isMainModule:
proc testRead(s: PAsyncSocket, no: int) = proc testRead(s: PAsyncSocket, no: int) =
echo("Reading! " & $no) echo("Reading! " & $no)
var data = "" var data = ""
if not s.readLine(data): if not s.readLine(data): return
OSError()
if data == "": if data == "":
echo("Closing connection. " & $no) echo("Closing connection. " & $no)
s.close() s.close()

View file

@ -1,7 +1,7 @@
# #
# #
# Nimrod's Runtime Library # Nimrod's Runtime Library
# (c) Copyright 2010 Andreas Rumpf # (c) Copyright 2013 Andreas Rumpf
# #
# See the file "copying.txt", included in this # See the file "copying.txt", included in this
# distribution, for details about the copyright. # distribution, for details about the copyright.
@ -35,8 +35,12 @@ type
tagAddress, ## the HTML ``address`` element tagAddress, ## the HTML ``address`` element
tagApplet, ## the deprecated HTML ``applet`` element tagApplet, ## the deprecated HTML ``applet`` element
tagArea, ## the HTML ``area`` element tagArea, ## the HTML ``area`` element
tagArticle, ## the HTML ``article`` element
tagAside, ## the HTML ``aside`` element
tagAudio, ## the HTML ``audio`` element
tagB, ## the HTML ``b`` element tagB, ## the HTML ``b`` element
tagBase, ## the HTML ``base`` element tagBase, ## the HTML ``base`` element
tagBdi, ## the HTML ``bdi`` element
tagBdo, ## the deprecated HTML ``dbo`` element tagBdo, ## the deprecated HTML ``dbo`` element
tagBasefont, ## the deprecated HTML ``basefont`` element tagBasefont, ## the deprecated HTML ``basefont`` element
tagBig, ## the HTML ``big`` element tagBig, ## the HTML ``big`` element
@ -44,22 +48,31 @@ type
tagBody, ## the HTML ``body`` element tagBody, ## the HTML ``body`` element
tagBr, ## the HTML ``br`` element tagBr, ## the HTML ``br`` element
tagButton, ## the HTML ``button`` element tagButton, ## the HTML ``button`` element
tagCanvas, ## the HTML ``canvas`` element
tagCaption, ## the HTML ``caption`` element tagCaption, ## the HTML ``caption`` element
tagCenter, ## the deprecated HTML ``center`` element tagCenter, ## the deprecated HTML ``center`` element
tagCite, ## the HTML ``cite`` element tagCite, ## the HTML ``cite`` element
tagCode, ## the HTML ``code`` element tagCode, ## the HTML ``code`` element
tagCol, ## the HTML ``col`` element tagCol, ## the HTML ``col`` element
tagColgroup, ## the HTML ``colgroup`` element tagColgroup, ## the HTML ``colgroup`` element
tagCommand, ## the HTML ``command`` element
tagDatalist, ## the HTML ``datalist`` element
tagDd, ## the HTML ``dd`` element tagDd, ## the HTML ``dd`` element
tagDel, ## the HTML ``del`` element tagDel, ## the HTML ``del`` element
tagDetails, ## the HTML ``details`` element
tagDfn, ## the HTML ``dfn`` element tagDfn, ## the HTML ``dfn`` element
tagDialog, ## the HTML ``dialog`` element
tagDiv, ## the HTML ``div`` element tagDiv, ## the HTML ``div`` element
tagDir, ## the deprecated HTLM ``dir`` element tagDir, ## the deprecated HTLM ``dir`` element
tagDl, ## the HTML ``dl`` element tagDl, ## the HTML ``dl`` element
tagDt, ## the HTML ``dt`` element tagDt, ## the HTML ``dt`` element
tagEm, ## the HTML ``em`` element tagEm, ## the HTML ``em`` element
tagEmbed, ## the HTML ``embed`` element
tagFieldset, ## the HTML ``fieldset`` element tagFieldset, ## the HTML ``fieldset`` element
tagFigcaption, ## the HTML ``figcaption`` element
tagFigure, ## the HTML ``figure`` element
tagFont, ## the deprecated HTML ``font`` element tagFont, ## the deprecated HTML ``font`` element
tagFooter, ## the HTML ``footer`` element
tagForm, ## the HTML ``form`` element tagForm, ## the HTML ``form`` element
tagFrame, ## the HTML ``frame`` element tagFrame, ## the HTML ``frame`` element
tagFrameset, ## the deprecated HTML ``frameset`` element tagFrameset, ## the deprecated HTML ``frameset`` element
@ -70,6 +83,8 @@ type
tagH5, ## the HTML ``h5`` element tagH5, ## the HTML ``h5`` element
tagH6, ## the HTML ``h6`` element tagH6, ## the HTML ``h6`` element
tagHead, ## the HTML ``head`` element tagHead, ## the HTML ``head`` element
tagHeader, ## the HTML ``header`` element
tagHgroup, ## the HTML ``hgroup`` element
tagHtml, ## the HTML ``html`` element tagHtml, ## the HTML ``html`` element
tagHr, ## the HTML ``hr`` element tagHr, ## the HTML ``hr`` element
tagI, ## the HTML ``i`` element tagI, ## the HTML ``i`` element
@ -79,13 +94,17 @@ type
tagIns, ## the HTML ``ins`` element tagIns, ## the HTML ``ins`` element
tagIsindex, ## the deprecated HTML ``isindex`` element tagIsindex, ## the deprecated HTML ``isindex`` element
tagKbd, ## the HTML ``kbd`` element tagKbd, ## the HTML ``kbd`` element
tagKeygen, ## the HTML ``keygen`` element
tagLabel, ## the HTML ``label`` element tagLabel, ## the HTML ``label`` element
tagLegend, ## the HTML ``legend`` element tagLegend, ## the HTML ``legend`` element
tagLi, ## the HTML ``li`` element tagLi, ## the HTML ``li`` element
tagLink, ## the HTML ``link`` element tagLink, ## the HTML ``link`` element
tagMap, ## the HTML ``map`` element tagMap, ## the HTML ``map`` element
tagMark, ## the HTML ``mark`` element
tagMenu, ## the deprecated HTML ``menu`` element tagMenu, ## the deprecated HTML ``menu`` element
tagMeta, ## the HTML ``meta`` element tagMeta, ## the HTML ``meta`` element
tagMeter, ## the HTML ``meter`` element
tagNav, ## the HTML ``nav`` element
tagNobr, ## the deprecated HTML ``nobr`` element tagNobr, ## the deprecated HTML ``nobr`` element
tagNoframes, ## the deprecated HTML ``noframes`` element tagNoframes, ## the deprecated HTML ``noframes`` element
tagNoscript, ## the HTML ``noscript`` element tagNoscript, ## the HTML ``noscript`` element
@ -93,20 +112,28 @@ type
tagOl, ## the HTML ``ol`` element tagOl, ## the HTML ``ol`` element
tagOptgroup, ## the HTML ``optgroup`` element tagOptgroup, ## the HTML ``optgroup`` element
tagOption, ## the HTML ``option`` element tagOption, ## the HTML ``option`` element
tagOutput, ## the HTML ``output`` element
tagP, ## the HTML ``p`` element tagP, ## the HTML ``p`` element
tagParam, ## the HTML ``param`` element tagParam, ## the HTML ``param`` element
tagPre, ## the HTML ``pre`` element tagPre, ## the HTML ``pre`` element
tagProgress, ## the HTML ``progress`` element
tagQ, ## the HTML ``q`` element tagQ, ## the HTML ``q`` element
tagRp, ## the HTML ``rp`` element
tagRt, ## the HTML ``rt`` element
tagRuby, ## the HTML ``ruby`` element
tagS, ## the deprecated HTML ``s`` element tagS, ## the deprecated HTML ``s`` element
tagSamp, ## the HTML ``samp`` element tagSamp, ## the HTML ``samp`` element
tagScript, ## the HTML ``script`` element tagScript, ## the HTML ``script`` element
tagSection, ## the HTML ``section`` element
tagSelect, ## the HTML ``select`` element tagSelect, ## the HTML ``select`` element
tagSmall, ## the HTML ``small`` element tagSmall, ## the HTML ``small`` element
tagSource, ## the HTML ``source`` element
tagSpan, ## the HTML ``span`` element tagSpan, ## the HTML ``span`` element
tagStrike, ## the deprecated HTML ``strike`` element tagStrike, ## the deprecated HTML ``strike`` element
tagStrong, ## the HTML ``strong`` element tagStrong, ## the HTML ``strong`` element
tagStyle, ## the HTML ``style`` element tagStyle, ## the HTML ``style`` element
tagSub, ## the HTML ``sub`` element tagSub, ## the HTML ``sub`` element
tagSummary, ## the HTML ``summary`` element
tagSup, ## the HTML ``sup`` element tagSup, ## the HTML ``sup`` element
tagTable, ## the HTML ``table`` element tagTable, ## the HTML ``table`` element
tagTbody, ## the HTML ``tbody`` element tagTbody, ## the HTML ``tbody`` element
@ -115,37 +142,46 @@ type
tagTfoot, ## the HTML ``tfoot`` element tagTfoot, ## the HTML ``tfoot`` element
tagTh, ## the HTML ``th`` element tagTh, ## the HTML ``th`` element
tagThead, ## the HTML ``thead`` element tagThead, ## the HTML ``thead`` element
tagTime, ## the HTML ``time`` element
tagTitle, ## the HTML ``title`` element tagTitle, ## the HTML ``title`` element
tagTr, ## the HTML ``tr`` element tagTr, ## the HTML ``tr`` element
tagTrack, ## the HTML ``track`` element
tagTt, ## the HTML ``tt`` element tagTt, ## the HTML ``tt`` element
tagU, ## the deprecated HTML ``u`` element tagU, ## the deprecated HTML ``u`` element
tagUl, ## the HTML ``ul`` element tagUl, ## the HTML ``ul`` element
tagVar ## the HTML ``var`` element tagVar, ## the HTML ``var`` element
tagVideo, ## the HTML ``video`` element
tagWbr ## the HTML ``wbr`` element
const const
tagToStr* = [ tagToStr* = [
"a", "abbr", "acronym", "address", "applet", "area", "a", "abbr", "acronym", "address", "applet", "area", "article",
"b", "base", "basefont", "bdo", "big", "blockquote", "body", "aside", "audio",
"br", "button", "caption", "center", "cite", "code", "b", "base", "basefont", "bdi", "bdo", "big", "blockquote", "body",
"col", "colgroup", "dd", "del", "dfn", "div", "br", "button", "canvas", "caption", "center", "cite", "code",
"dir", "dl", "dt", "em", "fieldset", "font", "col", "colgroup", "command",
"datalist", "dd", "del", "details", "dfn", "dialog", "div",
"dir", "dl", "dt", "em", "embed", "fieldset",
"figcaption", "figure", "font", "footer",
"form", "frame", "frameset", "h1", "h2", "h3", "form", "frame", "frameset", "h1", "h2", "h3",
"h4", "h5", "h6", "head", "html", "hr", "h4", "h5", "h6", "head", "header", "hgroup", "html", "hr",
"i", "iframe", "img", "input", "ins", "isindex", "i", "iframe", "img", "input", "ins", "isindex",
"kbd", "label", "legend", "li", "link", "map", "kbd", "keygen", "label", "legend", "li", "link", "map", "mark",
"menu", "meta", "nobr", "noframes", "noscript", "object", "ol", "menu", "meta", "meter", "nav", "nobr", "noframes", "noscript",
"optgroup", "option", "p", "param", "pre", "q", "object", "ol",
"s", "samp", "script", "select", "small", "span", "optgroup", "option", "output", "p", "param", "pre", "progress", "q",
"strike", "strong", "style", "sub", "sup", "table", "rp", "rt", "ruby", "s", "samp", "script", "section", "select", "small",
"tbody", "td", "textarea", "tfoot", "th", "thead", "source", "span", "strike", "strong", "style",
"title", "tr", "tt", "u", "ul", "var"] "sub", "summary", "sup", "table",
"tbody", "td", "textarea", "tfoot", "th", "thead", "time",
"title", "tr", "track", "tt", "u", "ul", "var", "video", "wbr"]
InlineTags* = {tagA, tagAbbr, tagAcronym, tagApplet, tagB, tagBasefont, InlineTags* = {tagA, tagAbbr, tagAcronym, tagApplet, tagB, tagBasefont,
tagBdo, tagBig, tagBr, tagButton, tagCite, tagCode, tagDel, tagDfn, tagBdo, tagBig, tagBr, tagButton, tagCite, tagCode, tagDel, tagDfn,
tagEm, tagFont, tagI, tagImg, tagIns, tagInput, tagIframe, tagKbd, tagEm, tagFont, tagI, tagImg, tagIns, tagInput, tagIframe, tagKbd,
tagLabel, tagMap, tagObject, tagQ, tagSamp, tagScript, tagSelect, tagLabel, tagMap, tagObject, tagQ, tagSamp, tagScript, tagSelect,
tagSmall, tagSpan, tagStrong, tagSub, tagSup, tagTextarea, tagTt, tagSmall, tagSpan, tagStrong, tagSub, tagSup, tagTextarea, tagTt,
tagVar, tagApplet, tagBasefont, tagFont, tagIframe, tagU, tagS, tagVar, tagApplet, tagBasefont, tagFont, tagIframe, tagU, tagS,
tagStrike} tagStrike, tagWbr}
BlockTags* = {tagAddress, tagBlockquote, tagCenter, tagDel, tagDir, tagDiv, BlockTags* = {tagAddress, tagBlockquote, tagCenter, tagDel, tagDir, tagDiv,
tagDl, tagFieldset, tagForm, tagH1, tagH2, tagH3, tagH4, tagDl, tagFieldset, tagForm, tagH1, tagH2, tagH3, tagH4,
tagH5, tagH6, tagHr, tagIns, tagIsindex, tagMenu, tagNoframes, tagNoscript, tagH5, tagH6, tagHr, tagIns, tagIsindex, tagMenu, tagNoframes, tagNoscript,
@ -153,7 +189,7 @@ const
tagMenu, tagNoframes} tagMenu, tagNoframes}
SingleTags* = {tagArea, tagBase, tagBasefont, SingleTags* = {tagArea, tagBase, tagBasefont,
tagBr, tagCol, tagFrame, tagHr, tagImg, tagIsindex, tagBr, tagCol, tagFrame, tagHr, tagImg, tagIsindex,
tagLink, tagMeta, tagParam} tagLink, tagMeta, tagParam, tagWbr}
Entities = [ Entities = [
("nbsp", 0x00A0), ("iexcl", 0x00A1), ("cent", 0x00A2), ("pound", 0x00A3), ("nbsp", 0x00A0), ("iexcl", 0x00A1), ("cent", 0x00A2), ("pound", 0x00A3),
@ -228,28 +264,149 @@ const
("loz", 0x25CA), ("spades", 0x2660), ("clubs", 0x2663), ("loz", 0x25CA), ("spades", 0x2660), ("clubs", 0x2663),
("hearts", 0x2665), ("diams", 0x2666)] ("hearts", 0x2665), ("diams", 0x2666)]
proc binaryStrSearch(x: openarray[string], y: string): int = proc allLower(s: string): bool =
## XXX put this into the library somewhere! for c in s:
var a = 0 if c < 'a' or c > 'z': return false
var b = len(x) - 1 return true
while a <= b:
var mid = (a + b) div 2 proc toHtmlTag(s: string): THtmlTag =
var c = cmp(x[mid], y) case s
if c < 0: a = mid + 1 of "a": tagA
elif c > 0: b = mid - 1 of "abbr": tagAbbr
else: return mid of "acronym": tagAcronym
result = - 1 of "address": tagAddress
of "applet": tagApplet
of "area": tagArea
of "article": tagArticle
of "aside": tagAside
of "audio": tagAudio
of "b": tagB
of "base": tagBase
of "basefont": tagBasefont
of "bdi": tagBdi
of "bdo": tagBdo
of "big": tagBig
of "blockquote": tagBlockquote
of "body": tagBody
of "br": tagBr
of "button": tagButton
of "canvas": tagCanvas
of "caption": tagCaption
of "center": tagCenter
of "cite": tagCite
of "code": tagCode
of "col": tagCol
of "colgroup": tagColgroup
of "command": tagCommand
of "datalist": tagDatalist
of "dd": tagDd
of "del": tagDel
of "details": tagDetails
of "dfn": tagDfn
of "dialog": tagDialog
of "div": tagDiv
of "dir": tagDir
of "dl": tagDl
of "dt": tagDt
of "em": tagEm
of "embed": tagEmbed
of "fieldset": tagFieldset
of "figcaption": tagFigcaption
of "figure": tagFigure
of "font": tagFont
of "footer": tagFooter
of "form": tagForm
of "frame": tagFrame
of "frameset": tagFrameset
of "h1": tagH1
of "h2": tagH2
of "h3": tagH3
of "h4": tagH4
of "h5": tagH5
of "h6": tagH6
of "head": tagHead
of "header": tagHeader
of "hgroup": tagHgroup
of "html": tagHtml
of "hr": tagHr
of "i": tagI
of "iframe": tagIframe
of "img": tagImg
of "input": tagInput
of "ins": tagIns
of "isindex": tagIsindex
of "kbd": tagKbd
of "keygen": tagKeygen
of "label": tagLabel
of "legend": tagLegend
of "li": tagLi
of "link": tagLink
of "map": tagMap
of "mark": tagMark
of "menu": tagMenu
of "meta": tagMeta
of "meter": tagMeter
of "nav": tagNav
of "nobr": tagNobr
of "noframes": tagNoframes
of "noscript": tagNoscript
of "object": tagObject
of "ol": tagOl
of "optgroup": tagOptgroup
of "option": tagOption
of "output": tagOutput
of "p": tagP
of "param": tagParam
of "pre": tagPre
of "progress": tagProgress
of "q": tagQ
of "rp": tagRp
of "rt": tagRt
of "ruby": tagRuby
of "s": tagS
of "samp": tagSamp
of "script": tagScript
of "section": tagSection
of "select": tagSelect
of "small": tagSmall
of "source": tagSource
of "span": tagSpan
of "strike": tagStrike
of "strong": tagStrong
of "style": tagStyle
of "sub": tagSub
of "summary": tagSummary
of "sup": tagSup
of "table": tagTable
of "tbody": tagTbody
of "td": tagTd
of "textarea": tagTextarea
of "tfoot": tagTfoot
of "th": tagTh
of "thead": tagThead
of "time": tagTime
of "title": tagTitle
of "tr": tagTr
of "track": tagTrack
of "tt": tagTt
of "u": tagU
of "ul": tagUl
of "var": tagVar
of "video": tagVideo
of "wbr": tagWbr
else: tagUnknown
proc htmlTag*(n: PXmlNode): THtmlTag = proc htmlTag*(n: PXmlNode): THtmlTag =
## gets `n`'s tag as a ``THtmlTag``. ## gets `n`'s tag as a ``THtmlTag``.
if n.clientData == 0: if n.clientData == 0:
n.clientData = binaryStrSearch(tagToStr, n.tag)+1 n.clientData = toHtmlTag(n.tag).ord
result = THtmlTag(n.clientData) result = THtmlTag(n.clientData)
proc htmlTag*(s: string): THtmlTag = proc htmlTag*(s: string): THtmlTag =
## converts `s` to a ``THtmlTag``. If `s` is no HTML tag, ``tagUnknown`` is ## converts `s` to a ``THtmlTag``. If `s` is no HTML tag, ``tagUnknown`` is
## returned. ## returned.
result = THtmlTag(binaryStrSearch(tagToStr, s.toLower)+1) let s = if allLower(s): s else: s.toLower
result = toHtmlTag(s)
proc entityToUtf8*(entity: string): string = proc entityToUtf8*(entity: string): string =
## converts an HTML entity name like ``&Uuml;`` to its UTF-8 equivalent. ## converts an HTML entity name like ``&Uuml;`` to its UTF-8 equivalent.
@ -267,11 +424,13 @@ proc parse(x: var TXmlParser, errors: var seq[string]): PXmlNode
proc expected(x: var TXmlParser, n: PXmlNode): string = proc expected(x: var TXmlParser, n: PXmlNode): string =
result = errorMsg(x, "</" & n.tag & "> expected") result = errorMsg(x, "</" & n.tag & "> expected")
template elemName(x: expr): expr = rawData(x)
proc untilElementEnd(x: var TXmlParser, result: PXmlNode, proc untilElementEnd(x: var TXmlParser, result: PXmlNode,
errors: var seq[string]) = errors: var seq[string]) =
# we parsed e.g. ``<br>`` and don't really expect a ``</br>``: # we parsed e.g. ``<br>`` and don't really expect a ``</br>``:
if result.htmlTag in singleTags: if result.htmlTag in singleTags:
if x.kind != xmlElementEnd or cmpIgnoreCase(x.elementName, result.tag) != 0: if x.kind != xmlElementEnd or cmpIgnoreCase(x.elemName, result.tag) != 0:
return return
while true: while true:
case x.kind case x.kind
@ -279,28 +438,29 @@ proc untilElementEnd(x: var TXmlParser, result: PXmlNode,
case result.htmlTag case result.htmlTag
of tagLi, tagP, tagDt, tagDd, tagInput, tagOption: of tagLi, tagP, tagDt, tagDd, tagInput, tagOption:
# some tags are common to have no ``</end>``, like ``<li>``: # some tags are common to have no ``</end>``, like ``<li>``:
if htmlTag(x.elementName) in {tagLi, tagP, tagDt, tagDd, tagInput, if htmlTag(x.elemName) in {tagLi, tagP, tagDt, tagDd, tagInput,
tagOption}: tagOption}:
errors.add(expected(x, result)) errors.add(expected(x, result))
break break
when false: of tagTd, tagTh, tagTfoot, tagThead:
if htmlTag(x.elementName) notin InlineTags: if htmlTag(x.elemName) in {tagTr, tagTd, tagTh, tagTfoot, tagThead}:
errors.add(expected(x, result)) errors.add(expected(x, result))
break break
of tagTr, tagTd, tagTh, tagTfoot, tagThead: of tagTr:
if htmlTag(x.elementName) in {tagTr, tagTd, tagTh, tagTfoot, tagThead}: if htmlTag(x.elemName) == tagTr:
errors.add(expected(x, result)) errors.add(expected(x, result))
break break
of tagOptgroup: of tagOptgroup:
if htmlTag(x.elementName) in {tagOption, tagOptgroup}: if htmlTag(x.elemName) in {tagOption, tagOptgroup}:
errors.add(expected(x, result)) errors.add(expected(x, result))
break break
else: nil else: nil
result.addNode(parse(x, errors)) result.addNode(parse(x, errors))
of xmlElementEnd: of xmlElementEnd:
if cmpIgnoreCase(x.elementName, result.tag) == 0: if cmpIgnoreCase(x.elemName, result.tag) == 0:
next(x) next(x)
else: else:
echo "5; expected: ", result.htmltag, " ", x.elemName
errors.add(expected(x, result)) errors.add(expected(x, result))
# do not skip it here! # do not skip it here!
break break
@ -313,10 +473,10 @@ proc untilElementEnd(x: var TXmlParser, result: PXmlNode,
proc parse(x: var TXmlParser, errors: var seq[string]): PXmlNode = proc parse(x: var TXmlParser, errors: var seq[string]): PXmlNode =
case x.kind case x.kind
of xmlComment: of xmlComment:
result = newComment(x.charData) result = newComment(x.rawData)
next(x) next(x)
of xmlCharData, xmlWhitespace: of xmlCharData, xmlWhitespace:
result = newText(x.charData) result = newText(x.rawData)
next(x) next(x)
of xmlPI, xmlSpecial: of xmlPI, xmlSpecial:
# we just ignore processing instructions for now # we just ignore processing instructions for now
@ -325,19 +485,19 @@ proc parse(x: var TXmlParser, errors: var seq[string]): PXmlNode =
errors.add(errorMsg(x)) errors.add(errorMsg(x))
next(x) next(x)
of xmlElementStart: of xmlElementStart:
result = newElement(x.elementName.toLower) result = newElement(x.elemName.toLower)
next(x) next(x)
untilElementEnd(x, result, errors) untilElementEnd(x, result, errors)
of xmlElementEnd: of xmlElementEnd:
errors.add(errorMsg(x, "unexpected ending tag: " & x.elementName)) errors.add(errorMsg(x, "unexpected ending tag: " & x.elemName))
of xmlElementOpen: of xmlElementOpen:
result = newElement(x.elementName.toLower) result = newElement(x.elemName.toLower)
next(x) next(x)
result.attrs = newStringTable() result.attrs = newStringTable()
while true: while true:
case x.kind case x.kind
of xmlAttribute: of xmlAttribute:
result.attrs[x.attrKey] = x.attrValue result.attrs[x.rawData] = x.rawData2
next(x) next(x)
of xmlElementClose: of xmlElementClose:
next(x) next(x)
@ -355,10 +515,10 @@ proc parse(x: var TXmlParser, errors: var seq[string]): PXmlNode =
errors.add(errorMsg(x, "<some_tag> expected")) errors.add(errorMsg(x, "<some_tag> expected"))
next(x) next(x)
of xmlCData: of xmlCData:
result = newCData(x.charData) result = newCData(x.rawData)
next(x) next(x)
of xmlEntity: of xmlEntity:
var u = entityToUtf8(x.entityName) var u = entityToUtf8(x.rawData)
if u.len != 0: result = newText(u) if u.len != 0: result = newText(u)
next(x) next(x)
of xmlEof: nil of xmlEof: nil
@ -372,9 +532,11 @@ proc parseHtml*(s: PStream, filename: string,
next(x) next(x)
# skip the DOCTYPE: # skip the DOCTYPE:
if x.kind == xmlSpecial: next(x) if x.kind == xmlSpecial: next(x)
result = parse(x, errors)
if x.kind != xmlEof: result = newElement("document")
errors.add(errorMsg(x, "EOF expected")) result.addNode(parse(x, errors))
#if x.kind != xmlEof:
# errors.add(errorMsg(x, "EOF expected"))
while x.kind != xmlEof: while x.kind != xmlEof:
var oldPos = x.bufpos # little hack to see if we made any progess var oldPos = x.bufpos # little hack to see if we made any progess
result.addNode(parse(x, errors)) result.addNode(parse(x, errors))
@ -382,6 +544,8 @@ proc parseHtml*(s: PStream, filename: string,
# force progress! # force progress!
next(x) next(x)
close(x) close(x)
if result.len == 1:
result = result[0]
proc parseHtml*(s: PStream): PXmlNode = proc parseHtml*(s: PStream): PXmlNode =
## parses the XTML from stream `s` and returns a ``PXmlNode``. All parsing ## parses the XTML from stream `s` and returns a ``PXmlNode``. All parsing
@ -403,19 +567,6 @@ proc loadHtml*(path: string): PXmlNode =
var errors: seq[string] = @[] var errors: seq[string] = @[]
result = loadHtml(path, errors) result = loadHtml(path, errors)
when true:
nil
else:
proc checkHtmlAux(n: PXmlNode, errors: var seq[string]) =
nil
proc checkHtmlStructure*(n: PXmlNode, errors: var seq[string]) =
## checks the HTML structure after parsing for other errors like
## a ``<h1>`` element within a ``<p>`` element.
if n == nil or n.htmlTag != tagHtml:
errors.add("<html> tag expected")
checkHtmlAux(n, errors)
when isMainModule: when isMainModule:
import os import os
@ -429,4 +580,3 @@ when isMainModule:
f.close() f.close()
else: else:
quit("cannot write test.txt") quit("cannot write test.txt")

View file

@ -47,6 +47,7 @@ proc badRequest(client: TSocket) =
send(client, "<p>Your browser sent a bad request, " & send(client, "<p>Your browser sent a bad request, " &
"such as a POST without a Content-Length.</p>" & wwwNL) "such as a POST without a Content-Length.</p>" & wwwNL)
when false:
proc cannotExec(client: TSocket) = proc cannotExec(client: TSocket) =
send(client, "HTTP/1.1 500 Internal Server Error" & wwwNL) send(client, "HTTP/1.1 500 Internal Server Error" & wwwNL)
sendTextContentType(client) sendTextContentType(client)
@ -79,11 +80,11 @@ proc unimplemented(client: TSocket) =
# ----------------- file serving --------------------------------------------- # ----------------- file serving ---------------------------------------------
when false:
proc discardHeaders(client: TSocket) = skip(client) proc discardHeaders(client: TSocket) = skip(client)
proc serveFile*(client: TSocket, filename: string) = proc serveFile*(client: TSocket, filename: string) =
## serves a file to the client. ## serves a file to the client.
when false: discardHeaders(client)
var f: TFile var f: TFile
if open(f, filename): if open(f, filename):
headers(client, filename) headers(client, filename)
@ -96,7 +97,7 @@ proc serveFile*(client: TSocket, filename: string) =
if bytesread != bytesWritten: if bytesread != bytesWritten:
dealloc(buf) dealloc(buf)
close(f) close(f)
OSError() OSError(OSLastError())
if bytesread != bufSize: break if bytesread != bufSize: break
dealloc(buf) dealloc(buf)
close(f) close(f)
@ -104,7 +105,8 @@ proc serveFile*(client: TSocket, filename: string) =
notFound(client) notFound(client)
# ------------------ CGI execution ------------------------------------------- # ------------------ CGI execution -------------------------------------------
when false:
# TODO: Fix this, or get rid of it.
type type
TRequestMethod = enum reqGet, reqPost TRequestMethod = enum reqGet, reqPost
@ -226,7 +228,7 @@ proc open*(s: var TServer, port = TPort(80)) =
## creates a new server at port `port`. If ``port == 0`` a free port is ## creates a new server at port `port`. If ``port == 0`` a free port is
## acquired that can be accessed later by the ``port`` proc. ## acquired that can be accessed later by the ``port`` proc.
s.socket = socket(AF_INET) s.socket = socket(AF_INET)
if s.socket == InvalidSocket: OSError() if s.socket == InvalidSocket: OSError(OSLastError())
bindAddr(s.socket, port) bindAddr(s.socket, port)
listen(s.socket) listen(s.socket)

View file

@ -252,7 +252,7 @@ when isMainModule:
["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) testit(x)
var test2: tuple[name: string, s: int] = ("tuple test", 56) var test2: tuple[name: string, s: uint] = ("tuple test", 56u)
testit(test2) testit(test2)
type type

View file

@ -50,11 +50,11 @@ proc open*(filename: string, mode: TFileMode = fmRead,
result.size = 0 result.size = 0
when defined(windows): when defined(windows):
template fail(msg: expr) = template fail(errCode: TOSErrorCode, msg: expr) =
rollback() rollback()
if result.fHandle != 0: discard CloseHandle(result.fHandle) if result.fHandle != 0: discard CloseHandle(result.fHandle)
if result.mapHandle != 0: discard CloseHandle(result.mapHandle) if result.mapHandle != 0: discard CloseHandle(result.mapHandle)
OSError() OSError(errCode)
# return false # return false
#raise newException(EIO, msg) #raise newException(EIO, msg)
@ -74,7 +74,7 @@ proc open*(filename: string, mode: TFileMode = fmRead,
result.fHandle = callCreateFile(CreateFileA, filename) result.fHandle = callCreateFile(CreateFileA, filename)
if result.fHandle == INVALID_HANDLE_VALUE: if result.fHandle == INVALID_HANDLE_VALUE:
fail "error opening file" fail(OSLastError(), "error opening file")
if newFileSize != -1: if newFileSize != -1:
var var
@ -83,9 +83,10 @@ proc open*(filename: string, mode: TFileMode = fmRead,
var status = SetFilePointer(result.fHandle, sizeLow, addr(sizeHigh), var status = SetFilePointer(result.fHandle, sizeLow, addr(sizeHigh),
FILE_BEGIN) FILE_BEGIN)
if (status == INVALID_SET_FILE_POINTER and GetLastError() != NO_ERROR) or let lastErr = OSLastError()
if (status == INVALID_SET_FILE_POINTER and lastErr.int32 != NO_ERROR) or
(SetEndOfFile(result.fHandle) == 0): (SetEndOfFile(result.fHandle) == 0):
fail "error setting file size" fail(lastErr, "error setting file size")
# since the strings are always 'nil', we simply always call # since the strings are always 'nil', we simply always call
# CreateFileMappingW which should be slightly faster anyway: # CreateFileMappingW which should be slightly faster anyway:
@ -95,7 +96,7 @@ proc open*(filename: string, mode: TFileMode = fmRead,
0, 0, nil) 0, 0, nil)
if result.mapHandle == 0: if result.mapHandle == 0:
fail "error creating mapping" fail(OSLastError(), "error creating mapping")
result.mem = MapViewOfFileEx( result.mem = MapViewOfFileEx(
result.mapHandle, result.mapHandle,
@ -106,22 +107,22 @@ proc open*(filename: string, mode: TFileMode = fmRead,
nil) nil)
if result.mem == nil: if result.mem == nil:
fail "error mapping view" fail(OSLastError(), "error mapping view")
var hi, low: int32 var hi, low: int32
low = GetFileSize(result.fHandle, addr(hi)) low = GetFileSize(result.fHandle, addr(hi))
if low == INVALID_FILE_SIZE: if low == INVALID_FILE_SIZE:
fail "error getting file size" fail(OSLastError(), "error getting file size")
else: else:
var fileSize = (int64(hi) shr 32) or low var fileSize = (int64(hi) shr 32) or low
if mappedSize != -1: result.size = min(fileSize, mappedSize).int if mappedSize != -1: result.size = min(fileSize, mappedSize).int
else: result.size = fileSize.int else: result.size = fileSize.int
else: else:
template fail(msg: expr) = template fail(errCode: TOSErrorCode, msg: expr) =
rollback() rollback()
if result.handle != 0: discard close(result.handle) if result.handle != 0: discard close(result.handle)
OSError() OSError(errCode)
var flags = if readonly: O_RDONLY else: O_RDWR var flags = if readonly: O_RDONLY else: O_RDWR
@ -132,11 +133,11 @@ proc open*(filename: string, mode: TFileMode = fmRead,
if result.handle == -1: if result.handle == -1:
# XXX: errno is supposed to be set here # XXX: errno is supposed to be set here
# Is there an exception that wraps it? # Is there an exception that wraps it?
fail "error opening file" fail(OSLastError(), "error opening file")
if newFileSize != -1: if newFileSize != -1:
if ftruncate(result.handle, newFileSize) == -1: if ftruncate(result.handle, newFileSize) == -1:
fail "error setting file size" fail(OSLastError(), "error setting file size")
if mappedSize != -1: if mappedSize != -1:
result.size = mappedSize result.size = mappedSize
@ -147,7 +148,7 @@ proc open*(filename: string, mode: TFileMode = fmRead,
# Why is mmap taking int anyway? # Why is mmap taking int anyway?
result.size = int(stat.st_size) result.size = int(stat.st_size)
else: else:
fail "error getting file size" fail(OSLastError(), "error getting file size")
result.mem = mmap( result.mem = mmap(
nil, nil,
@ -158,21 +159,24 @@ proc open*(filename: string, mode: TFileMode = fmRead,
offset) offset)
if result.mem == cast[pointer](MAP_FAILED): if result.mem == cast[pointer](MAP_FAILED):
fail "file mapping failed" fail(OSLastError(), "file mapping failed")
proc close*(f: var TMemFile) = proc close*(f: var TMemFile) =
## closes the memory mapped file `f`. All changes are written back to the ## closes the memory mapped file `f`. All changes are written back to the
## file system, if `f` was opened with write access. ## file system, if `f` was opened with write access.
var error = false var error = false
var lastErr: TOSErrorCode
when defined(windows): when defined(windows):
if f.fHandle != INVALID_HANDLE_VALUE: if f.fHandle != INVALID_HANDLE_VALUE:
lastErr = OSLastError()
error = UnmapViewOfFile(f.mem) == 0 error = UnmapViewOfFile(f.mem) == 0
error = (CloseHandle(f.mapHandle) == 0) or error error = (CloseHandle(f.mapHandle) == 0) or error
error = (CloseHandle(f.fHandle) == 0) or error error = (CloseHandle(f.fHandle) == 0) or error
else: else:
if f.handle != 0: if f.handle != 0:
lastErr = OSLastError()
error = munmap(f.mem, f.size) != 0 error = munmap(f.mem, f.size) != 0
error = (close(f.handle) != 0) or error error = (close(f.handle) != 0) or error
@ -185,5 +189,5 @@ proc close*(f: var TMemFile) =
else: else:
f.handle = 0 f.handle = 0
if error: OSError() if error: OSError(lastErr)

View file

@ -39,6 +39,8 @@ type
FWriteDir* = object of FWriteIO ## effect that denotes a write operation to FWriteDir* = object of FWriteIO ## effect that denotes a write operation to
## the directory structure ## the directory structure
TOSErrorCode* = distinct int32 ## Specifies an OS Error Code.
const const
doslike = defined(windows) or defined(OS2) or defined(DOS) doslike = defined(windows) or defined(OS2) or defined(DOS)
# DOS-like filesystem # DOS-like filesystem
@ -171,10 +173,13 @@ const
## The character which separates the base filename from the extension; ## The character which separates the base filename from the extension;
## for example, the '.' in ``os.nim``. ## for example, the '.' in ``os.nim``.
proc OSErrorMsg*(): string {.rtl, extern: "nos$1".} = proc OSErrorMsg*(): string {.rtl, extern: "nos$1", deprecated.} =
## Retrieves the operating system's error flag, ``errno``. ## Retrieves the operating system's error flag, ``errno``.
## On Windows ``GetLastError`` is checked before ``errno``. ## On Windows ``GetLastError`` is checked before ``errno``.
## Returns "" if no error occured. ## Returns "" if no error occured.
##
## **Deprecated since version 0.9.4**: use the other ``OSErrorMsg`` proc.
result = "" result = ""
when defined(Windows): when defined(Windows):
var err = GetLastError() var err = GetLastError()
@ -194,17 +199,89 @@ proc OSErrorMsg*(): string {.rtl, extern: "nos$1".} =
if errno != 0'i32: if errno != 0'i32:
result = $os.strerror(errno) result = $os.strerror(errno)
proc OSError*(msg: string = "") {.noinline, rtl, extern: "nos$1".} = {.push warning[deprecated]: off.}
proc OSError*(msg: string = "") {.noinline, rtl, extern: "nos$1", deprecated.} =
## raises an EOS exception with the given message ``msg``. ## raises an EOS exception with the given message ``msg``.
## If ``msg == ""``, the operating system's error flag ## If ``msg == ""``, the operating system's error flag
## (``errno``) is converted to a readable error message. On Windows ## (``errno``) is converted to a readable error message. On Windows
## ``GetLastError`` is checked before ``errno``. ## ``GetLastError`` is checked before ``errno``.
## If no error flag is set, the message ``unknown OS error`` is used. ## If no error flag is set, the message ``unknown OS error`` is used.
##
## **Deprecated since version 0.9.4**: use the other ``OSError`` proc.
if len(msg) == 0: if len(msg) == 0:
var m = OSErrorMsg() var m = OSErrorMsg()
raise newException(EOS, if m.len > 0: m else: "unknown OS error") raise newException(EOS, if m.len > 0: m else: "unknown OS error")
else: else:
raise newException(EOS, msg) raise newException(EOS, msg)
{.pop.}
proc `==`*(err1, err2: TOSErrorCode): bool {.borrow.}
proc `$`*(err: TOSErrorCode): string {.borrow.}
proc OSErrorMsg*(errorCode: TOSErrorCode): string =
## Converts an OS error code into a human readable string.
##
## The error code can be retrieved using the ``OSLastError`` proc.
##
## If conversion fails, or ``errorCode`` is ``0`` then ``""`` will be
## returned.
##
## On Windows, the ``-d:useWinAnsi`` compilation flag can be used to
## make this procedure use the non-unicode Win API calls to retrieve the
## message.
result = ""
when defined(Windows):
if errorCode != TOSErrorCode(0'i32):
when useWinUnicode:
var msgbuf: widecstring
if FormatMessageW(0x00000100 or 0x00001000 or 0x00000200,
nil, errorCode.int32, 0, addr(msgbuf), 0, nil) != 0'i32:
result = $msgbuf
if msgbuf != nil: LocalFree(cast[pointer](msgbuf))
else:
var msgbuf: cstring
if FormatMessageA(0x00000100 or 0x00001000 or 0x00000200,
nil, errorCode.int32, 0, addr(msgbuf), 0, nil) != 0'i32:
result = $msgbuf
if msgbuf != nil: LocalFree(msgbuf)
else:
if errorCode != TOSErrorCode(0'i32):
result = $os.strerror(errorCode.int32)
proc OSError*(errorCode: TOSErrorCode) =
## Raises an ``EOS`` exception. The ``errorCode`` will determine the
## message, ``OSErrorMsg`` will be used to get this message.
##
## The error code can be retrieved using the ``OSLastError`` proc.
##
## If the error code is ``0`` or an error message could not be retrieved,
## the message ``unknown OS error`` will be used.
let msg = OSErrorMsg(errorCode)
if msg == "":
raise newException(EOS, "unknown OS error")
else:
raise newException(EOS, msg)
{.push stackTrace:off.}
proc OSLastError*(): TOSErrorCode =
## Retrieves the last operating system error code.
##
## This procedure is useful in the event when an OS call fails. In that case
## this procedure will return the error code describing the reason why the
## OS call failed. The ``OSErrorMsg`` procedure can then be used to convert
## this code into a string.
##
## **Warning**:
## The behaviour of this procedure varies between Windows and POSIX systems.
## On Windows some OS calls can reset the error code to ``0`` causing this
## procedure to return ``0``. It is therefore advised to call this procedure
## immediately after an OS call fails. On POSIX systems this is not a problem.
when defined(windows):
result = TOSErrorCode(GetLastError())
else:
result = TOSErrorCode(errno)
{.pop.}
proc UnixToNativePath*(path: string): string {. proc UnixToNativePath*(path: string): string {.
noSideEffect, rtl, extern: "nos$1".} = noSideEffect, rtl, extern: "nos$1".} =
@ -311,12 +388,12 @@ proc getLastModificationTime*(file: string): TTime {.rtl, extern: "nos$1".} =
## Returns the `file`'s last modification time. ## Returns the `file`'s last modification time.
when defined(posix): when defined(posix):
var res: TStat var res: TStat
if stat(file, res) < 0'i32: OSError() if stat(file, res) < 0'i32: OSError(OSLastError())
return res.st_mtime return res.st_mtime
else: else:
var f: TWIN32_Find_Data var f: TWIN32_Find_Data
var h = findfirstFile(file, f) var h = findfirstFile(file, f)
if h == -1'i32: OSError() if h == -1'i32: OSError(OSLastError())
result = winTimeToUnixTime(rdFileTime(f.ftLastWriteTime)) result = winTimeToUnixTime(rdFileTime(f.ftLastWriteTime))
findclose(h) findclose(h)
@ -324,12 +401,12 @@ proc getLastAccessTime*(file: string): TTime {.rtl, extern: "nos$1".} =
## Returns the `file`'s last read or write access time. ## Returns the `file`'s last read or write access time.
when defined(posix): when defined(posix):
var res: TStat var res: TStat
if stat(file, res) < 0'i32: OSError() if stat(file, res) < 0'i32: OSError(OSLastError())
return res.st_atime return res.st_atime
else: else:
var f: TWIN32_Find_Data var f: TWIN32_Find_Data
var h = findfirstFile(file, f) var h = findfirstFile(file, f)
if h == -1'i32: OSError() if h == -1'i32: OSError(OSLastError())
result = winTimeToUnixTime(rdFileTime(f.ftLastAccessTime)) result = winTimeToUnixTime(rdFileTime(f.ftLastAccessTime))
findclose(h) findclose(h)
@ -337,12 +414,12 @@ proc getCreationTime*(file: string): TTime {.rtl, extern: "nos$1".} =
## Returns the `file`'s creation time. ## Returns the `file`'s creation time.
when defined(posix): when defined(posix):
var res: TStat var res: TStat
if stat(file, res) < 0'i32: OSError() if stat(file, res) < 0'i32: OSError(OSLastError())
return res.st_ctime return res.st_ctime
else: else:
var f: TWIN32_Find_Data var f: TWIN32_Find_Data
var h = findfirstFile(file, f) var h = findfirstFile(file, f)
if h == -1'i32: OSError() if h == -1'i32: OSError(OSLastError())
result = winTimeToUnixTime(rdFileTime(f.ftCreationTime)) result = winTimeToUnixTime(rdFileTime(f.ftCreationTime))
findclose(h) findclose(h)
@ -358,30 +435,30 @@ proc getCurrentDir*(): string {.rtl, extern: "nos$1", tags: [].} =
when useWinUnicode: when useWinUnicode:
var res = newWideCString("", bufsize) var res = newWideCString("", bufsize)
var L = GetCurrentDirectoryW(bufsize, res) var L = GetCurrentDirectoryW(bufsize, res)
if L == 0'i32: OSError() if L == 0'i32: OSError(OSLastError())
result = res$L result = res$L
else: else:
result = newString(bufsize) result = newString(bufsize)
var L = GetCurrentDirectoryA(bufsize, result) var L = GetCurrentDirectoryA(bufsize, result)
if L == 0'i32: OSError() if L == 0'i32: OSError(OSLastError())
setLen(result, L) setLen(result, L)
else: else:
result = newString(bufsize) result = newString(bufsize)
if getcwd(result, bufsize) != nil: if getcwd(result, bufsize) != nil:
setlen(result, c_strlen(result)) setlen(result, c_strlen(result))
else: else:
OSError() OSError(OSLastError())
proc setCurrentDir*(newDir: string) {.inline, tags: [].} = proc setCurrentDir*(newDir: string) {.inline, tags: [].} =
## Sets the `current working directory`:idx:; `EOS` is raised if ## Sets the `current working directory`:idx:; `EOS` is raised if
## `newDir` cannot been set. ## `newDir` cannot been set.
when defined(Windows): when defined(Windows):
when useWinUnicode: when useWinUnicode:
if SetCurrentDirectoryW(newWideCString(newDir)) == 0'i32: OSError() if SetCurrentDirectoryW(newWideCString(newDir)) == 0'i32: OSError(OSLastError())
else: else:
if SetCurrentDirectoryA(newDir) == 0'i32: OSError() if SetCurrentDirectoryA(newDir) == 0'i32: OSError(OSLastError())
else: else:
if chdir(newDir) != 0'i32: OSError() if chdir(newDir) != 0'i32: OSError(OSLastError())
proc JoinPath*(head, tail: string): string {. proc JoinPath*(head, tail: string): string {.
noSideEffect, rtl, extern: "nos$1".} = noSideEffect, rtl, extern: "nos$1".} =
@ -571,23 +648,23 @@ proc expandFilename*(filename: string): string {.rtl, extern: "nos$1",
var res = newWideCString("", bufsize div 2) var res = newWideCString("", bufsize div 2)
var L = GetFullPathNameW(newWideCString(filename), bufsize, res, unused) var L = GetFullPathNameW(newWideCString(filename), bufsize, res, unused)
if L <= 0'i32 or L >= bufsize: if L <= 0'i32 or L >= bufsize:
OSError() OSError(OSLastError())
result = res$L result = res$L
else: else:
var unused: cstring var unused: cstring
result = newString(bufsize) result = newString(bufsize)
var L = GetFullPathNameA(filename, bufsize, result, unused) var L = GetFullPathNameA(filename, bufsize, result, unused)
if L <= 0'i32 or L >= bufsize: OSError() if L <= 0'i32 or L >= bufsize: OSError(OSLastError())
setLen(result, L) setLen(result, L)
elif defined(macosx) or defined(bsd): elif defined(macosx) or defined(bsd):
# On Mac OS X 10.5, realpath does not allocate the buffer on its own # On Mac OS X 10.5, realpath does not allocate the buffer on its own
var pathBuffer: cstring = newString(pathMax) var pathBuffer: cstring = newString(pathMax)
var resultBuffer = realpath(filename, pathBuffer) var resultBuffer = realpath(filename, pathBuffer)
if resultBuffer == nil: OSError() if resultBuffer == nil: OSError(OSLastError())
result = $resultBuffer result = $resultBuffer
else: else:
var res = realpath(filename, nil) var res = realpath(filename, nil)
if res == nil: OSError() if res == nil: OSError(OSLastError())
result = $res result = $res
c_free(res) c_free(res)
@ -677,6 +754,7 @@ proc sameFile*(path1, path2: string): bool {.rtl, extern: "nos$1",
var f1 = OpenHandle(path1) var f1 = OpenHandle(path1)
var f2 = OpenHandle(path2) var f2 = OpenHandle(path2)
var lastErr: TOSErrorCode
if f1 != INVALID_HANDLE_VALUE and f2 != INVALID_HANDLE_VALUE: if f1 != INVALID_HANDLE_VALUE and f2 != INVALID_HANDLE_VALUE:
var fi1, fi2: TBY_HANDLE_FILE_INFORMATION var fi1, fi2: TBY_HANDLE_FILE_INFORMATION
@ -685,17 +763,21 @@ proc sameFile*(path1, path2: string): bool {.rtl, extern: "nos$1",
result = fi1.dwVolumeSerialNumber == fi2.dwVolumeSerialNumber and result = fi1.dwVolumeSerialNumber == fi2.dwVolumeSerialNumber and
fi1.nFileIndexHigh == fi2.nFileIndexHigh and fi1.nFileIndexHigh == fi2.nFileIndexHigh and
fi1.nFileIndexLow == fi2.nFileIndexLow fi1.nFileIndexLow == fi2.nFileIndexLow
else: success = false else:
else: success = false lastErr = OSLastError()
success = false
else:
lastErr = OSLastError()
success = false
discard CloseHandle(f1) discard CloseHandle(f1)
discard CloseHandle(f2) discard CloseHandle(f2)
if not success: OSError() if not success: OSError(lastErr)
else: else:
var a, b: TStat var a, b: TStat
if stat(path1, a) < 0'i32 or stat(path2, b) < 0'i32: if stat(path1, a) < 0'i32 or stat(path2, b) < 0'i32:
OSError() OSError(OSLastError())
else: else:
result = a.st_dev == b.st_dev and a.st_ino == b.st_ino result = a.st_dev == b.st_dev and a.st_ino == b.st_ino
@ -738,17 +820,17 @@ proc copyFile*(source, dest: string) {.rtl, extern: "nos$1",
when useWinUnicode: when useWinUnicode:
let s = newWideCString(source) let s = newWideCString(source)
let d = newWideCString(dest) let d = newWideCString(dest)
if CopyFileW(s, d, 0'i32) == 0'i32: OSError() if CopyFileW(s, d, 0'i32) == 0'i32: OSError(OSLastError())
else: else:
if CopyFileA(source, dest, 0'i32) == 0'i32: OSError() if CopyFileA(source, dest, 0'i32) == 0'i32: OSError(OSLastError())
else: else:
# generic version of copyFile which works for any platform: # generic version of copyFile which works for any platform:
const bufSize = 8000 # better for memory manager const bufSize = 8000 # better for memory manager
var d, s: TFile var d, s: TFile
if not open(s, source): OSError() if not open(s, source): OSError(OSLastError())
if not open(d, dest, fmWrite): if not open(d, dest, fmWrite):
close(s) close(s)
OSError() OSError(OSLastError())
var buf = alloc(bufsize) var buf = alloc(bufsize)
while True: while True:
var bytesread = readBuffer(s, buf, bufsize) var bytesread = readBuffer(s, buf, bufsize)
@ -758,7 +840,7 @@ proc copyFile*(source, dest: string) {.rtl, extern: "nos$1",
dealloc(buf) dealloc(buf)
close(s) close(s)
close(d) close(d)
OSError() OSError(OSLastError())
if bytesread != bufSize: break if bytesread != bufSize: break
dealloc(buf) dealloc(buf)
close(s) close(s)
@ -767,7 +849,8 @@ proc copyFile*(source, dest: string) {.rtl, extern: "nos$1",
proc moveFile*(source, dest: string) {.rtl, extern: "nos$1", proc moveFile*(source, dest: string) {.rtl, extern: "nos$1",
tags: [FReadIO, FWriteIO].} = tags: [FReadIO, FWriteIO].} =
## Moves a file from `source` to `dest`. If this fails, `EOS` is raised. ## Moves a file from `source` to `dest`. If this fails, `EOS` is raised.
if crename(source, dest) != 0'i32: OSError() if crename(source, dest) != 0'i32:
raise newException(EOS, $strerror(errno))
when not defined(ENOENT): when not defined(ENOENT):
var ENOENT {.importc, header: "<errno.h>".}: cint var ENOENT {.importc, header: "<errno.h>".}: cint
@ -775,7 +858,8 @@ when not defined(ENOENT):
proc removeFile*(file: string) {.rtl, extern: "nos$1", tags: [FWriteDir].} = proc removeFile*(file: string) {.rtl, extern: "nos$1", tags: [FWriteDir].} =
## Removes the `file`. If this fails, `EOS` is raised. This does not fail ## Removes the `file`. If this fails, `EOS` is raised. This does not fail
## if the file never existed in the first place. ## if the file never existed in the first place.
if cremove(file) != 0'i32 and errno != ENOENT: OSError() if cremove(file) != 0'i32 and errno != ENOENT:
raise newException(EOS, $strerror(errno))
proc execShellCmd*(command: string): int {.rtl, extern: "nos$1", proc execShellCmd*(command: string): int {.rtl, extern: "nos$1",
tags: [FExecIO].} = tags: [FExecIO].} =
@ -907,14 +991,14 @@ proc putEnv*(key, val: string) {.tags: [FWriteEnv].} =
indx = high(environment) indx = high(environment)
when defined(unix): when defined(unix):
if cputenv(environment[indx]) != 0'i32: if cputenv(environment[indx]) != 0'i32:
OSError() OSError(OSLastError())
else: else:
when useWinUnicode: when useWinUnicode:
var k = newWideCString(key) var k = newWideCString(key)
var v = newWideCString(val) var v = newWideCString(val)
if SetEnvironmentVariableW(k, v) == 0'i32: OSError() if SetEnvironmentVariableW(k, v) == 0'i32: OSError(OSLastError())
else: else:
if SetEnvironmentVariableA(key, val) == 0'i32: OSError() if SetEnvironmentVariableA(key, val) == 0'i32: OSError(OSLastError())
iterator envPairs*(): tuple[key, value: TaintedString] {.tags: [FReadEnv].} = iterator envPairs*(): tuple[key, value: TaintedString] {.tags: [FReadEnv].} =
## Iterate over all `environments variables`:idx:. In the first component ## Iterate over all `environments variables`:idx:. In the first component
@ -1044,10 +1128,12 @@ proc rawRemoveDir(dir: string) =
wrapUnary(res, RemoveDirectoryW, dir) wrapUnary(res, RemoveDirectoryW, dir)
else: else:
var res = RemoveDirectoryA(dir) var res = RemoveDirectoryA(dir)
if res == 0'i32 and GetLastError() != 3'i32 and let lastError = OSLastError()
GetLastError() != 18'i32: OSError() if res == 0'i32 and lastError.int32 != 3'i32 and
lastError.int32 != 18'i32 and lastError.int32 != 2'i32:
OSError(lastError)
else: else:
if rmdir(dir) != 0'i32 and errno != ENOENT: OSError() if rmdir(dir) != 0'i32 and errno != ENOENT: OSError(OSLastError())
proc removeDir*(dir: string) {.rtl, extern: "nos$1", tags: [ proc removeDir*(dir: string) {.rtl, extern: "nos$1", tags: [
FWriteDir, FReadDir].} = FWriteDir, FReadDir].} =
@ -1065,14 +1151,14 @@ proc removeDir*(dir: string) {.rtl, extern: "nos$1", tags: [
proc rawCreateDir(dir: string) = proc rawCreateDir(dir: string) =
when defined(unix): when defined(unix):
if mkdir(dir, 0o711) != 0'i32 and errno != EEXIST: if mkdir(dir, 0o711) != 0'i32 and errno != EEXIST:
OSError() OSError(OSLastError())
else: else:
when useWinUnicode: when useWinUnicode:
wrapUnary(res, CreateDirectoryW, dir) wrapUnary(res, CreateDirectoryW, dir)
else: else:
var res = CreateDirectoryA(dir) var res = CreateDirectoryA(dir)
if res == 0'i32 and GetLastError() != 183'i32: if res == 0'i32 and GetLastError() != 183'i32:
OSError() OSError(OSLastError())
proc createDir*(dir: string) {.rtl, extern: "nos$1", tags: [FWriteDir].} = proc createDir*(dir: string) {.rtl, extern: "nos$1", tags: [FWriteDir].} =
## Creates the `directory`:idx: `dir`. ## Creates the `directory`:idx: `dir`.
@ -1213,7 +1299,7 @@ proc getFilePermissions*(filename: string): set[TFilePermission] {.
## permission is available in any case. ## permission is available in any case.
when defined(posix): when defined(posix):
var a: TStat var a: TStat
if stat(filename, a) < 0'i32: OSError() if stat(filename, a) < 0'i32: OSError(OSLastError())
result = {} result = {}
if (a.st_mode and S_IRUSR) != 0'i32: result.incl(fpUserRead) if (a.st_mode and S_IRUSR) != 0'i32: result.incl(fpUserRead)
if (a.st_mode and S_IWUSR) != 0'i32: result.incl(fpUserWrite) if (a.st_mode and S_IWUSR) != 0'i32: result.incl(fpUserWrite)
@ -1231,7 +1317,7 @@ proc getFilePermissions*(filename: string): set[TFilePermission] {.
wrapUnary(res, GetFileAttributesW, filename) wrapUnary(res, GetFileAttributesW, filename)
else: else:
var res = GetFileAttributesA(filename) var res = GetFileAttributesA(filename)
if res == -1'i32: OSError() if res == -1'i32: OSError(OSLastError())
if (res and FILE_ATTRIBUTE_READONLY) != 0'i32: if (res and FILE_ATTRIBUTE_READONLY) != 0'i32:
result = {fpUserExec, fpUserRead, fpGroupExec, fpGroupRead, result = {fpUserExec, fpUserRead, fpGroupExec, fpGroupRead,
fpOthersExec, fpOthersRead} fpOthersExec, fpOthersRead}
@ -1257,13 +1343,13 @@ proc setFilePermissions*(filename: string, permissions: set[TFilePermission]) {.
if fpOthersWrite in permissions: p = p or S_IWOTH if fpOthersWrite in permissions: p = p or S_IWOTH
if fpOthersExec in permissions: p = p or S_IXOTH if fpOthersExec in permissions: p = p or S_IXOTH
if chmod(filename, p) != 0: OSError() if chmod(filename, p) != 0: OSError(OSLastError())
else: else:
when useWinUnicode: when useWinUnicode:
wrapUnary(res, GetFileAttributesW, filename) wrapUnary(res, GetFileAttributesW, filename)
else: else:
var res = GetFileAttributesA(filename) var res = GetFileAttributesA(filename)
if res == -1'i32: OSError() if res == -1'i32: OSError(OSLastError())
if fpUserWrite in permissions: if fpUserWrite in permissions:
res = res and not FILE_ATTRIBUTE_READONLY res = res and not FILE_ATTRIBUTE_READONLY
else: else:
@ -1272,7 +1358,7 @@ proc setFilePermissions*(filename: string, permissions: set[TFilePermission]) {.
wrapBinary(res2, SetFileAttributesW, filename, res) wrapBinary(res2, SetFileAttributesW, filename, res)
else: else:
var res2 = SetFileAttributesA(filename, res) var res2 = SetFileAttributesA(filename, res)
if res2 == - 1'i32: OSError() if res2 == - 1'i32: OSError(OSLastError())
proc inclFilePermissions*(filename: string, proc inclFilePermissions*(filename: string,
permissions: set[TFilePermission]) {. permissions: set[TFilePermission]) {.
@ -1368,6 +1454,9 @@ when defined(macosx):
proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [FReadIO].} = proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [FReadIO].} =
## Returns the filename of the application's executable. ## Returns the filename of the application's executable.
##
## This procedure will resolve symlinks.
##
## **Note**: This does not work reliably on BSD. ## **Note**: This does not work reliably on BSD.
# Linux: /proc/<pid>/exe # Linux: /proc/<pid>/exe
@ -1397,6 +1486,8 @@ proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [FReadIO].} =
result = newString(int(size)) result = newString(int(size))
if getExecPath2(result, size): if getExecPath2(result, size):
result = "" # error! result = "" # error!
if result.len > 0:
result = result.expandFilename
else: else:
# little heuristic that may work on other POSIX-like systems: # little heuristic that may work on other POSIX-like systems:
result = string(getEnv("_")) result = string(getEnv("_"))
@ -1443,7 +1534,7 @@ proc getFileSize*(file: string): biggestInt {.rtl, extern: "nos$1",
when defined(windows): when defined(windows):
var a: TWin32FindData var a: TWin32FindData
var resA = findfirstFile(file, a) var resA = findfirstFile(file, a)
if resA == -1: OSError() if resA == -1: OSError(OSLastError())
result = rdFileSize(a) result = rdFileSize(a)
findclose(resA) findclose(resA)
else: else:
@ -1451,7 +1542,7 @@ proc getFileSize*(file: string): biggestInt {.rtl, extern: "nos$1",
if open(f, file): if open(f, file):
result = getFileSize(f) result = getFileSize(f)
close(f) close(f)
else: OSError() else: OSError(OSLastError())
proc findExe*(exe: string): string {.tags: [FReadDir, FReadEnv].} = proc findExe*(exe: string): string {.tags: [FReadDir, FReadEnv].} =
## Searches for `exe` in the current working directory and then ## Searches for `exe` in the current working directory and then

View file

@ -264,7 +264,7 @@ when defined(Windows) and not defined(useNimRtl):
# TRUE and zero bytes returned (EOF). # TRUE and zero bytes returned (EOF).
# TRUE and n (>0) bytes returned (good data). # TRUE and n (>0) bytes returned (good data).
# FALSE and bytes returned undefined (system error). # FALSE and bytes returned undefined (system error).
if a == 0 and br != 0: OSError() if a == 0 and br != 0: OSError(OSLastError())
s.atTheEnd = br < bufLen s.atTheEnd = br < bufLen
result = br result = br
@ -272,7 +272,7 @@ when defined(Windows) and not defined(useNimRtl):
var s = PFileHandleStream(s) var s = PFileHandleStream(s)
var bytesWritten: int32 var bytesWritten: int32
var a = winlean.writeFile(s.handle, buffer, bufLen.cint, bytesWritten, nil) var a = winlean.writeFile(s.handle, buffer, bufLen.cint, bytesWritten, nil)
if a == 0: OSError() if a == 0: OSError(OSLastError())
proc newFileHandleStream(handle: THandle): PFileHandleStream = proc newFileHandleStream(handle: THandle): PFileHandleStream =
new(result) new(result)
@ -313,7 +313,7 @@ when defined(Windows) and not defined(useNimRtl):
piInheritablePipe.lpSecurityDescriptor = nil piInheritablePipe.lpSecurityDescriptor = nil
piInheritablePipe.Binherithandle = 1 piInheritablePipe.Binherithandle = 1
if CreatePipe(Rdhandle, Wrhandle, piInheritablePipe, 1024) == 0'i32: if CreatePipe(Rdhandle, Wrhandle, piInheritablePipe, 1024) == 0'i32:
OSError() OSError(OSLastError())
proc fileClose(h: THandle) {.inline.} = proc fileClose(h: THandle) {.inline.} =
if h > 4: discard CloseHandle(h) if h > 4: discard CloseHandle(h)
@ -370,6 +370,7 @@ when defined(Windows) and not defined(useNimRtl):
else: else:
success = winlean.CreateProcessA(nil, success = winlean.CreateProcessA(nil,
cmdl, nil, nil, 1, NORMAL_PRIORITY_CLASS, e, wd, SI, ProcInfo) cmdl, nil, nil, 1, NORMAL_PRIORITY_CLASS, e, wd, SI, ProcInfo)
let lastError = OSLastError()
if poParentStreams notin options: if poParentStreams notin options:
FileClose(si.hStdInput) FileClose(si.hStdInput)
@ -379,7 +380,7 @@ when defined(Windows) and not defined(useNimRtl):
if e != nil: dealloc(e) if e != nil: dealloc(e)
dealloc(cmdl) dealloc(cmdl)
if success == 0: OSError() if success == 0: OSError(lastError)
# Close the handle now so anyone waiting is woken: # Close the handle now so anyone waiting is woken:
discard closeHandle(procInfo.hThread) discard closeHandle(procInfo.hThread)
result.FProcessHandle = procInfo.hProcess result.FProcessHandle = procInfo.hProcess
@ -450,7 +451,7 @@ when defined(Windows) and not defined(useNimRtl):
var res = winlean.CreateProcessA(nil, command, nil, nil, 0, var res = winlean.CreateProcessA(nil, command, nil, nil, 0,
NORMAL_PRIORITY_CLASS, nil, nil, SI, ProcInfo) NORMAL_PRIORITY_CLASS, nil, nil, SI, ProcInfo)
if res == 0: if res == 0:
OSError() OSError(OSLastError())
else: else:
Process = ProcInfo.hProcess Process = ProcInfo.hProcess
discard CloseHandle(ProcInfo.hThread) discard CloseHandle(ProcInfo.hThread)
@ -473,7 +474,7 @@ when defined(Windows) and not defined(useNimRtl):
of WAIT_TIMEOUT: of WAIT_TIMEOUT:
return 0 return 0
of WAIT_FAILED: of WAIT_FAILED:
OSError() OSError(OSLastError())
else: else:
var i = ret - WAIT_OBJECT_0 var i = ret - WAIT_OBJECT_0
readfds.del(i) readfds.del(i)
@ -531,7 +532,7 @@ elif not defined(useNimRtl):
if poParentStreams notin options: if poParentStreams notin options:
if pipe(p_stdin) != 0'i32 or pipe(p_stdout) != 0'i32 or if pipe(p_stdin) != 0'i32 or pipe(p_stdout) != 0'i32 or
pipe(p_stderr) != 0'i32: pipe(p_stderr) != 0'i32:
OSError() OSError(OSLastError())
var pid: TPid var pid: TPid
when defined(posix_spawn) and not defined(useFork): when defined(posix_spawn) and not defined(useFork):
@ -539,7 +540,7 @@ elif not defined(useNimRtl):
var fops: Tposix_spawn_file_actions var fops: Tposix_spawn_file_actions
template chck(e: expr) = template chck(e: expr) =
if e != 0'i32: OSError() if e != 0'i32: OSError(OSLastError())
chck posix_spawn_file_actions_init(fops) chck posix_spawn_file_actions_init(fops)
chck posix_spawnattr_init(attr) chck posix_spawnattr_init(attr)
@ -584,20 +585,20 @@ elif not defined(useNimRtl):
else: else:
Pid = fork() Pid = fork()
if Pid < 0: OSError() if Pid < 0: OSError(OSLastError())
if pid == 0: if pid == 0:
## child process: ## child process:
if poParentStreams notin options: if poParentStreams notin options:
discard close(p_stdin[writeIdx]) discard close(p_stdin[writeIdx])
if dup2(p_stdin[readIdx], readIdx) < 0: OSError() if dup2(p_stdin[readIdx], readIdx) < 0: OSError(OSLastError())
discard close(p_stdout[readIdx]) discard close(p_stdout[readIdx])
if dup2(p_stdout[writeIdx], writeIdx) < 0: OSError() if dup2(p_stdout[writeIdx], writeIdx) < 0: OSError(OSLastError())
discard close(p_stderr[readIdx]) discard close(p_stderr[readIdx])
if poStdErrToStdOut in options: if poStdErrToStdOut in options:
if dup2(p_stdout[writeIdx], 2) < 0: OSError() if dup2(p_stdout[writeIdx], 2) < 0: OSError(OSLastError())
else: else:
if dup2(p_stderr[writeIdx], 2) < 0: OSError() if dup2(p_stderr[writeIdx], 2) < 0: OSError(OSLastError())
# Create a new process group # Create a new process group
if setpgid(0, 0) == -1: quit("setpgid call failed: " & $strerror(errno)) if setpgid(0, 0) == -1: quit("setpgid call failed: " & $strerror(errno))
@ -652,10 +653,10 @@ elif not defined(useNimRtl):
discard close(p.errorHandle) discard close(p.errorHandle)
proc suspend(p: PProcess) = proc suspend(p: PProcess) =
if kill(-p.id, SIGSTOP) != 0'i32: OSError() if kill(-p.id, SIGSTOP) != 0'i32: OSError(OSLastError())
proc resume(p: PProcess) = proc resume(p: PProcess) =
if kill(-p.id, SIGCONT) != 0'i32: OSError() if kill(-p.id, SIGCONT) != 0'i32: OSError(OSLastError())
proc running(p: PProcess): bool = proc running(p: PProcess): bool =
var ret = waitPid(p.id, p.exitCode, WNOHANG) var ret = waitPid(p.id, p.exitCode, WNOHANG)
@ -665,8 +666,8 @@ elif not defined(useNimRtl):
proc terminate(p: PProcess) = proc terminate(p: PProcess) =
if kill(-p.id, SIGTERM) == 0'i32: if kill(-p.id, SIGTERM) == 0'i32:
if p.running(): if p.running():
if kill(-p.id, SIGKILL) != 0'i32: OSError() if kill(-p.id, SIGKILL) != 0'i32: OSError(OSLastError())
else: OSError() else: OSError(OSLastError())
proc waitForExit(p: PProcess, timeout: int = -1): int = proc waitForExit(p: PProcess, timeout: int = -1): int =
#if waitPid(p.id, p.exitCode, 0) == int(p.id): #if waitPid(p.id, p.exitCode, 0) == int(p.id):
@ -676,7 +677,7 @@ elif not defined(useNimRtl):
if p.exitCode != -3: return p.exitCode if p.exitCode != -3: return p.exitCode
if waitPid(p.id, p.exitCode, 0) < 0: if waitPid(p.id, p.exitCode, 0) < 0:
p.exitCode = -3 p.exitCode = -3
OSError() OSError(OSLastError())
result = int(p.exitCode) shr 8 result = int(p.exitCode) shr 8
proc peekExitCode(p: PProcess): int = proc peekExitCode(p: PProcess): int =
@ -690,7 +691,7 @@ elif not defined(useNimRtl):
proc createStream(stream: var PStream, handle: var TFileHandle, proc createStream(stream: var PStream, handle: var TFileHandle,
fileMode: TFileMode) = fileMode: TFileMode) =
var f: TFile var f: TFile
if not open(f, handle, fileMode): OSError() if not open(f, handle, fileMode): OSError(OSLastError())
stream = newFileStream(f) stream = newFileStream(f)
proc inputStream(p: PProcess): PStream = proc inputStream(p: PProcess): PStream =

View file

@ -67,7 +67,8 @@ type
# implementation # implementation
const const
SymChars: TCharSet = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\x80'..'\xFF', '.'} SymChars: TCharSet = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\x80'..'\xFF', '.',
'/', '\\'}
proc rawGetTok(c: var TCfgParser, tok: var TToken) proc rawGetTok(c: var TCfgParser, tok: var TToken)
@ -149,7 +150,7 @@ proc getEscapedChar(c: var TCfgParser, tok: var TToken) =
of 't', 'T': of 't', 'T':
add(tok.literal, '\t') add(tok.literal, '\t')
Inc(c.bufpos) Inc(c.bufpos)
of '\'', '\"': of '\'', '"':
add(tok.literal, c.buf[c.bufpos]) add(tok.literal, c.buf[c.bufpos])
Inc(c.bufpos) Inc(c.bufpos)
of '\\': of '\\':
@ -178,7 +179,7 @@ proc getString(c: var TCfgParser, tok: var TToken, rawMode: bool) =
var pos = c.bufPos + 1 # skip " var pos = c.bufPos + 1 # skip "
var buf = c.buf # put `buf` in a register var buf = c.buf # put `buf` in a register
tok.kind = tkSymbol tok.kind = tkSymbol
if (buf[pos] == '\"') and (buf[pos + 1] == '\"'): if (buf[pos] == '"') and (buf[pos + 1] == '"'):
# long string literal: # long string literal:
inc(pos, 2) # skip "" inc(pos, 2) # skip ""
# skip leading newline: # skip leading newline:
@ -186,9 +187,9 @@ proc getString(c: var TCfgParser, tok: var TToken, rawMode: bool) =
buf = c.buf buf = c.buf
while true: while true:
case buf[pos] case buf[pos]
of '\"': of '"':
if (buf[pos + 1] == '\"') and (buf[pos + 2] == '\"'): break if (buf[pos + 1] == '"') and (buf[pos + 2] == '"'): break
add(tok.literal, '\"') add(tok.literal, '"')
Inc(pos) Inc(pos)
of '\c', '\L': of '\c', '\L':
pos = HandleCRLF(c, pos) pos = HandleCRLF(c, pos)
@ -205,7 +206,7 @@ proc getString(c: var TCfgParser, tok: var TToken, rawMode: bool) =
# ordinary string literal # ordinary string literal
while true: while true:
var ch = buf[pos] var ch = buf[pos]
if ch == '\"': if ch == '"':
inc(pos) # skip '"' inc(pos) # skip '"'
break break
if ch in {'\c', '\L', lexbase.EndOfFile}: if ch in {'\c', '\L', lexbase.EndOfFile}:
@ -278,7 +279,7 @@ proc rawGetTok(c: var TCfgParser, tok: var TToken) =
tok.kind = tkBracketRi tok.kind = tkBracketRi
Inc(c.bufpos) Inc(c.bufpos)
tok.literal = "]" tok.literal = "]"
of '\"': of '"':
getString(c, tok, false) getString(c, tok, false)
of lexbase.EndOfFile: of lexbase.EndOfFile:
tok.kind = tkEof tok.kind = tkEof
@ -351,7 +352,7 @@ proc next*(c: var TCfgParser): TCfgEvent {.rtl, extern: "npc$1".} =
rawGetTok(c, c.tok) rawGetTok(c, c.tok)
else: else:
result.kind = cfgError result.kind = cfgError
result.msg = errorStr(c, "\']\' expected, but found: " & c.tok.literal) result.msg = errorStr(c, "']' expected, but found: " & c.tok.literal)
of tkInvalid, tkEquals, tkColon, tkBracketRi: of tkInvalid, tkEquals, tkColon, tkBracketRi:
result.kind = cfgError result.kind = cfgError
result.msg = errorStr(c, "invalid token: " & c.tok.literal) result.msg = errorStr(c, "invalid token: " & c.tok.literal)

View file

@ -131,6 +131,10 @@ proc close*(my: var TXmlParser) {.inline.} =
## closes the parser `my` and its associated input stream. ## closes the parser `my` and its associated input stream.
lexbase.close(my) lexbase.close(my)
proc kind*(my: TXmlParser): TXmlEventKind {.inline.} =
## returns the current event type for the XML parser
return my.kind
proc charData*(my: TXmlParser): string {.inline.} = proc charData*(my: TXmlParser): string {.inline.} =
## returns the character data for the events: ``xmlCharData``, ## returns the character data for the events: ``xmlCharData``,
## ``xmlWhitespace``, ``xmlComment``, ``xmlCData``, ``xmlSpecial`` ## ``xmlWhitespace``, ``xmlComment``, ``xmlCData``, ``xmlSpecial``
@ -138,10 +142,6 @@ proc charData*(my: TXmlParser): string {.inline.} =
xmlSpecial}) xmlSpecial})
return my.a return my.a
proc kind*(my: TXmlParser): TXmlEventKind {.inline.} =
## returns the current event type for the XML parser
return my.kind
proc elementName*(my: TXmlParser): string {.inline.} = proc elementName*(my: TXmlParser): string {.inline.} =
## returns the element name for the events: ``xmlElementStart``, ## returns the element name for the events: ``xmlElementStart``,
## ``xmlElementEnd``, ``xmlElementOpen`` ## ``xmlElementEnd``, ``xmlElementOpen``
@ -173,6 +173,16 @@ proc PIRest*(my: TXmlParser): string {.inline.} =
assert(my.kind == xmlPI) assert(my.kind == xmlPI)
return my.b return my.b
proc rawData*(my: TXmlParser): string {.inline.} =
## returns the underlying 'data' string by reference.
## This is only used for speed hacks.
shallowCopy(result, my.a)
proc rawData2*(my: TXmlParser): string {.inline.} =
## returns the underlying second 'data' string by reference.
## This is only used for speed hacks.
shallowCopy(result, my.b)
proc getColumn*(my: TXmlParser): int {.inline.} = proc getColumn*(my: TXmlParser): int {.inline.} =
## get the current column the parser has arrived at. ## get the current column the parser has arrived at.
result = getColNumber(my, my.bufPos) result = getColNumber(my, my.bufPos)
@ -439,6 +449,7 @@ proc parseTag(my: var TXmlParser) =
if my.buf[my.bufpos] == '/' and my.buf[my.bufpos+1] == '>': if my.buf[my.bufpos] == '/' and my.buf[my.bufpos+1] == '>':
inc(my.bufpos, 2) inc(my.bufpos, 2)
my.state = stateEmptyElementTag my.state = stateEmptyElementTag
my.c = nil
elif my.buf[my.bufpos] == '>': elif my.buf[my.bufpos] == '>':
inc(my.bufpos) inc(my.bufpos)
else: else:

View file

@ -82,7 +82,7 @@ type
of pkChar, pkGreedyRepChar: ch: char of pkChar, pkGreedyRepChar: ch: char
of pkCharChoice, pkGreedyRepSet: charChoice: ref set[char] of pkCharChoice, pkGreedyRepSet: charChoice: ref set[char]
of pkNonTerminal: nt: PNonTerminal of pkNonTerminal: nt: PNonTerminal
of pkBackRef..pkBackRefIgnoreStyle: index: range[1..MaxSubpatterns] of pkBackRef..pkBackRefIgnoreStyle: index: range[0..MaxSubpatterns]
else: sons: seq[TNode] else: sons: seq[TNode]
PNonTerminal* = ref TNonTerminal PNonTerminal* = ref TNonTerminal
@ -870,7 +870,6 @@ template `=~`*(s: string, pattern: TPeg): bool =
bind maxSubpatterns bind maxSubpatterns
when not definedInScope(matches): when not definedInScope(matches):
var matches {.inject.}: array[0..maxSubpatterns-1, string] var matches {.inject.}: array[0..maxSubpatterns-1, string]
{.warning: "injected 'matches' might be affected by new scoping rules in 0.9.4".}
match(s, pattern, matches) match(s, pattern, matches)
# ------------------------- more string handling ------------------------------ # ------------------------- more string handling ------------------------------

View file

@ -36,7 +36,7 @@ proc open*(host = "localhost", port = 6379.TPort): TRedis =
## Opens a connection to the redis server. ## Opens a connection to the redis server.
result.socket = socket(buffered = false) result.socket = socket(buffered = false)
if result.socket == InvalidSocket: if result.socket == InvalidSocket:
OSError() OSError(OSLastError())
result.socket.connect(host, port) result.socket.connect(host, port)
proc raiseInvalidReply(expected, got: char) = proc raiseInvalidReply(expected, got: char) =
@ -50,7 +50,7 @@ proc raiseNoOK(status: string) =
proc parseStatus(r: TRedis): TRedisStatus = proc parseStatus(r: TRedis): TRedisStatus =
var line = "" var line = ""
if r.socket.recvLine(line): r.socket.readLine(line)
if line == "": if line == "":
raise newException(ERedis, "Server closed connection prematurely") raise newException(ERedis, "Server closed connection prematurely")
@ -60,12 +60,10 @@ proc parseStatus(r: TRedis): TRedisStatus =
raiseInvalidReply('+', line[0]) raiseInvalidReply('+', line[0])
return line.substr(1) # Strip '+' return line.substr(1) # Strip '+'
else:
OSError()
proc parseInteger(r: TRedis): TRedisInteger = proc parseInteger(r: TRedis): TRedisInteger =
var line = "" var line = ""
if r.socket.recvLine(line): r.socket.readLine(line)
if line == "": if line == "":
raise newException(ERedis, "Server closed connection prematurely") raise newException(ERedis, "Server closed connection prematurely")
@ -77,7 +75,6 @@ proc parseInteger(r: TRedis): TRedisInteger =
# Strip ':' # Strip ':'
if parseBiggestInt(line, result, 1) == 0: if parseBiggestInt(line, result, 1) == 0:
raise newException(EInvalidReply, "Unable to parse integer.") raise newException(EInvalidReply, "Unable to parse integer.")
else: OSError()
proc recv(sock: TSocket, size: int): TaintedString = proc recv(sock: TSocket, size: int): TaintedString =
result = newString(size).TaintedString result = newString(size).TaintedString
@ -86,8 +83,7 @@ proc recv(sock: TSocket, size: int): TaintedString =
proc parseBulk(r: TRedis, allowMBNil = False): TRedisString = proc parseBulk(r: TRedis, allowMBNil = False): TRedisString =
var line = "" var line = ""
if not r.socket.recvLine(line.TaintedString): r.socket.readLine(line.TaintedString)
raise newException(EInvalidReply, "recvLine failed")
# Error. # Error.
if line[0] == '-': if line[0] == '-':
@ -110,8 +106,7 @@ proc parseBulk(r: TRedis, allowMBNil = False): TRedisString =
proc parseMultiBulk(r: TRedis): TRedisList = proc parseMultiBulk(r: TRedis): TRedisList =
var line = TaintedString"" var line = TaintedString""
if not r.socket.recvLine(line): r.socket.readLine(line)
raise newException(EInvalidReply, "recvLine failed")
if line.string[0] != '*': if line.string[0] != '*':
raiseInvalidReply('*', line.string[0]) raiseInvalidReply('*', line.string[0])
@ -848,10 +843,8 @@ proc shutdown*(r: TRedis) =
## Synchronously save the dataset to disk and then shut down the server ## Synchronously save the dataset to disk and then shut down the server
r.sendCommand("SHUTDOWN") r.sendCommand("SHUTDOWN")
var s = "".TaintedString var s = "".TaintedString
if r.socket.recvLine(s): r.socket.readLine(s)
if s.string.len != 0: raise newException(ERedis, s.string) if s.string.len != 0: raise newException(ERedis, s.string)
else:
OSError()
proc slaveof*(r: TRedis, host: string, port: string) = proc slaveof*(r: TRedis, host: string, port: string) =
## Make the server a slave of another instance, or promote it as master ## Make the server a slave of another instance, or promote it as master

View file

@ -175,6 +175,15 @@ proc recvBufferAsync(client: PAsyncClient, L: int): TReadLineResult =
if ret == L: if ret == L:
return ReadFullLine return ReadFullLine
proc checkCloseSocket(client: PAsyncClient) =
if not client.c.isClosed:
if client.c.isSendDataBuffered:
client.c.setHandleWrite do (s: PAsyncSocket):
if not s.isClosed and not s.isSendDataBuffered:
s.close()
s.delHandleWrite()
else: client.c.close()
proc handleClientRead(client: PAsyncClient, s: PAsyncScgiState) = proc handleClientRead(client: PAsyncClient, s: PAsyncScgiState) =
case client.mode case client.mode
of ClientReadChar: of ClientReadChar:
@ -206,7 +215,7 @@ proc handleClientRead(client: PAsyncClient, s: PAsyncScgiState) =
client.mode = ClientReadContent client.mode = ClientReadContent
else: else:
s.handleRequest(client.c, client.input, client.headers) s.handleRequest(client.c, client.input, client.headers)
if not client.c.isClosed: client.c.close() checkCloseSocket(client)
of ReadPartialLine, ReadDisconnected, ReadNone: return of ReadPartialLine, ReadDisconnected, ReadNone: return
of ClientReadContent: of ClientReadContent:
let L = parseInt(client.headers["CONTENT_LENGTH"])-client.input.len let L = parseInt(client.headers["CONTENT_LENGTH"])-client.input.len
@ -215,11 +224,11 @@ proc handleClientRead(client: PAsyncClient, s: PAsyncScgiState) =
case ret case ret
of ReadFullLine: of ReadFullLine:
s.handleRequest(client.c, client.input, client.headers) s.handleRequest(client.c, client.input, client.headers)
if not client.c.isClosed: client.c.close() checkCloseSocket(client)
of ReadPartialLine, ReadDisconnected, ReadNone: return of ReadPartialLine, ReadDisconnected, ReadNone: return
else: else:
s.handleRequest(client.c, client.input, client.headers) s.handleRequest(client.c, client.input, client.headers)
if not client.c.isClosed: client.c.close() checkCloseSocket(client)
proc handleAccept(sock: PAsyncSocket, s: PAsyncScgiState) = proc handleAccept(sock: PAsyncSocket, s: PAsyncScgiState) =
var client: PAsyncSocket var client: PAsyncSocket

View file

@ -231,7 +231,7 @@ when defined(ssl):
if err == 0: if err == 0:
raise newException(ESSL, "No error reported.") raise newException(ESSL, "No error reported.")
if err == -1: if err == -1:
OSError() OSError(OSLastError())
var errStr = ErrErrorString(err, nil) var errStr = ErrErrorString(err, nil)
raise newException(ESSL, $errStr) raise newException(ESSL, $errStr)
@ -347,24 +347,23 @@ proc SocketError*(socket: TSocket, err: int = -1, async = false) =
else: SSLError("Unknown Error") else: SSLError("Unknown Error")
if err == -1 and not (when defined(ssl): socket.isSSL else: false): if err == -1 and not (when defined(ssl): socket.isSSL else: false):
let lastError = OSLastError()
if async: if async:
when defined(windows): when defined(windows):
# TODO: Test on Windows if lastError.int32 == WSAEWOULDBLOCK:
var err = WSAGetLastError()
if err == WSAEWOULDBLOCK:
return return
else: OSError() else: OSError(lastError)
else: else:
if errno == EAGAIN or errno == EWOULDBLOCK: if lastError.int32 == EAGAIN or lastError.int32 == EWOULDBLOCK:
return return
else: OSError() else: OSError(lastError)
else: OSError() else: OSError(lastError)
proc listen*(socket: TSocket, backlog = SOMAXCONN) {.tags: [FReadIO].} = proc listen*(socket: TSocket, backlog = SOMAXCONN) {.tags: [FReadIO].} =
## Marks ``socket`` as accepting connections. ## Marks ``socket`` as accepting connections.
## ``Backlog`` specifies the maximum length of the ## ``Backlog`` specifies the maximum length of the
## queue of pending connections. ## queue of pending connections.
if listen(socket.fd, cint(backlog)) < 0'i32: OSError() if listen(socket.fd, cint(backlog)) < 0'i32: OSError(OSLastError())
proc invalidIp4(s: string) {.noreturn, noinline.} = proc invalidIp4(s: string) {.noreturn, noinline.} =
raise newException(EInvalidValue, "invalid ip4 address: " & s) raise newException(EInvalidValue, "invalid ip4 address: " & s)
@ -403,7 +402,7 @@ template gaiNim(a, p, h, list: expr): stmt =
var gaiResult = getAddrInfo(a, $p, addr(h), list) var gaiResult = getAddrInfo(a, $p, addr(h), list)
if gaiResult != 0'i32: if gaiResult != 0'i32:
when defined(windows): when defined(windows):
OSError() OSError(OSLastError())
else: else:
OSError($gai_strerror(gaiResult)) OSError($gai_strerror(gaiResult))
@ -423,7 +422,7 @@ proc bindAddr*(socket: TSocket, port = TPort(0), address = "") {.
name.sin_addr.s_addr = sockets.htonl(INADDR_ANY) name.sin_addr.s_addr = sockets.htonl(INADDR_ANY)
if bindSocket(socket.fd, cast[ptr TSockAddr](addr(name)), if bindSocket(socket.fd, cast[ptr TSockAddr](addr(name)),
sizeof(name).TSockLen) < 0'i32: sizeof(name).TSockLen) < 0'i32:
OSError() OSError(OSLastError())
else: else:
var hints: TAddrInfo var hints: TAddrInfo
var aiList: ptr TAddrInfo = nil var aiList: ptr TAddrInfo = nil
@ -432,21 +431,7 @@ proc bindAddr*(socket: TSocket, port = TPort(0), address = "") {.
hints.ai_protocol = toInt(IPPROTO_TCP) hints.ai_protocol = toInt(IPPROTO_TCP)
gaiNim(address, port, hints, aiList) gaiNim(address, port, hints, aiList)
if bindSocket(socket.fd, aiList.ai_addr, aiList.ai_addrLen.TSockLen) < 0'i32: if bindSocket(socket.fd, aiList.ai_addr, aiList.ai_addrLen.TSockLen) < 0'i32:
OSError() OSError(OSLastError())
when false:
proc bindAddr*(socket: TSocket, port = TPort(0)) =
## binds a port number to a socket.
var name: Tsockaddr_in
when defined(Windows):
name.sin_family = int16(ord(AF_INET))
else:
name.sin_family = posix.AF_INET
name.sin_port = sockets.htons(int16(port))
name.sin_addr.s_addr = sockets.htonl(INADDR_ANY)
if bindSocket(cint(socket), cast[ptr TSockAddr](addr(name)),
sizeof(name).TSockLen) < 0'i32:
OSError()
proc getSockName*(socket: TSocket): TPort = proc getSockName*(socket: TSocket): TPort =
## returns the socket's associated port number. ## returns the socket's associated port number.
@ -460,7 +445,7 @@ proc getSockName*(socket: TSocket): TPort =
var namelen = sizeof(name).TSockLen var namelen = sizeof(name).TSockLen
if getsockname(socket.fd, cast[ptr TSockAddr](addr(name)), if getsockname(socket.fd, cast[ptr TSockAddr](addr(name)),
addr(namelen)) == -1'i32: addr(namelen)) == -1'i32:
OSError() OSError(OSLastError())
result = TPort(sockets.ntohs(name.sin_port)) result = TPort(sockets.ntohs(name.sin_port))
template acceptAddrPlain(noClientRet, successRet: expr, template acceptAddrPlain(noClientRet, successRet: expr,
@ -472,26 +457,25 @@ template acceptAddrPlain(noClientRet, successRet: expr,
addr(addrLen)) addr(addrLen))
if sock < 0: if sock < 0:
# TODO: Test on Windows. let err = OSLastError()
when defined(windows): when defined(windows):
var err = WSAGetLastError() if err.int32 == WSAEINPROGRESS:
if err == WSAEINPROGRESS:
client = InvalidSocket client = InvalidSocket
address = "" address = ""
when noClientRet.int == -1: when noClientRet.int == -1:
return return
else: else:
return noClientRet return noClientRet
else: OSError() else: OSError(err)
else: else:
if errno == EAGAIN or errno == EWOULDBLOCK: if err.int32 == EAGAIN or err.int32 == EWOULDBLOCK:
client = InvalidSocket client = InvalidSocket
address = "" address = ""
when noClientRet.int == -1: when noClientRet.int == -1:
return return
else: else:
return noClientRet return noClientRet
else: OSError() else: OSError(err)
else: else:
client.fd = sock client.fd = sock
client.isBuffered = server.isBuffered client.isBuffered = server.isBuffered
@ -644,7 +628,7 @@ proc getServByName*(name, proto: string): TServent {.tags: [FReadIO].} =
var s = winlean.getservbyname(name, proto) var s = winlean.getservbyname(name, proto)
else: else:
var s = posix.getservbyname(name, proto) var s = posix.getservbyname(name, proto)
if s == nil: OSError() if s == nil: OSError(OSLastError())
result.name = $s.s_name result.name = $s.s_name
result.aliases = cstringArrayToSeq(s.s_aliases) result.aliases = cstringArrayToSeq(s.s_aliases)
result.port = TPort(s.s_port) result.port = TPort(s.s_port)
@ -656,7 +640,7 @@ proc getServByPort*(port: TPort, proto: string): TServent {.tags: [FReadIO].} =
var s = winlean.getservbyport(ze(int16(port)).cint, proto) var s = winlean.getservbyport(ze(int16(port)).cint, proto)
else: else:
var s = posix.getservbyport(ze(int16(port)).cint, proto) var s = posix.getservbyport(ze(int16(port)).cint, proto)
if s == nil: OSError() if s == nil: OSError(OSLastError())
result.name = $s.s_name result.name = $s.s_name
result.aliases = cstringArrayToSeq(s.s_aliases) result.aliases = cstringArrayToSeq(s.s_aliases)
result.port = TPort(s.s_port) result.port = TPort(s.s_port)
@ -670,7 +654,7 @@ proc getHostByAddr*(ip: string): THostEnt {.tags: [FReadIO].} =
when defined(windows): when defined(windows):
var s = winlean.gethostbyaddr(addr(myaddr), sizeof(myaddr).cuint, var s = winlean.gethostbyaddr(addr(myaddr), sizeof(myaddr).cuint,
cint(sockets.AF_INET)) cint(sockets.AF_INET))
if s == nil: OSError() if s == nil: OSError(OSLastError())
else: else:
var s = posix.gethostbyaddr(addr(myaddr), sizeof(myaddr).TSockLen, var s = posix.gethostbyaddr(addr(myaddr), sizeof(myaddr).TSockLen,
cint(posix.AF_INET)) cint(posix.AF_INET))
@ -687,7 +671,7 @@ proc getHostByAddr*(ip: string): THostEnt {.tags: [FReadIO].} =
elif s.h_addrtype == posix.AF_INET6: elif s.h_addrtype == posix.AF_INET6:
result.addrType = AF_INET6 result.addrType = AF_INET6
else: else:
OSError("unknown h_addrtype") raise newException(EOS, "unknown h_addrtype")
result.addrList = cstringArrayToSeq(s.h_addr_list) result.addrList = cstringArrayToSeq(s.h_addr_list)
result.length = int(s.h_length) result.length = int(s.h_length)
@ -697,7 +681,7 @@ proc getHostByName*(name: string): THostEnt {.tags: [FReadIO].} =
var s = winlean.gethostbyname(name) var s = winlean.gethostbyname(name)
else: else:
var s = posix.gethostbyname(name) var s = posix.gethostbyname(name)
if s == nil: OSError() if s == nil: OSError(OSLastError())
result.name = $s.h_name result.name = $s.h_name
result.aliases = cstringArrayToSeq(s.h_aliases) result.aliases = cstringArrayToSeq(s.h_aliases)
when defined(windows): when defined(windows):
@ -708,7 +692,7 @@ proc getHostByName*(name: string): THostEnt {.tags: [FReadIO].} =
elif s.h_addrtype == posix.AF_INET6: elif s.h_addrtype == posix.AF_INET6:
result.addrType = AF_INET6 result.addrType = AF_INET6
else: else:
OSError("unknown h_addrtype") raise newException(EOS, "unknown h_addrtype")
result.addrList = cstringArrayToSeq(s.h_addr_list) result.addrList = cstringArrayToSeq(s.h_addr_list)
result.length = int(s.h_length) result.length = int(s.h_length)
@ -719,7 +703,7 @@ proc getSockOptInt*(socket: TSocket, level, optname: int): int {.
var size = sizeof(res).TSockLen var size = sizeof(res).TSockLen
if getsockopt(socket.fd, cint(level), cint(optname), if getsockopt(socket.fd, cint(level), cint(optname),
addr(res), addr(size)) < 0'i32: addr(res), addr(size)) < 0'i32:
OSError() OSError(OSLastError())
result = int(res) result = int(res)
proc setSockOptInt*(socket: TSocket, level, optname, optval: int) {. proc setSockOptInt*(socket: TSocket, level, optname, optval: int) {.
@ -728,7 +712,7 @@ proc setSockOptInt*(socket: TSocket, level, optname, optval: int) {.
var value = cint(optval) var value = cint(optval)
if setsockopt(socket.fd, cint(level), cint(optname), addr(value), if setsockopt(socket.fd, cint(level), cint(optname), addr(value),
sizeof(value).TSockLen) < 0'i32: sizeof(value).TSockLen) < 0'i32:
OSError() OSError(OSLastError())
proc connect*(socket: TSocket, address: string, port = TPort(0), proc connect*(socket: TSocket, address: string, port = TPort(0),
af: TDomain = AF_INET) {.tags: [FReadIO].} = af: TDomain = AF_INET) {.tags: [FReadIO].} =
@ -746,15 +730,17 @@ proc connect*(socket: TSocket, address: string, port = TPort(0),
gaiNim(address, port, hints, aiList) gaiNim(address, port, hints, aiList)
# try all possibilities: # try all possibilities:
var success = false var success = false
var lastError: TOSErrorCode
var it = aiList var it = aiList
while it != nil: while it != nil:
if connect(socket.fd, it.ai_addr, it.ai_addrlen.TSockLen) == 0'i32: if connect(socket.fd, it.ai_addr, it.ai_addrlen.TSockLen) == 0'i32:
success = true success = true
break break
else: lastError = OSLastError()
it = it.ai_next it = it.ai_next
freeaddrinfo(aiList) freeaddrinfo(aiList)
if not success: OSError() if not success: OSError(lastError)
when defined(ssl): when defined(ssl):
if socket.isSSL: if socket.isSSL:
@ -807,6 +793,7 @@ proc connectAsync*(socket: TSocket, name: string, port = TPort(0),
gaiNim(name, port, hints, aiList) gaiNim(name, port, hints, aiList)
# try all possibilities: # try all possibilities:
var success = false var success = false
var lastError: TOSErrorCode
var it = aiList var it = aiList
while it != nil: while it != nil:
var ret = connect(socket.fd, it.ai_addr, it.ai_addrlen.TSockLen) var ret = connect(socket.fd, it.ai_addr, it.ai_addrlen.TSockLen)
@ -814,22 +801,21 @@ proc connectAsync*(socket: TSocket, name: string, port = TPort(0),
success = true success = true
break break
else: else:
# TODO: Test on Windows. lastError = OSLastError()
when defined(windows): when defined(windows):
var err = WSAGetLastError()
# Windows EINTR doesn't behave same as POSIX. # Windows EINTR doesn't behave same as POSIX.
if err == WSAEWOULDBLOCK: if lastError.int32 == WSAEWOULDBLOCK:
success = true success = true
break break
else: else:
if errno == EINTR or errno == EINPROGRESS: if lastError.int32 == EINTR or lastError.int32 == EINPROGRESS:
success = true success = true
break break
it = it.ai_next it = it.ai_next
freeaddrinfo(aiList) freeaddrinfo(aiList)
if not success: OSError() if not success: OSError(lastError)
when defined(ssl): when defined(ssl):
if socket.isSSL: if socket.isSSL:
socket.sslNoHandshake = true socket.sslNoHandshake = true
@ -1110,7 +1096,7 @@ proc waitFor(socket: TSocket, waited: var float, timeout, size: int,
var s = @[socket] var s = @[socket]
var startTime = epochTime() var startTime = epochTime()
let selRet = select(s, timeout - int(waited * 1000.0)) let selRet = select(s, timeout - int(waited * 1000.0))
if selRet < 0: OSError() if selRet < 0: OSError(OSLastError())
if selRet != 1: if selRet != 1:
raise newException(ETimeout, "Call to '" & funcName & "' timed out.") raise newException(ETimeout, "Call to '" & funcName & "' timed out.")
waited += (epochTime() - startTime) waited += (epochTime() - startTime)
@ -1259,14 +1245,14 @@ proc readLine*(socket: TSocket, line: var TaintedString, timeout = -1) {.
var c: char var c: char
discard waitFor(socket, waited, timeout, 1, "readLine") discard waitFor(socket, waited, timeout, 1, "readLine")
var n = recv(socket, addr(c), 1) var n = recv(socket, addr(c), 1)
if n < 0: OSError() if n < 0: OSError(OSLastError())
elif n == 0: return elif n == 0: return
if c == '\r': if c == '\r':
discard waitFor(socket, waited, timeout, 1, "readLine") discard waitFor(socket, waited, timeout, 1, "readLine")
n = peekChar(socket, c) n = peekChar(socket, c)
if n > 0 and c == '\L': if n > 0 and c == '\L':
discard recv(socket, addr(c), 1) discard recv(socket, addr(c), 1)
elif n <= 0: OSError() elif n <= 0: OSError(OSLastError())
addNlIfEmpty() addNlIfEmpty()
return return
elif c == '\L': elif c == '\L':
@ -1326,6 +1312,7 @@ proc readLineAsync*(socket: TSocket,
while true: while true:
var c: char var c: char
var n = recv(socket, addr(c), 1) var n = recv(socket, addr(c), 1)
#echo(n)
if n < 0: if n < 0:
if line.len == 0: errorOrNone else: return ReadPartialLine if line.len == 0: errorOrNone else: return ReadPartialLine
elif n == 0: elif n == 0:
@ -1352,7 +1339,7 @@ proc recv*(socket: TSocket): TaintedString {.tags: [FReadIO], deprecated.} =
var pos = 0 var pos = 0
while true: while true:
var bytesRead = recv(socket, addr(string(result)[pos]), bufSize-1) var bytesRead = recv(socket, addr(string(result)[pos]), bufSize-1)
if bytesRead == -1: OSError() if bytesRead == -1: OSError(OSLastError())
setLen(result.string, pos + bytesRead) setLen(result.string, pos + bytesRead)
if bytesRead != bufSize-1: break if bytesRead != bufSize-1: break
# increase capacity: # increase capacity:
@ -1364,7 +1351,7 @@ proc recv*(socket: TSocket): TaintedString {.tags: [FReadIO], deprecated.} =
while true: while true:
var bytesRead = recv(socket, cstring(buf), bufSize-1) var bytesRead = recv(socket, cstring(buf), bufSize-1)
# Error # Error
if bytesRead == -1: OSError() if bytesRead == -1: OSError(OSLastError())
buf[bytesRead] = '\0' # might not be necessary buf[bytesRead] = '\0' # might not be necessary
setLen(buf, bytesRead) setLen(buf, bytesRead)
@ -1421,16 +1408,15 @@ proc recvAsync*(socket: TSocket, s: var TaintedString): bool {.
else: SSLError("Unknown Error") else: SSLError("Unknown Error")
if bytesRead == -1 and not (when defined(ssl): socket.isSSL else: false): if bytesRead == -1 and not (when defined(ssl): socket.isSSL else: false):
let err = OSLastError()
when defined(windows): when defined(windows):
# TODO: Test on Windows if err.int32 == WSAEWOULDBLOCK:
var err = WSAGetLastError()
if err == WSAEWOULDBLOCK:
return False return False
else: OSError() else: OSError(err)
else: else:
if errno == EAGAIN or errno == EWOULDBLOCK: if err.int32 == EAGAIN or err.int32 == EWOULDBLOCK:
return False return False
else: OSError() else: OSError(err)
setLen(s.string, pos + bytesRead) setLen(s.string, pos + bytesRead)
if bytesRead != bufSize-1: break if bytesRead != bufSize-1: break
@ -1475,16 +1461,15 @@ proc recvFromAsync*(socket: TSocket, data: var String, length: int,
result = true result = true
var callRes = recvFrom(socket, data, length, address, port, flags) var callRes = recvFrom(socket, data, length, address, port, flags)
if callRes < 0: if callRes < 0:
let err = OSLastError()
when defined(windows): when defined(windows):
# TODO: Test on Windows if err.int32 == WSAEWOULDBLOCK:
var err = WSAGetLastError()
if err == WSAEWOULDBLOCK:
return False return False
else: OSError() else: OSError(err)
else: else:
if errno == EAGAIN or errno == EWOULDBLOCK: if err.int32 == EAGAIN or err.int32 == EWOULDBLOCK:
return False return False
else: OSError() else: OSError(err)
proc skip*(socket: TSocket) {.tags: [FReadIO], deprecated.} = proc skip*(socket: TSocket) {.tags: [FReadIO], deprecated.} =
## skips all the data that is pending for the socket ## skips all the data that is pending for the socket
@ -1531,7 +1516,7 @@ proc send*(socket: TSocket, data: string) {.tags: [FWriteIO].} =
if socket.isSSL: if socket.isSSL:
SSLError() SSLError()
OSError() OSError(OSLastError())
proc sendAsync*(socket: TSocket, data: string): int {.tags: [FWriteIO].} = proc sendAsync*(socket: TSocket, data: string): int {.tags: [FWriteIO].} =
## sends data to a non-blocking socket. ## sends data to a non-blocking socket.
@ -1561,16 +1546,15 @@ proc sendAsync*(socket: TSocket, data: string): int {.tags: [FWriteIO].} =
else: else:
return return
if result == -1: if result == -1:
let err = OSLastError()
when defined(windows): when defined(windows):
var err = WSAGetLastError() if err.int32 == WSAEINPROGRESS:
# TODO: Test on windows.
if err == WSAEINPROGRESS:
return 0 return 0
else: OSError() else: OSError(err)
else: else:
if errno == EAGAIN or errno == EWOULDBLOCK: if err.int32 == EAGAIN or err.int32 == EWOULDBLOCK:
return 0 return 0
else: OSError() else: OSError(err)
proc trySend*(socket: TSocket, data: string): bool {.tags: [FWriteIO].} = proc trySend*(socket: TSocket, data: string): bool {.tags: [FWriteIO].} =
@ -1626,15 +1610,15 @@ proc setBlocking(s: TSocket, blocking: bool) =
when defined(Windows): when defined(Windows):
var mode = clong(ord(not blocking)) # 1 for non-blocking, 0 for blocking var mode = clong(ord(not blocking)) # 1 for non-blocking, 0 for blocking
if ioctlsocket(TWinSocket(s.fd), FIONBIO, addr(mode)) == -1: if ioctlsocket(TWinSocket(s.fd), FIONBIO, addr(mode)) == -1:
OSError() OSError(OSLastError())
else: # BSD sockets else: # BSD sockets
var x: int = fcntl(s.fd, F_GETFL, 0) var x: int = fcntl(s.fd, F_GETFL, 0)
if x == -1: if x == -1:
OSError() OSError(OSLastError())
else: else:
var mode = if blocking: x and not O_NONBLOCK else: x or O_NONBLOCK var mode = if blocking: x and not O_NONBLOCK else: x or O_NONBLOCK
if fcntl(s.fd, F_SETFL, mode) == -1: if fcntl(s.fd, F_SETFL, mode) == -1:
OSError() OSError(OSLastError())
s.nonblocking = not blocking s.nonblocking = not blocking
proc connect*(socket: TSocket, address: string, port = TPort(0), timeout: int, proc connect*(socket: TSocket, address: string, port = TPort(0), timeout: int,
@ -1665,6 +1649,6 @@ proc getFD*(socket: TSocket): cint = return socket.fd
when defined(Windows): when defined(Windows):
var wsa: TWSADATA var wsa: TWSADATA
if WSAStartup(0x0101'i16, wsa) != 0: OSError() if WSAStartup(0x0101'i16, addr wsa) != 0: OSError(OSLastError())

View file

@ -27,11 +27,11 @@ when defined(windows):
var hTemp = GetStdHandle(STD_OUTPUT_HANDLE) var hTemp = GetStdHandle(STD_OUTPUT_HANDLE)
if DuplicateHandle(GetCurrentProcess(), hTemp, GetCurrentProcess(), if DuplicateHandle(GetCurrentProcess(), hTemp, GetCurrentProcess(),
addr(conHandle), 0, 1, DUPLICATE_SAME_ACCESS) == 0: addr(conHandle), 0, 1, DUPLICATE_SAME_ACCESS) == 0:
OSError() OSError(OSLastError())
proc getCursorPos(): tuple [x,y: int] = proc getCursorPos(): tuple [x,y: int] =
var c: TCONSOLE_SCREEN_BUFFER_INFO var c: TCONSOLE_SCREEN_BUFFER_INFO
if GetConsoleScreenBufferInfo(conHandle, addr(c)) == 0: OSError() if GetConsoleScreenBufferInfo(conHandle, addr(c)) == 0: OSError(OSLastError())
return (int(c.dwCursorPosition.x), int(c.dwCursorPosition.y)) return (int(c.dwCursorPosition.x), int(c.dwCursorPosition.y))
proc getAttributes(): int16 = proc getAttributes(): int16 =
@ -39,8 +39,6 @@ when defined(windows):
# workaround Windows bugs: try several times # workaround Windows bugs: try several times
if GetConsoleScreenBufferInfo(conHandle, addr(c)) != 0: if GetConsoleScreenBufferInfo(conHandle, addr(c)) != 0:
return c.wAttributes return c.wAttributes
else:
OSError()
return 0x70'i16 # ERROR: return white background, black text return 0x70'i16 # ERROR: return white background, black text
var var
@ -53,7 +51,7 @@ proc setCursorPos*(x, y: int) =
var c: TCoord var c: TCoord
c.x = int16(x) c.x = int16(x)
c.y = int16(y) c.y = int16(y)
if SetConsoleCursorPosition(conHandle, c) == 0: OSError() if SetConsoleCursorPosition(conHandle, c) == 0: OSError(OSLastError())
else: else:
stdout.write("\e[" & $y & ';' & $x & 'f') stdout.write("\e[" & $y & ';' & $x & 'f')
@ -63,10 +61,10 @@ proc setCursorXPos*(x: int) =
when defined(windows): when defined(windows):
var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO
var hStdout = conHandle var hStdout = conHandle
if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError() if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError(OSLastError())
var origin = scrbuf.dwCursorPosition var origin = scrbuf.dwCursorPosition
origin.x = int16(x) origin.x = int16(x)
if SetConsoleCursorPosition(conHandle, origin) == 0: OSError() if SetConsoleCursorPosition(conHandle, origin) == 0: OSError(OSLastError())
else: else:
stdout.write("\e[" & $x & 'G') stdout.write("\e[" & $x & 'G')
@ -77,10 +75,10 @@ when defined(windows):
when defined(windows): when defined(windows):
var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO
var hStdout = conHandle var hStdout = conHandle
if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError() if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError(OSLastError())
var origin = scrbuf.dwCursorPosition var origin = scrbuf.dwCursorPosition
origin.y = int16(y) origin.y = int16(y)
if SetConsoleCursorPosition(conHandle, origin) == 0: OSError() if SetConsoleCursorPosition(conHandle, origin) == 0: OSError(OSLastError())
else: else:
nil nil
@ -157,18 +155,18 @@ proc EraseLine* =
var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO
var numwrote: DWORD var numwrote: DWORD
var hStdout = conHandle var hStdout = conHandle
if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError() if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError(OSLastError())
var origin = scrbuf.dwCursorPosition var origin = scrbuf.dwCursorPosition
origin.x = 0'i16 origin.x = 0'i16
if SetConsoleCursorPosition(conHandle, origin) == 0: OSError() if SetConsoleCursorPosition(conHandle, origin) == 0: OSError(OSLastError())
var ht = scrbuf.dwSize.Y - origin.Y var ht = scrbuf.dwSize.Y - origin.Y
var wt = scrbuf.dwSize.X - origin.X var wt = scrbuf.dwSize.X - origin.X
if FillConsoleOutputCharacter(hStdout,' ', ht*wt, if FillConsoleOutputCharacter(hStdout,' ', ht*wt,
origin, addr(numwrote)) == 0: origin, addr(numwrote)) == 0:
OSError() OSError(OSLastError())
if FillConsoleOutputAttribute(hStdout, scrbuf.wAttributes, ht * wt, if FillConsoleOutputAttribute(hStdout, scrbuf.wAttributes, ht * wt,
scrbuf.dwCursorPosition, addr(numwrote)) == 0: scrbuf.dwCursorPosition, addr(numwrote)) == 0:
OSError() OSError(OSLastError())
else: else:
stdout.write("\e[2K") stdout.write("\e[2K")
setCursorXPos(0) setCursorXPos(0)
@ -180,14 +178,14 @@ proc EraseScreen* =
var numwrote: DWORD var numwrote: DWORD
var origin: TCoord # is inititalized to 0, 0 var origin: TCoord # is inititalized to 0, 0
var hStdout = conHandle var hStdout = conHandle
if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError() if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError(OSLastError())
if FillConsoleOutputCharacter(hStdout, ' ', scrbuf.dwSize.X*scrbuf.dwSize.Y, if FillConsoleOutputCharacter(hStdout, ' ', scrbuf.dwSize.X*scrbuf.dwSize.Y,
origin, addr(numwrote)) == 0: origin, addr(numwrote)) == 0:
OSError() OSError(OSLastError())
if FillConsoleOutputAttribute(hStdout, scrbuf.wAttributes, if FillConsoleOutputAttribute(hStdout, scrbuf.wAttributes,
scrbuf.dwSize.X * scrbuf.dwSize.Y, scrbuf.dwSize.X * scrbuf.dwSize.Y,
origin, addr(numwrote)) == 0: origin, addr(numwrote)) == 0:
OSError() OSError(OSLastError())
setCursorXPos(0) setCursorXPos(0)
else: else:
stdout.write("\e[2J") stdout.write("\e[2J")

View file

@ -1,7 +1,7 @@
# #
# #
# Nimrod's Runtime Library # Nimrod's Runtime Library
# (c) Copyright 2012 Andreas Rumpf # (c) Copyright 2013 Andreas Rumpf
# #
# See the file "copying.txt", included in this # See the file "copying.txt", included in this
# distribution, for details about the copyright. # distribution, for details about the copyright.
@ -338,24 +338,24 @@ when not defined(JS):
const const
weekDays: array [0..6, TWeekDay] = [ weekDays: array [0..6, TWeekDay] = [
dSun, dMon, dTue, dWed, dThu, dFri, dSat] dSun, dMon, dTue, dWed, dThu, dFri, dSat]
result.second = int(tm.second) TTimeInfo(second: int(tm.second),
result.minute = int(tm.minute) minute: int(tm.minute),
result.hour = int(tm.hour) hour: int(tm.hour),
result.monthday = int(tm.monthday) monthday: int(tm.monthday),
result.month = TMonth(tm.month) month: TMonth(tm.month),
result.year = tm.year + 1900'i32 year: tm.year + 1900'i32,
result.weekday = weekDays[int(tm.weekDay)] weekday: weekDays[int(tm.weekDay)],
result.yearday = int(tm.yearday) yearday: int(tm.yearday),
result.isDST = tm.isDST > 0 isDST: tm.isDST > 0,
if local: tzname: if local:
if result.isDST: if tm.isDST > 0:
result.tzname = getTzname().DST getTzname().DST
else: else:
result.tzname = getTzname().nonDST getTzname().nonDST
else: else:
result.tzname = "UTC" "UTC",
timezone: if local: getTimezone() else: 0
result.timezone = if local: getTimezone() else: 0 )
proc timeInfoToTM(t: TTimeInfo): structTM = proc timeInfoToTM(t: TTimeInfo): structTM =
const const

View file

@ -110,11 +110,9 @@ proc parseXml*(s: PStream, filename: string,
of xmlElementOpen, xmlElementStart: of xmlElementOpen, xmlElementStart:
result = parse(x, errors) result = parse(x, errors)
break break
of xmlComment, xmlWhitespace: nil # just skip it of xmlComment, xmlWhitespace, xmlSpecial: nil # just skip it
of xmlError: of xmlError:
errors.add(errorMsg(x)) errors.add(errorMsg(x))
of xmlSpecial:
errors.add(errorMsg(x, "<some_tag> expected"))
else: else:
errors.add(errorMsg(x, "<some_tag> expected")) errors.add(errorMsg(x, "<some_tag> expected"))
break break

View file

@ -71,6 +71,16 @@ proc text*(n: PXmlNode): string {.inline.} =
assert n.k in {xnText, xnComment, xnCData, xnEntity} assert n.k in {xnText, xnComment, xnCData, xnEntity}
result = n.fText result = n.fText
proc rawText*(n: PXmlNode): string {.inline.} =
## returns the underlying 'text' string by reference.
## This is only used for speed hacks.
shallowCopy(result, n.fText)
proc rawTag*(n: PXmlNode): string {.inline.} =
## returns the underlying 'tag' string by reference.
## This is only used for speed hacks.
shallowCopy(result, n.fTag)
proc innerText*(n: PXmlNode): string = proc innerText*(n: PXmlNode): string =
## gets the inner text of `n`. `n` has to be an ``xnElement`` node. Only ## gets the inner text of `n`. `n` has to be an ``xnElement`` node. Only
## ``xnText`` and ``xnEntity`` nodes are considered part of `n`'s inner text, ## ``xnText`` and ``xnEntity`` nodes are considered part of `n`'s inner text,

View file

@ -489,7 +489,7 @@ proc FD_SET*(Socket: TWinSocket, FDSet: var TFDSet) =
proc FD_ZERO*(FDSet: var TFDSet) = proc FD_ZERO*(FDSet: var TFDSet) =
FDSet.fd_count = 0 FDSet.fd_count = 0
proc WSAStartup*(wVersionRequired: int16, WSData: var TWSAData): cint {. proc WSAStartup*(wVersionRequired: int16, WSData: ptr TWSAData): cint {.
stdcall, importc: "WSAStartup", dynlib: ws2dll.} stdcall, importc: "WSAStartup", dynlib: ws2dll.}
proc getaddrinfo*(nodename, servname: cstring, hints: ptr TAddrInfo, proc getaddrinfo*(nodename, servname: cstring, hints: ptr TAddrInfo,

View file

@ -27,7 +27,7 @@ type
gshort* = cshort gshort* = cshort
glong* = clong glong* = clong
gint* = cint gint* = cint
gboolean* = bool gboolean* = distinct gint
guchar* = char guchar* = char
gushort* = int16 gushort* = int16
gulong* = int gulong* = int
@ -173,6 +173,12 @@ type
TGBoxedFreeFunc* = proc (boxed: gpointer){.cdecl.} TGBoxedFreeFunc* = proc (boxed: gpointer){.cdecl.}
PGsource = pointer # I don't know and don't care PGsource = pointer # I don't know and don't care
converter gbool*(nimbool: bool): gboolean =
return ord(nimbool).gboolean
converter toBool*(gbool: gboolean): bool =
return int(gbool) == 1
const const
G_TYPE_FUNDAMENTAL_SHIFT* = 2 G_TYPE_FUNDAMENTAL_SHIFT* = 2
G_TYPE_FUNDAMENTAL_MAX* = 255 shl G_TYPE_FUNDAMENTAL_SHIFT G_TYPE_FUNDAMENTAL_MAX* = 255 shl G_TYPE_FUNDAMENTAL_SHIFT

View file

@ -2,6 +2,8 @@
import import
glib2, atk, pango, gdk2pixbuf, gdk2 glib2, atk, pango, gdk2pixbuf, gdk2
export gbool, toBool
when defined(win32): when defined(win32):
const const
lib = "libgtk-win32-2.0-0.dll" lib = "libgtk-win32-2.0-0.dll"

View file

@ -26,9 +26,9 @@ import
proc pushstring*(L: PState, s: string) proc pushstring*(L: PState, s: string)
# compatibilty macros # compatibilty macros
proc getn*(L: PState, n: int): int proc getn*(L: PState, n: cint): cint
# calls lua_objlen # calls lua_objlen
proc setn*(L: PState, t, n: int) proc setn*(L: PState, t, n: cint)
# does nothing! # does nothing!
type type
Treg*{.final.} = object Treg*{.final.} = object
@ -37,58 +37,45 @@ type
Preg* = ptr Treg Preg* = ptr Treg
proc openlib*(L: PState, libname: cstring, lr: Preg, nup: int){.cdecl,
dynlib: lua.LIB_NAME, importc: "luaL_openlib".} {.push callConv: cdecl, dynlib: lua.LIB_NAME.}
proc register*(L: PState, libname: cstring, lr: Preg){.cdecl, {.push importc: "luaL_$1".}
dynlib: lua.LIB_NAME, importc: "luaL_register".}
proc getmetafield*(L: PState, obj: int, e: cstring): int{.cdecl, proc openlib*(L: PState, libname: cstring, lr: Preg, nup: cint)
dynlib: lua.LIB_NAME, importc: "luaL_getmetafield".} proc register*(L: PState, libname: cstring, lr: Preg)
proc callmeta*(L: PState, obj: int, e: cstring): int{.cdecl,
dynlib: LIB_NAME, importc: "luaL_callmeta".} proc getmetafield*(L: PState, obj: cint, e: cstring): cint
proc typerror*(L: PState, narg: int, tname: cstring): int{.cdecl, proc callmeta*(L: PState, obj: cint, e: cstring): cint
dynlib: LIB_NAME, importc: "luaL_typerror".} proc typerror*(L: PState, narg: cint, tname: cstring): cint
proc argerror*(L: PState, numarg: int, extramsg: cstring): int{.cdecl, proc argerror*(L: PState, numarg: cint, extramsg: cstring): cint
dynlib: LIB_NAME, importc: "luaL_argerror".} proc checklstring*(L: PState, numArg: cint, len: ptr int): cstring
proc checklstring*(L: PState, numArg: int, len: ptr int): cstring{.cdecl, proc optlstring*(L: PState, numArg: cint, def: cstring, len: ptr cint): cstring
dynlib: LIB_NAME, importc: "luaL_checklstring".} proc checknumber*(L: PState, numArg: cint): Number
proc optlstring*(L: PState, numArg: int, def: cstring, len: ptr int): cstring{. proc optnumber*(L: PState, nArg: cint, def: Number): Number
cdecl, dynlib: LIB_NAME, importc: "luaL_optlstring".} proc checkinteger*(L: PState, numArg: cint): Integer
proc checknumber*(L: PState, numArg: int): Number{.cdecl, proc optinteger*(L: PState, nArg: cint, def: Integer): Integer
dynlib: LIB_NAME, importc: "luaL_checknumber".} proc checkstack*(L: PState, sz: cint, msg: cstring)
proc optnumber*(L: PState, nArg: int, def: Number): Number{.cdecl, proc checktype*(L: PState, narg, t: cint)
dynlib: LIB_NAME, importc: "luaL_optnumber".}
proc checkinteger*(L: PState, numArg: int): Integer{.cdecl, proc checkany*(L: PState, narg: cint)
dynlib: LIB_NAME, importc: "luaL_checkinteger".} proc newmetatable*(L: PState, tname: cstring): cint
proc optinteger*(L: PState, nArg: int, def: Integer): Integer{.
cdecl, dynlib: LIB_NAME, importc: "luaL_optinteger".} proc checkudata*(L: PState, ud: cint, tname: cstring): Pointer
proc checkstack*(L: PState, sz: int, msg: cstring){.cdecl, proc where*(L: PState, lvl: cint)
dynlib: LIB_NAME, importc: "luaL_checkstack".} proc error*(L: PState, fmt: cstring): cint{.varargs.}
proc checktype*(L: PState, narg, t: int){.cdecl, dynlib: LIB_NAME, proc checkoption*(L: PState, narg: cint, def: cstring, lst: cstringArray): cint
importc: "luaL_checktype".}
proc checkany*(L: PState, narg: int){.cdecl, dynlib: LIB_NAME, proc unref*(L: PState, t, theref: cint)
importc: "luaL_checkany".} proc loadfile*(L: PState, filename: cstring): cint
proc newmetatable*(L: PState, tname: cstring): int{.cdecl, proc loadbuffer*(L: PState, buff: cstring, size: cint, name: cstring): cint
dynlib: LIB_NAME, importc: "luaL_newmetatable".} proc loadstring*(L: PState, s: cstring): cint
proc checkudata*(L: PState, ud: int, tname: cstring): Pointer{.cdecl, proc newstate*(): PState
dynlib: LIB_NAME, importc: "luaL_checkudata".}
proc where*(L: PState, lvl: int){.cdecl, dynlib: LIB_NAME, {.pop.}
importc: "luaL_where".} proc reference*(L: PState, t: cint): cint{.importc: "luaL_ref".}
proc error*(L: PState, fmt: cstring): int{.cdecl, varargs,
dynlib: LIB_NAME, importc: "luaL_error".} {.pop.}
proc checkoption*(L: PState, narg: int, def: cstring, lst: cstringArray): int{.
cdecl, dynlib: LIB_NAME, importc: "luaL_checkoption".}
proc reference*(L: PState, t: int): int{.cdecl, dynlib: LIB_NAME,
importc: "luaL_ref".}
proc unref*(L: PState, t, theref: int){.cdecl, dynlib: LIB_NAME,
importc: "luaL_unref".}
proc loadfile*(L: PState, filename: cstring): int{.cdecl,
dynlib: LIB_NAME, importc: "luaL_loadfile".}
proc loadbuffer*(L: PState, buff: cstring, size: int, name: cstring): int{.
cdecl, dynlib: LIB_NAME, importc: "luaL_loadbuffer".}
proc loadstring*(L: PState, s: cstring): int{.cdecl, dynlib: LIB_NAME,
importc: "luaL_loadstring".}
proc newstate*(): PState{.cdecl, dynlib: LIB_NAME,
importc: "luaL_newstate".}
proc open*(): PState proc open*(): PState
# compatibility; moved from unit lua to lauxlib because it needs luaL_newstate # compatibility; moved from unit lua to lauxlib because it needs luaL_newstate
# #
@ -96,15 +83,15 @@ proc open*(): PState
#** some useful macros #** some useful macros
#** =============================================================== #** ===============================================================
# #
proc argcheck*(L: PState, cond: bool, numarg: int, extramsg: cstring) proc argcheck*(L: PState, cond: bool, numarg: cint, extramsg: cstring)
proc checkstring*(L: PState, n: int): cstring proc checkstring*(L: PState, n: cint): cstring
proc optstring*(L: PState, n: int, d: cstring): cstring proc optstring*(L: PState, n: cint, d: cstring): cstring
proc checkint*(L: PState, n: int): int proc checkint*(L: PState, n: cint): cint
proc checklong*(L: PState, n: int): int32 proc checklong*(L: PState, n: cint): clong
proc optint*(L: PState, n: int, d: float64): int proc optint*(L: PState, n: cint, d: float64): cint
proc optlong*(L: PState, n: int, d: float64): int32 proc optlong*(L: PState, n: cint, d: float64): clong
proc dofile*(L: PState, filename: cstring): int proc dofile*(L: PState, filename: cstring): cint
proc dostring*(L: PState, str: cstring): int proc dostring*(L: PState, str: cstring): cint
proc getmetatable*(L: PState, tname: cstring) proc getmetatable*(L: PState, tname: cstring)
# not translated: # not translated:
# #define luaL_opt(L,f,n,d) (lua_isnoneornil(L,(n)) ? (d) : f(L,(n))) # #define luaL_opt(L,f,n,d) (lua_isnoneornil(L,(n)) ? (d) : f(L,(n)))
@ -119,7 +106,7 @@ const # note: this is just arbitrary, as it related to t
type type
Buffer*{.final.} = object Buffer*{.final.} = object
p*: cstring # current position in buffer p*: cstring # current position in buffer
lvl*: int # number of strings in the stack (level) lvl*: cint # number of strings in the stack (level)
L*: PState L*: PState
buffer*: array[0..BUFFERSIZE - 1, Char] # warning: see note above about LUAL_BUFFERSIZE buffer*: array[0..BUFFERSIZE - 1, Char] # warning: see note above about LUAL_BUFFERSIZE
@ -130,80 +117,76 @@ proc addchar*(B: PBuffer, c: Char)
# compatibility only (alias for luaL_addchar) # compatibility only (alias for luaL_addchar)
proc putchar*(B: PBuffer, c: Char) proc putchar*(B: PBuffer, c: Char)
# warning: see note above about LUAL_BUFFERSIZE # warning: see note above about LUAL_BUFFERSIZE
proc addsize*(B: PBuffer, n: int) proc addsize*(B: PBuffer, n: cint)
proc buffinit*(L: PState, B: PBuffer){.cdecl, dynlib: LIB_NAME,
importc: "luaL_buffinit".} {.push callConv: cdecl, dynlib: lua.LIB_NAME, importc: "luaL_$1".}
proc prepbuffer*(B: PBuffer): cstring{.cdecl, dynlib: LIB_NAME, proc buffinit*(L: PState, B: PBuffer)
importc: "luaL_prepbuffer".} proc prepbuffer*(B: PBuffer): cstring
proc addlstring*(B: PBuffer, s: cstring, L: int){.cdecl, proc addlstring*(B: PBuffer, s: cstring, L: cint)
dynlib: LIB_NAME, importc: "luaL_addlstring".} proc addstring*(B: PBuffer, s: cstring)
proc addstring*(B: PBuffer, s: cstring){.cdecl, dynlib: LIB_NAME, proc addvalue*(B: PBuffer)
importc: "luaL_addstring".} proc pushresult*(B: PBuffer)
proc addvalue*(B: PBuffer){.cdecl, dynlib: LIB_NAME, proc gsub*(L: PState, s, p, r: cstring): cstring
importc: "luaL_addvalue".} proc findtable*(L: PState, idx: cint, fname: cstring, szhint: cint): cstring
proc pushresult*(B: PBuffer){.cdecl, dynlib: LIB_NAME,
importc: "luaL_pushresult".}
proc gsub*(L: PState, s, p, r: cstring): cstring{.cdecl,
dynlib: LIB_NAME, importc: "luaL_gsub".}
proc findtable*(L: PState, idx: int, fname: cstring, szhint: int): cstring{.
cdecl, dynlib: LIB_NAME, importc: "luaL_findtable".}
# compatibility with ref system # compatibility with ref system
# pre-defined references # pre-defined references
{.pop.}
const const
NOREF* = - 2 NOREF* = - 2
REFNIL* = - 1 REFNIL* = - 1
proc unref*(L: PState, theref: int) proc unref*(L: PState, theref: cint)
proc getref*(L: PState, theref: int) proc getref*(L: PState, theref: cint)
# #
#** Compatibility macros and functions #** Compatibility macros and functions
# #
# implementation # implementation
proc pushstring(L: PState, s: string) = proc pushstring(L: PState, s: string) =
pushlstring(L, cstring(s), len(s)) pushlstring(L, cstring(s), s.len.cint)
proc getn(L: PState, n: int): int = proc getn(L: PState, n: cint): cint =
Result = objlen(L, n) Result = objlen(L, n)
proc setn(L: PState, t, n: int) = proc setn(L: PState, t, n: cint) =
# does nothing as this operation is deprecated # does nothing as this operation is deprecated
nil nil
proc open(): PState = proc open(): PState =
Result = newstate() Result = newstate()
proc dofile(L: PState, filename: cstring): int = proc dofile(L: PState, filename: cstring): cint =
Result = loadfile(L, filename) Result = loadfile(L, filename)
if Result == 0: Result = pcall(L, 0, MULTRET, 0) if Result == 0: Result = pcall(L, 0, MULTRET, 0)
proc dostring(L: PState, str: cstring): int = proc dostring(L: PState, str: cstring): cint =
Result = loadstring(L, str) Result = loadstring(L, str)
if Result == 0: Result = pcall(L, 0, MULTRET, 0) if Result == 0: Result = pcall(L, 0, MULTRET, 0)
proc getmetatable(L: PState, tname: cstring) = proc getmetatable(L: PState, tname: cstring) =
getfield(L, REGISTRYINDEX, tname) getfield(L, REGISTRYINDEX, tname)
proc argcheck(L: PState, cond: bool, numarg: int, extramsg: cstring) = proc argcheck(L: PState, cond: bool, numarg: cint, extramsg: cstring) =
if not cond: if not cond:
discard argerror(L, numarg, extramsg) discard argerror(L, numarg, extramsg)
proc checkstring(L: PState, n: int): cstring = proc checkstring(L: PState, n: cint): cstring =
Result = checklstring(L, n, nil) Result = checklstring(L, n, nil)
proc optstring(L: PState, n: int, d: cstring): cstring = proc optstring(L: PState, n: cint, d: cstring): cstring =
Result = optlstring(L, n, d, nil) Result = optlstring(L, n, d, nil)
proc checkint(L: PState, n: int): int = proc checkint(L: PState, n: cint): cint =
Result = toInt(checknumber(L, n)) Result = cint(checknumber(L, n))
proc checklong(L: PState, n: int): int32 = proc checklong(L: PState, n: cint): clong =
Result = int32(ToInt(checknumber(L, n))) Result = int32(ToInt(checknumber(L, n)))
proc optint(L: PState, n: int, d: float64): int = proc optint(L: PState, n: cint, d: float64): cint =
Result = int(ToInt(optnumber(L, n, d))) Result = optnumber(L, n, d).cint
proc optlong(L: PState, n: int, d: float64): int32 = proc optlong(L: PState, n: cint, d: float64): clong =
Result = int32(ToInt(optnumber(L, n, d))) Result = int32(ToInt(optnumber(L, n, d)))
proc addchar(B: PBuffer, c: Char) = proc addchar(B: PBuffer, c: Char) =
@ -215,11 +198,11 @@ proc addchar(B: PBuffer, c: Char) =
proc putchar(B: PBuffer, c: Char) = proc putchar(B: PBuffer, c: Char) =
addchar(B, c) addchar(B, c)
proc addsize(B: PBuffer, n: int) = proc addsize(B: PBuffer, n: cint) =
B.p = cast[cstring](cast[int](B.p) + n) B.p = cast[cstring](cast[int](B.p) + n)
proc unref(L: PState, theref: int) = proc unref(L: PState, theref: cint) =
unref(L, REGISTRYINDEX, theref) unref(L, REGISTRYINDEX, theref)
proc getref(L: PState, theref: int) = proc getref(L: PState, theref: cint) =
rawgeti(L, REGISTRYINDEX, theref) rawgeti(L, REGISTRYINDEX, theref)

View file

@ -51,16 +51,16 @@ when defined(useLuajit):
else: else:
when defined(MACOSX): when defined(MACOSX):
const const
NAME* = "liblua(|5.2|5.1|5.0).dylib" NAME* = "liblua(|5.1|5.0).dylib"
LIB_NAME* = "liblua(|5.2|5.1|5.0).dylib" LIB_NAME* = "liblua(|5.1|5.0).dylib"
elif defined(UNIX): elif defined(UNIX):
const const
NAME* = "liblua(|5.2|5.1|5.0).so(|.0)" NAME* = "liblua(|5.1|5.0).so(|.0)"
LIB_NAME* = "liblua(|5.2|5.1|5.0).so(|.0)" LIB_NAME* = "liblua(|5.1|5.0).so(|.0)"
else: else:
const const
NAME* = "lua(|5.2|5.1|5.0).dll" NAME* = "lua(|5.1|5.0).dll"
LIB_NAME* = "lua(|5.2|5.1|5.0).dll" LIB_NAME* = "lua(|5.1|5.0).dll"
const const
VERSION* = "Lua 5.1" VERSION* = "Lua 5.1"
@ -76,7 +76,7 @@ const
ENVIRONINDEX* = - 10001 ENVIRONINDEX* = - 10001
GLOBALSINDEX* = - 10002 GLOBALSINDEX* = - 10002
proc upvalueindex*(I: int): int proc upvalueindex*(I: cint): cint
const # thread status; 0 is OK const # thread status; 0 is OK
constYIELD* = 1 constYIELD* = 1
ERRRUN* = 2 ERRRUN* = 2
@ -86,16 +86,16 @@ const # thread status; 0 is OK
type type
PState* = Pointer PState* = Pointer
CFunction* = proc (L: PState): int{.cdecl.} CFunction* = proc (L: PState): cint{.cdecl.}
# #
#** functions that read/write blocks when loading/dumping Lua chunks #** functions that read/write blocks when loading/dumping Lua chunks
# #
type type
Reader* = proc (L: PState, ud: Pointer, sz: ptr int): cstring{.cdecl.} Reader* = proc (L: PState, ud: Pointer, sz: ptr cint): cstring{.cdecl.}
Writer* = proc (L: PState, p: Pointer, sz: int, ud: Pointer): int{.cdecl.} Writer* = proc (L: PState, p: Pointer, sz: cint, ud: Pointer): cint{.cdecl.}
Alloc* = proc (ud, theptr: Pointer, osize, nsize: int){.cdecl.} Alloc* = proc (ud, theptr: Pointer, osize, nsize: cint){.cdecl.}
const const
TNONE* = - 1 TNONE* = - 1
@ -112,130 +112,86 @@ const
type # Type of Numbers in Lua type # Type of Numbers in Lua
Number* = float Number* = float
Integer* = int Integer* = cint
{.pragma: ilua, importc: "lua_$1".}
{.push callConv: cdecl, dynlib: LibName.}
#{.push importc: "lua_$1".}
proc newstate*(f: Alloc, ud: Pointer): PState {.ilua.}
proc close*(L: PState){.ilua.}
proc newthread*(L: PState): PState{.ilua.}
proc atpanic*(L: PState, panicf: CFunction): CFunction{.ilua.}
proc gettop*(L: PState): cint{.ilua.}
proc settop*(L: PState, idx: cint){.ilua.}
proc pushvalue*(L: PState, Idx: cint){.ilua.}
proc remove*(L: PState, idx: cint){.ilua.}
proc insert*(L: PState, idx: cint){.ilua.}
proc replace*(L: PState, idx: cint){.ilua.}
proc checkstack*(L: PState, sz: cint): cint{.ilua.}
proc xmove*(`from`, `to`: PState, n: cint){.ilua.}
proc isnumber*(L: PState, idx: cint): cint{.ilua.}
proc isstring*(L: PState, idx: cint): cint{.ilua.}
proc iscfunction*(L: PState, idx: cint): cint{.ilua.}
proc isuserdata*(L: PState, idx: cint): cint{.ilua.}
proc luatype*(L: PState, idx: cint): cint{.importc: "lua_type".}
proc typename*(L: PState, tp: cint): cstring{.ilua.}
proc equal*(L: PState, idx1, idx2: cint): cint{.ilua.}
proc rawequal*(L: PState, idx1, idx2: cint): cint{.ilua.}
proc lessthan*(L: PState, idx1, idx2: cint): cint{.ilua.}
proc tonumber*(L: PState, idx: cint): Number{.ilua.}
proc tointeger*(L: PState, idx: cint): Integer{.ilua.}
proc toboolean*(L: PState, idx: cint): cint{.ilua.}
proc tolstring*(L: PState, idx: cint, length: ptr cint): cstring{.ilua.}
proc objlen*(L: PState, idx: cint): cint{.ilua.}
proc tocfunction*(L: PState, idx: cint): CFunction{.ilua.}
proc touserdata*(L: PState, idx: cint): Pointer{.ilua.}
proc tothread*(L: PState, idx: cint): PState{.ilua.}
proc topointer*(L: PState, idx: cint): Pointer{.ilua.}
proc pushnil*(L: PState){.ilua.}
proc pushnumber*(L: PState, n: Number){.ilua.}
proc pushinteger*(L: PState, n: Integer){.ilua.}
proc pushlstring*(L: PState, s: cstring, len: cint){.ilua.}
proc pushstring*(L: PState, s: cstring){.ilua.}
proc pushvfstring*(L: PState, fmt: cstring, argp: Pointer): cstring{.ilua.}
proc pushfstring*(L: PState, fmt: cstring): cstring{.varargs,ilua.}
proc pushcclosure*(L: PState, fn: CFunction, n: cint){.ilua.}
proc pushboolean*(L: PState, b: cint){.ilua.}
proc pushlightuserdata*(L: PState, p: Pointer){.ilua.}
proc pushthread*(L: PState){.ilua.}
proc gettable*(L: PState, idx: cint){.ilua.}
proc getfield*(L: Pstate, idx: cint, k: cstring){.ilua.}
proc rawget*(L: PState, idx: cint){.ilua.}
proc rawgeti*(L: PState, idx, n: cint){.ilua.}
proc createtable*(L: PState, narr, nrec: cint){.ilua.}
proc newuserdata*(L: PState, sz: cint): Pointer{.ilua.}
proc getmetatable*(L: PState, objindex: cint): cint{.ilua.}
proc getfenv*(L: PState, idx: cint){.ilua.}
proc settable*(L: PState, idx: cint){.ilua.}
proc setfield*(L: PState, idx: cint, k: cstring){.ilua.}
proc rawset*(L: PState, idx: cint){.ilua.}
proc rawseti*(L: PState, idx, n: cint){.ilua.}
proc setmetatable*(L: PState, objindex: cint): cint{.ilua.}
proc setfenv*(L: PState, idx: cint): cint{.ilua.}
proc call*(L: PState, nargs, nresults: cint){.ilua.}
proc pcall*(L: PState, nargs, nresults, errf: cint): cint{.ilua.}
proc cpcall*(L: PState, func: CFunction, ud: Pointer): cint{.ilua.}
proc load*(L: PState, reader: Reader, dt: Pointer, chunkname: cstring): cint{.ilua.}
proc dump*(L: PState, writer: Writer, data: Pointer): cint{.ilua.}
proc luayield*(L: PState, nresults: cint): cint{.importc: "lua_yield".}
proc resume*(L: PState, narg: cint): cint{.ilua.}
proc status*(L: PState): cint{.ilua.}
proc gc*(L: PState, what, data: cint): cint{.ilua.}
proc error*(L: PState): cint{.ilua.}
proc next*(L: PState, idx: cint): cint{.ilua.}
proc concat*(L: PState, n: cint){.ilua.}
proc getallocf*(L: PState, ud: ptr Pointer): Alloc{.ilua.}
proc setallocf*(L: PState, f: Alloc, ud: Pointer){.ilua.}
{.pop.}
proc newstate*(f: Alloc, ud: Pointer): PState{.cdecl, dynlib: NAME,
importc: "lua_newstate".}
proc close*(L: PState){.cdecl, dynlib: NAME, importc: "lua_close".}
proc newthread*(L: PState): PState{.cdecl, dynlib: NAME,
importc: "lua_newthread".}
proc atpanic*(L: PState, panicf: CFunction): CFunction{.cdecl, dynlib: NAME,
importc: "lua_atpanic".}
proc gettop*(L: PState): int{.cdecl, dynlib: NAME, importc: "lua_gettop".}
proc settop*(L: PState, idx: int){.cdecl, dynlib: NAME, importc: "lua_settop".}
proc pushvalue*(L: PState, Idx: int){.cdecl, dynlib: NAME,
importc: "lua_pushvalue".}
proc remove*(L: PState, idx: int){.cdecl, dynlib: NAME, importc: "lua_remove".}
proc insert*(L: PState, idx: int){.cdecl, dynlib: NAME, importc: "lua_insert".}
proc replace*(L: PState, idx: int){.cdecl, dynlib: NAME, importc: "lua_replace".}
proc checkstack*(L: PState, sz: int): cint{.cdecl, dynlib: NAME,
importc: "lua_checkstack".}
proc xmove*(`from`, `to`: PState, n: int){.cdecl, dynlib: NAME,
importc: "lua_xmove".}
proc isnumber*(L: PState, idx: int): cint{.cdecl, dynlib: NAME,
importc: "lua_isnumber".}
proc isstring*(L: PState, idx: int): cint{.cdecl, dynlib: NAME,
importc: "lua_isstring".}
proc iscfunction*(L: PState, idx: int): cint{.cdecl, dynlib: NAME,
importc: "lua_iscfunction".}
proc isuserdata*(L: PState, idx: int): cint{.cdecl, dynlib: NAME,
importc: "lua_isuserdata".}
proc luatype*(L: PState, idx: int): int{.cdecl, dynlib: NAME, importc: "lua_type".}
proc typename*(L: PState, tp: int): cstring{.cdecl, dynlib: NAME,
importc: "lua_typename".}
proc equal*(L: PState, idx1, idx2: int): cint{.cdecl, dynlib: NAME,
importc: "lua_equal".}
proc rawequal*(L: PState, idx1, idx2: int): cint{.cdecl, dynlib: NAME,
importc: "lua_rawequal".}
proc lessthan*(L: PState, idx1, idx2: int): cint{.cdecl, dynlib: NAME,
importc: "lua_lessthan".}
proc tonumber*(L: PState, idx: int): Number{.cdecl, dynlib: NAME,
importc: "lua_tonumber".}
proc tointeger*(L: PState, idx: int): Integer{.cdecl, dynlib: NAME,
importc: "lua_tointeger".}
proc toboolean*(L: PState, idx: int): cint{.cdecl, dynlib: NAME,
importc: "lua_toboolean".}
proc tolstring*(L: PState, idx: int, length: ptr int): cstring{.cdecl,
dynlib: NAME, importc: "lua_tolstring".}
proc objlen*(L: PState, idx: int): int{.cdecl, dynlib: NAME,
importc: "lua_objlen".}
proc tocfunction*(L: PState, idx: int): CFunction{.cdecl, dynlib: NAME,
importc: "lua_tocfunction".}
proc touserdata*(L: PState, idx: int): Pointer{.cdecl, dynlib: NAME,
importc: "lua_touserdata".}
proc tothread*(L: PState, idx: int): PState{.cdecl, dynlib: NAME,
importc: "lua_tothread".}
proc topointer*(L: PState, idx: int): Pointer{.cdecl, dynlib: NAME,
importc: "lua_topointer".}
proc pushnil*(L: PState){.cdecl, dynlib: NAME, importc: "lua_pushnil".}
proc pushnumber*(L: PState, n: Number){.cdecl, dynlib: NAME,
importc: "lua_pushnumber".}
proc pushinteger*(L: PState, n: Integer){.cdecl, dynlib: NAME,
importc: "lua_pushinteger".}
proc pushlstring*(L: PState, s: cstring, len: int){.cdecl, dynlib: NAME,
importc: "lua_pushlstring".}
proc pushstring*(L: PState, s: cstring){.cdecl, dynlib: NAME,
importc: "lua_pushstring".}
proc pushvfstring*(L: PState, fmt: cstring, argp: Pointer): cstring{.cdecl,
dynlib: NAME, importc: "lua_pushvfstring".}
proc pushfstring*(L: PState, fmt: cstring): cstring{.cdecl, varargs,
dynlib: NAME, importc: "lua_pushfstring".}
proc pushcclosure*(L: PState, fn: CFunction, n: int){.cdecl, dynlib: NAME,
importc: "lua_pushcclosure".}
proc pushboolean*(L: PState, b: cint){.cdecl, dynlib: NAME,
importc: "lua_pushboolean".}
proc pushlightuserdata*(L: PState, p: Pointer){.cdecl, dynlib: NAME,
importc: "lua_pushlightuserdata".}
proc pushthread*(L: PState){.cdecl, dynlib: NAME, importc: "lua_pushthread".}
proc gettable*(L: PState, idx: int){.cdecl, dynlib: NAME,
importc: "lua_gettable".}
proc getfield*(L: Pstate, idx: int, k: cstring){.cdecl, dynlib: NAME,
importc: "lua_getfield".}
proc rawget*(L: PState, idx: int){.cdecl, dynlib: NAME, importc: "lua_rawget".}
proc rawgeti*(L: PState, idx, n: int){.cdecl, dynlib: NAME,
importc: "lua_rawgeti".}
proc createtable*(L: PState, narr, nrec: int){.cdecl, dynlib: NAME,
importc: "lua_createtable".}
proc newuserdata*(L: PState, sz: int): Pointer{.cdecl, dynlib: NAME,
importc: "lua_newuserdata".}
proc getmetatable*(L: PState, objindex: int): int{.cdecl, dynlib: NAME,
importc: "lua_getmetatable".}
proc getfenv*(L: PState, idx: int){.cdecl, dynlib: NAME, importc: "lua_getfenv".}
proc settable*(L: PState, idx: int){.cdecl, dynlib: NAME,
importc: "lua_settable".}
proc setfield*(L: PState, idx: int, k: cstring){.cdecl, dynlib: NAME,
importc: "lua_setfield".}
proc rawset*(L: PState, idx: int){.cdecl, dynlib: NAME, importc: "lua_rawset".}
proc rawseti*(L: PState, idx, n: int){.cdecl, dynlib: NAME,
importc: "lua_rawseti".}
proc setmetatable*(L: PState, objindex: int): int{.cdecl, dynlib: NAME,
importc: "lua_setmetatable".}
proc setfenv*(L: PState, idx: int): int{.cdecl, dynlib: NAME,
importc: "lua_setfenv".}
proc call*(L: PState, nargs, nresults: int){.cdecl, dynlib: NAME,
importc: "lua_call".}
proc pcall*(L: PState, nargs, nresults, errf: int): int{.cdecl, dynlib: NAME,
importc: "lua_pcall".}
proc cpcall*(L: PState, func: CFunction, ud: Pointer): int{.cdecl, dynlib: NAME,
importc: "lua_cpcall".}
proc load*(L: PState, reader: Reader, dt: Pointer, chunkname: cstring): int{.
cdecl, dynlib: NAME, importc: "lua_load".}
proc dump*(L: PState, writer: Writer, data: Pointer): int{.cdecl, dynlib: NAME,
importc: "lua_dump".}
proc luayield*(L: PState, nresults: int): int{.cdecl, dynlib: NAME,
importc: "lua_yield".}
proc resume*(L: PState, narg: int): int{.cdecl, dynlib: NAME,
importc: "lua_resume".}
proc status*(L: PState): int{.cdecl, dynlib: NAME, importc: "lua_status".}
proc gc*(L: PState, what, data: int): int{.cdecl, dynlib: NAME,
importc: "lua_gc".}
proc error*(L: PState): int{.cdecl, dynlib: NAME, importc: "lua_error".}
proc next*(L: PState, idx: int): int{.cdecl, dynlib: NAME, importc: "lua_next".}
proc concat*(L: PState, n: int){.cdecl, dynlib: NAME, importc: "lua_concat".}
proc getallocf*(L: PState, ud: ptr Pointer): Alloc{.cdecl, dynlib: NAME,
importc: "lua_getallocf".}
proc setallocf*(L: PState, f: Alloc, ud: Pointer){.cdecl, dynlib: NAME,
importc: "lua_setallocf".}
# #
#** Garbage-collection functions and options #** Garbage-collection functions and options
# #
@ -256,29 +212,29 @@ const
#** =============================================================== #** ===============================================================
# #
proc pop*(L: PState, n: int) proc pop*(L: PState, n: cint)
proc newtable*(L: Pstate) proc newtable*(L: Pstate)
proc register*(L: PState, n: cstring, f: CFunction) proc register*(L: PState, n: cstring, f: CFunction)
proc pushcfunction*(L: PState, f: CFunction) proc pushcfunction*(L: PState, f: CFunction)
proc strlen*(L: Pstate, i: int): int proc strlen*(L: Pstate, i: cint): cint
proc isfunction*(L: PState, n: int): bool proc isfunction*(L: PState, n: cint): bool
proc istable*(L: PState, n: int): bool proc istable*(L: PState, n: cint): bool
proc islightuserdata*(L: PState, n: int): bool proc islightuserdata*(L: PState, n: cint): bool
proc isnil*(L: PState, n: int): bool proc isnil*(L: PState, n: cint): bool
proc isboolean*(L: PState, n: int): bool proc isboolean*(L: PState, n: cint): bool
proc isthread*(L: PState, n: int): bool proc isthread*(L: PState, n: cint): bool
proc isnone*(L: PState, n: int): bool proc isnone*(L: PState, n: cint): bool
proc isnoneornil*(L: PState, n: int): bool proc isnoneornil*(L: PState, n: cint): bool
proc pushliteral*(L: PState, s: cstring) proc pushliteral*(L: PState, s: cstring)
proc setglobal*(L: PState, s: cstring) proc setglobal*(L: PState, s: cstring)
proc getglobal*(L: PState, s: cstring) proc getglobal*(L: PState, s: cstring)
proc tostring*(L: PState, i: int): cstring proc tostring*(L: PState, i: cint): cstring
# #
#** compatibility macros and functions #** compatibility macros and functions
# #
proc getregistry*(L: PState) proc getregistry*(L: PState)
proc getgccount*(L: PState): int proc getgccount*(L: PState): cint
type type
Chunkreader* = Reader Chunkreader* = Reader
Chunkwriter* = Writer Chunkwriter* = Writer
@ -307,18 +263,18 @@ const
type type
TDebug*{.final.} = object # activation record TDebug*{.final.} = object # activation record
event*: int event*: cint
name*: cstring # (n) name*: cstring # (n)
namewhat*: cstring # (n) `global', `local', `field', `method' namewhat*: cstring # (n) `global', `local', `field', `method'
what*: cstring # (S) `Lua', `C', `main', `tail' what*: cstring # (S) `Lua', `C', `main', `tail'
source*: cstring # (S) source*: cstring # (S)
currentline*: int # (l) currentline*: cint # (l)
nups*: int # (u) number of upvalues nups*: cint # (u) number of upvalues
linedefined*: int # (S) linedefined*: cint # (S)
lastlinedefined*: int # (S) lastlinedefined*: cint # (S)
short_src*: array[0..IDSIZE - 1, Char] # (S) short_src*: array[0.. <IDSIZE, Char] # (S) \
# private part # private part
i_ci*: int # active function i_ci*: cint # active function
PDebug* = ptr TDebug PDebug* = ptr TDebug
Hook* = proc (L: PState, ar: PDebug){.cdecl.} Hook* = proc (L: PState, ar: PDebug){.cdecl.}
@ -329,31 +285,27 @@ type
#** ====================================================================== #** ======================================================================
# #
proc getstack*(L: PState, level: int, ar: PDebug): int{.cdecl, dynlib: NAME, {.push callConv: cdecl, dynlib: lua.LIB_NAME.}
importc: "lua_getstack".}
proc getinfo*(L: PState, what: cstring, ar: PDebug): int{.cdecl, dynlib: NAME, proc getstack*(L: PState, level: cint, ar: PDebug): cint{.ilua.}
importc: "lua_getinfo".} proc getinfo*(L: PState, what: cstring, ar: PDebug): cint{.ilua.}
proc getlocal*(L: PState, ar: PDebug, n: int): cstring{.cdecl, dynlib: NAME, proc getlocal*(L: PState, ar: PDebug, n: cint): cstring{.ilua.}
importc: "lua_getlocal".} proc setlocal*(L: PState, ar: PDebug, n: cint): cstring{.ilua.}
proc setlocal*(L: PState, ar: PDebug, n: int): cstring{.cdecl, dynlib: NAME, proc getupvalue*(L: PState, funcindex: cint, n: cint): cstring{.ilua.}
importc: "lua_setlocal".} proc setupvalue*(L: PState, funcindex: cint, n: cint): cstring{.ilua.}
proc getupvalue*(L: PState, funcindex: int, n: int): cstring{.cdecl, proc sethook*(L: PState, func: Hook, mask: cint, count: cint): cint{.ilua.}
dynlib: NAME, importc: "lua_getupvalue".} proc gethook*(L: PState): Hook{.ilua.}
proc setupvalue*(L: PState, funcindex: int, n: int): cstring{.cdecl, proc gethookmask*(L: PState): cint{.ilua.}
dynlib: NAME, importc: "lua_setupvalue".} proc gethookcount*(L: PState): cint{.ilua.}
proc sethook*(L: PState, func: Hook, mask: int, count: int): int{.cdecl,
dynlib: NAME, importc: "lua_sethook".} {.pop.}
proc gethook*(L: PState): Hook{.cdecl, dynlib: NAME, importc: "lua_gethook".}
proc gethookmask*(L: PState): int{.cdecl, dynlib: NAME,
importc: "lua_gethookmask".}
proc gethookcount*(L: PState): int{.cdecl, dynlib: NAME,
importc: "lua_gethookcount".}
# implementation # implementation
proc upvalueindex(I: int): int = proc upvalueindex(I: cint): cint =
Result = GLOBALSINDEX - i Result = GLOBALSINDEX - i
proc pop(L: PState, n: int) = proc pop(L: PState, n: cint) =
settop(L, - n - 1) settop(L, - n - 1)
proc newtable(L: PState) = proc newtable(L: PState) =
@ -366,35 +318,35 @@ proc register(L: PState, n: cstring, f: CFunction) =
proc pushcfunction(L: PState, f: CFunction) = proc pushcfunction(L: PState, f: CFunction) =
pushcclosure(L, f, 0) pushcclosure(L, f, 0)
proc strlen(L: PState, i: int): int = proc strlen(L: PState, i: cint): cint =
Result = objlen(L, i) Result = objlen(L, i)
proc isfunction(L: PState, n: int): bool = proc isfunction(L: PState, n: cint): bool =
Result = luatype(L, n) == TFUNCTION Result = luatype(L, n) == TFUNCTION
proc istable(L: PState, n: int): bool = proc istable(L: PState, n: cint): bool =
Result = luatype(L, n) == TTABLE Result = luatype(L, n) == TTABLE
proc islightuserdata(L: PState, n: int): bool = proc islightuserdata(L: PState, n: cint): bool =
Result = luatype(L, n) == TLIGHTUSERDATA Result = luatype(L, n) == TLIGHTUSERDATA
proc isnil(L: PState, n: int): bool = proc isnil(L: PState, n: cint): bool =
Result = luatype(L, n) == TNIL Result = luatype(L, n) == TNIL
proc isboolean(L: PState, n: int): bool = proc isboolean(L: PState, n: cint): bool =
Result = luatype(L, n) == TBOOLEAN Result = luatype(L, n) == TBOOLEAN
proc isthread(L: PState, n: int): bool = proc isthread(L: PState, n: cint): bool =
Result = luatype(L, n) == TTHREAD Result = luatype(L, n) == TTHREAD
proc isnone(L: PState, n: int): bool = proc isnone(L: PState, n: cint): bool =
Result = luatype(L, n) == TNONE Result = luatype(L, n) == TNONE
proc isnoneornil(L: PState, n: int): bool = proc isnoneornil(L: PState, n: cint): bool =
Result = luatype(L, n) <= 0 Result = luatype(L, n) <= 0
proc pushliteral(L: PState, s: cstring) = proc pushliteral(L: PState, s: cstring) =
pushlstring(L, s, len(s)) pushlstring(L, s, s.len.cint)
proc setglobal(L: PState, s: cstring) = proc setglobal(L: PState, s: cstring) =
setfield(L, GLOBALSINDEX, s) setfield(L, GLOBALSINDEX, s)
@ -402,11 +354,11 @@ proc setglobal(L: PState, s: cstring) =
proc getglobal(L: PState, s: cstring) = proc getglobal(L: PState, s: cstring) =
getfield(L, GLOBALSINDEX, s) getfield(L, GLOBALSINDEX, s)
proc tostring(L: PState, i: int): cstring = proc tostring(L: PState, i: cint): cstring =
Result = tolstring(L, i, nil) Result = tolstring(L, i, nil)
proc getregistry(L: PState) = proc getregistry(L: PState) =
pushvalue(L, REGISTRYINDEX) pushvalue(L, REGISTRYINDEX)
proc getgccount(L: PState): int = proc getgccount(L: PState): cint =
Result = gc(L, GCCOUNT, 0) Result = gc(L, GCCOUNT, 0)

View file

@ -32,35 +32,33 @@ const
DBLIBNAME* = "debug" DBLIBNAME* = "debug"
LOADLIBNAME* = "package" LOADLIBNAME* = "package"
proc open_base*(L: PState): cint{.cdecl, dynlib: LIB_NAME, {.pragma: ilua, importc: "lua$1".}
importc: "luaopen_base".}
proc open_table*(L: PState): cint{.cdecl, dynlib: LIB_NAME, {.push callConv: cdecl, dynlib: lua.LIB_NAME.}
importc: "luaopen_table".} proc open_base*(L: PState): cint{.ilua.}
proc open_io*(L: PState): cint{.cdecl, dynlib: LIB_NAME, importc: "luaopen_io".} proc open_table*(L: PState): cint{.ilua.}
proc open_string*(L: PState): cint{.cdecl, dynlib: LIB_NAME, proc open_io*(L: PState): cint{.ilua.}
importc: "luaopen_string".} proc open_string*(L: PState): cint{.ilua.}
proc open_math*(L: PState): cint{.cdecl, dynlib: LIB_NAME, proc open_math*(L: PState): cint{.ilua.}
importc: "luaopen_math".} proc open_debug*(L: PState): cint{.ilua.}
proc open_debug*(L: PState): cint{.cdecl, dynlib: LIB_NAME, proc open_package*(L: PState): cint{.ilua.}
importc: "luaopen_debug".} proc openlibs*(L: PState){.importc: "luaL_openlibs".}
proc open_package*(L: PState): cint{.cdecl, dynlib: LIB_NAME, {.pop.}
importc: "luaopen_package".}
proc openlibs*(L: PState){.cdecl, dynlib: LIB_NAME, importc: "luaL_openlibs".}
proc baselibopen*(L: PState): Bool = proc baselibopen*(L: PState): Bool =
Result = open_base(L) != 0'i32 open_base(L) != 0'i32
proc tablibopen*(L: PState): Bool = proc tablibopen*(L: PState): Bool =
Result = open_table(L) != 0'i32 open_table(L) != 0'i32
proc iolibopen*(L: PState): Bool = proc iolibopen*(L: PState): Bool =
Result = open_io(L) != 0'i32 open_io(L) != 0'i32
proc strlibopen*(L: PState): Bool = proc strlibopen*(L: PState): Bool =
Result = open_string(L) != 0'i32 open_string(L) != 0'i32
proc mathlibopen*(L: PState): Bool = proc mathlibopen*(L: PState): Bool =
Result = open_math(L) != 0'i32 open_math(L) != 0'i32
proc dblibopen*(L: PState): Bool = proc dblibopen*(L: PState): Bool =
Result = open_debug(L) != 0'i32 open_debug(L) != 0'i32

View file

@ -196,7 +196,7 @@ const
proc SSL_library_init*(): cInt{.cdecl, dynlib: DLLSSLName, importc, discardable.} proc SSL_library_init*(): cInt{.cdecl, dynlib: DLLSSLName, importc, discardable.}
proc SSL_load_error_strings*(){.cdecl, dynlib: DLLSSLName, importc.} proc SSL_load_error_strings*(){.cdecl, dynlib: DLLSSLName, importc.}
proc ERR_load_BIO_strings*(){.cdecl, dynlib: DLLSSLName, importc.} proc ERR_load_BIO_strings*(){.cdecl, dynlib: DLLUtilName, importc.}
proc SSLv23_client_method*(): PSSL_METHOD{.cdecl, dynlib: DLLSSLName, importc.} proc SSLv23_client_method*(): PSSL_METHOD{.cdecl, dynlib: DLLSSLName, importc.}
proc SSLv23_method*(): PSSL_METHOD{.cdecl, dynlib: DLLSSLName, importc.} proc SSLv23_method*(): PSSL_METHOD{.cdecl, dynlib: DLLSSLName, importc.}
@ -262,13 +262,14 @@ proc ERR_error_string*(e: cInt, buf: cstring): cstring{.cdecl,
proc ERR_get_error*(): cInt{.cdecl, dynlib: DLLUtilName, importc.} proc ERR_get_error*(): cInt{.cdecl, dynlib: DLLUtilName, importc.}
proc ERR_peek_last_error*(): cInt{.cdecl, dynlib: DLLUtilName, importc.} proc ERR_peek_last_error*(): cInt{.cdecl, dynlib: DLLUtilName, importc.}
proc OpenSSL_add_all_algorithms*(){.cdecl, dynlib: DLLSSLName, importc: "OPENSSL_add_all_algorithms_conf".} proc OpenSSL_add_all_algorithms*(){.cdecl, dynlib: DLLUtilName, importc: "OPENSSL_add_all_algorithms_conf".}
proc OPENSSL_config*(configName: cstring){.cdecl, dynlib: DLLSSLName, importc.} proc OPENSSL_config*(configName: cstring){.cdecl, dynlib: DLLSSLName, importc.}
proc CRYPTO_set_mem_functions(a,b,c: pointer){.cdecl, dynlib: DLLSSLName, importc.} proc CRYPTO_set_mem_functions(a,b,c: pointer){.cdecl, dynlib: DLLSSLName, importc.}
proc CRYPTO_malloc_init*() = proc CRYPTO_malloc_init*() =
when not defined(windows):
CRYPTO_set_mem_functions(alloc, realloc, dealloc) CRYPTO_set_mem_functions(alloc, realloc, dealloc)
when True: when True:

View file

@ -156,6 +156,7 @@ const
type type
Thook_func* = proc (a2: cstring): cstring{.cdecl.} Thook_func* = proc (a2: cstring): cstring{.cdecl.}
when not defined(macosx):
# If non-null, this contains the address of a function that the application # If non-null, this contains the address of a function that the application
# wants called before trying the standard tilde expansions. The function # wants called before trying the standard tilde expansions. The function
# is called with the text sans tilde, and returns a malloc()'ed string # is called with the text sans tilde, and returns a malloc()'ed string
@ -229,6 +230,7 @@ type
# The current undo list for RL_LINE_BUFFER. # The current undo list for RL_LINE_BUFFER.
when not defined(macosx):
var undo_list*{.importc: "rl_undo_list", dynlib: readlineDll.}: ptr TUNDO_LIST var undo_list*{.importc: "rl_undo_list", dynlib: readlineDll.}: ptr TUNDO_LIST
# The data structure for mapping textual names to code addresses. # The data structure for mapping textual names to code addresses.
@ -239,6 +241,7 @@ type
function*: TCommandFunc function*: TCommandFunc
when not defined(macosx):
var funmap*{.importc: "funmap", dynlib: readlineDll.}: ptr ptr TFUNMAP var funmap*{.importc: "funmap", dynlib: readlineDll.}: ptr ptr TFUNMAP
# **************************************************************** # ****************************************************************

View file

@ -1,16 +1,16 @@
# #
# #
# Nimrod's Runtime Library # Nimrod's Runtime Library
# (c) Copyright 2008 Andreas Rumpf # (c) Copyright 2013 Andreas Rumpf
# #
# See the file "copying.txt", included in this # See the file "copying.txt", included in this
# distribution, for details about the copyright. # distribution, for details about the copyright.
# #
## Interface to the `libzip <http://www.nih.at/libzip/index.html>`_ library by ## Interface to the `libzip <http://www.nih.at/libzip/index.html>`_ library by
## Dieter Baron and Thomas Klausner. However, this does not need any external ## Dieter Baron and Thomas Klausner. This version links
## library (DLL, lib*.so), as the source for this library is included and ## against ``libzip2.so.2`` unless you define the symbol ``useLibzipSrc``; then
## compiled with this interface. ## it is compiled against some old ``libizp_all.c`` file.
# #
# zip.h -- exported declarations. # zip.h -- exported declarations.
@ -47,9 +47,16 @@
import times import times
{.compile: "libzip_all.c".} when defined(unix) and not defined(useLibzipSrc):
when defined(macosx):
{.pragma: mydll, dynlib: "libzip2.dylib".}
else:
{.pragma: mydll, dynlib: "libzip2.so(|.2|.1|.0)".}
else:
when defined(unix): when defined(unix):
{.passl: "-lz".} {.passl: "-lz".}
{.compile: "libzip_all.c".}
{.pragma: mydll.}
type type
Tzip_source_cmd* = int32 Tzip_source_cmd* = int32
@ -57,7 +64,7 @@ type
Tzip_source_callback* = proc (state: pointer, data: pointer, length: int, Tzip_source_callback* = proc (state: pointer, data: pointer, length: int,
cmd: Tzip_source_cmd): int {.cdecl.} cmd: Tzip_source_cmd): int {.cdecl.}
Pzip_stat* = ptr Tzip_stat Pzip_stat* = ptr Tzip_stat
Tzip_stat* {.final, pure.} = object Tzip_stat* = object ## the 'zip_stat' struct
name*: cstring ## name of the file name*: cstring ## name of the file
index*: int32 ## index within archive index*: int32 ## index within archive
crc*: int32 ## crc of file data crc*: int32 ## crc of file data
@ -67,9 +74,9 @@ type
comp_method*: int16 ## compression method used comp_method*: int16 ## compression method used
encryption_method*: int16 ## encryption method used encryption_method*: int16 ## encryption method used
Tzip {.final, pure.} = object Tzip = object
Tzip_source {.final, pure.} = object Tzip_source = object
Tzip_file {.final, pure.} = object Tzip_file = object
Pzip* = ptr Tzip ## represents a zip archive Pzip* = ptr Tzip ## represents a zip archive
Pzip_file* = ptr Tzip_file ## represents a file within an archive Pzip_file* = ptr Tzip_file ## represents a file within an archive
@ -160,82 +167,83 @@ const
constZIP_SOURCE_FREE* = 5'i32 ## cleanup and free resources constZIP_SOURCE_FREE* = 5'i32 ## cleanup and free resources
proc zip_add*(para1: Pzip, para2: cstring, para3: Pzip_source): int32 {.cdecl, proc zip_add*(para1: Pzip, para2: cstring, para3: Pzip_source): int32 {.cdecl,
importc: "zip_add".} importc: "zip_add", mydll.}
proc zip_add_dir*(para1: Pzip, para2: cstring): int32 {.cdecl, proc zip_add_dir*(para1: Pzip, para2: cstring): int32 {.cdecl,
importc: "zip_add_dir".} importc: "zip_add_dir", mydll.}
proc zip_close*(para1: Pzip) {.cdecl, importc: "zip_close".} proc zip_close*(para1: Pzip) {.cdecl, importc: "zip_close", mydll.}
proc zip_delete*(para1: Pzip, para2: int32): int32 {.cdecl, proc zip_delete*(para1: Pzip, para2: int32): int32 {.cdecl, mydll,
importc: "zip_delete".} importc: "zip_delete".}
proc zip_error_clear*(para1: Pzip) {.cdecl, importc: "zip_error_clear".} proc zip_error_clear*(para1: Pzip) {.cdecl, importc: "zip_error_clear", mydll.}
proc zip_error_get*(para1: Pzip, para2: ptr int32, para3: ptr int32) {.cdecl, proc zip_error_get*(para1: Pzip, para2: ptr int32, para3: ptr int32) {.cdecl,
importc: "zip_error_get".} importc: "zip_error_get", mydll.}
proc zip_error_get_sys_type*(para1: int32): int32 {.cdecl, proc zip_error_get_sys_type*(para1: int32): int32 {.cdecl, mydll,
importc: "zip_error_get_sys_type".} importc: "zip_error_get_sys_type".}
proc zip_error_to_str*(para1: cstring, para2: int, para3: int32, proc zip_error_to_str*(para1: cstring, para2: int, para3: int32,
para4: int32): int32 {.cdecl, para4: int32): int32 {.cdecl, mydll,
importc: "zip_error_to_str".} importc: "zip_error_to_str".}
proc zip_fclose*(para1: Pzip_file) {.cdecl, proc zip_fclose*(para1: Pzip_file) {.cdecl, mydll,
importc: "zip_fclose".} importc: "zip_fclose".}
proc zip_file_error_clear*(para1: Pzip_file) {.cdecl, proc zip_file_error_clear*(para1: Pzip_file) {.cdecl, mydll,
importc: "zip_file_error_clear".} importc: "zip_file_error_clear".}
proc zip_file_error_get*(para1: Pzip_file, para2: ptr int32, para3: ptr int32) {. proc zip_file_error_get*(para1: Pzip_file, para2: ptr int32, para3: ptr int32) {.
cdecl, importc: "zip_file_error_get".} cdecl, mydll, importc: "zip_file_error_get".}
proc zip_file_strerror*(para1: Pzip_file): cstring {.cdecl, proc zip_file_strerror*(para1: Pzip_file): cstring {.cdecl, mydll,
importc: "zip_file_strerror".} importc: "zip_file_strerror".}
proc zip_fopen*(para1: Pzip, para2: cstring, para3: int32): Pzip_file {.cdecl, proc zip_fopen*(para1: Pzip, para2: cstring, para3: int32): Pzip_file {.cdecl,
importc: "zip_fopen".} mydll, importc: "zip_fopen".}
proc zip_fopen_index*(para1: Pzip, para2: int32, para3: int32): Pzip_file {. proc zip_fopen_index*(para1: Pzip, para2: int32, para3: int32): Pzip_file {.
cdecl, importc: "zip_fopen_index".} cdecl, mydll, importc: "zip_fopen_index".}
proc zip_fread*(para1: Pzip_file, para2: pointer, para3: int): int {. proc zip_fread*(para1: Pzip_file, para2: pointer, para3: int): int {.
cdecl, importc: "zip_fread".} cdecl, mydll, importc: "zip_fread".}
proc zip_get_archive_comment*(para1: Pzip, para2: ptr int32, para3: int32): cstring {. proc zip_get_archive_comment*(para1: Pzip, para2: ptr int32, para3: int32): cstring {.
cdecl, importc: "zip_get_archive_comment".} cdecl, mydll, importc: "zip_get_archive_comment".}
proc zip_get_archive_flag*(para1: Pzip, para2: int32, para3: int32): int32 {. proc zip_get_archive_flag*(para1: Pzip, para2: int32, para3: int32): int32 {.
cdecl, importc: "zip_get_archive_flag".} cdecl, mydll, importc: "zip_get_archive_flag".}
proc zip_get_file_comment*(para1: Pzip, para2: int32, para3: ptr int32, proc zip_get_file_comment*(para1: Pzip, para2: int32, para3: ptr int32,
para4: int32): cstring {.cdecl, para4: int32): cstring {.cdecl, mydll,
importc: "zip_get_file_comment".} importc: "zip_get_file_comment".}
proc zip_get_name*(para1: Pzip, para2: int32, para3: int32): cstring {.cdecl, proc zip_get_name*(para1: Pzip, para2: int32, para3: int32): cstring {.cdecl,
importc: "zip_get_name".} mydll, importc: "zip_get_name".}
proc zip_get_num_files*(para1: Pzip): int32 {.cdecl, proc zip_get_num_files*(para1: Pzip): int32 {.cdecl,
importc: "zip_get_num_files".} mydll, importc: "zip_get_num_files".}
proc zip_name_locate*(para1: Pzip, para2: cstring, para3: int32): int32 {.cdecl, proc zip_name_locate*(para1: Pzip, para2: cstring, para3: int32): int32 {.cdecl,
importc: "zip_name_locate".} mydll, importc: "zip_name_locate".}
proc zip_open*(para1: cstring, para2: int32, para3: ptr int32): Pzip {.cdecl, proc zip_open*(para1: cstring, para2: int32, para3: ptr int32): Pzip {.cdecl,
importc: "zip_open".} mydll, importc: "zip_open".}
proc zip_rename*(para1: Pzip, para2: int32, para3: cstring): int32 {.cdecl, proc zip_rename*(para1: Pzip, para2: int32, para3: cstring): int32 {.cdecl,
importc: "zip_rename".} mydll, importc: "zip_rename".}
proc zip_replace*(para1: Pzip, para2: int32, para3: Pzip_source): int32 {.cdecl, proc zip_replace*(para1: Pzip, para2: int32, para3: Pzip_source): int32 {.cdecl,
importc: "zip_replace".} mydll, importc: "zip_replace".}
proc zip_set_archive_comment*(para1: Pzip, para2: cstring, para3: int32): int32 {. proc zip_set_archive_comment*(para1: Pzip, para2: cstring, para3: int32): int32 {.
cdecl, importc: "zip_set_archive_comment".} cdecl, mydll, importc: "zip_set_archive_comment".}
proc zip_set_archive_flag*(para1: Pzip, para2: int32, para3: int32): int32 {. proc zip_set_archive_flag*(para1: Pzip, para2: int32, para3: int32): int32 {.
cdecl, importc: "zip_set_archive_flag".} cdecl, mydll, importc: "zip_set_archive_flag".}
proc zip_set_file_comment*(para1: Pzip, para2: int32, para3: cstring, proc zip_set_file_comment*(para1: Pzip, para2: int32, para3: cstring,
para4: int32): int32 {.cdecl, para4: int32): int32 {.cdecl, mydll,
importc: "zip_set_file_comment".} importc: "zip_set_file_comment".}
proc zip_source_buffer*(para1: Pzip, para2: pointer, para3: int, para4: int32): Pzip_source {. proc zip_source_buffer*(para1: Pzip, para2: pointer, para3: int, para4: int32): Pzip_source {.
cdecl, importc: "zip_source_buffer".} cdecl, mydll, importc: "zip_source_buffer".}
proc zip_source_file*(para1: Pzip, para2: cstring, para3: int, para4: int): Pzip_source {. proc zip_source_file*(para1: Pzip, para2: cstring, para3: int, para4: int): Pzip_source {.
cdecl, importc: "zip_source_file".} cdecl, mydll, importc: "zip_source_file".}
proc zip_source_filep*(para1: Pzip, para2: TFile, para3: int, para4: int): Pzip_source {. proc zip_source_filep*(para1: Pzip, para2: TFile, para3: int, para4: int): Pzip_source {.
cdecl, importc: "zip_source_filep".} cdecl, mydll, importc: "zip_source_filep".}
proc zip_source_free*(para1: Pzip_source) {.cdecl, proc zip_source_free*(para1: Pzip_source) {.cdecl, mydll,
importc: "zip_source_free".} importc: "zip_source_free".}
proc zip_source_function*(para1: Pzip, para2: Tzip_source_callback, proc zip_source_function*(para1: Pzip, para2: Tzip_source_callback,
para3: pointer): Pzip_source {.cdecl, para3: pointer): Pzip_source {.cdecl, mydll,
importc: "zip_source_function".} importc: "zip_source_function".}
proc zip_source_zip*(para1: Pzip, para2: Pzip, para3: int32, para4: int32, proc zip_source_zip*(para1: Pzip, para2: Pzip, para3: int32, para4: int32,
para5: int, para6: int): Pzip_source {.cdecl, para5: int, para6: int): Pzip_source {.cdecl, mydll,
importc: "zip_source_zip".} importc: "zip_source_zip".}
proc zip_stat*(para1: Pzip, para2: cstring, para3: int32, para4: Pzip_stat): int32 {. proc zip_stat*(para1: Pzip, para2: cstring, para3: int32, para4: Pzip_stat): int32 {.
cdecl, importc: "zip_stat".} cdecl, mydll, importc: "zip_stat".}
proc zip_stat_index*(para1: Pzip, para2: int32, para3: int32, para4: Pzip_stat): int32 {. proc zip_stat_index*(para1: Pzip, para2: int32, para3: int32, para4: Pzip_stat): int32 {.
cdecl, importc: "zip_stat_index".} cdecl, mydll, importc: "zip_stat_index".}
proc zip_stat_init*(para1: Pzip_stat) {.cdecl, importc: "zip_stat_init".} proc zip_stat_init*(para1: Pzip_stat) {.cdecl, mydll, importc: "zip_stat_init".}
proc zip_strerror*(para1: Pzip): cstring {.cdecl, importc: "zip_strerror".} proc zip_strerror*(para1: Pzip): cstring {.cdecl, mydll, importc: "zip_strerror".}
proc zip_unchange*(para1: Pzip, para2: int32): int32 {.cdecl, proc zip_unchange*(para1: Pzip, para2: int32): int32 {.cdecl, mydll,
importc: "zip_unchange".} importc: "zip_unchange".}
proc zip_unchange_all*(para1: Pzip): int32 {.cdecl, importc: "zip_unchange_all".} proc zip_unchange_all*(para1: Pzip): int32 {.cdecl, mydll,
proc zip_unchange_archive*(para1: Pzip): int32 {.cdecl, importc: "zip_unchange_all".}
proc zip_unchange_archive*(para1: Pzip): int32 {.cdecl, mydll,
importc: "zip_unchange_archive".} importc: "zip_unchange_archive".}

View file

@ -0,0 +1,29 @@
# Tries to test the full ownership path generated by idetools.
proc lev1(t1: string) =
var temp = t1
for i in 0..len(temp)-1:
temp[i] = chr(int(temp[i]) + 1)
proc lev2(t2: string) =
var temp = t2
for i in 0..len(temp)-1:
temp[i] = chr(int(temp[i]) + 1)
proc lev3(t3: string) =
var temp = t3
for i in 0..len(temp)-1:
temp[i] = chr(int(temp[i]) + 1)
proc lev4(t4: string) =
var temp = t4
for i in 0..len(temp)-1:
temp[i] = chr(int(temp[i]) + 1)
echo temp & "(lev4)"
lev4(temp & "(lev3)")
lev3(temp & "(lev2)")
lev2(temp & "(lev1)")
when isMainModule:
lev1("abcd")

View file

@ -0,0 +1,29 @@
absurd_nesting.nim
> c --verbosity:0 --hints:on
SuccessX
> idetools --track:$TESTNIM,6,6 --def $SILENT
skVar\tabsurd_nesting.lev1.temp\tstring
> idetools --track:$TESTNIM,21,13 --def $SILENT
skVar\tabsurd_nesting.lev1.lev2.lev3.lev4.temp\tstring
> idetools --track:$TESTNIM,6,27 --def $SILENT
skForVar\tabsurd_nesting.lev1.i\tint
> idetools --track:$TESTNIM,21,33 --def $SILENT
skForVar\tabsurd_nesting.lev1.lev1.lev3.lev4.i\tint
> idetools --track:$TESTNIM,24,8 --def $SILENT
skProc\tabsurd_nesting.lev1.lev1.lev3.lev4\tproc \(string\)
> idetools --track:$TESTNIM,4,13 --def $SILENT
skParam\tabsurd_nesting.lev1.t1\tstring
> idetools --track:$TESTNIM,4,13 --def $SILENT
skParam\tabsurd_nesting.lev1.t1\tstring
> idetools --track:$TESTNIM,19,19 --def $SILENT
skParam\tabsurd_nesting.lev1.lev2.lev3.lev4.t4\tstring

View file

@ -1,7 +1,10 @@
main.nim main.nim
> c > c --verbosity:0 --hints:on
SuccessX
> c
! Processing
SuccessX SuccessX
# The "Processing" string will be found always in proc mode since each
# compilation command will generate it. We need to test it only in Caas mode to
# verify the server is not recompiling again the file.
CaasRun > c --verbosity:0 --hints:on
CaasRun ! Processing
CaasRun SuccessX

View file

@ -1,7 +1,7 @@
main.nim main.nim
> c > c --verbosity:0 --hints:on
SuccessX SuccessX
> idetools --trackDirty:main_dirty.nim,main.nim,12,7 --suggest main.nim > idetools --trackDirty:main_dirty.nim,$TESTNIM,12,7 --suggest $SILENT
skField\tx skField\tx
skField\ty skField\ty

View file

@ -1,9 +1,11 @@
main.nim main.nim
> c > c --verbosity:0 --hints:on
SuccessX SuccessX
> idetools --track:main.nim,5,18 --def main.nim
> idetools --track:$TESTNIM,5,18 --def --verbosity:0 --hints:on
strutils.toUpper strutils.toUpper
SuccessX ! SuccessX
> idetools --track:main.nim,5,18 --def main.nim
> idetools --track:$TESTNIM,5,18 --def --verbosity:0 --hints:on
strutils.toUpper strutils.toUpper
SuccessX ! SuccessX

View file

@ -1,10 +1,12 @@
main.nim main.nim
> idetools --track:main.nim,5,18 --def main.nim > idetools --track:$TESTNIM,5,18 --def --verbosity:0 --hints:on
strutils.toUpper strutils.toUpper
SuccessX ! SuccessX
> idetools --track:main.nim,5,18 --def main.nim
> idetools --track:$TESTNIM,5,18 --def --verbosity:0 --hints:on
strutils.toUpper strutils.toUpper
SuccessX ! SuccessX
> c
> c --verbosity:0 --hints:on
SuccessX SuccessX

View file

@ -1,7 +1,8 @@
main.nim main.nim
> idetools --track:main.nim,5,18 --def main.nim > idetools --track:$TESTNIM,5,18 --def --verbosity:0 --hints:on
strutils.toUpper strutils.toUpper
SuccessX ! SuccessX
> c
> c --verbosity:0 --hints:on
SuccessX SuccessX

View file

@ -0,0 +1,15 @@
# This example shows that idetools returns an empty signature for a forward
# declared proc in proc/symproc runs, but correctly returns the full signature
# in caas mode.
proc echoHello(text: string)
proc testForward() =
echo "T"
echoHello("T")
proc echoHello(text: string) =
echo "Hello Mr." & text
when isMainModule:
testForward()

View file

@ -0,0 +1,9 @@
forward_declarations.nim
> idetools --track:$TESTNIM,9,5 --def $SILENT
skProc
proc \(string\)
> idetools --track:$TESTNIM,5,9 --def $SILENT
skProc
proc \(string\)

View file

@ -0,0 +1,17 @@
forward_declarations.nim
> c --verbosity:0 --hints:on
SuccessX
# None of the following return three instances of the echoHello proc, the first
# being the forward declaration, the second being the usage inside testForward,
# and the third being the actual implementation.
> idetools --track:$TESTNIM,5,5 --usages $SILENT
skProc.*\n.*skProc.*\n.*skProc
> idetools --track:$TESTNIM,9,5 --usages $SILENT
skProc.*\n.*skProc.*\n.*skProc
> idetools --track:$TESTNIM,11,5 --usages $SILENT
skProc.*\n.*skProc.*\n.*skProc

View file

@ -0,0 +1,43 @@
import unicode, sequtils
proc test_enums() =
var o: Tfile
if o.open("files " & "test.txt", fmWrite):
o.write("test")
o.close()
proc test_iterators(filename = "tests.nim") =
let
input = readFile(filename)
letters = toSeq(runes(string(input)))
for letter in letters: echo int(letter)
const SOME_SEQUENCE = @[1, 2]
type
bad_string = distinct string
TPerson = object of TObject
name*: bad_string
age: int
proc adder(a, b: int): int =
result = a + b
type
PExpr = ref object of TObject ## abstract base class for an expression
PLiteral = ref object of PExpr
x: int
PPlusExpr = ref object of PExpr
a, b: PExpr
# watch out: 'eval' relies on dynamic binding
method eval(e: PExpr): int =
# override this base method
quit "to override!"
method eval(e: PLiteral): int = e.x
method eval(e: PPlusExpr): int = eval(e.a) + eval(e.b)
proc newLit(x: int): PLiteral = PLiteral(x: x)
proc newPlus(a, b: PExpr): PPlusExpr = PPlusExpr(a: a, b: b)
echo eval(newPlus(newPlus(newLit(1), newLit(2)), newLit(4)))

View file

@ -0,0 +1,44 @@
idetools_api.nim
> c --verbosity:0 --hints:on
SuccessX
> idetools --track:$TESTNIM,4,11 --def $SILENT
def\tskType\tsystem.TFile\tTFile
> idetools --track:$TESTNIM,5,7 --def $SILENT
def\tskProc\tsystem.Open\tproc \(var TFile, string, TFileMode, int\): bool
> idetools --track:$TESTNIM,5,21 --def $SILENT
def\tskProc\tsystem.\&\tproc \(string, string\): string\{.noSideEffect.\}
> idetools --track:$TESTNIM,5,38 --def $SILENT
def\tskEnumField\tsystem.TFileMode.fmWrite\tTFileMode
> idetools --track:$TESTNIM,7,6 --def $SILENT
def\tskProc\tsystem.Close\tproc \(TFile\)
> idetools --track:$TESTNIM,12,23 --def $SILENT
def\tskIterator\tunicode.runes\titerator \(string\): TRune
> idetools --track:$TESTNIM,12,15 --def $SILENT
def\tskTemplate\tsequtils.toSeq\tproc \(expr\): expr
> idetools --track:$TESTNIM,15,7 --def $SILENT
# ProcRun mode will fail the next line, because the type is returned empty.
def\tskConst\t$MODULE.SOME_SEQUENCE\tseq\[int\]\t
> idetools --track:$TESTNIM,15,23 --def $SILENT
def\tskProc\tsystem.@\tproc \(array\[IDX, T\]\): seq\[T\]\{.noSideEffect.\}
> idetools --track:$TESTNIM,17,3 --def $SILENT
# ProcRun mode will fail the next line, because the type is returned empty.
def\tskType\t$MODULE.bad_string\tbad_string\t
> idetools --track:$TESTNIM,11,24 --def $SILENT
def\tskParam\t$MODULE.test_iterators.filename\tstring
> idetools --track:$TESTNIM,6,5 --def $SILENT
def\tskVar\t$MODULE.test_enums.o\tTFile
> idetools --track:$TESTNIM,12,34 --def $SILENT
def\tskLet\t$MODULE.test_iterators.input\tTaintedString
> idetools --track:$TESTNIM,13,35 --def $SILENT
def\tskForVar\t$MODULE.test_iterators.letter\tTRune
> idetools --track:$TESTNIM,23,3 --def $SILENT
def\tskResult\t$MODULE.adder.result\tint
> idetools --track:$TESTNIM,19,6 --def $SILENT
# ProcRun mode will fail the next line, because the type is returned empty.
def\tskField\t$MODULE.TPerson.name\tbad_string\t
> idetools --track:$TESTNIM,43,7 --def $SILENT
def\tskMethod\t$MODULE.eval\tproc \(PPlusExpr\): int\t

View file

@ -0,0 +1,17 @@
import unicode, sequtils
proc test() =
let input = readFile("weird.nim")
for letter in runes(string(input)):
echo int(letter)
when 1 > 0:
proc failtest() =
let
input = readFile("weird.nim")
letters = toSeq(runes(string(input)))
for letter in letters:
echo int(letter)
when isMainModule:
test()

View file

@ -0,0 +1,14 @@
issue_416_template_shift.nim
> c --verbosity:0 --hints:on
SuccessX
> idetools --track:$TESTNIM,12,28 --def $SILENT
def\tskType\tsystem.string\tstring
> idetools --track:$TESTNIM,12,35 --def $SILENT
def\tskLet\t$MODULE.failtest.input\tTaintedString
# The following fail because they seem shifted one colum to the right.
> idetools --track:$TESTNIM,12,16 --def $SILENT
def\tskTemplate\tsequtils.toSeq\tproc \(expr\): expr
> idetools --track:$TESTNIM,12,22 --def $SILENT
def\tskIterator\tunicode.runes\titerator \(string\): TRune

View file

@ -0,0 +1,8 @@
const
VERSION_STR1* = "0.5.0" ## Idetools shifts this one column.
VERSION_STR2 = "0.5.0" ## This one is ok.
VERSION_STR3* = "0.5.0" ## Bad.
VERSION_STR4 = "0.5.0" ## Ok.
proc forward1*(): string = result = ""
proc forward2(): string = result = ""

View file

@ -0,0 +1,11 @@
issue_452_export_shift.nim
> c --verbosity:0 --hints:on
SuccessX
> idetools --track:$TESTNIM,2,2 --def $SILENT
def\tskConst\t$MODULE.VERSION_STR1\tstring
> idetools --track:$TESTNIM,3,2 --def $SILENT
def\tskConst\t$MODULE.VERSION_STR2\tstring
> idetools --track:$TESTNIM,7,5 --def $SILENT
def\tskProc\t$MODULE.forward1\tproc \(\): string\t
> idetools --track:$TESTNIM,8,5 --def $SILENT
def\tskProc\t$MODULE.forward2\tproc \(\): string\t

View file

@ -0,0 +1,19 @@
type
TThing = object of TObject
TUnit = object of TThing
x: int
method collide(a, b: TThing) {.inline.} =
quit "to override!"
method collide(a: TThing, b: TUnit) {.inline.} =
echo "collide1"
method collide(a: TUnit, b: TThing) {.inline.} =
echo "collide2"
var
a, b: TUnit
when isMainModule:
collide(a, b) # output: 2

View file

@ -0,0 +1,5 @@
issue_477_dynamic_dispatch.nim
> c --run
SuccessX
> idetools --track:issue_477_dynamic_dispatch.nim,19,5 --def $SILENT
def\tskMethod\tissue_477_dynamic_dispatch.collide\tproc \(TUnit, TThing\)\{.inline.\}

View file

@ -0,0 +1,29 @@
import unicode, sequtils
# This example shows that idetools returns proc as signature for everything
# which can be called. While a clever person would use the second column to
# differentiate betwen procs, methods and others, why does the output contain
# incorrect information?
type
TThing = object of TObject
TUnit = object of TThing
x: int
method collide(a, b: TThing) {.inline.} =
quit "to override!"
method collide(a: TThing, b: TUnit) {.inline.} =
echo "1"
method collide(a: TUnit, b: TThing) {.inline.} =
echo "2"
var
a, b: TUnit
let
input = readFile("its_full_of_procs.nim")
letters = toSeq(runes(string(input)))
collide(a, b) # output: 2

View file

@ -0,0 +1,20 @@
its_full_of_procs.nim
> idetools --track:$TESTNIM,26,15 --def $SILENT
skProc
proc \(
> idetools --track:$TESTNIM,27,21 --def $SILENT
skIterator
iterator \(
!proc \(
> idetools --track:$TESTNIM,29,0 --def $SILENT
skMethod
method \(
!proc \(
> idetools --track:$TESTNIM,27,15 --def $SILENT
skTemplate
template \(
!proc \(

View file

@ -1,7 +1,7 @@
main.nim main.nim
> idetools --trackDirty:main_dirty.nim,main.nim,12,7 --suggest main.nim > idetools --trackDirty:main_dirty.nim,$TESTNIM,12,7 --suggest $SILENT
skField\tx skField\tx
skField\ty skField\ty
> c > c --verbosity:0 --hints:on
SuccessX SuccessX

View file

@ -1,22 +1,97 @@
import osproc, streams, os, strutils, re import osproc, streams, os, strutils, re
type ## Compiler as a service tester.
TNimrodSession* = object ##
nim: PProcess ## This test cases uses the txt files in the caas/ subdirectory.
##
## Each of the text files inside encodes a session with the compiler:
##
## The first line indicates the main project file.
##
## Lines starting with '>' indicate a command to be sent to the compiler and
## the lines following a command include checks for expected or forbidden
## output (! for forbidden).
##
## If a line starts with '#' it will be ignored completely, so you can use that
## for comments.
##
## All the tests are run both in ProcRun (each command creates a separate
## process) and CaasRun (first command starts up a server and it is reused for
## the rest) modes. Since some cases are specific to either ProcRun or CaasRun
## modes, you can prefix a line with the mode and the line will be processed
## only in that mode.
##
## The rest of the line is treated as a regular expression, so be careful
## escaping metacharacters like parenthesis. Before the line is processed as a
## regular expression, some basic variables are searched for and replaced in
## the tests. The variables which will be replaced are:
##
## - $TESTNIM: filename specified in the first line of the script.
## - $MODULE: like $TESTNIM but without extension, useful for expected output.
##
## You can optionally pass parameters at the command line to modify the
## behaviour of the test suite. By default only tests which fail will be echoed
## to stdout. If you want to see all the output pass the word "verbose" as a
## parameter.
##
## If you don't want to run all the test case files, you can pass any substring
## as a parameter. Only files matching the passed substring will be run. The
## filtering doesn't use any globbing metacharacters, it's a plain match.
##
## Example to run only "*-compile*.txt" tests in verbose mode:
##
## ./caasdriver verbose -compile
proc dirname(path: string): string = path.splitPath()[0]
type
TRunMode = enum
ProcRun, CaasRun, SymbolProcRun
TNimrodSession* = object
nim: PProcess # Holds the open process for CaasRun sessions, nil otherwise.
mode: TRunMode # Stores the type of run mode the session was started with.
lastOutput: string # Preserves the last output, needed for ProcRun mode.
filename: string # Appended to each command starting with '>'. Also a var.
modname: string # Like filename but without extension.
nimcache: string # Input script based name for the nimcache dir.
const
modes = [CaasRun, ProcRun, SymbolProcRun]
filenameReplaceVar = "$TESTNIM"
moduleReplaceVar = "$MODULE"
silentReplaceVar = "$SILENT"
silentReplaceText = "--verbosity:0 --hints:off"
var var
TesterDir = getAppDir() TesterDir = getAppDir()
NimrodBin = TesterDir / "../bin/nimrod" NimrodBin = TesterDir / "../bin/nimrod"
proc startNimrodSession*(project: string): TNimrodSession = proc replaceVars(session: var TNimrodSession, text: string): string =
result.nim = startProcess(NimrodBin, result = text.replace(filenameReplaceVar, session.filename)
workingDir = project.dirname, result = result.replace(moduleReplaceVar, session.modname)
args = ["serve", "--server.type:stdin", project]) result = result.replace(silentReplaceVar, silentReplaceText)
proc doCommand*(session: var TNimrodSession, command: string): string = proc startNimrodSession(project, script: string, mode: TRunMode):
session.nim.inputStream.write(command & "\n") TNimrodSession =
let (dir, name, ext) = project.splitFile
result.mode = mode
result.lastOutput = ""
result.filename = name & ext
result.modname = name
let (nimcacheDir, nimcacheName, nimcacheExt) = script.splitFile
result.nimcache = "SymbolProcRun." & nimcacheName
if mode == SymbolProcRun:
removeDir(nimcacheDir / result.nimcache)
if mode == CaasRun:
result.nim = startProcess(NimrodBin, workingDir = dir,
args = ["serve", "--server.type:stdin", name])
proc doCaasCommand(session: var TNimrodSession, command: string): string =
assert session.mode == CaasRun
session.nim.inputStream.write(session.replaceVars(command) & "\n")
session.nim.inputStream.flush session.nim.inputStream.flush
result = "" result = ""
@ -30,10 +105,40 @@ proc doCommand*(session: var TNimrodSession, command: string): string =
result = "FAILED TO EXECUTE: " & command & "\n" & result result = "FAILED TO EXECUTE: " & command & "\n" & result
break break
proc doProcCommand(session: var TNimrodSession, command: string): string =
assert session.mode == ProcRun or session.mode == SymbolProcRun
except: result = "FAILED TO EXECUTE: " & command & "\n" & result
var
process = startProcess(NimrodBin, args = session.replaceVars(command).split)
stream = outputStream(process)
line = TaintedString("")
result = ""
while stream.readLine(line):
if result.len > 0: result &= "\n"
result &= line.string
process.close()
proc doCommand(session: var TNimrodSession, command: string) =
if session.mode == CaasRun:
session.lastOutput = doCaasCommand(session,
command & " " & session.filename)
else:
var command = command
# For symbol runs we prepend the necessary parameters to avoid clobbering
# the normal nimcache.
if session.mode == SymbolProcRun:
command = "--symbolFiles:on --nimcache:" & session.nimcache &
" " & command
session.lastOutput = doProcCommand(session,
command & " " & session.filename)
proc close(session: var TNimrodSession) {.destructor.} = proc close(session: var TNimrodSession) {.destructor.} =
if session.mode == CaasRun:
session.nim.close session.nim.close
proc doScenario(script: string, output: PStream): bool = proc doScenario(script: string, output: PStream, mode: TRunMode): bool =
result = true result = true
var f = open(script) var f = open(script)
@ -41,27 +146,40 @@ proc doScenario(script: string, output: PStream): bool =
if f.readLine(project): if f.readLine(project):
var var
s = startNimrodSession(script.dirname / project.string) s = startNimrodSession(script.parentDir / project.string, script, mode)
tline = TaintedString("") tline = TaintedString("")
lastOutput = ""
ln = 1 ln = 1
while f.readLine(tline): while f.readLine(tline):
var line = tline.string var line = tline.string
inc ln inc ln
# Filter lines by run mode, removing the prefix if the mode is current.
for testMode in modes:
if line.startsWith($testMode):
if testMode != mode:
line = ""
else:
line = line[len($testMode)..len(line) - 1].strip
break
if line.strip.len == 0: continue if line.strip.len == 0: continue
if line.startsWith(">"): if line.startsWith("#"):
lastOutput = s.doCommand(line.substr(1).strip) output.writeln line
output.writeln line, "\n", lastOutput continue
elif line.startsWith(">"):
s.doCommand(line.substr(1).strip)
output.writeln line, "\n", s.lastOutput
else: else:
var expectMatch = true var expectMatch = true
var pattern = line var pattern = s.replaceVars(line)
if line.startsWith("!"): if line.startsWith("!"):
pattern = line.substr(1).strip pattern = pattern.substr(1).strip
expectMatch = false expectMatch = false
var actualMatch = lastOutput.find(re(pattern)) != -1 let actualMatch =
s.lastOutput.find(re(pattern, flags = {reStudy})) != -1
if expectMatch == actualMatch: if expectMatch == actualMatch:
output.writeln "SUCCESS ", line output.writeln "SUCCESS ", line
@ -70,17 +188,33 @@ proc doScenario(script: string, output: PStream): bool =
result = false result = false
iterator caasTestsRunner*(filter = ""): tuple[test, output: string, iterator caasTestsRunner*(filter = ""): tuple[test, output: string,
status: bool] = status: bool, mode: TRunMode] =
for scenario in os.walkFiles(TesterDir / "caas/*.txt"): for scenario in os.walkFiles(TesterDir / "caas/*.txt"):
if filter.len > 0 and find(scenario, filter) == -1: continue if filter.len > 0 and find(scenario, filter) == -1: continue
for mode in modes:
var outStream = newStringStream() var outStream = newStringStream()
let r = doScenario(scenario, outStream) let r = doScenario(scenario, outStream, mode)
yield (scenario, outStream.data, r) yield (scenario, outStream.data, r, mode)
when isMainModule: when isMainModule:
var filter = "" var
if paramCount() > 0: filter = paramStr(1) filter = ""
failures = 0
verbose = false
for t, o, r in caasTestsRunner(filter): for i in 0..ParamCount() - 1:
echo t, "\n", o let param = string(paramStr(i + 1))
case param
of "verbose": verbose = true
else: filter = param
if verbose and len(filter) > 0:
echo "Running only test cases matching filter '$1'" % [filter]
for test, output, result, mode in caasTestsRunner(filter):
if not result or verbose:
echo test, "\n", output, "-> ", $mode, ":", $result, "\n-----"
if not result:
failures += 1
quit(failures)

View file

@ -15,3 +15,12 @@ else:
return tkUnknown return tkUnknown
case_token: inc i case_token: inc i
#bug #488
macro foo: stmt =
var exp = newCall("whatwhat", newIntLitNode(1))
if compiles(getAst(exp)): return exp
else: echo "Does not compute!"
foo()

View file

@ -10,6 +10,8 @@ proc parseInt(x: TFile): int {.noSideEffect.} = nil
proc parseInt(x: char): int {.noSideEffect.} = nil proc parseInt(x: char): int {.noSideEffect.} = nil
proc parseInt(x: int16): int {.noSideEffect.} = nil proc parseInt(x: int16): int {.noSideEffect.} = nil
proc parseInt[T](x: T): int = echo x; 34
type type
TParseInt = proc (x: string): int {.noSideEffect.} TParseInt = proc (x: string): int {.noSideEffect.}
@ -33,3 +35,5 @@ type
proc bar[a,b](f: TFoo[a,b], x: a) = echo(x, " ", f.lorem, f.ipsum) proc bar[a,b](f: TFoo[a,b], x: a) = echo(x, " ", f.lorem, f.ipsum)
proc bar[a,b](f: TFoo[a,b], x: b) = echo(x, " ", f.lorem, f.ipsum) proc bar[a,b](f: TFoo[a,b], x: b) = echo(x, " ", f.lorem, f.ipsum)
discard parseInt[string]("yay")

View file

@ -4,8 +4,8 @@ s = socket()
s.connect("www.google.com", TPort(80)) s.connect("www.google.com", TPort(80))
var recvData: string = "" var data: string = ""
echo(s.recvLine(recvData)) s.readLine(data)
echo(recvData) echo(data)

View file

@ -0,0 +1,494 @@
## Command line parsing module for Nimrod.
##
## `Nimrod <http://nimrod-code.org>`_ provides the `parseopt module
## <http://nimrod-code.org/parseopt.html>`_ to parse options from the
## commandline. This module tries to provide functionality to prevent you from
## writing commandline parsing and let you concentrate on providing the best
## possible experience for your users.
##
## Source code for this module can be found at
## https://github.com/gradha/argument_parser.
import os, strutils, tables, math, parseutils, sequtils, sets, algorithm,
unicode
const
VERSION_STR* = "0.1.2" ## Module version as a string.
VERSION_INT* = (major: 0, minor: 1, maintenance: 2) ## \
## Module version as an integer tuple.
##
## Major versions changes mean a break in API backwards compatibility, either
## through removal of symbols or modification of their purpose.
##
## Minor version changes can add procs (and maybe default parameters). Minor
## odd versions are development/git/unstable versions. Minor even versions
## are public stable releases.
##
## Maintenance version changes mean bugfixes or non API changes.
# - Types
type
Tparam_kind* = enum ## Different types of results for parameter parsing.
PK_EMPTY, PK_INT, PK_FLOAT, PK_STRING, PK_BOOL,
PK_BIGGEST_INT, PK_BIGGEST_FLOAT, PK_HELP
Tparameter_callback* =
proc (parameter: string; value: var Tparsed_parameter): string ## \
## Prototype of parameter callbacks
##
## A parameter callback is just a custom proc you provide which is invoked
## after a parameter is parsed passing the basic type validation. The
## `parameter` parameter is the string which triggered the option. The
## `value` parameter contains the string passed by the user already parsed
## into the basic type you specified for it.
##
## The callback proc has modification access to the Tparsed_parameter
## `value` parameter that will be put into Tcommandline_results: you can
## read it and also modify it, maybe changing its type. In fact, if you
## need special parsing, most likely you will end up specifying PK_STRING
## in the parameter input specification so that the parse() proc doesn't
## *mangle* the string before you can process it yourself.
##
## If the callback decides to abort the validation of the parameter, it has
## to put into result a non zero length string with a message for the user
## explaining why the validation failed, and maybe offer a hint as to what
## can be done to pass validation.
Tparameter_specification* = object ## \
## Holds the expectations of a parameter.
##
## You create these objects and feed them to the parse() proc, which then
## uses them to detect parameters and turn them into something uself.
names*: seq[string] ## List of possible parameters to catch for this.
consumes*: Tparam_kind ## Expected type of the parameter (empty for none)
custom_validator*: Tparameter_callback ## Optional custom callback
## to run after type conversion.
help_text*: string ## Help for this group of parameters.
Tparsed_parameter* = object ## \
## Contains the parsed value from the user.
##
## This implements an object variant through the kind field. You can 'case'
## this field to write a generic proc to deal with parsed parameters, but
## nothing prevents you from accessing directly the type of field you want
## if you expect only one kind.
case kind*: Tparam_kind
of PK_EMPTY: nil
of PK_INT: int_val*: int
of PK_BIGGEST_INT: big_int_val*: biggestInt
of PK_FLOAT: float_val*: float
of PK_BIGGEST_FLOAT: big_float_val*: biggestFloat
of PK_STRING: str_val*: string
of PK_BOOL: bool_val*: bool
of PK_HELP: nil
Tcommandline_results* = object of TObject ## \
## Contains the results of the parsing.
##
## Usually this is the result of the parse() call, but you can inherit from
## it to add your own fields for convenience.
##
## Note that you always have to access the ``options`` ordered table with
## the first variant of a parameter name. For instance, if you have an
## option specified like ``@["-s", "--silent"]`` and the user types
## ``--silent`` at the commandline, you have to use
## ``options.hasKey("-s")`` to test for it. This standarizes access through
## the first name variant for all options to avoid you repeating the test
## with different keys.
positional_parameters*: seq[Tparsed_parameter]
options*: TOrderedTable[string, Tparsed_parameter]
# - Tparam_kind procs
proc `$`*(value: Tparam_kind): string {.procvar.} =
## Stringifies the type, used to generate help texts.
case value:
of PK_EMPTY: result = ""
of PK_INT: result = "INT"
of PK_BIGGEST_INT: result = "BIG_INT"
of PK_FLOAT: result = "FLOAT"
of PK_BIGGEST_FLOAT: result = "BIG_FLOAG"
of PK_STRING: result = "STRING"
of PK_BOOL: result = "BOOL"
of PK_HELP: result = ""
# - Tparameter_specification procs
proc init*(param: var Tparameter_specification, consumes = PK_EMPTY,
custom_validator: Tparameter_callback = nil, help_text = "",
names: varargs[string]) =
## Initialization helper with default parameters.
##
## You can decide to miss some if you like the defaults, reducing code. You
## can also use new_parameter_specification() for single assignment
## variables.
param.names = @names
param.consumes = consumes
param.custom_validator = custom_validator
param.help_text = help_text
proc new_parameter_specification*(consumes = PK_EMPTY,
custom_validator: Tparameter_callback = nil, help_text = "",
names: varargs[string]): Tparameter_specification =
## Initialization helper for single assignment variables.
result.init(consumes, custom_validator, help_text, names)
# - Tparsed_parameter procs
proc `$`*(data: Tparsed_parameter): string {.procvar.} =
## Stringifies the value, mostly for debug purposes.
##
## The proc will display the value followed by non string type in brackets.
## The non string types would be PK_INT (i), PK_BIGGEST_INT (I), PK_FLOAT
## (f), PK_BIGGEST_FLOAT (F), PK_BOOL (b). The string type would be enclosed
## inside quotes. PK_EMPTY produces the word `nil`, and PK_HELP produces the
## world `help`.
case data.kind:
of PK_EMPTY: result = "nil"
of PK_INT: result = "$1(i)" % $data.int_val
of PK_BIGGEST_INT: result = "$1(I)" % $data.big_int_val
of PK_FLOAT: result = "$1(f)" % $data.float_val
of PK_BIGGEST_FLOAT: result = "$1(F)" % $data.big_float_val
of PK_STRING: result = "\"" & $data.str_val & "\""
of PK_BOOL: result = "$1(b)" % $data.bool_val
of PK_HELP: result = "help"
template new_parsed_parameter*(tkind: Tparam_kind, expr): Tparsed_parameter =
## Handy compile time template to build Tparsed_parameter object variants.
##
## The problem with object variants is that you first have to initialise them
## to a kind, then assign values to the correct variable, and it is a little
## bit annoying.
##
## Through this template you specify as the first parameter the kind of the
## Tparsed_parameter you want to build, and directly the value it will be
## initialised with. The template figures out at compile time what field to
## assign the variable to, and thus you reduce code clutter and may use this
## to initialise single assignments variables in `let` blocks. Example:
##
## .. code-block:: nimrod
## let
## parsed_param1 = new_parsed_parameter(PK_FLOAT, 3.41)
## parsed_param2 = new_parsed_parameter(PK_BIGGEST_INT, 2358123 * 23123)
## # The following line doesn't compile due to
## # type mismatch: got (string) but expected 'int'
## #parsed_param3 = new_parsed_parameter(PK_INT, "231")
var result {.gensym.}: Tparsed_parameter
result.kind = tkind
when tkind == PK_EMPTY: nil
elif tkind == PK_INT: result.int_val = expr
elif tkind == PK_BIGGEST_INT: result.big_int_val = expr
elif tkind == PK_FLOAT: result.float_val = expr
elif tkind == PK_BIGGEST_FLOAT: result.big_float_val = expr
elif tkind == PK_STRING: result.str_val = expr
elif tkind == PK_BOOL: result.bool_val = expr
elif tkind == PK_HELP: nil
else: {.error: "unknown kind".}
result
# - Tcommandline_results procs
proc init*(param: var Tcommandline_results;
positional_parameters: seq[Tparsed_parameter] = @[];
options: TOrderedTable[string, Tparsed_parameter] =
initOrderedTable[string, Tparsed_parameter](4)) =
## Initialization helper with default parameters.
param.positional_parameters = positional_parameters
param.options = options
proc `$`*(data: Tcommandline_results): string =
## Stringifies a Tcommandline_results structure for debug output
var dict: seq[string] = @[]
for key, value in data.options:
dict.add("$1: $2" % [escape(key), $value])
result = "Tcommandline_result{positional_parameters:[$1], options:{$2}}" % [
join(map(data.positional_parameters, `$`), ", "), join(dict, ", ")]
# - Parse code
template raise_or_quit(exception, message: expr): stmt {.immediate.} =
## Avoids repeating if check based on the default quit_on_failure variable.
##
## As a special case, if message has a zero length the call to quit won't
## generate any messages or errors (used by the mechanism to echo help to the
## user).
if quit_on_failure:
if len(message) > 0:
quit(message)
else:
quit()
else:
raise newException(exception, message)
template run_custom_proc(parsed_parameter: Tparsed_parameter,
custom_validator: Tparameter_callback,
parameter: TaintedString) =
## Runs the custom validator if it is not nil.
##
## Pass in the string of the parameter triggering the call. If the
if not custom_validator.isNil:
except:
raise_or_quit(EInvalidValue, ("Couldn't run custom proc for " &
"parameter $1:\n$2" % [escape(parameter),
getCurrentExceptionMsg()]))
let message = custom_validator(parameter, parsed_parameter)
if not message.isNil and message.len > 0:
raise_or_quit(EInvalidValue, ("Failed to validate value for " &
"parameter $1:\n$2" % [escape(parameter), message]))
proc parse_parameter(quit_on_failure: bool, param, value: string,
param_kind: Tparam_kind): Tparsed_parameter =
## Tries to parse a text according to the specified type.
##
## Pass the parameter string which requires a value and the text the user
## passed in for it. It will be parsed according to the param_kind. This proc
## will raise (EInvalidValue, EOverflow) if something can't be parsed.
result.kind = param_kind
case param_kind:
of PK_INT:
try: result.int_val = value.parseInt
except EOverflow:
raise_or_quit(EOverflow, ("parameter $1 requires an " &
"integer, but $2 is too large to fit into one") % [param,
escape(value)])
except EInvalidValue:
raise_or_quit(EInvalidValue, ("parameter $1 requires an " &
"integer, but $2 can't be parsed into one") % [param, escape(value)])
of PK_STRING:
result.str_val = value
of PK_FLOAT:
try: result.float_val = value.parseFloat
except EInvalidValue:
raise_or_quit(EInvalidValue, ("parameter $1 requires a " &
"float, but $2 can't be parsed into one") % [param, escape(value)])
of PK_BOOL:
try: result.bool_val = value.parseBool
except EInvalidValue:
raise_or_quit(EInvalidValue, ("parameter $1 requires a " &
"boolean, but $2 can't be parsed into one. Valid values are: " &
"y, yes, true, 1, on, n, no, false, 0, off") % [param, escape(value)])
of PK_BIGGEST_INT:
try:
let parsed_len = parseBiggestInt(value, result.big_int_val)
if value.len != parsed_len or parsed_len < 1:
raise_or_quit(EInvalidValue, ("parameter $1 requires an " &
"integer, but $2 can't be parsed completely into one") % [
param, escape(value)])
except EInvalidValue:
raise_or_quit(EInvalidValue, ("parameter $1 requires an " &
"integer, but $2 can't be parsed into one") % [param, escape(value)])
of PK_BIGGEST_FLOAT:
try:
let parsed_len = parseBiggestFloat(value, result.big_float_val)
if value.len != parsed_len or parsed_len < 1:
raise_or_quit(EInvalidValue, ("parameter $1 requires a " &
"float, but $2 can't be parsed completely into one") % [
param, escape(value)])
except EInvalidValue:
raise_or_quit(EInvalidValue, ("parameter $1 requires a " &
"float, but $2 can't be parsed into one") % [param, escape(value)])
of PK_EMPTY:
nil
of PK_HELP:
nil
template build_specification_lookup():
TOrderedTable[string, ptr Tparameter_specification] =
## Returns the table used to keep pointers to all of the specifications.
var result {.gensym.}: TOrderedTable[string, ptr Tparameter_specification]
result = initOrderedTable[string, ptr Tparameter_specification](
nextPowerOfTwo(expected.len))
for i in 0..expected.len-1:
for param_to_detect in expected[i].names:
if result.hasKey(param_to_detect):
raise_or_quit(EInvalidKey,
"Parameter $1 repeated in input specification" % param_to_detect)
else:
result[param_to_detect] = addr(expected[i])
result
proc echo_help*(expected: seq[Tparameter_specification] = @[],
type_of_positional_parameters = PK_STRING,
bad_prefixes = @["-", "--"], end_of_options = "--")
proc parse*(expected: seq[Tparameter_specification] = @[],
type_of_positional_parameters = PK_STRING, args: seq[TaintedString] = nil,
bad_prefixes = @["-", "--"], end_of_options = "--",
quit_on_failure = true): Tcommandline_results =
## Parses parameters and returns results.
##
## The expected array should contain a list of the parameters you want to
## detect, which can capture additional values. Uncaptured parameters are
## considered positional parameters for which you can specify a type with
## type_of_positional_parameters.
##
## Before accepting a positional parameter, the list of bad_prefixes is
## compared against it. If the positional parameter starts with any of them,
## an error is displayed to the user due to ambiguity. The user can overcome
## the ambiguity by typing the special string specified by end_of_options.
## Note that values captured by parameters are not checked against bad
## prefixes, otherwise it would be a problem to specify the dash as synonim
## for standard input for many programs.
##
## The args sequence should be the list of parameters passed to your program
## without the program binary (usually OSes provide the path to the binary as
## the zeroth parameter). If args is nil, the list will be retrieved from the
## OS.
##
## If there is any kind of error and quit_on_failure is true, the quit proc
## will be called with a user error message. If quit_on_failure is false
## errors will raise exceptions (usually EInvalidValue or EOverflow) instead
## for you to catch and handle.
assert type_of_positional_parameters != PK_EMPTY and
type_of_positional_parameters != PK_HELP
for bad_prefix in bad_prefixes:
assert bad_prefix.len > 0, "Can't pass in a bad prefix of zero length"
var
expected = expected
adding_options = true
result.init()
# Prepare the input parameter list, maybe get it from the OS if not available.
var args = args
if args == nil:
let total_params = ParamCount()
#echo "Got no explicit args, retrieving from OS. Count: ", total_params
newSeq(args, total_params)
for i in 0..total_params - 1:
#echo ($i)
args[i] = paramStr(i + 1)
# Generate lookup table for each type of parameter based on strings.
var lookup = build_specification_lookup()
# Loop through the input arguments detecting their type and doing stuff.
var i = 0
while i < args.len:
let arg = args[i]
block adding_positional_parameter:
if arg.len > 0 and adding_options:
if arg == end_of_options:
# Looks like we found the end_of_options marker, disable options.
adding_options = false
break adding_positional_parameter
elif lookup.hasKey(arg):
var parsed: Tparsed_parameter
let param = lookup[arg]
# Insert check here for help, which aborts parsing.
if param.consumes == PK_HELP:
echo_help(expected, type_of_positional_parameters,
bad_prefixes, end_of_options)
raise_or_quit(EInvalidKey, "")
if param.consumes != PK_EMPTY:
if i + 1 < args.len:
parsed = parse_parameter(quit_on_failure,
arg, args[i + 1], param.consumes)
run_custom_proc(parsed, param.custom_validator, arg)
i += 1
else:
raise_or_quit(EInvalidValue, ("parameter $1 requires a " &
"value, but none was provided") % [arg])
result.options[param.names[0]] = parsed
break adding_positional_parameter
else:
for bad_prefix in bad_prefixes:
if arg.startsWith(bad_prefix):
raise_or_quit(EInvalidValue, ("Found ambiguos parameter '$1' " &
"starting with '$2', put '$3' as the previous parameter " &
"if you want to force it as positional parameter.") % [arg,
bad_prefix, end_of_options])
# Unprocessed, add the parameter to the list of positional parameters.
result.positional_parameters.add(parse_parameter(quit_on_failure,
$(1 + i), arg, type_of_positional_parameters))
i += 1
proc toString(runes: seq[TRune]): string =
result = ""
for rune in runes: result.add(rune.toUTF8)
proc ascii_cmp(a, b: string): int =
## Comparison ignoring non ascii characters, for better switch sorting.
let a = filterIt(toSeq(runes(a)), it.isAlpha())
# Can't use filterIt twice, github bug #351.
let b = filter(toSeq(runes(b)), proc(x: TRune): bool = x.isAlpha())
return system.cmp(toString(a), toString(b))
proc build_help*(expected: seq[Tparameter_specification] = @[],
type_of_positional_parameters = PK_STRING,
bad_prefixes = @["-", "--"], end_of_options = "--"): seq[string] =
## Builds basic help text and returns it as a sequence of strings.
##
## Note that this proc doesn't do as much sanity checks as the normal parse()
## proc, though it's unlikely you will be using one without the other, so if
## you had a parameter specification problem you would find out soon.
result = @["Usage parameters: "]
# Generate lookup table for each type of parameter based on strings.
let quit_on_failure = false
var
expected = expected
lookup = build_specification_lookup()
keys = toSeq(lookup.keys())
# First generate the joined version of input parameters in a list.
var
seen = initSet[string]()
prefixes: seq[string] = @[]
helps: seq[string] = @[]
for key in keys:
if seen.contains(key):
continue
# Add the joined string to the list.
let param = lookup[key][]
var param_names = param.names
sort(param_names, ascii_cmp)
var prefix = join(param_names, ", ")
# Don't forget about the type, if the parameter consumes values
if param.consumes != PK_EMPTY and param.consumes != PK_HELP:
prefix &= " " & $param.consumes
prefixes.add(prefix)
helps.add(param.help_text)
# Ignore future elements.
for name in param.names: seen.incl(name)
# Calculate the biggest width and try to use that
let width = prefixes.map(proc (x: string): int = 3 + len(x)).max
for line in zip(prefixes, helps):
result.add(line.a & repeatChar(width - line.a.len) & line.b)
proc echo_help*(expected: seq[Tparameter_specification] = @[],
type_of_positional_parameters = PK_STRING,
bad_prefixes = @["-", "--"], end_of_options = "--") =
## Prints out help on the terminal.
##
## This is just a wrapper around build_help. Note that calling this proc
## won't exit your program, you should call quit() yourself.
for line in build_help(expected,
type_of_positional_parameters, bad_prefixes, end_of_options):
echo line
when isMainModule:
# Simply tests code embedded in docs.
let
parsed_param1 = new_parsed_parameter(PK_FLOAT, 3.41)
parsed_param2 = new_parsed_parameter(PK_BIGGEST_INT, 2358123 * 23123)
#parsed_param3 = new_parsed_parameter(PK_INT, "231")

View file

@ -33,18 +33,9 @@ proc emptyNode*(): PNimrodNode {.compileTime.} =
proc dot*(left, right: PNimrodNode): PNimrodNode {.compileTime.} = proc dot*(left, right: PNimrodNode): PNimrodNode {.compileTime.} =
result = newNimNode(nnkDotExpr).und(left, right) result = newNimNode(nnkDotExpr).und(left, right)
proc postfix*(a: PNimrodNode, b: string): PNimrodNode {.compileTime.} =
result = newNimNode(nnkPostfix).und(newIdentNode(!b), a)
proc prefix*(a: string, b: PNimrodNode): PNimrodNode {.compileTime.} = proc prefix*(a: string, b: PNimrodNode): PNimrodNode {.compileTime.} =
result = newNimNode(nnkPrefix).und(newIdentNode(!a), b) result = newNimNode(nnkPrefix).und(newIdentNode(!a), b)
proc infix*(a, b, c: PNimrodNode): PNimrodNode {.compileTime.} =
## 5.infix("+", 10) ## => 5 + 10
result = newNimNode(nnkInfix).und(b, a, c)
proc infix*(a: PNimrodNode; b: string; c: PNimrodNode): PNimrodNode {.compileTime.} =
## Infix operation: infix(5, "+", 10) ## =>
result = newNimNode(nnkInfix).und(newIdentNode(b), a, c)
proc quoted2ident*(a: PNimrodNode): PNimrodNode {.compileTime.} = proc quoted2ident*(a: PNimrodNode): PNimrodNode {.compileTime.} =
if a.kind != nnkAccQuoted: if a.kind != nnkAccQuoted:
return a return a

View file

@ -145,7 +145,7 @@ proc lobbyInit*() =
var pos = vec2f(10, 10) var pos = vec2f(10, 10)
u_alias = gui.newTextEntry( u_alias = gui.newTextEntry(
if s.existsKey("alias"): s["alias"].str else: "alias", if s.hasKey("alias"): s["alias"].str else: "alias",
pos) pos)
pos.y += 20 pos.y += 20
u_passwd = gui.newTextEntry("buzz", pos) u_passwd = gui.newTextEntry("buzz", pos)

View file

@ -227,12 +227,13 @@ template checkFile(path: expr): stmt {.dirty, immediate.} =
cacheImpl newSprite, SpriteSheets, PSpriteSheet: cacheImpl newSprite, SpriteSheets, PSpriteSheet:
result.file = filename result.file = filename
if not(filename =~ re"\S+_(\d+)x(\d+)\.\S\S\S"): if filename =~ re"\S+_(\d+)x(\d+)\.\S\S\S":
errors.add "Bad file: "&filename&" must be in format name_WxH.png"
return
result.framew = strutils.parseInt(matches[0]) result.framew = strutils.parseInt(matches[0])
result.frameh = strutils.parseInt(matches[1]) result.frameh = strutils.parseInt(matches[1])
checkFile("data/gfx"/result.file) checkFile("data/gfx"/result.file)
else:
errors.add "Bad file: "&filename&" must be in format name_WxH.png"
return
cacheImpl newSound, SoundCache, PSoundRecord: cacheImpl newSound, SoundCache, PSoundRecord:
result.file = filename result.file = filename
@ -288,23 +289,23 @@ proc validateSettings*(settings: PJsonNode, errors: var seq[string]): bool =
if settings.kind != JObject: if settings.kind != JObject:
addError("Settings root must be an object") addError("Settings root must be an object")
return return
if not settings.existsKey("vehicles"): if not settings.hasKey("vehicles"):
addError("Vehicles section missing") addError("Vehicles section missing")
if not settings.existsKey("objects"): if not settings.hasKey("objects"):
errors.add("Objects section is missing") errors.add("Objects section is missing")
result = false result = false
if not settings.existsKey("level"): if not settings.hasKey("level"):
errors.add("Level settings section is missing") errors.add("Level settings section is missing")
result = false result = false
else: else:
let lvl = settings["level"] let lvl = settings["level"]
if lvl.kind != JObject or not lvl.existsKey("size"): if lvl.kind != JObject or not lvl.hasKey("size"):
errors.add("Invalid level settings") errors.add("Invalid level settings")
result = false result = false
elif not lvl.existsKey("size") or lvl["size"].kind != JArray or lvl["size"].len != 2: elif not lvl.hasKey("size") or lvl["size"].kind != JArray or lvl["size"].len != 2:
errors.add("Invalid/missing level size") errors.add("Invalid/missing level size")
result = false result = false
if not settings.existsKey("items"): if not settings.hasKey("items"):
errors.add("Items section missing") errors.add("Items section missing")
result = false result = false
else: else:
@ -363,7 +364,7 @@ proc loadSettings*(rawJson: string, errors: var seq[string]): bool =
nameToVehID[veh.name] = veh.id nameToVehID[veh.name] = veh.id
inc vID inc vID
vID = 0 vID = 0
if settings.existsKey("bullets"): if settings.hasKey("bullets"):
for blt in settings["bullets"].items: for blt in settings["bullets"].items:
var bullet = importBullet(blt, errors) var bullet = importBullet(blt, errors)
bullet.id = bID bullet.id = bID
@ -408,21 +409,21 @@ proc fetchBullet(name: string): PBulletRecord =
return cfg.bullets[nameToBulletID[name]] return cfg.bullets[nameToBulletID[name]]
proc getField(node: PJsonNode, field: string, target: var float) = proc getField(node: PJsonNode, field: string, target: var float) =
if not node.existsKey(field): if not node.hasKey(field):
return return
if node[field].kind == JFloat: if node[field].kind == JFloat:
target = node[field].fnum target = node[field].fnum
elif node[field].kind == JInt: elif node[field].kind == JInt:
target = node[field].num.float target = node[field].num.float
proc getField(node: PJsonNode, field: string, target: var int) = proc getField(node: PJsonNode, field: string, target: var int) =
if not node.existsKey(field): if not node.hasKey(field):
return return
if node[field].kind == JInt: if node[field].kind == JInt:
target = node[field].num.int target = node[field].num.int
elif node[field].kind == JFloat: elif node[field].kind == JFloat:
target = node[field].fnum.int target = node[field].fnum.int
proc getField(node: PJsonNode; field: string; target: var bool) = proc getField(node: PJsonNode; field: string; target: var bool) =
if not node.existsKey(field): if not node.hasKey(field):
return return
case node[field].kind case node[field].kind
of JBool: of JBool:
@ -434,7 +435,7 @@ proc getField(node: PJsonNode; field: string; target: var bool) =
else: nil else: nil
template checkKey(node: expr; key: string): stmt = template checkKey(node: expr; key: string): stmt =
if not existsKey(node, key): if not hasKey(node, key):
return return
proc importTrail(data: PJsonNode; errors: var seq[string]): TTrailRecord = proc importTrail(data: PJsonNode; errors: var seq[string]): TTrailRecord =
@ -450,17 +451,17 @@ proc importLevel(data: PJsonNode; errors: var seq[string]): PLevelSettings =
checkKey(data, "level") checkKey(data, "level")
var level = data["level"] var level = data["level"]
if level.existsKey("size") and level["size"].kind == JArray and level["size"].len == 2: if level.hasKey("size") and level["size"].kind == JArray and level["size"].len == 2:
result.size.x = level["size"][0].num.cint result.size.x = level["size"][0].num.cint
result.size.y = level["size"][1].num.cint result.size.y = level["size"][1].num.cint
if level.existsKey("starfield"): if level.hasKey("starfield"):
for star in level["starfield"].items: for star in level["starfield"].items:
result.starfield.add(newSprite(star.str, errors)) result.starfield.add(newSprite(star.str, errors))
proc importPhys(data: PJsonNode): TPhysicsRecord = proc importPhys(data: PJsonNode): TPhysicsRecord =
result.radius = 20.0 result.radius = 20.0
result.mass = 10.0 result.mass = 10.0
if data.existsKey("physics") and data["physics"].kind == JObject: if data.hasKey("physics") and data["physics"].kind == JObject:
let phys = data["physics"] let phys = data["physics"]
phys.getField("radius", result.radius) phys.getField("radius", result.radius)
phys.getField("mass", result.mass) phys.getField("mass", result.mass)
@ -489,10 +490,10 @@ proc importAnim(data: PJsonNode, errors: var seq[string]): PAnimationRecord =
result.delay = 1000.0 result.delay = 1000.0
result.spriteSheet = nil result.spriteSheet = nil
if data.existsKey("anim"): if data.hasKey("anim"):
let anim = data["anim"] let anim = data["anim"]
if anim.kind == JObject: if anim.kind == JObject:
if anim.existsKey("file"): if anim.hasKey("file"):
result.spriteSheet = newSprite(anim["file"].str, errors) result.spriteSheet = newSprite(anim["file"].str, errors)
anim.getField "angle", result.angle anim.getField "angle", result.angle

View file

@ -55,7 +55,8 @@ idPacket(ZoneQuery, 'Q',
tuple[pad: char = '\0']) tuple[pad: char = '\0'])
type SpawnKind = enum type SpawnKind = enum
SpawnItem = 1'i8, SpawnVehicle, SpawnObject SpawnDummy,
SpawnItem, SpawnVehicle, SpawnObject
forwardPacketT(SpawnKind, int8) forwardPacketT(SpawnKind, int8)
defPacket(ScSpawn, tuple[ defPacket(ScSpawn, tuple[
kind: SpawnKind; id: uint16; record: uint16; amount: uint16]) kind: SpawnKind; id: uint16; record: uint16; amount: uint16])
@ -64,7 +65,8 @@ defPacket(ScSpawn, tuple[
type TAssetType* = enum type TAssetType* = enum
FZoneCfg = 1'i8, FGraphics, FSound FDummy,
FZoneCfg, FGraphics, FSound
forwardPacketT(TAssetType, int8) forwardPacketT(TAssetType, int8)
forwardPacket(MD5Digest, array[0..15, int8]) forwardPacket(MD5Digest, array[0..15, int8])

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