From b0a16fb61935355f60ba2dddc7981e3077d7c3f6 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Fri, 11 Apr 2014 21:36:02 +0100 Subject: [PATCH 1/5] Fixes problems with JS code gen. --out for JS backend now works. setLen now works, this also fixes the base64 module. getCurrentExceptionMsg now also works with exceptions thrown by JS. log() supports a variable number of args now. Fixed some case sensitivity issues with arrayConstr and other functions. --- compiler/jsgen.nim | 15 ++++++++---- compiler/nimrod.nim | 8 +++++-- lib/system/jssys.nim | 54 ++++++++++++++++++++++++++++---------------- 3 files changed, 51 insertions(+), 26 deletions(-) diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index a34d8f1239..373a11e9ac 100644 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -564,7 +564,7 @@ proc genTry(p: PProc, n: PNode, r: var TCompRes) = moveInto(p, a, r) var i = 1 if p.target == targetJS and length > 1 and n.sons[i].kind == nkExceptBranch: - appf(p.body, "} catch (EXC) {$n") + appf(p.body, "} catch (EXC) {$n lastJSError = EXC;$n") elif p.target == targetLua: appf(p.body, "end)$n") while i < length and n.sons[i].kind == nkExceptBranch: @@ -1114,6 +1114,8 @@ proc createVar(p: PProc, typ: PType, indirect: bool): PRope = var e = elemType(t) if length > 32: useMagic(p, "arrayConstr") + # XXX: arrayConstr depends on nimCopy. This line shouldn't be necessary. + useMagic(p, "nimCopy") result = ropef("arrayConstr($1, $2, $3)", [toRope(length), createVar(p, e, false), genTypeInfo(p, e)]) else: @@ -1314,7 +1316,7 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) = if skipTypes(n.sons[1].typ, abstractVarRange).kind == tyCString: binaryExpr(p, n, r, "", "$1 += $2") else: - binaryExpr(p, n, r, "", "$1 = ($1.slice(0,-1)).concat($2)") + binaryExpr(p, n, r, "", "$1 = ($1.slice(0, -1)).concat($2)") # XXX: make a copy of $2, because of Javascript's sucking semantics of mAppendSeqElem: binaryExpr(p, n, r, "", "$1.push($2)") of mConStrStr: genConStrStr(p, n, r) @@ -1341,7 +1343,7 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) = of ast.mDec: if optOverflowCheck notin p.options: binaryExpr(p, n, r, "", "$1 -= $2") else: binaryExpr(p, n, r, "subInt", "$1 = subInt($1, $2)") - of mSetLengthStr: binaryExpr(p, n, r, "", "$1.length = ($2)-1") + of mSetLengthStr: binaryExpr(p, n, r, "", "$1.length = $2+1; $1[$1.length-1] = 0") of mSetLengthSeq: binaryExpr(p, n, r, "", "$1.length = $2") of mCard: unaryExpr(p, n, r, "SetCard", "SetCard($1)") of mLtSet: binaryExpr(p, n, r, "SetLt", "SetLt($1, $2)") @@ -1698,7 +1700,12 @@ proc myClose(b: PPassContext, n: PNode): PNode = var m = BModule(b) if sfMainModule in m.module.flags: let code = wholeCode(m) - var outfile = changeFileExt(completeCFilePath(m.module.filename), "js") + let outfile = + if options.outFile.len > 0: + if options.outFile.isAbsolute: options.outFile + else: getCurrentDir() / options.outFile + else: + changeFileExt(completeCFilePath(m.module.filename), "js") discard writeRopeIfNotEqual(con(genHeader(), code), outfile) proc myOpenCached(s: PSym, rd: PRodReader): PPassContext = diff --git a/compiler/nimrod.nim b/compiler/nimrod.nim index 38d440adeb..efe3d83bfe 100644 --- a/compiler/nimrod.nim +++ b/compiler/nimrod.nim @@ -61,8 +61,12 @@ proc handleCmdLine() = tccgen.run() if optRun in gGlobalOptions: if gCmd == cmdCompileToJS: - var ex = quoteShell( - completeCFilePath(changeFileExt(gProjectFull, "js").prependCurDir)) + var ex: string + if options.outFile.len > 0: + ex = options.outFile.prependCurDir.quoteShell + else: + ex = quoteShell( + completeCFilePath(changeFileExt(gProjectFull, "js").prependCurDir)) execExternalProgram("node " & ex & ' ' & service.arguments) else: var binPath: string diff --git a/lib/system/jssys.nim b/lib/system/jssys.nim index 1720804c4e..52f8873cf4 100644 --- a/lib/system/jssys.nim +++ b/lib/system/jssys.nim @@ -12,7 +12,7 @@ when defined(nodejs): else: proc alert*(s: cstring) {.importc, nodecl.} -proc log*(s: cstring) {.importc: "console.log", nodecl.} +proc log*(s: cstring) {.importc: "console.log", varargs, nodecl.} type PSafePoint = ptr TSafePoint @@ -27,11 +27,19 @@ type line: int # current line number filename: cstring + PJSError = ref object + columnNumber {.importc.}: int + fileName {.importc.}: cstring + lineNumber {.importc.}: int + message {.importc.}: cstring + stack {.importc.}: cstring + var framePtr {.importc, nodecl, volatile.}: PCallFrame excHandler {.importc, nodecl, volatile.}: PSafePoint = nil # list of exception handlers # a global variable for the root of all try blocks + lastJSError {.importc, nodecl, volatile.}: PJSError = nil {.push stacktrace: off, profiler:off.} proc nimBoolToStr(x: bool): string {.compilerproc.} = @@ -43,8 +51,12 @@ proc nimCharToStr(x: char): string {.compilerproc.} = result[0] = x proc getCurrentExceptionMsg*(): string = - if excHandler != nil: return $excHandler.exc.msg - return "" + if excHandler != nil and excHandler.exc != nil: + return $excHandler.exc.msg + elif lastJSError != nil: + return $lastJSError.message + else: + return "" proc auxWriteStackTrace(f: PCallFrame): string = type @@ -77,11 +89,13 @@ proc auxWriteStackTrace(f: PCallFrame): string = add(result, "\n") proc rawWriteStackTrace(): string = - if framePtr == nil: - result = "No stack traceback available\n" - else: - result = "Traceback (most recent call last)\n"& auxWriteStackTrace(framePtr) + if framePtr != nil: + result = "Traceback (most recent call last)\n" & auxWriteStackTrace(framePtr) framePtr = nil + elif lastJSError != nil: + result = $lastJSError.stack + else: + result = "No stack traceback available\n" proc raiseException(e: ref E_Base, ename: cstring) {. compilerproc, asmNoStackFrame.} = @@ -472,17 +486,17 @@ proc ze*(a: int): int {.compilerproc.} = proc ze64*(a: int64): int64 {.compilerproc.} = result = a -proc ToU8(a: int): int8 {.asmNoStackFrame, compilerproc.} = +proc toU8*(a: int): int8 {.asmNoStackFrame, compilerproc.} = asm """ return `a`; """ -proc ToU16(a: int): int16 {.asmNoStackFrame, compilerproc.} = +proc toU16*(a: int): int16 {.asmNoStackFrame, compilerproc.} = asm """ return `a`; """ -proc ToU32(a: int): int32 {.asmNoStackFrame, compilerproc.} = +proc toU32*(a: int64): int32 {.asmNoStackFrame, compilerproc.} = asm """ return `a`; """ @@ -503,17 +517,17 @@ proc nimCopy(x: pointer, ti: PNimType): pointer {.compilerproc.} proc nimCopyAux(dest, src: Pointer, n: ptr TNimNode) {.compilerproc.} = case n.kind - of nkNone: sysAssert(false, "NimCopyAux") + of nkNone: sysAssert(false, "nimCopyAux") of nkSlot: - asm "`dest`[`n`.offset] = NimCopy(`src`[`n`.offset], `n`.typ);" + asm "`dest`[`n`.offset] = nimCopy(`src`[`n`.offset], `n`.typ);" of nkList: for i in 0..n.len-1: - NimCopyAux(dest, src, n.sons[i]) + nimCopyAux(dest, src, n.sons[i]) of nkCase: asm """ - `dest`[`n`.offset] = NimCopy(`src`[`n`.offset], `n`.typ); + `dest`[`n`.offset] = nimCopy(`src`[`n`.offset], `n`.typ); for (var i = 0; i < `n`.sons.length; ++i) { - NimCopyAux(`dest`, `src`, `n`.sons[i][1]); + nimCopyAux(`dest`, `src`, `n`.sons[i][1]); } """ @@ -534,17 +548,17 @@ proc nimCopy(x: pointer, ti: PNimType): pointer = for (var key in `x`) { `result`[key] = `x`[key]; } """ of tyTuple, tyObject: - if ti.base != nil: result = NimCopy(x, ti.base) + if ti.base != nil: result = nimCopy(x, ti.base) elif ti.kind == tyObject: asm "`result` = {m_type: `ti`};" else: asm "`result` = {};" - NimCopyAux(result, x, ti.node) + nimCopyAux(result, x, ti.node) of tySequence, tyArrayConstr, tyOpenArray, tyArray: asm """ `result` = new Array(`x`.length); for (var i = 0; i < `x`.length; ++i) { - `result`[i] = NimCopy(`x`[i], `ti`.base); + `result`[i] = nimCopy(`x`[i], `ti`.base); } """ of tyString: @@ -584,12 +598,12 @@ proc genericReset(x: Pointer, ti: PNimType): pointer {.compilerproc.} = else: result = nil -proc ArrayConstr(len: int, value: pointer, typ: PNimType): pointer {. +proc arrayConstr(len: int, value: pointer, typ: PNimType): pointer {. asmNoStackFrame, compilerproc.} = # types are fake asm """ var result = new Array(`len`); - for (var i = 0; i < `len`; ++i) result[i] = NimCopy(`value`, `typ`); + for (var i = 0; i < `len`; ++i) result[i] = nimCopy(`value`, `typ`); return result; """ From 96c97d41035cc4e0f109ac559452174cfef25a72 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Fri, 11 Apr 2014 21:41:45 +0100 Subject: [PATCH 2/5] JS: Fixes math and streams module and implements json. --- lib/pure/json.nim | 107 +++++++++++++++++++++++++++++++++++-------- lib/pure/math.nim | 4 +- lib/pure/streams.nim | 72 +++++++++++++++-------------- 3 files changed, 128 insertions(+), 55 deletions(-) diff --git a/lib/pure/json.nim b/lib/pure/json.nim index 7424bbae95..4250847e56 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -861,26 +861,97 @@ proc parseJson(p: var TJsonParser): PJsonNode = of tkError, tkCurlyRi, tkBracketRi, tkColon, tkComma, tkEof: raiseParseErr(p, "{") -proc parseJson*(s: PStream, filename: string): PJsonNode = - ## Parses from a stream `s` into a `PJsonNode`. `filename` is only needed - ## for nice error messages. - var p: TJsonParser - p.open(s, filename) - discard getTok(p) # read first token - result = p.parseJson() - p.close() +when not defined(js): + proc parseJson*(s: PStream, filename: string): PJsonNode = + ## Parses from a stream `s` into a `PJsonNode`. `filename` is only needed + ## for nice error messages. + var p: TJsonParser + p.open(s, filename) + discard getTok(p) # read first token + result = p.parseJson() + p.close() -proc parseJson*(buffer: string): PJsonNode = - ## Parses JSON from `buffer`. - result = parseJson(newStringStream(buffer), "input") + proc parseJson*(buffer: string): PJsonNode = + ## Parses JSON from `buffer`. + result = parseJson(newStringStream(buffer), "input") + + proc parseFile*(filename: string): PJsonNode = + ## Parses `file` into a `PJsonNode`. + var stream = newFileStream(filename, fmRead) + if stream == nil: + raise newException(EIO, "cannot read from file: " & filename) + result = parseJson(stream, filename) +else: + from math import `mod` + type + TJSObject = object + proc parseNativeJson(x: cstring): TJSObject {.importc: "JSON.parse".} + + proc getVarType(x): TJsonNodeKind = + result = JNull + proc getProtoName(y): cstring + {.importc: "Object.prototype.toString.call".} + case $getProtoName(x) # TODO: Implicit returns fail here. + of "[object Array]": return JArray + of "[object Object]": return JObject + of "[object Number]": + if cast[float](x) mod 1.0 == 0: + return JInt + else: + return JFloat + of "[object Boolean]": return JBool + of "[object Null]": return JNull + of "[object String]": return JString + else: assert false + + proc len(x: TJSObject): int = + assert x.getVarType == JArray + asm """ + return `x`.length; + """ + + proc `[]`(x: TJSObject, y: string): TJSObject = + assert x.getVarType == JObject + asm """ + return `x`[`y`]; + """ + + proc `[]`(x: TJSObject, y: int): TJSObject = + assert x.getVarType == JArray + asm """ + return `x`[`y`]; + """ + + proc convertObject(x: TJSObject): PJsonNode = + case getVarType(x) + of JArray: + result = newJArray() + for i in 0 .. `_ module for a list of available TFileMode enums. - var f: TFile - if open(f, filename, mode): result = newFileStream(f) + proc fsWriteData(s: PStream, buffer: pointer, bufLen: int) = + if writeBuffer(PFileStream(s).f, buffer, bufLen) != bufLen: + raise newEIO("cannot write to stream") + + proc newFileStream*(f: TFile): PFileStream = + ## creates a new stream from the file `f`. + new(result) + result.f = f + result.closeImpl = fsClose + result.atEndImpl = fsAtEnd + result.setPositionImpl = fsSetPosition + result.getPositionImpl = fsGetPosition + result.readDataImpl = fsReadData + result.writeDataImpl = fsWriteData + result.flushImpl = fsFlush + + proc newFileStream*(filename: string, mode: TFileMode): PFileStream = + ## creates a new stream from the file named `filename` with the mode `mode`. + ## If the file cannot be opened, nil is returned. See the `system + ## `_ module for a list of available TFileMode enums. + var f: TFile + if open(f, filename, mode): result = newFileStream(f) when true: From 7cddb39969a8404c8f51a681c95db7afe55a8901 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Fri, 11 Apr 2014 21:42:44 +0100 Subject: [PATCH 3/5] Adds split proc for strings with a string separator. --- lib/pure/strutils.nim | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index b63224cec4..bd6814dcca 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -264,6 +264,19 @@ iterator split*(s: string, sep: char): string = yield substr(s, first, last-1) inc(last) +iterator split*(s: string, sep: string): string = + ## Splits the string `s` into substrings using a string separator. + ## + ## Substrings are separated by the string `sep`. + var last = 0 + if len(s) > 0: + while last <= len(s): + var first = last + while last < len(s) and s.substr(last, last + Date: Sat, 12 Apr 2014 00:09:03 +0100 Subject: [PATCH 4/5] Implemented babel package list in lib.html. --- doc/lib.txt | 32 ++++++++++++++++++++ tools/nimweb.nim | 5 +++ web/babelpkglist.nim | 72 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 web/babelpkglist.nim diff --git a/doc/lib.txt b/doc/lib.txt index 62efe6a5d5..f79c980e25 100644 --- a/doc/lib.txt +++ b/doc/lib.txt @@ -573,3 +573,35 @@ Scientific computing * `libsvm `_ Low level wrapper for `lib svm `_. + +Babel +==================== + +Babel is a package manager for the Nimrod programming language. +For instructions on how to install Babel packages see +`its README `_. + +Official packages +----------------- + +These packages are officially supported and will therefore be continually +maintained to ensure that they work with the latest versions of the Nimrod +compiler. + +.. raw:: html + +
+ +Unofficial packages +------------------- + +These packages have been developed by independent Nimrod developers and as +such may not always be up to date with the latest developments in the +Nimrod programming language. + +.. raw:: html + +
+ + + diff --git a/tools/nimweb.nim b/tools/nimweb.nim index 5c78f3f450..9a83a5ccaa 100644 --- a/tools/nimweb.nim +++ b/tools/nimweb.nim @@ -348,6 +348,10 @@ proc buildNewsRss(c: var TConfigData, destPath: string) = generateRss(destFilename, parseNewsTitles(srcFilename)) +proc buildJS(destPath: string) = + exec("nimrod js -d:release --out:$1 web/babelpkglist.nim" % + [destPath / "babelpkglist.js"]) + proc main(c: var TConfigData) = const cmd = "nimrod rst2html --compileonly $1 -o:web/$2.temp web/$2.txt" @@ -377,6 +381,7 @@ proc main(c: var TConfigData) = quit("[Error] cannot write file: " & outfile) removeFile(temp) copyDir("web/assets", "web/upload/assets") + buildJS("web/upload") buildNewsRss(c, "web/upload") buildAddDoc(c, "web/upload") buildDocSamples(c, "web/upload") diff --git a/web/babelpkglist.nim b/web/babelpkglist.nim new file mode 100644 index 0000000000..378d4ce301 --- /dev/null +++ b/web/babelpkglist.nim @@ -0,0 +1,72 @@ +import base64, strutils, json, htmlgen, dom, algorithm + +type + TData = object + content {.importc.}: cstring + +proc decodeContent(content: string): string = + result = "" + for line in content.splitLines: + if line != "": + result.add decode(line) + +proc contains(x: seq[PJSonNode], s: string): bool = + for i in x: + assert i.kind == JString + if i.str == s: return true + +proc processContent(content: string) = + var jsonDoc = parseJson(content) + assert jsonDoc.kind == JArray + var jsonArr = jsonDoc.elems + + jsonArr.sort do (x, y: PJsonNode) -> int: + system.cmp(x["name"].str, y["name"].str) + + var + officialList = "" + officialCount = 0 + unofficialList = "" + unofficialCount = 0 + + for pkg in jsonArr: + assert pkg.kind == JObject + let pkgWeb = + if pkg.hasKey("web"): pkg["web"].str + else: pkg["url"].str + let listItem = li(a(href=pkgWeb, pkg["name"].str), " ", pkg["description"].str) + if pkg["url"].str.startsWith("git://github.com/nimrod-code") or + "official" in pkg["tags"].elems: + officialCount.inc + officialList.add listItem & "\n" + else: + unofficialCount.inc + unofficialList.add listItem & "\n" + + var officialPkgListDiv = document.getElementById("officialPkgList") + + officialPkgListDiv.innerHTML.add( + p("There are currently " & $officialCount & + " official packages in the Babel package repository.") & + ul(officialList) + ) + + var unofficialPkgListDiv = document.getElementById("unofficialPkgList") + + unofficialPkgListDiv.innerHTML.add( + p("There are currently " & $unofficialCount & + " unofficial packages in the Babel package repository.") & + ul(unofficialList) + ) + +proc gotPackageList(apiReply: TData) {.exportc.} = + let decoded = decodeContent($apiReply.content) + try: + processContent(decoded) + except: + var officialPkgListDiv = document.getElementById("officialPkgList") + var unofficialPkgListDiv = document.getElementById("unofficialPkgList") + let msg = p("Unable to retrieve package list: ", + code(getCurrentExceptionMsg())) + officialPkgListDiv.innerHTML = msg + unofficialPkgListDiv.innerHTML = msg From e333b8bc1ef27014066172c04881df191a4971fe Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sat, 12 Apr 2014 01:42:09 +0100 Subject: [PATCH 5/5] Rewrote sentence about Babel in lib.txt. --- doc/lib.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/lib.txt b/doc/lib.txt index f79c980e25..a209357f7c 100644 --- a/doc/lib.txt +++ b/doc/lib.txt @@ -16,10 +16,11 @@ Pure libraries do not depend on any external ``*.dll`` or ``lib*.so`` binary while impure libraries do. A wrapper is an impure library that is a very low-level interface to a C library. -Read this `document `_ for a quick overview of the API design. If -you can't find here some functionality you are looking for you could try using -the 3rd party `package manager Babel `_ -and its list of packages. +Read this `document `_ for a quick overview of the API design. + +The `bottom <#babel>`_ of this page includes a list of 3rd party packages +created by the Nimrod community. These packages are a useful addition to the +modules in the standard library. Pure libraries