# # # Nim's Runtime Library # (c) Copyright 2015 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # proc log*(s: cstring) {.importc: "console.log", varargs, nodecl.} type PSafePoint = ptr SafePoint SafePoint {.compilerproc, final.} = object prev: PSafePoint # points to next safe point exc: ref Exception PCallFrame = ptr CallFrame CallFrame {.importc, nodecl, final.} = object prev: PCallFrame procname: cstring 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 JSRef = ref RootObj # Fake type. {.deprecated: [TSafePoint: SafePoint, TCallFrame: CallFrame].} var framePtr {.importc, nodecl, volatile.}: PCallFrame excHandler {.importc, nodecl, volatile.}: int = 0 lastJSError {.importc, nodecl, volatile.}: PJSError = nil {.push stacktrace: off, profiler:off.} proc nimBoolToStr(x: bool): string {.compilerproc.} = if x: result = "true" else: result = "false" proc nimCharToStr(x: char): string {.compilerproc.} = result = newString(1) result[0] = x proc isNimException(): bool {.asmNoStackFrame.} = when defined(nimphp): asm "return isset(`lastJSError`['m_type']);" else: asm "return `lastJSError`.m_type;" proc getCurrentException*(): ref Exception {.compilerRtl, benign.} = if isNimException(): result = cast[ref Exception](lastJSError) proc getCurrentExceptionMsg*(): string = if lastJSError != nil: if isNimException(): return cast[Exception](lastJSError).msg else: when not defined(nimphp): var msg: cstring {.emit: """ if (`lastJSError`.message !== undefined) { `msg` = `lastJSError`.message; } """.} if not msg.isNil: return $msg return "" proc auxWriteStackTrace(f: PCallFrame): string = type TempFrame = tuple[procname: cstring, line: int] var it = f i = 0 total = 0 tempFrames: array[0..63, TempFrame] while it != nil and i <= high(tempFrames): tempFrames[i].procname = it.procname tempFrames[i].line = it.line inc(i) inc(total) it = it.prev while it != nil: inc(total) it = it.prev result = "" # if the buffer overflowed print '...': if total != i: add(result, "(") add(result, $(total-i)) add(result, " calls omitted) ...\n") for j in countdown(i-1, 0): add(result, tempFrames[j].procname) if tempFrames[j].line > 0: add(result, ", line: ") add(result, $tempFrames[j].line) add(result, "\n") proc rawWriteStackTrace(): string = if framePtr != nil: result = "Traceback (most recent call last)\n" & auxWriteStackTrace(framePtr) else: result = "No stack traceback available\n" proc getStackTrace*(): string = rawWriteStackTrace() proc getStackTrace*(e: ref Exception): string = e.trace proc unhandledException(e: ref Exception) {. compilerproc, asmNoStackFrame.} = var buf = "" if e.msg != nil and e.msg[0] != '\0': add(buf, "Error: unhandled exception: ") add(buf, e.msg) else: add(buf, "Error: unhandled exception") add(buf, " [") add(buf, e.name) add(buf, "]\n") when NimStackTrace: add(buf, rawWriteStackTrace()) let cbuf : cstring = buf framePtr = nil {.emit: """ if (typeof(Error) !== "undefined") { throw new Error(`cbuf`); } else { throw `cbuf`; } """.} proc raiseException(e: ref Exception, ename: cstring) {. compilerproc, asmNoStackFrame.} = e.name = ename if excHandler == 0: unhandledException(e) when defined(nimphp): asm """throw new Exception($`e`["message"]);""" else: when NimStackTrace: e.trace = rawWriteStackTrace() asm "throw `e`;" proc reraiseException() {.compilerproc, asmNoStackFrame.} = if lastJSError == nil: raise newException(ReraiseError, "no exception to reraise") else: if excHandler == 0: if isNimException(): unhandledException(cast[ref Exception](lastJSError)) asm "throw lastJSError;" proc raiseOverflow {.exportc: "raiseOverflow", noreturn, compilerProc.} = raise newException(OverflowError, "over- or underflow") proc raiseDivByZero {.exportc: "raiseDivByZero", noreturn, compilerProc.} = raise newException(DivByZeroError, "division by zero") proc raiseRangeError() {.compilerproc, noreturn.} = raise newException(RangeError, "value out of range") proc raiseIndexError() {.compilerproc, noreturn.} = raise newException(IndexError, "index out of bounds") proc raiseFieldError(f: string) {.compilerproc, noreturn.} = raise newException(FieldError, f & " is not accessible") proc setConstr() {.varargs, asmNoStackFrame, compilerproc.} = when defined(nimphp): asm """ $args = func_get_args(); $result = array(); foreach ($args as $x) { if (is_array($x)) { for ($j = $x[0]; $j <= $x[1]; $j++) { $result[$j] = true; } } else { $result[$x] = true; } } return $result; """ else: asm """ var result = {}; for (var i = 0; i < arguments.length; ++i) { var x = arguments[i]; if (typeof(x) == "object") { for (var j = x[0]; j <= x[1]; ++j) { result[j] = true; } } else { result[x] = true; } } return result; """ proc makeNimstrLit(c: cstring): string {.asmNoStackFrame, compilerproc.} = when defined(nimphp): {.emit: """return `c`;""".} else: {.emit: """ var ln = `c`.length; var result = new Array(ln + 1); var i = 0; for (; i < ln; ++i) { result[i] = `c`.charCodeAt(i); } result[i] = 0; // terminating zero return result; """.} proc cstrToNimstr(c: cstring): string {.asmNoStackFrame, compilerproc.} = when defined(nimphp): {.emit: """return `c`;""".} else: {.emit: """ var ln = `c`.length; var result = new Array(ln); var r = 0; for (var i = 0; i < ln; ++i) { var ch = `c`.charCodeAt(i); if (ch < 128) { result[r] = ch; } else { if (ch < 2048) { result[r] = (ch >> 6) | 192; } else { if (ch < 55296 || ch >= 57344) { result[r] = (ch >> 12) | 224; } else { ++i; ch = 65536 + (((ch & 1023) << 10) | (`c`.charCodeAt(i) & 1023)); result[r] = (ch >> 18) | 240; ++r; result[r] = ((ch >> 12) & 63) | 128; } ++r; result[r] = ((ch >> 6) & 63) | 128; } ++r; result[r] = (ch & 63) | 128; } ++r; } result[r] = 0; // terminating zero return result; """.} proc toJSStr(s: string): cstring {.asmNoStackFrame, compilerproc.} = when defined(nimphp): {.emit: """return `s`;""".} else: asm """ var len = `s`.length-1; var asciiPart = new Array(len); var fcc = String.fromCharCode; var nonAsciiPart = null; var nonAsciiOffset = 0; for (var i = 0; i < len; ++i) { if (nonAsciiPart !== null) { var offset = (i - nonAsciiOffset) * 2; var code = `s`[i].toString(16); if (code.length == 1) { code = "0"+code; } nonAsciiPart[offset] = "%"; nonAsciiPart[offset + 1] = code; } else if (`s`[i] < 128) asciiPart[i] = fcc(`s`[i]); else { asciiPart.length = i; nonAsciiOffset = i; nonAsciiPart = new Array((len - i) * 2); --i; } } asciiPart = asciiPart.join(""); return (nonAsciiPart === null) ? asciiPart : asciiPart + decodeURIComponent(nonAsciiPart.join("")); """ proc mnewString(len: int): string {.asmNoStackFrame, compilerproc.} = when defined(nimphp): asm """ return str_repeat(chr(0),`len`); """ else: asm """ var result = new Array(`len`+1); result[0] = 0; result[`len`] = 0; return result; """ when defined(nimphp): proc nimAt(x: string; i: int): string {.asmNoStackFrame, compilerproc.} = asm """ return `x`[`i`]; """ when defined(nimphp): proc nimSubstr(s: string; a, b: int): string {. asmNoStackFrame, compilerproc.} = asm """return substr(`s`,`a`,`b`-`a`+1);""" proc SetCard(a: int): int {.compilerproc, asmNoStackFrame.} = # argument type is a fake when defined(nimphp): asm """ return count(`a`); """ else: asm """ var result = 0; for (var elem in `a`) { ++result; } return result; """ proc SetEq(a, b: int): bool {.compilerproc, asmNoStackFrame.} = when defined(nimphp): asm """ foreach (`a` as $elem=>$_) { if (!isset(`b`[$elem])) return false; } foreach (`b` as $elem=>$_) { if (!isset(`a`[$elem])) return false; } return true; """ else: asm """ for (var elem in `a`) { if (!`b`[elem]) return false; } for (var elem in `b`) { if (!`a`[elem]) return false; } return true; """ proc SetLe(a, b: int): bool {.compilerproc, asmNoStackFrame.} = when defined(nimphp): asm """ foreach (`a` as $elem=>$_) { if (!isset(`b`[$elem])) return false; } return true; """ else: asm """ for (var elem in `a`) { if (!`b`[elem]) return false; } return true; """ proc SetLt(a, b: int): bool {.compilerproc.} = result = SetLe(a, b) and not SetEq(a, b) proc SetMul(a, b: int): int {.compilerproc, asmNoStackFrame.} = when defined(nimphp): asm """ var $result = array(); foreach (`a` as $elem=>$_) { if (isset(`b`[$elem])) { $result[$elem] = true; } } return $result; """ else: asm """ var result = {}; for (var elem in `a`) { if (`b`[elem]) { result[elem] = true; } } return result; """ proc SetPlus(a, b: int): int {.compilerproc, asmNoStackFrame.} = when defined(nimphp): asm """ var $result = array(); foreach (`a` as $elem=>$_) { $result[$elem] = true; } foreach (`b` as $elem=>$_) { $result[$elem] = true; } return $result; """ else: asm """ var result = {}; for (var elem in `a`) { result[elem] = true; } for (var elem in `b`) { result[elem] = true; } return result; """ proc SetMinus(a, b: int): int {.compilerproc, asmNoStackFrame.} = when defined(nimphp): asm """ $result = array(); foreach (`a` as $elem=>$_) { if (!isset(`b`[$elem])) { $result[$elem] = true; } } return $result; """ else: asm """ var result = {}; for (var elem in `a`) { if (!`b`[elem]) { result[elem] = true; } } return result; """ proc cmpStrings(a, b: string): int {.asmNoStackFrame, compilerProc.} = asm """ if (`a` == `b`) return 0; if (!`a`) return -1; if (!`b`) return 1; for (var i = 0; i < `a`.length-1; ++i) { var result = `a`[i] - `b`[i]; if (result != 0) return result; } return 0; """ proc cmp(x, y: string): int = when defined(nimphp): asm """ if(`x` < `y`) `result` = -1; elseif (`x` > `y`) `result` = 1; else `result` = 0; """ else: return cmpStrings(x, y) proc eqStrings(a, b: string): bool {.asmNoStackFrame, compilerProc.} = asm """ if (`a` == `b`) return true; if ((!`a`) || (!`b`)) return false; var alen = `a`.length; if (alen != `b`.length) return false; for (var i = 0; i < alen; ++i) if (`a`[i] != `b`[i]) return false; return true; """ when defined(kwin): proc rawEcho {.compilerproc, asmNoStackFrame.} = asm """ var buf = ""; for (var i = 0; i < arguments.length; ++i) { buf += `toJSStr`(arguments[i]); } print(buf); """ elif not defined(nimOldEcho): proc ewriteln(x: cstring) = log(x) proc rawEcho {.compilerproc, asmNoStackFrame.} = asm """ var buf = ""; for (var i = 0; i < arguments.length; ++i) { buf += `toJSStr`(arguments[i]); } console.log(buf); """ elif not defined(nimphp): proc ewriteln(x: cstring) = var node : JSRef {.emit: "`node` = document.getElementsByTagName('body')[0];".} if node.isNil: raise newException(ValueError, " element does not exist yet!") {.emit: """ `node`.appendChild(document.createTextNode(`x`)); `node`.appendChild(document.createElement("br")); """.} proc rawEcho {.compilerproc.} = var node : JSRef {.emit: "`node` = document.getElementsByTagName('body')[0];".} if node.isNil: raise newException(IOError, " element does not exist yet!") {.emit: """ for (var i = 0; i < arguments.length; ++i) { var x = `toJSStr`(arguments[i]); `node`.appendChild(document.createTextNode(x)); } `node`.appendChild(document.createElement("br")); """.} # Arithmetic: proc addInt(a, b: int): int {.asmNoStackFrame, compilerproc.} = when defined(nimphp): asm """ return `a` + `b`; """ else: asm """ var result = `a` + `b`; if (result > 2147483647 || result < -2147483648) `raiseOverflow`(); return result; """ proc subInt(a, b: int): int {.asmNoStackFrame, compilerproc.} = when defined(nimphp): asm """ return `a` - `b`; """ else: asm """ var result = `a` - `b`; if (result > 2147483647 || result < -2147483648) `raiseOverflow`(); return result; """ proc mulInt(a, b: int): int {.asmNoStackFrame, compilerproc.} = when defined(nimphp): asm """ return `a` * `b`; """ else: asm """ var result = `a` * `b`; if (result > 2147483647 || result < -2147483648) `raiseOverflow`(); return result; """ proc divInt(a, b: int): int {.asmNoStackFrame, compilerproc.} = when defined(nimphp): asm """ return trunc(`a` / `b`); """ else: asm """ if (`b` == 0) `raiseDivByZero`(); if (`b` == -1 && `a` == 2147483647) `raiseOverflow`(); return Math.trunc(`a` / `b`); """ proc modInt(a, b: int): int {.asmNoStackFrame, compilerproc.} = when defined(nimphp): asm """ return `a` % `b`; """ else: asm """ if (`b` == 0) `raiseDivByZero`(); if (`b` == -1 && `a` == 2147483647) `raiseOverflow`(); return Math.trunc(`a` % `b`); """ proc addInt64(a, b: int): int {.asmNoStackFrame, compilerproc.} = when defined(nimphp): asm """ return `a` + `b`; """ else: asm """ var result = `a` + `b`; if (result > 9223372036854775807 || result < -9223372036854775808) `raiseOverflow`(); return result; """ proc subInt64(a, b: int): int {.asmNoStackFrame, compilerproc.} = when defined(nimphp): asm """ return `a` - `b`; """ else: asm """ var result = `a` - `b`; if (result > 9223372036854775807 || result < -9223372036854775808) `raiseOverflow`(); return result; """ proc mulInt64(a, b: int): int {.asmNoStackFrame, compilerproc.} = when defined(nimphp): asm """ return `a` * `b`; """ else: asm """ var result = `a` * `b`; if (result > 9223372036854775807 || result < -9223372036854775808) `raiseOverflow`(); return result; """ proc divInt64(a, b: int): int {.asmNoStackFrame, compilerproc.} = when defined(nimphp): asm """ return trunc(`a` / `b`); """ else: asm """ if (`b` == 0) `raiseDivByZero`(); if (`b` == -1 && `a` == 9223372036854775807) `raiseOverflow`(); return Math.trunc(`a` / `b`); """ proc modInt64(a, b: int): int {.asmNoStackFrame, compilerproc.} = when defined(nimphp): asm """ return `a` % `b`; """ else: asm """ if (`b` == 0) `raiseDivByZero`(); if (`b` == -1 && `a` == 9223372036854775807) `raiseOverflow`(); return Math.trunc(`a` % `b`); """ proc negInt(a: int): int {.compilerproc.} = result = a*(-1) proc negInt64(a: int64): int64 {.compilerproc.} = result = a*(-1) proc absInt(a: int): int {.compilerproc.} = result = if a < 0: a*(-1) else: a proc absInt64(a: int64): int64 {.compilerproc.} = result = if a < 0: a*(-1) else: a proc ze*(a: int): int {.compilerproc.} = result = a proc ze64*(a: int64): int64 {.compilerproc.} = result = a proc toU8*(a: int): int8 {.asmNoStackFrame, compilerproc.} = asm """ return `a`; """ proc toU16*(a: int): int16 {.asmNoStackFrame, compilerproc.} = asm """ return `a`; """ proc toU32*(a: int64): int32 {.asmNoStackFrame, compilerproc.} = asm """ return `a`; """ proc nimMin(a, b: int): int {.compilerproc.} = return if a <= b: a else: b proc nimMax(a, b: int): int {.compilerproc.} = return if a >= b: a else: b proc chckNilDisp(p: pointer) {.compilerproc.} = if p == nil: sysFatal(NilAccessError, "cannot dispatch; dispatcher is nil") type NimString = string # hack for hti.nim include "system/hti" proc isFatPointer(ti: PNimType): bool = # This has to be consistent with the code generator! return ti.base.kind notin {tyObject, tyArray, tyArrayConstr, tyTuple, tyOpenArray, tySet, tyVar, tyRef, tyPtr} proc nimCopy(dest, src: JSRef, ti: PNimType): JSRef {.compilerproc.} proc nimCopyAux(dest, src: JSRef, n: ptr TNimNode) {.compilerproc.} = case n.kind of nkNone: sysAssert(false, "nimCopyAux") of nkSlot: asm """ `dest`[`n`.offset] = nimCopy(`dest`[`n`.offset], `src`[`n`.offset], `n`.typ); """ of nkList: for i in 0..n.len-1: nimCopyAux(dest, src, n.sons[i]) of nkCase: asm """ `dest`[`n`.offset] = nimCopy(`dest`[`n`.offset], `src`[`n`.offset], `n`.typ); for (var i = 0; i < `n`.sons.length; ++i) { nimCopyAux(`dest`, `src`, `n`.sons[i][1]); } """ proc nimCopy(dest, src: JSRef, ti: PNimType): JSRef = case ti.kind of tyPtr, tyRef, tyVar, tyNil: if not isFatPointer(ti): result = src else: asm "`result` = [`src`[0], `src`[1]];" of tySet: asm """ if (`dest` === null || `dest` === undefined) { `dest` = {}; } else { for (var key in `dest`) { delete `dest`[key]; } } for (var key in `src`) { `dest`[key] = `src`[key]; } `result` = `dest`; """ of tyTuple, tyObject: if ti.base != nil: result = nimCopy(dest, src, ti.base) elif ti.kind == tyObject: asm "`result` = (`dest` === null || `dest` === undefined) ? {m_type: `ti`} : `dest`;" else: asm "`result` = (`dest` === null || `dest` === undefined) ? {} : `dest`;" nimCopyAux(result, src, ti.node) of tySequence, tyArrayConstr, tyOpenArray, tyArray: asm """ if (`src` === null) { `result` = null; } else { if (`dest` === null || `dest` === undefined) { `dest` = new Array(`src`.length); } else { `dest`.length = `src`.length; } `result` = `dest`; for (var i = 0; i < `src`.length; ++i) { `result`[i] = nimCopy(`result`[i], `src`[i], `ti`.base); } } """ of tyString: asm """ if (`src` !== null) { `result` = `src`.slice(0); } """ else: result = src proc genericReset(x: JSRef, ti: PNimType): JSRef {.compilerproc.} = asm "`result` = null;" case ti.kind of tyPtr, tyRef, tyVar, tyNil: if isFatPointer(ti): asm """ `result` = [null, 0]; """ of tySet: asm """ `result` = {}; """ of tyTuple, tyObject: if ti.kind == tyObject: asm "`result` = {m_type: `ti`};" else: asm "`result` = {};" of tySequence, tyOpenArray: asm """ `result` = []; """ of tyArrayConstr, tyArray: asm """ `result` = new Array(`x`.length); for (var i = 0; i < `x`.length; ++i) { `result`[i] = genericReset(`x`[i], `ti`.base); } """ else: discard when defined(nimphp): proc arrayConstr(len: int, value: string, typ: string): JSRef {. asmNoStackFrame, compilerproc.} = # types are fake asm """ $result = array(); for ($i = 0; $i < `len`; $i++) $result[] = `value`; return $result; """ else: proc arrayConstr(len: int, value: JSRef, typ: PNimType): JSRef {. asmNoStackFrame, compilerproc.} = # types are fake asm """ var result = new Array(`len`); for (var i = 0; i < `len`; ++i) result[i] = nimCopy(null, `value`, `typ`); return result; """ proc chckIndx(i, a, b: int): int {.compilerproc.} = if i >= a and i <= b: return i else: raiseIndexError() proc chckRange(i, a, b: int): int {.compilerproc.} = if i >= a and i <= b: return i else: raiseRangeError() proc chckObj(obj, subclass: PNimType) {.compilerproc.} = # checks if obj is of type subclass: var x = obj if x == subclass: return # optimized fast path while x != subclass: if x == nil: raise newException(ObjectConversionError, "invalid object conversion") x = x.base proc isObj(obj, subclass: PNimType): bool {.compilerproc.} = # checks if obj is of type subclass: var x = obj if x == subclass: return true # optimized fast path while x != subclass: if x == nil: return false x = x.base return true proc addChar(x: string, c: char) {.compilerproc, asmNoStackFrame.} = asm """ `x`[`x`.length-1] = `c`; `x`.push(0); """ {.pop.} proc tenToThePowerOf(b: int): BiggestFloat = var b = b var a = 10.0 result = 1.0 while true: if (b and 1) == 1: result = result * a b = b shr 1 if b == 0: break a = a * a const IdentChars = {'a'..'z', 'A'..'Z', '0'..'9', '_'} # XXX use JS's native way here proc nimParseBiggestFloat(s: string, number: var BiggestFloat, start = 0): int {. compilerProc.} = var esign = 1.0 sign = 1.0 i = start exponent: int flags: int number = 0.0 if s[i] == '+': inc(i) elif s[i] == '-': sign = -1.0 inc(i) if s[i] == 'N' or s[i] == 'n': if s[i+1] == 'A' or s[i+1] == 'a': if s[i+2] == 'N' or s[i+2] == 'n': if s[i+3] notin IdentChars: number = NaN return i+3 - start return 0 if s[i] == 'I' or s[i] == 'i': if s[i+1] == 'N' or s[i+1] == 'n': if s[i+2] == 'F' or s[i+2] == 'f': if s[i+3] notin IdentChars: number = Inf*sign return i+3 - start return 0 while s[i] in {'0'..'9'}: # Read integer part flags = flags or 1 number = number * 10.0 + toFloat(ord(s[i]) - ord('0')) inc(i) while s[i] == '_': inc(i) # Decimal? if s[i] == '.': var hd = 1.0 inc(i) while s[i] in {'0'..'9'}: # Read fractional part flags = flags or 2 number = number * 10.0 + toFloat(ord(s[i]) - ord('0')) hd = hd * 10.0 inc(i) while s[i] == '_': inc(i) number = number / hd # this complicated way preserves precision # Again, read integer and fractional part if flags == 0: return 0 # Exponent? if s[i] in {'e', 'E'}: inc(i) if s[i] == '+': inc(i) elif s[i] == '-': esign = -1.0 inc(i) if s[i] notin {'0'..'9'}: return 0 while s[i] in {'0'..'9'}: exponent = exponent * 10 + ord(s[i]) - ord('0') inc(i) while s[i] == '_': inc(i) # Calculate Exponent let hd = tenToThePowerOf(exponent) if esign > 0.0: number = number * hd else: number = number / hd # evaluate sign number = number * sign result = i - start when defined(nodejs): # Deprecated. Use `alert` defined in dom.nim proc alert*(s: cstring) {.importc: "console.log", nodecl, deprecated.} else: # Deprecated. Use `alert` defined in dom.nim proc alert*(s: cstring) {.importc, nodecl, deprecated.}