diff options
-rwxr-xr-x | config/nimrod.cfg | 1 | ||||
-rwxr-xr-x | doc/advopt.txt | 1 | ||||
-rwxr-xr-x | doc/basicopt.txt | 2 | ||||
-rw-r--r-- | lib/core/threads.nim | 255 | ||||
-rwxr-xr-x | lib/system/cgprocs.nim | 26 | ||||
-rwxr-xr-x | rod/cgen.nim | 14 | ||||
-rwxr-xr-x | rod/commands.nim | 29 | ||||
-rwxr-xr-x | rod/lookups.nim | 89 | ||||
-rwxr-xr-x | rod/options.nim | 8 | ||||
-rwxr-xr-x | rod/semexprs.nim | 10 | ||||
-rwxr-xr-x | rod/semgnrc.nim | 36 | ||||
-rwxr-xr-x | rod/seminst.nim | 8 | ||||
-rwxr-xr-x | rod/semstmts.nim | 16 | ||||
-rwxr-xr-x | rod/semtypes.nim | 2 | ||||
-rwxr-xr-x | rod/sigmatch.nim | 32 | ||||
-rwxr-xr-x | rod/types.nim | 4 | ||||
-rwxr-xr-x | rod/wordrecg.nim | 6 | ||||
-rwxr-xr-x | todo.txt | 5 | ||||
-rwxr-xr-x | tools/install.tmpl | 22 | ||||
-rwxr-xr-x | web/news.txt | 4 |
20 files changed, 393 insertions, 177 deletions
diff --git a/config/nimrod.cfg b/config/nimrod.cfg index 4914d0993..96504b483 100755 --- a/config/nimrod.cfg +++ b/config/nimrod.cfg @@ -30,6 +30,7 @@ path="$lib/windows" path="$lib/posix" path="$lib/ecmas" path="$lib/pure/unidecode" +#recursivePath:"$user/.babel/lib" @if release or quick: obj_checks:off diff --git a/doc/advopt.txt b/doc/advopt.txt index cf0839386..fd624d93e 100755 --- a/doc/advopt.txt +++ b/doc/advopt.txt @@ -22,6 +22,7 @@ Advanced options: --os:SYMBOL set the target operating system (cross-compilation) --cpu:SYMBOL set the target processor (cross-compilation) --debuginfo enables debug information + --debugger:on|off turn Embedded Nimrod Debugger on|off -t, --passc:OPTION pass an option to the C compiler -l, --passl:OPTION pass an option to the linker --genMapping generate a mapping file containing diff --git a/doc/basicopt.txt b/doc/basicopt.txt index 7d2a9b26d..45efff4d0 100755 --- a/doc/basicopt.txt +++ b/doc/basicopt.txt @@ -18,7 +18,7 @@ Options: -f, --forceBuild force rebuilding of all modules --stackTrace:on|off turn stack tracing on|off --lineTrace:on|off turn line tracing on|off - --debugger:on|off turn Embedded Nimrod Debugger on|off + --threads:on|off turn support for multi-threading on|off -x, --checks:on|off turn all runtime checks on|off --objChecks:on|off turn obj conversion checks on|off --fieldChecks:on|off turn case variant field checks on|off diff --git a/lib/core/threads.nim b/lib/core/threads.nim index feb026547..fc120f1a3 100644 --- a/lib/core/threads.nim +++ b/lib/core/threads.nim @@ -16,11 +16,11 @@ ## .. code-block:: nimrod ## ## var -## thr: array [0..4, TThread] +## thr: array [0..4, TThread[tuple[a,b: int]]] ## L: TLock ## -## proc threadFunc(c: pointer) {.procvar.} = -## for i in 0..9: +## proc threadFunc(interval: tuple[a,b: int]) {.procvar.} = +## for i in interval.a..interval.b: ## Aquire(L) # lock stdout ## echo i ## Release(L) @@ -28,10 +28,12 @@ ## InitLock(L) ## ## for i in 0..high(thr): -## createThread(thr[i], threadFunc) +## createThread(thr[i], threadFunc, (i*10, i*10+5)) ## for i in 0..high(thr): ## joinThread(thr[i]) +when not defined(boehmgc) and not defined(nogc): + {.error: "Thread support requires --gc:boehm or --gc:none".} # We jump through some hops here to ensure that Nimrod thread procs can have # the Nimrod calling convention. This is needed because thread procs are @@ -41,10 +43,9 @@ # GC'ed closures in Nimrod. type - TThreadProc* = proc (closure: pointer) ## Standard Nimrod thread proc. - TThreadProcClosure {.pure, final.} = object - fn: TThreadProc - data: pointer + TThreadProcClosure {.pure, final.}[TParam] = object + fn: proc (p: TParam) + data: TParam when defined(Windows): type @@ -60,17 +61,22 @@ when defined(Windows): TWinThreadProc = proc (x: pointer): int32 {.stdcall.} - TLock* = TSysLock ## Standard Nimrod Lock type. - - proc InitLock*(L: var TLock) {.stdcall, + proc InitSysLock(L: var TSysLock) {.stdcall, dynlib: "kernel32", importc: "InitializeCriticalSection".} ## Initializes the lock `L`. - proc Aquire*(L: var TLock) {.stdcall, + proc TryAquireSysAux(L: var TSysLock): int32 {.stdcall, + dynlib: "kernel32", importc: "TryEnterCriticalSection".} + ## Tries to aquire the lock `L`. + + proc TryAquireSys(L: var TSysLock): bool {.inline.} = + result = TryAquireSysAux(L) != 0'i32 + + proc AquireSys(L: var TSysLock) {.stdcall, dynlib: "kernel32", importc: "EnterCriticalSection".} ## Aquires the lock `L`. - proc Release*(L: var TLock) {.stdcall, + proc ReleaseSys(L: var TSysLock) {.stdcall, dynlib: "kernel32", importc: "LeaveCriticalSection".} ## Releases the lock `L`. @@ -99,8 +105,8 @@ when defined(Windows): proc TerminateThread(hThread: THandle, dwExitCode: int32): int32 {. stdcall, dynlib: "kernel32", importc: "TerminateThread".} - proc threadProcWrapper(closure: pointer): int32 {.stdcall.} = - var c = cast[ptr TThreadProcClosure](closure) + proc threadProcWrapper[TParam](closure: pointer): int32 {.stdcall.} = + var c = cast[ptr TThreadProcClosure[TParam]](closure) c.fn(c.data) # implicitely return 0 @@ -109,16 +115,18 @@ else: TSysLock {.importc: "pthread_mutex_t", header: "<sys/types.h>".} = int TSysThread {.importc: "pthread_t", header: "<sys/types.h>".} = int - TLock* = TSysLock - - proc InitLockAux(L: var TSysLock, attr: pointer = nil) {. + proc InitSysLock(L: var TSysLock, attr: pointer = nil) {. importc: "pthread_mutex_init", header: "<pthread.h>".} - proc InitLock*(L: var TLock) {.inline.} = - InitLockAux(L) - proc Aquire*(L: var TLock) {. + proc AquireSys(L: var TSysLock) {. importc: "pthread_mutex_lock", header: "<pthread.h>".} - proc Release*(L: var TLock) {. + proc TryAquireSysAux(L: var TSysLock): cint {. + importc: "pthread_mutex_trylock", header: "<pthread.h>".} + + proc TryAquireSys(L: var TSysLock): bool {.inline.} = + result = TryAquireSysAux(L) == 0'i32 + + proc ReleaseSys(L: var TSysLock) {. importc: "pthread_mutex_unlock", header: "<pthread.h>".} proc pthread_create(a1: var TSysThread, a2: ptr int, @@ -131,44 +139,205 @@ else: proc pthread_cancel(a1: TSysThread): cint {. importc: "pthread_cancel", header: "<pthread.h>".} - proc threadProcWrapper(closure: pointer) {.noconv.} = - var c = cast[ptr TThreadProcClosure](closure) + proc threadProcWrapper[TParam](closure: pointer) {.noconv.} = + var c = cast[ptr TThreadProcClosure[TParam]](closure) c.fn(c.data) {.passL: "-pthread".} {.passC: "-pthread".} +const + noDeadlocks = true # compileOption("deadlockPrevention") + +when noDeadLocks: + type + TLock* {.pure, final.} = object ## Standard Nimrod Lock type. + key: int # used for identity and global order! + sys: TSysLock + next: ptr TLock +else: + type + TLock* = TSysLock + type - TThread* = object of TObject ## Nimrod thread. + TThread* {.pure, final.}[TParam] = object ## Nimrod thread. sys: TSysThread - c: TThreadProcClosure + c: TThreadProcClosure[TParam] + + +when nodeadlocks: + var + lockList {.threadvar.}: ptr TLock + deadlocksPrevented* = 0 ## counts the number of times a + ## deadlock has been prevented + +proc InitLock*(L: var TLock) {.inline.} = + ## Initializes the lock `L`. + when noDeadlocks: + InitSysLock(L.sys) + L.key = cast[int](addr(L)) + else: + InitSysLock(L) + +proc TryAquire*(L: var TLock): bool {.inline.} = + ## Try to aquires the lock `L`. Returns `true` on success. + when noDeadlocks: + result = TryAquireSys(L.sys) + else: + result = TryAquireSys(L) +proc Aquire*(L: var TLock) = + ## Aquires the lock `L`. + when nodeadlocks: + # Note: we MUST NOT change the linked list of locks before we have aquired + # the proper locks! This is because the pointer to the next lock is part + # of the lock itself! + assert L.key != 0 + var p = lockList + if p == nil: + # simple case: no lock aquired yet: + AquireSys(L.sys) + locklist = addr(L) + L.next = nil + else: + # check where to put L into the list: + var r = p + var last: ptr TLock = nil + while L.key < r.key: + if r.next == nil: + # best case: L needs to be aquired as last lock, so we can + # skip a good amount of work: + AquireSys(L.sys) + r.next = addr(L) + L.next = nil + return + last = r + r = r.next + # special case: thread already holds L! + if L.key == r.key: return + + # bad case: L needs to be somewhere in between + # release all locks after L: + var rollback = r + while r != nil: + ReleaseSys(r.sys) + r = r.next + # and aquire them in the correct order again: + AquireSys(L.sys) + r = rollback + while r != nil: + assert r.key < L.key + AquireSys(r.sys) + r = r.next + # now that we have all the locks we need, we can insert L + # into our list: + if last != nil: + L.next = last.next + last.next = addr(L) + else: + L.next = lockList + lockList = addr(L) + inc(deadlocksPrevented) + else: + AquireSys(L) -proc createThread*(t: var TThread, tp: TThreadProc, - closure: pointer = nil) = - ## creates a new thread `t` and starts its execution. Entry point is the - ## proc `tp`. `closure` is passed to `tp`. - t.c.data = closure - t.c.fn = tp - when defined(windows): - var dummyThreadId: int32 - t.sys = CreateThread(nil, 0'i32, threadProcWrapper, addr(t.c), 0'i32, - dummyThreadId) - else: - discard pthread_create(t.sys, nil, threadProcWrapper, addr(t.c)) +proc Release*(L: var TLock) = + ## Releases the lock `L`. + when nodeadlocks: + assert L.key != 0 + var p = lockList + var last: ptr TLock = nil + while true: + # if we don't find the lock, die by reading from nil! + if p.key == L.key: + if last != nil: + last.next = p.next + else: + assert p == lockList + lockList = locklist.next + L.next = nil + break + last = p + p = p.next + ReleaseSys(L.sys) + else: + ReleaseSys(L) -proc joinThread*(t: TThread) = +proc joinThread*[TParam](t: TThread[TParam]) {.inline.} = ## waits for the thread `t` until it has terminated. - when defined(windows): + when hostOS == "windows": discard WaitForSingleObject(t.sys, -1'i32) else: discard pthread_join(t.sys, nil) -proc destroyThread*(t: var TThread) = +proc destroyThread*[TParam](t: var TThread[TParam]) {.inline.} = ## forces the thread `t` to terminate. This is potentially dangerous if - ## you don't have full control over `t` and its aquired ressources. - when defined(windows): + ## you don't have full control over `t` and its aquired resources. + when hostOS == "windows": discard TerminateThread(t.sys, 1'i32) else: discard pthread_cancel(t.sys) +proc createThread*[TParam](t: var TThread[TParam], + tp: proc (param: TParam), + param: TParam) = + ## creates a new thread `t` and starts its execution. Entry point is the + ## proc `tp`. `param` is passed to `tp`. + t.c.data = param + t.c.fn = tp + when hostOS == "windows": + var dummyThreadId: int32 + t.sys = CreateThread(nil, 0'i32, threadProcWrapper[TParam], + addr(t.c), 0'i32, dummyThreadId) + else: + discard pthread_create(t.sys, nil, threadProcWrapper[TParam], addr(t.c)) + +when isMainModule: + var + thr: array [0..4, TThread[tuple[a,b: int]]] + L, M, N: TLock + + proc threadFunc(interval: tuple[a,b: int]) {.procvar.} = + for i in interval.a..interval.b: + case i mod 6 + of 0: + Aquire(L) # lock stdout + Aquire(M) + Aquire(N) + of 1: + Aquire(L) + Aquire(N) # lock stdout + Aquire(M) + of 2: + Aquire(M) + Aquire(L) + Aquire(N) + of 3: + Aquire(M) + Aquire(N) + Aquire(L) + of 4: + Aquire(N) + Aquire(M) + Aquire(L) + of 5: + Aquire(N) + Aquire(L) + Aquire(M) + else: assert false + echo i + echo "deadlocks prevented: ", deadlocksPrevented + Release(L) + Release(M) + Release(N) + + InitLock(L) + InitLock(M) + InitLock(N) + + for i in 0..high(thr): + createThread(thr[i], threadFunc, (i*100, i*100+50)) + for i in 0..high(thr): + joinThread(thr[i]) + + diff --git a/lib/system/cgprocs.nim b/lib/system/cgprocs.nim index cabdcafc4..945ce4692 100755 --- a/lib/system/cgprocs.nim +++ b/lib/system/cgprocs.nim @@ -1,7 +1,7 @@ # # # Nimrod's Runtime Library -# (c) Copyright 2010 Andreas Rumpf +# (c) Copyright 2011 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -24,3 +24,27 @@ proc nimLoadLibraryError(path: string) {.compilerproc, noinline.} proc setStackBottom(theStackBottom: pointer) {.compilerRtl, noinline.} +# Support for thread local storage: +when false: + when defined(windows): + proc TlsAlloc(): int32 {.importc: "TlsAlloc", stdcall, dynlib: "kernel32".} + proc TlsSetValue(dwTlsIndex: int32, lpTlsValue: pointer) {. + importc: "TlsSetValue", stdcall, dynlib: "kernel32".} + proc TlsGetValue(dwTlsIndex: int32): pointer {. + importc: "TlsGetValue", stdcall, dynlib: "kernel32".} + + else: + type + Tpthread_key {.importc: "pthread_key_t", header: "<sys/types.h>".} = int + + proc pthread_getspecific(a1: Tpthread_key): pointer {. + importc: "pthread_getspecific", header: "<pthread.h>".} + proc pthread_key_create(a1: ptr Tpthread_key, + a2: proc (x: pointer) {.noconv.}): int32 {. + importc: "pthread_key_create", header: "<pthread.h>".} + proc pthread_key_delete(a1: Tpthread_key): int32 {. + importc: "pthread_key_delete", header: "<pthread.h>".} + + proc pthread_setspecific(a1: Tpthread_key, a2: pointer): int32 {. + importc: "pthread_setspecific", header: "<pthread.h>".} + diff --git a/rod/cgen.nim b/rod/cgen.nim index f99a06a97..685d912c4 100755 --- a/rod/cgen.nim +++ b/rod/cgen.nim @@ -380,14 +380,20 @@ proc assignLocalVar(p: BProc, s: PSym) = appf(p.s[cpsLocals], " $1;$n", [s.loc.r]) localDebugInfo(p, s) +proc declareThreadVar(m: BModule, s: PSym) = + if optThreads in gGlobalOptions: + app(m.s[cfsVars], "NIM_THREADVAR ") + app(m.s[cfsVars], getTypeDesc(m, s.loc.t)) + + proc assignGlobalVar(p: BProc, s: PSym) = if s.loc.k == locNone: fillLoc(s.loc, locGlobalVar, s.typ, mangleName(s), OnHeap) useHeader(p.module, s) if lfNoDecl in s.loc.flags: return if sfImportc in s.flags: app(p.module.s[cfsVars], "extern ") - if sfThreadVar in s.flags: app(p.module.s[cfsVars], "NIM_THREADVAR ") - app(p.module.s[cfsVars], getTypeDesc(p.module, s.loc.t)) + if sfThreadVar in s.flags: declareThreadVar(p.module, s) + else: app(p.module.s[cfsVars], getTypeDesc(p.module, s.loc.t)) if sfRegister in s.flags: app(p.module.s[cfsVars], " register") if sfVolatile in s.flags: app(p.module.s[cfsVars], " volatile") appf(p.module.s[cfsVars], " $1;$n", [s.loc.r]) @@ -686,8 +692,8 @@ proc genVarPrototype(m: BModule, sym: PSym) = [sym.loc.r, getTypeDesc(m, sym.loc.t)]) else: app(m.s[cfsVars], "extern ") - if sfThreadVar in sym.flags: app(m.s[cfsVars], "NIM_THREADVAR ") - app(m.s[cfsVars], getTypeDesc(m, sym.loc.t)) + if sfThreadVar in sym.flags: declareThreadVar(m, sym) + else: app(m.s[cfsVars], getTypeDesc(m, sym.loc.t)) if sfRegister in sym.flags: app(m.s[cfsVars], " register") if sfVolatile in sym.flags: app(m.s[cfsVars], " volatile") appf(m.s[cfsVars], " $1;$n", [sym.loc.r]) diff --git a/rod/commands.nim b/rod/commands.nim index ffd117cd8..7a74cac7f 100755 --- a/rod/commands.nim +++ b/rod/commands.nim @@ -25,7 +25,7 @@ proc ProcessCommand*(switch: string, pass: TCmdLinePass) proc processSwitch*(switch, arg: string, pass: TCmdlinePass, info: TLineInfo) # implementation -const +const HelpMessage = "Nimrod Compiler Version $1 (" & compileDate & ") [$2: $3]\n" & "Copyright (c) 2004-2011 by Andreas Rumpf\n" @@ -49,7 +49,7 @@ Options: -f, --forceBuild force rebuilding of all modules --stackTrace:on|off turn stack tracing on|off --lineTrace:on|off turn line tracing on|off - --debugger:on|off turn Embedded Nimrod Debugger on|off + --threads:on|off turn support for multi-threading on|off -x, --checks:on|off turn all runtime checks on|off --objChecks:on|off turn obj conversion checks on|off --fieldChecks:on|off turn case variant field checks on|off @@ -92,6 +92,7 @@ Advanced options: --os:SYMBOL set the target operating system (cross-compilation) --cpu:SYMBOL set the target processor (cross-compilation) --debuginfo enables debug information + --debugger:on|off turn Embedded Nimrod Debugger on|off -t, --passc:OPTION pass an option to the C compiler -l, --passl:OPTION pass an option to the linker --genMapping generate a mapping file containing @@ -212,9 +213,6 @@ proc ProcessSpecificNote(arg: string, state: TSpecialWord, pass: TCmdlinePass, of wOn: incl(gNotes, n) of wOff: excl(gNotes, n) else: liMessage(info, errOnOrOffExpectedButXFound, arg) - -proc processPath(path: string): string = - result = UnixToNativePath(path % ["nimrod", getPrefixDir(), "lib", libpath]) proc processCompile(filename: string) = var found = findFile(filename) @@ -269,20 +267,32 @@ proc testCompileOption*(switch: string, info: TLineInfo): bool = of wRun, wR: result = contains(gGlobalOptions, optRun) of wSymbolFiles: result = contains(gGlobalOptions, optSymbolFiles) of wGenScript: result = contains(gGlobalOptions, optGenScript) + of wThreads: result = contains(gGlobalOptions, optThreads) else: InvalidCmdLineOption(passCmd1, switch, info) + +proc processPath(path: string): string = + result = UnixToNativePath(path % ["nimrod", getPrefixDir(), "lib", libpath, + "home", removeTrailingDirSep(os.getHomeDir())]) + +proc addPath(path: string) = + if not contains(options.searchPaths, path): + lists.PrependStr(options.searchPaths, path) proc processSwitch(switch, arg: string, pass: TCmdlinePass, info: TLineInfo) = var theOS: TSystemOS cpu: TSystemCPU - key, val, path: string + key, val: string case whichKeyword(switch) of wPath, wP: expectArg(switch, arg, pass, info) - path = processPath(arg) - if not contains(options.searchPaths, path): - lists.PrependStr(options.searchPaths, path) + addPath(processPath(arg)) #discard lists.IncludeStr(options.searchPaths, path) + of wRecursivePath: + expectArg(switch, arg, pass, info) + var path = processPath(arg) + for p in os.walkDirRec(path, {pcDir}): addPath(p) + addPath(path) of wOut, wO: expectArg(switch, arg, pass, info) options.outFile = arg @@ -356,6 +366,7 @@ proc processSwitch(switch, arg: string, pass: TCmdlinePass, info: TLineInfo) = of wLineDir: ProcessOnOffSwitch({optLineDir}, arg, pass, info) of wAssertions, wA: ProcessOnOffSwitch({optAssert}, arg, pass, info) of wDeadCodeElim: ProcessOnOffSwitchG({optDeadCodeElim}, arg, pass, info) + of wThreads: ProcessOnOffSwitchG({optThreads}, arg, pass, info) of wOpt: expectArg(switch, arg, pass, info) case whichKeyword(arg) diff --git a/rod/lookups.nim b/rod/lookups.nim index 474933604..f872224c0 100755 --- a/rod/lookups.nim +++ b/rod/lookups.nim @@ -1,7 +1,7 @@ # # # The Nimrod Compiler -# (c) Copyright 2009 Andreas Rumpf +# (c) Copyright 2011 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -21,28 +21,12 @@ type m*: PSym mode*: TOverloadIterMode - -proc getSymRepr*(s: PSym): string -proc CloseScope*(tab: var TSymTab) -proc AddSym*(t: var TStrTable, n: PSym) -proc addDecl*(c: PContext, sym: PSym) -proc addDeclAt*(c: PContext, sym: PSym, at: Natural) -proc addOverloadableSymAt*(c: PContext, fn: PSym, at: Natural) -proc addInterfaceDecl*(c: PContext, sym: PSym) -proc addInterfaceOverloadableSymAt*(c: PContext, sym: PSym, at: int) -proc lookUp*(c: PContext, n: PNode): PSym - # Looks up a symbol. Generates an error in case of nil. -proc QualifiedLookUp*(c: PContext, n: PNode, ambiguousCheck: bool): PSym -proc InitOverloadIter*(o: var TOverloadIter, c: PContext, n: PNode): PSym -proc nextOverloadIter*(o: var TOverloadIter, c: PContext, n: PNode): PSym -# implementation - -proc getSymRepr(s: PSym): string = +proc getSymRepr*(s: PSym): string = case s.kind of skProc, skMethod, skConverter, skIterator: result = getProcHeader(s) else: result = s.name.s -proc CloseScope(tab: var TSymTab) = +proc CloseScope*(tab: var TSymTab) = var it: TTabIter s: PSym @@ -59,14 +43,14 @@ proc CloseScope(tab: var TSymTab) = s = NextIter(it, tab.stack[tab.tos - 1]) astalgo.rawCloseScope(tab) -proc AddSym(t: var TStrTable, n: PSym) = +proc AddSym*(t: var TStrTable, n: PSym) = if StrTableIncl(t, n): liMessage(n.info, errAttemptToRedefine, n.name.s) -proc addDecl(c: PContext, sym: PSym) = +proc addDecl*(c: PContext, sym: PSym) = if SymTabAddUnique(c.tab, sym) == Failure: liMessage(sym.info, errAttemptToRedefine, sym.Name.s) -proc addDeclAt(c: PContext, sym: PSym, at: Natural) = +proc addDeclAt*(c: PContext, sym: PSym, at: Natural) = if SymTabAddUniqueAt(c.tab, sym, at) == Failure: liMessage(sym.info, errAttemptToRedefine, sym.Name.s) @@ -81,7 +65,7 @@ proc addInterfaceDeclAt*(c: PContext, sym: PSym, at: Natural) = addDeclAt(c, sym, at) AddInterfaceDeclAux(c, sym) -proc addOverloadableSymAt(c: PContext, fn: PSym, at: Natural) = +proc addOverloadableSymAt*(c: PContext, fn: PSym, at: Natural) = if not (fn.kind in OverloadableSyms): InternalError(fn.info, "addOverloadableSymAt") var check = StrTableGet(c.tab.stack[at], fn.name) @@ -89,17 +73,17 @@ proc addOverloadableSymAt(c: PContext, fn: PSym, at: Natural) = liMessage(fn.info, errAttemptToRedefine, fn.Name.s) SymTabAddAt(c.tab, fn, at) -proc addInterfaceDecl(c: PContext, sym: PSym) = +proc addInterfaceDecl*(c: PContext, sym: PSym) = # it adds the symbol to the interface if appropriate addDecl(c, sym) AddInterfaceDeclAux(c, sym) -proc addInterfaceOverloadableSymAt(c: PContext, sym: PSym, at: int) = +proc addInterfaceOverloadableSymAt*(c: PContext, sym: PSym, at: int) = # it adds the symbol to the interface if appropriate addOverloadableSymAt(c, sym, at) AddInterfaceDeclAux(c, sym) -proc lookUp(c: PContext, n: PNode): PSym = +proc lookUp*(c: PContext, n: PNode): PSym = # Looks up a symbol. Generates an error in case of nil. case n.kind of nkAccQuoted: @@ -118,26 +102,32 @@ proc lookUp(c: PContext, n: PNode): PSym = liMessage(n.info, errUseQualifier, result.name.s) if result.kind == skStub: loadStub(result) -proc QualifiedLookUp(c: PContext, n: PNode, ambiguousCheck: bool): PSym = +type + TLookupFlag* = enum + checkAmbiguity, checkUndeclared + +proc QualifiedLookUp*(c: PContext, n: PNode, flags = {checkUndeclared}): PSym = case n.kind of nkIdent: result = SymtabGet(c.Tab, n.ident) - if result == nil: + if result == nil and checkUndeclared in flags: liMessage(n.info, errUndeclaredIdentifier, n.ident.s) - elif ambiguousCheck and IntSetContains(c.AmbiguousSymbols, result.id): + elif checkAmbiguity in flags and IntSetContains(c.AmbiguousSymbols, + result.id): liMessage(n.info, errUseQualifier, n.ident.s) of nkSym: # - # result := SymtabGet(c.Tab, n.sym.name); - # if result = nil then + # result = SymtabGet(c.Tab, n.sym.name) + # if result == nil: # liMessage(n.info, errUndeclaredIdentifier, n.sym.name.s) # else result = n.sym - if ambiguousCheck and IntSetContains(c.AmbiguousSymbols, result.id): + if checkAmbiguity in flags and IntSetContains(c.AmbiguousSymbols, + result.id): liMessage(n.info, errUseQualifier, n.sym.name.s) of nkDotExpr: result = nil - var m = qualifiedLookUp(c, n.sons[0], false) + var m = qualifiedLookUp(c, n.sons[0], flags*{checkUndeclared}) if (m != nil) and (m.kind == skModule): var ident: PIdent = nil if (n.sons[1].kind == nkIdent): @@ -150,17 +140,17 @@ proc QualifiedLookUp(c: PContext, n: PNode, ambiguousCheck: bool): PSym = result = StrTableGet(c.tab.stack[ModuleTablePos], ident) else: result = StrTableGet(m.tab, ident) - if result == nil: + if result == nil and checkUndeclared in flags: liMessage(n.sons[1].info, errUndeclaredIdentifier, ident.s) - else: + elif checkUndeclared in flags: liMessage(n.sons[1].info, errIdentifierExpected, renderTree(n.sons[1])) of nkAccQuoted: - result = QualifiedLookup(c, n.sons[0], ambiguousCheck) + result = QualifiedLookup(c, n.sons[0], flags) else: - result = nil #liMessage(n.info, errIdentifierExpected, '') + result = nil if (result != nil) and (result.kind == skStub): loadStub(result) -proc InitOverloadIter(o: var TOverloadIter, c: PContext, n: PNode): PSym = +proc InitOverloadIter*(o: var TOverloadIter, c: PContext, n: PNode): PSym = var ident: PIdent result = nil case n.kind @@ -173,17 +163,16 @@ proc InitOverloadIter(o: var TOverloadIter, c: PContext, n: PNode): PSym = result = InitIdentIter(o.it, c.tab.stack[o.stackPtr], n.ident) of nkSym: result = n.sym - o.mode = oimDone # - # o.stackPtr := c.tab.tos; - # o.mode := oimNoQualifier; - # while (result = nil) do begin - # dec(o.stackPtr); - # if o.stackPtr < 0 then break; - # result := InitIdentIter(o.it, c.tab.stack[o.stackPtr], n.sym.name); - # end; + o.mode = oimDone + # o.stackPtr = c.tab.tos + # o.mode = oimNoQualifier + # while result == nil: + # dec(o.stackPtr) + # if o.stackPtr < 0: break + # result = InitIdentIter(o.it, c.tab.stack[o.stackPtr], n.sym.name) of nkDotExpr: o.mode = oimOtherModule - o.m = qualifiedLookUp(c, n.sons[0], false) + o.m = qualifiedLookUp(c, n.sons[0]) if (o.m != nil) and (o.m.kind == skModule): ident = nil if (n.sons[1].kind == nkIdent): @@ -210,7 +199,7 @@ proc InitOverloadIter(o: var TOverloadIter, c: PContext, n: PNode): PSym = nil if (result != nil) and (result.kind == skStub): loadStub(result) -proc nextOverloadIter(o: var TOverloadIter, c: PContext, n: PNode): PSym = +proc nextOverloadIter*(o: var TOverloadIter, c: PContext, n: PNode): PSym = case o.mode of oimDone: result = nil @@ -222,8 +211,8 @@ proc nextOverloadIter(o: var TOverloadIter, c: PContext, n: PNode): PSym = while (result == nil): dec(o.stackPtr) if o.stackPtr < 0: break - result = InitIdentIter(o.it, c.tab.stack[o.stackPtr], o.it.name) # BUGFIX: - # o.it.name <-> n.ident + result = InitIdentIter(o.it, c.tab.stack[o.stackPtr], o.it.name) + # BUGFIX: o.it.name <-> n.ident else: result = nil of oimSelfModule: diff --git a/rod/options.nim b/rod/options.nim index cdba2fcdc..1503770c3 100755 --- a/rod/options.nim +++ b/rod/options.nim @@ -30,7 +30,8 @@ type # please make sure we have under 32 options TOptions* = set[TOption] TGlobalOption* = enum gloptNone, optForceFullMake, optBoehmGC, optRefcGC, optDeadCodeElim, - optListCmd, optCompileOnly, optNoLinking, optSafeCode, # only allow safe code + optListCmd, optCompileOnly, optNoLinking, + optSafeCode, # only allow safe code optCDebug, # turn on debugging information optGenDynLib, # generate a dynamic library optGenGuiApp, # generate a GUI application @@ -40,7 +41,8 @@ type # please make sure we have under 32 options optSymbolFiles, # use symbol files for speeding up compilation optSkipConfigFile, # skip the general config file optSkipProjConfigFile, # skip the project's config file - optNoMain # do not generate a "main" proc + optNoMain, # do not generate a "main" proc + optThreads # support for multi-threading TGlobalOptions* = set[TGlobalOption] TCommands* = enum # Nimrod's commands cmdNone, cmdCompileToC, cmdCompileToCpp, cmdCompileToOC, @@ -142,7 +144,7 @@ proc shortenDir(dir: string): string = return copy(dir, len(prefix)) result = dir -proc removeTrailingDirSep(path: string): string = +proc removeTrailingDirSep*(path: string): string = if (len(path) > 0) and (path[len(path) - 1] == dirSep): result = copy(path, 0, len(path) - 2) else: diff --git a/rod/semexprs.nim b/rod/semexprs.nim index e6cf097e5..cdcf09d3e 100755 --- a/rod/semexprs.nim +++ b/rod/semexprs.nim @@ -631,7 +631,7 @@ proc makeDeref(n: PNode): PNode = proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = ## returns nil if it's not a built-in field access - var s = qualifiedLookup(c, n, true) # check for ambiguity + var s = qualifiedLookup(c, n, {checkAmbiguity, checkUndeclared}) if s != nil: return semSym(c, n, s, flags) @@ -940,7 +940,7 @@ proc semMacroStmt(c: PContext, n: PNode, semCheck: bool = true): PNode = var a: PNode if isCallExpr(n.sons[0]): a = n.sons[0].sons[0] else: a = n.sons[0] - var s = qualifiedLookup(c, a, false) + var s = qualifiedLookup(c, a, {checkUndeclared}) if s != nil: case s.kind of skMacro: @@ -1014,7 +1014,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = of nkCall, nkInfix, nkPrefix, nkPostfix, nkCommand, nkCallStrLit: # check if it is an expression macro: checkMinSonsLen(n, 1) - var s = qualifiedLookup(c, n.sons[0], false) + var s = qualifiedLookup(c, n.sons[0], {checkUndeclared}) if s != nil: case s.kind of skMacro: result = semMacroExpr(c, n, s) @@ -1037,10 +1037,10 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = result = semMacroStmt(c, n) of nkBracketExpr: checkMinSonsLen(n, 1) - var s = qualifiedLookup(c, n.sons[0], false) + var s = qualifiedLookup(c, n.sons[0], {checkUndeclared}) if s != nil and s.kind in {skProc, skMethod, skConverter, skIterator}: # type parameters: partial generic specialization - result = partialSpecialization(c, n, s) + result = explictGenericInstantiation(c, n, s) else: result = semArrayAccess(c, n, flags) of nkPragmaExpr: diff --git a/rod/semgnrc.nim b/rod/semgnrc.nim index 0a291cd42..3ccd415f9 100755 --- a/rod/semgnrc.nim +++ b/rod/semgnrc.nim @@ -21,12 +21,14 @@ type TSemGenericFlags = set[TSemGenericFlag] proc semGenericStmt(c: PContext, n: PNode, flags: TSemGenericFlags = {}): PNode -proc semGenericStmtScope(c: PContext, n: PNode, flags: TSemGenericFlags = {}): PNode = +proc semGenericStmtScope(c: PContext, n: PNode, + flags: TSemGenericFlags = {}): PNode = openScope(c.tab) result = semGenericStmt(c, n, flags) closeScope(c.tab) proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym): PNode = + incl(s.flags, sfUsed) case s.kind of skUnknown: # Introduced in this pass! Leave it as an identifier. @@ -55,20 +57,29 @@ proc getIdentNode(n: PNode): PNode = illFormedAst(n) result = nil +# of nkAccQuoted: +# s = lookUp(c, n) +# if withinBind in flags: result = symChoice(c, n, s) +# else: result = semGenericStmtSymbol(c, n, s) + proc semGenericStmt(c: PContext, n: PNode, flags: TSemGenericFlags = {}): PNode = var L: int a: PNode - s: PSym result = n if n == nil: return case n.kind - of nkIdent, nkAccQuoted: - s = lookUp(c, n) - if withinBind in flags: result = symChoice(c, n, s) - else: result = semGenericStmtSymbol(c, n, s) + of nkIdent: + var s = SymtabGet(c.Tab, n.ident) + if s == nil: + # no error if symbol cannot be bound, unless in ``bind`` context: + if withinBind in flags: + liMessage(n.info, errUndeclaredIdentifier, n.ident.s) + else: + if withinBind in flags: result = symChoice(c, n, s) + else: result = semGenericStmtSymbol(c, n, s) of nkDotExpr: - s = QualifiedLookUp(c, n, true) + var s = QualifiedLookUp(c, n, {}) if s != nil: result = semGenericStmtSymbol(c, n, s) of nkSym..nkNilLit: nil @@ -77,8 +88,9 @@ proc semGenericStmt(c: PContext, n: PNode, flags: TSemGenericFlags = {}): PNode of nkCall, nkHiddenCallConv, nkInfix, nkPrefix, nkCommand, nkCallStrLit: # check if it is an expression macro: checkMinSonsLen(n, 1) - s = qualifiedLookup(c, n.sons[0], false) - if (s != nil): + var s = qualifiedLookup(c, n.sons[0], {}) + if s != nil: + incl(s.flags, sfUsed) case s.kind of skMacro: return semMacroExpr(c, n, s, false) @@ -100,16 +112,16 @@ proc semGenericStmt(c: PContext, n: PNode, flags: TSemGenericFlags = {}): PNode of nkMacroStmt: result = semMacroStmt(c, n, false) of nkIfStmt: - for i in countup(0, sonsLen(n) - 1): + for i in countup(0, sonsLen(n)-1): n.sons[i] = semGenericStmtScope(c, n.sons[i]) of nkWhileStmt: openScope(c.tab) - for i in countup(0, sonsLen(n) - 1): n.sons[i] = semGenericStmt(c, n.sons[i]) + for i in countup(0, sonsLen(n)-1): n.sons[i] = semGenericStmt(c, n.sons[i]) closeScope(c.tab) of nkCaseStmt: openScope(c.tab) n.sons[0] = semGenericStmt(c, n.sons[0]) - for i in countup(1, sonsLen(n) - 1): + for i in countup(1, sonsLen(n)-1): a = n.sons[i] checkMinSonsLen(a, 1) L = sonsLen(a) diff --git a/rod/seminst.nim b/rod/seminst.nim index 417922825..7954d2e8f 100755 --- a/rod/seminst.nim +++ b/rod/seminst.nim @@ -106,6 +106,7 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable, else: result.typ = newTypeS(tyProc, c) addSon(result.typ, nil) + result.typ.callConv = fn.typ.callConv oldPrc = GenericCacheGet(c, fn, result) if oldPrc == nil: # add it here, so that recursive generic procs are possible: @@ -244,10 +245,3 @@ proc generateTypeInstance(p: PContext, pt: TIdTable, arg: PNode, result = ReplaceTypeVarsT(cl, t) popInfoContext() -proc partialSpecialization(c: PContext, n: PNode, s: PSym): PNode = - for i in 1..sonsLen(n)-1: - n.sons[i].typ = semTypeNode(c, n.sons[i], nil) - # we cannot check for the proper number of type parameters because in - # `f[a,b](x, y)` `f` is not resolved yet properly. - # XXX: BUG this should be checked somehow! - result = n diff --git a/rod/semstmts.nim b/rod/semstmts.nim index 0546c24dd..0faf3e5fc 100755 --- a/rod/semstmts.nim +++ b/rod/semstmts.nim @@ -162,7 +162,7 @@ proc propertyWriteAccess(c: PContext, n, a: PNode): PNode = addSon(result, newIdentNode(getIdent(id.s & '='), n.info)) # a[0] is already checked for semantics, that does ``builtinFieldAccess`` # this is ugly. XXX Semantic checking should use the ``nfSem`` flag for - # nodes! + # nodes? addSon(result, a[0]) addSon(result, semExpr(c, n[1])) result = semDirectCallAnalyseEffects(c, result, {}) @@ -182,18 +182,6 @@ proc semAsgn(c: PContext, n: PNode): PNode = a = builtinFieldAccess(c, a, {efLValue}) if a == nil: return propertyWriteAccess(c, n, n[0]) - when false: - checkSonsLen(a, 2) - var id = considerAcc(a.sons[1]) - result = newNodeI(nkCall, n.info) - addSon(result, newIdentNode(getIdent(id.s & '='), n.info)) - addSon(result, semExpr(c, a.sons[0])) - addSon(result, semExpr(c, n.sons[1])) - result = semDirectCallAnalyseEffects(c, result, {}) - if result != nil: - fixAbstractType(c, result) - analyseIfAddressTakenInCall(c, result) - return of nkBracketExpr: # a[i..j] = x # --> `[..]=`(a, i, j, x) @@ -204,7 +192,6 @@ proc semAsgn(c: PContext, n: PNode): PNode = return semExprNoType(c, result) else: a = semExprWithType(c, a, {efLValue}) - #n.sons[0] = semExprWithType(c, n.sons[0], {efLValue}) n.sons[0] = a n.sons[1] = semExprWithType(c, n.sons[1]) var le = a.typ @@ -288,7 +275,6 @@ proc semVar(c: PContext, n: PNode): PNode = else: def = nil if not typeAllowed(typ, skVar): - #debug(typ); liMessage(a.info, errXisNoType, typeToString(typ)) tup = skipTypes(typ, {tyGenericInst}) if a.kind == nkVarTuple: diff --git a/rod/semtypes.nim b/rod/semtypes.nim index 52766f174..0fb359170 100755 --- a/rod/semtypes.nim +++ b/rod/semtypes.nim @@ -178,7 +178,7 @@ proc semOrdinal(c: PContext, n: PNode, prev: PType): PType = liMessage(n.info, errXExpectsOneTypeParam, "ordinal") proc semTypeIdent(c: PContext, n: PNode): PSym = - result = qualifiedLookup(c, n, true) + result = qualifiedLookup(c, n, {checkAmbiguity, checkUndeclared}) if (result != nil): markUsed(n, result) if result.kind != skType: liMessage(n.info, errTypeExpected) diff --git a/rod/sigmatch.nim b/rod/sigmatch.nim index 5dc9de7b0..bcb9198da 100755 --- a/rod/sigmatch.nim +++ b/rod/sigmatch.nim @@ -1,7 +1,7 @@ # # # The Nimrod Compiler -# (c) Copyright 2010 Andreas Rumpf +# (c) Copyright 2011 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -82,11 +82,11 @@ proc cmpCandidates(a, b: TCandidate): int = result = a.convMatches - b.convMatches proc writeMatches(c: TCandidate) = - Writeln(stdout, "exact matches: " & $(c.exactMatches)) - Writeln(stdout, "subtype matches: " & $(c.subtypeMatches)) - Writeln(stdout, "conv matches: " & $(c.convMatches)) - Writeln(stdout, "intconv matches: " & $(c.intConvMatches)) - Writeln(stdout, "generic matches: " & $(c.genericMatches)) + Writeln(stdout, "exact matches: " & $c.exactMatches) + Writeln(stdout, "subtype matches: " & $c.subtypeMatches) + Writeln(stdout, "conv matches: " & $c.convMatches) + Writeln(stdout, "intconv matches: " & $c.intConvMatches) + Writeln(stdout, "generic matches: " & $c.genericMatches) proc getNotFoundError(c: PContext, n: PNode): string = # Gives a detailed error message; this is separated from semDirectCall, @@ -710,8 +710,7 @@ proc semDirectCallWithBinding(c: PContext, n, f: PNode, filter: TSymKinds, elif y.state == csMatch and cmpCandidates(x, y) == 0 and not sameMethodDispatcher(x.calleeSym, y.calleeSym): if x.state != csMatch: - InternalError(n.info, "x.state is not csMatch") #writeMatches(x); - #writeMatches(y); + InternalError(n.info, "x.state is not csMatch") liMessage(n.Info, errGenerated, msgKindToString(errAmbiguousCallXYZ) % [ getProcHeader(x.calleeSym), getProcHeader(y.calleeSym), x.calleeSym.Name.s]) @@ -740,3 +739,20 @@ proc semDirectCall(c: PContext, n: PNode, filter: TSymKinds): PNode = initialBinding = nil result = semDirectCallWithBinding(c, n, f, filter, initialBinding) +proc explictGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode = + assert n.kind == nkBracketExpr + for i in 1..sonsLen(n)-1: + n.sons[i].typ = semTypeNode(c, n.sons[i], nil) + # we cannot check for the proper number of type parameters because in + # `f[a,b](x, y)` `f` is not resolved yet properly. + # XXX: BUG this should be checked somehow! + assert n.sons[0].kind == nkSym + + var x: TCandidate + initCandidate(x, s, n) + var newInst = generateInstance(c, s, x.bindings, n.info) + + markUsed(n, s) + result = newSymNode(newInst) + result.info = n.info + diff --git a/rod/types.nim b/rod/types.nim index d7b956983..d26136aa9 100755 --- a/rod/types.nim +++ b/rod/types.nim @@ -1,7 +1,7 @@ # # # The Nimrod Compiler -# (c) Copyright 2010 Andreas Rumpf +# (c) Copyright 2011 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -747,7 +747,7 @@ proc typeAllowedNode(marker: var TIntSet, n: PNode, kind: TSymKind): bool = result = true if n != nil: result = typeAllowedAux(marker, n.typ, kind) - if not result: debug(n.typ) + #if not result: debug(n.typ) if result: case n.kind of nkNone..nkNilLit: diff --git a/rod/wordrecg.nim b/rod/wordrecg.nim index 0b5b48bb2..d4bbb8680 100755 --- a/rod/wordrecg.nim +++ b/rod/wordrecg.nim @@ -59,7 +59,8 @@ type wPretty, wDoc, wGenDepend, wListDef, wCheck, wParse, wScan, wJs, wOC, wRst2html, wRst2tex, wI, - wWrite, wPutEnv, wPrependEnv, wAppendEnv, wThreadVar, wEmit + wWrite, wPutEnv, wPrependEnv, wAppendEnv, wThreadVar, wEmit, wThreads, + wRecursivePath TSpecialWords* = set[TSpecialWord] @@ -107,7 +108,8 @@ const "compiletooc", "pretty", "doc", "gendepend", "listdef", "check", "parse", "scan", "js", "oc", "rst2html", "rst2tex", "i", - "write", "putenv", "prependenv", "appendenv", "threadvar", "emit"] + "write", "putenv", "prependenv", "appendenv", "threadvar", "emit", + "threads", "recursivepath"] proc whichKeyword*(id: PIdent): TSpecialWord proc whichKeyword*(id: String): TSpecialWord diff --git a/todo.txt b/todo.txt index 08b1865b1..51ab7d592 100755 --- a/todo.txt +++ b/todo.txt @@ -1,6 +1,6 @@ -- built-in serialization - thread support: threadvar on Windows seems broken; add --deadlock_prevention:on|off switch +- built-in serialization - ban ``nil`` from the AST. This might also fix bugs concerning macros. @@ -66,7 +66,8 @@ Low priority - tlastmod returns wrong results on BSD (Linux, MacOS X: works) - nested tuple unpacking - fast assignment optimization for TPeg - +- better error messages for used keywords as identifiers +- case statement branches should support constant sets Library ------- diff --git a/tools/install.tmpl b/tools/install.tmpl index 34f77aac1..9dea6887b 100755 --- a/tools/install.tmpl +++ b/tools/install.tmpl @@ -47,13 +47,13 @@ if [ $# -eq 1 ] ; then docdir="$1/?proj/doc" datadir="$1/?proj/data" - mkdir -p $1/?proj - mkdir -p $bindir - mkdir -p $configdir + mkdir -p $1/?proj || exit 1 + mkdir -p $bindir || exit 1 + mkdir -p $configdir || exit 1 ;; esac - mkdir -p $libdir - mkdir -p $docdir + mkdir -p $libdir || exit 1 + mkdir -p $docdir || exit 1 echo "copying files..." #var createdDirs = newStringTable() #for cat in fcConfig..fcLib: @@ -63,30 +63,30 @@ if [ $# -eq 1 ] ; then # mk = unixDirVars[cat] & "/" & mk # if not createdDirs.hasKey(mk): # createdDirs[mk] = "true" - mkdir -p ?mk + mkdir -p ?mk || exit 1 # end if # end if # end for #end for #for f in items(c.cat[fcUnixBin]): - cp ?f $bindir/?f.skipRoot + cp ?f $bindir/?f.skipRoot || exit 1 chmod 755 $bindir/?f.skipRoot #end for #for f in items(c.cat[fcConfig]): - cp ?f $configdir/?f.skipRoot + cp ?f $configdir/?f.skipRoot || exit 1 chmod 644 $configdir/?f.skipRoot #end for #for f in items(c.cat[fcData]): - cp ?f $datadir/?f.skipRoot + cp ?f $datadir/?f.skipRoot || exit 1 chmod 644 $datadir/?f.skipRoot #end for #for f in items(c.cat[fcDoc]): - cp ?f $docdir/?f.skipRoot + cp ?f $docdir/?f.skipRoot || exit 1 chmod 644 $docdir/?f.skipRoot #end for #for f in items(c.cat[fcLib]): - cp ?f $libdir/?f.skipRoot + cp ?f $libdir/?f.skipRoot || exit 1 chmod 644 $libdir/?f.skipRoot #end for diff --git a/web/news.txt b/web/news.txt index e0c436a20..181ec706a 100755 --- a/web/news.txt +++ b/web/news.txt @@ -33,6 +33,8 @@ Changes affecting backwards compatibility Additions --------- +- Added ``scgi`` module. +- Added ``smtp`` module. - Added ``re.findAll``, ``pegs.findAll``. - Added ``os.findExe``. - Added ``strutils.align``, ``strutils.tokenize``, ``strutils.wordWrap``. @@ -40,7 +42,7 @@ Additions - Pegs support new built-ins: ``\letter``, ``\upper``, ``\lower``, ``\title``, ``\white``. - Pegs support the new built-in ``\skip`` operation. -- Source code filters are now properly documented. +- Source code filters are now documented. - Added ``emit`` pragma for direct code generator control. - Additional operations were added to the ``complex`` module. - Added ``strutils.formatFloat``, ``strutils.formatBiggestFloat``. |