# # # The Nim Compiler # (c) Copyright 2015 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # # This is the JavaScript code generator. # Also a PHP code generator. ;-) discard """ The JS code generator contains only 2 tricks: Trick 1 ------- Some locations (for example 'var int') require "fat pointers" (``etyBaseIndex``) which are pairs (array, index). The derefence operation is then 'array[index]'. Check ``mapType`` for the details. Trick 2 ------- It is preferable to generate '||' and '&&' if possible since that is more idiomatic and hence should be friendlier for the JS JIT implementation. However code like ``foo and (let bar = baz())`` cannot be translated this way. Instead the expressions need to be transformed into statements. ``isSimpleExpr`` implements the required case distinction. """ import ast, astalgo, strutils, hashes, trees, platform, magicsys, extccomp, options, nversion, nimsets, msgs, securehash, bitsets, idents, types, os, times, ropes, math, passes, ccgutils, wordrecg, renderer, rodread, rodutils, intsets, cgmeth, lowerings from modulegraphs import ModuleGraph type TTarget = enum targetJS, targetPHP TJSGen = object of TPassContext module: PSym target: TTarget BModule = ref TJSGen TJSTypeKind = enum # necessary JS "types" etyNone, # no type etyNull, # null type etyProc, # proc type etyBool, # bool type etySeq, # Nim seq or string type etyInt, # JavaScript's int etyFloat, # JavaScript's float etyString, # JavaScript's string etyObject, # JavaScript's reference to an object etyBaseIndex # base + index needed TResKind = enum resNone, # not set resExpr, # is some complex expression resVal, # is a temporary/value/l-value resCallee # expression is callee TCompRes = object kind: TResKind typ: TJSTypeKind res: Rope # result part; index if this is an # (address, index)-tuple address: Rope # address of an (address, index)-tuple TBlock = object id: int # the ID of the label; positive means that it # has been used (i.e. the label should be emitted) isLoop: bool # whether it's a 'block' or 'while' TGlobals = object typeInfo, constants, code: Rope forwarded: seq[PSym] generatedSyms: IntSet typeInfoGenerated: IntSet classes: seq[(PType, Rope)] unique: int # for temp identifier generation PGlobals = ref TGlobals PProc = ref TProc TProc = object procDef: PNode prc: PSym globals, locals, body: Rope options: TOptions module: BModule g: PGlobals beforeRetNeeded: bool target: TTarget # duplicated here for faster dispatching unique: int # for temp identifier generation blocks: seq[TBlock] extraIndent: int up: PProc # up the call chain; required for closure support declaredGlobals: IntSet template `|`(a, b: untyped): untyped {.dirty.} = (if p.target == targetJS: a else: b) var indent = "\t".rope proc indentLine(p: PProc, r: Rope): Rope = result = r var p = p while true: for i in countup(0, p.blocks.len - 1 + p.extraIndent): prepend(result, indent) if p.up == nil or p.up.prc != p.prc.owner: break p = p.up template line(p: PProc, added: string) = add(p.body, indentLine(p, rope(added))) template line(p: PProc, added: Rope) = add(p.body, indentLine(p, added)) template lineF(p: PProc, frmt: FormatStr, args: varargs[Rope]) = add(p.body, indentLine(p, ropes.`%`(frmt, args))) template nested(p, body) = inc p.extraIndent body dec p.extraIndent proc newGlobals(): PGlobals = new(result) result.forwarded = @[] result.generatedSyms = initIntSet() result.typeInfoGenerated = initIntSet() result.classes = @[] proc initCompRes(r: var TCompRes) = r.address = nil r.res = nil r.typ = etyNone r.kind = resNone proc rdLoc(a: TCompRes): Rope {.inline.} = result = a.res when false: if a.typ != etyBaseIndex: result = a.res else: result = "$1[$2]" % [a.address, a.res] proc newProc(globals: PGlobals, module: BModule, procDef: PNode, options: TOptions): PProc = result = PProc( blocks: @[], options: options, module: module, procDef: procDef, g: globals, target: module.target, extraIndent: int(procDef != nil)) if procDef != nil: result.prc = procDef.sons[namePos].sym if result.target == targetPHP: result.declaredGlobals = initIntSet() proc declareGlobal(p: PProc; id: int; r: Rope) = if p.prc != nil and not p.declaredGlobals.containsOrIncl(id): p.locals.addf("global $1;$n", [r]) const MappedToObject = {tyObject, tyArray, tyTuple, tyOpenArray, tySet, tyVarargs} proc mapType(typ: PType): TJSTypeKind = let t = skipTypes(typ, abstractInst) case t.kind of tyVar, tyRef, tyPtr: if skipTypes(t.lastSon, abstractInst).kind in MappedToObject: result = etyObject else: result = etyBaseIndex of tyPointer: # treat a tyPointer like a typed pointer to an array of bytes result = etyBaseIndex of tyRange, tyDistinct, tyOrdinal, tyProxy: result = mapType(t.sons[0]) of tyInt..tyInt64, tyUInt..tyUInt64, tyEnum, tyChar: result = etyInt of tyBool: result = etyBool of tyFloat..tyFloat128: result = etyFloat of tySet: result = etyObject # map a set to a table of tyString, tySequence, tyOpt: result = etySeq of tyObject, tyArray, tyTuple, tyOpenArray, tyVarargs: result = etyObject of tyNil: result = etyNull of tyGenericInst, tyGenericParam, tyGenericBody, tyGenericInvocation, tyNone, tyFromExpr, tyForward, tyEmpty, tyExpr, tyStmt, tyTypeDesc, tyTypeClasses, tyVoid, tyAlias: result = etyNone of tyInferred: result = mapType(typ.lastSon) of tyStatic: if t.n != nil: result = mapType(lastSon t) else: result = etyNone of tyProc: result = etyProc of tyCString: result = etyString of tyUnused, tyOptAsRef, tyUnused1, tyUnused2: internalError("mapType") proc mapType(p: PProc; typ: PType): TJSTypeKind = if p.target == targetPHP: result = etyObject else: result = mapType(typ) proc mangleName(s: PSym; target: TTarget): Rope = proc validJsName(name: string): bool = result = true const reservedWords = ["abstract", "await", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "debugger", "default", "delete", "do", "double", "else", "enum", "export", "extends", "false", "final", "finally", "float", "for", "function", "goto", "if", "implements", "import", "in", "instanceof", "int", "interface", "let", "long", "native", "new", "null", "package", "private", "protected", "public", "return", "short", "static", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "true", "try", "typeof", "var", "void", "volatile", "while", "with", "yield"] case name of reservedWords: return false else: discard if name[0] in {'0'..'9'}: return false for chr in name: if chr notin {'A'..'Z','a'..'z','_','$','0'..'9'}: return false result = s.loc.r if result == nil: if s.kind == skField and s.name.s.validJsName: result = rope(s.name.s) elif target == targetJS or s.kind == skTemp: result = rope(mangle(s.name.s)) else: var x = newStringOfCap(s.name.s.len) var i = 0 while i < s.name.s.len: let c = s.name.s[i] case c of 'A'..'Z': if i > 0 and s.name.s[i-1] in {'a'..'z'}: x.add '_' x.add(chr(c.ord - 'A'.ord + 'a'.ord)) of 'a'..'z', '_', '0'..'9': x.add c else: x.add("HEX" & toHex(ord(c), 2)) inc i result = rope(x) if s.name.s != "this" and s.kind != skField: add(result, "_") add(result, rope(s.id)) s.loc.r = result proc escapeJSString(s: string): string = result = newStringOfCap(s.len + s.len shr 2) result.add("\"") for c in items(s): case c of '\l': result.add("\\n") of '\r': result.add("\\r") of '\t': result.add("\\t") of '\b': result.add("\\b") of '\a': result.add("\\a") of '\e': result.add("\\e") of '\v': result.add("\\v") of '\\': result.add("\\\\") of '\"': result.add("\\\"") else: add(result, c) result.add("\"") proc makeJSString(s: string, escapeNonAscii = true): Rope = if s.isNil: result = "null".rope elif escapeNonAscii: result = strutils.escape(s).rope else: result = escapeJSString(s).rope include jstypes proc gen(p: PProc, n: PNode, r: var TCompRes) proc genStmt(p: PProc, n: PNode) proc genProc(oldProc: PProc, prc: PSym): Rope proc genConstant(p: PProc, c: PSym) proc useMagic(p: PProc, name: string) = if name.len == 0: return var s = magicsys.getCompilerProc(name) if s != nil: internalAssert s.kind in {skProc, skFunc, skMethod, skConverter} if not p.g.generatedSyms.containsOrIncl(s.id): let code = genProc(p, s) add(p.g.constants, code) else: # we used to exclude the system module from this check, but for DLL # generation support this sloppyness leads to hard to detect bugs, so # we're picky here for the system module too: if p.prc != nil: globalError(p.prc.info, errSystemNeeds, name) else: rawMessage(errSystemNeeds, name) proc isSimpleExpr(p: PProc; n: PNode): bool = # calls all the way down --> can stay expression based if n.kind in nkCallKinds+{nkBracketExpr, nkDotExpr, nkPar} or (p.target == targetJS and n.kind in {nkObjConstr, nkBracket, nkCurly}): for c in n: if not p.isSimpleExpr(c): return false result = true elif n.isAtom: result = true proc getTemp(p: PProc, defineInLocals: bool = true): Rope = inc(p.unique) if p.target == targetJS: result = "Tmp$1" % [rope(p.unique)] if defineInLocals: add(p.locals, p.indentLine("var $1;$n" % [result])) else: result = "$$Tmp$1" % [rope(p.unique)] proc genAnd(p: PProc, a, b: PNode, r: var TCompRes) = assert r.kind == resNone var x, y: TCompRes if p.isSimpleExpr(a) and p.isSimpleExpr(b): gen(p, a, x) gen(p, b, y) r.kind = resExpr r.res = "($1 && $2)" % [x.rdLoc, y.rdLoc] else: r.res = p.getTemp r.kind = resVal # while a and b: # --> # while true: # aa # if not a: tmp
# All and any
template all(container, cond: expr): expr {.immediate.} =
block:
var result = true
for it in items(container):
if not cond(it):
result = false
break
result
template any(container, cond: expr): expr {.immediate.} =
block:
var result = false
for it in items(container):
if cond(it):
result = true
break
result
if all("mystring", {'a'..'z'}.contains) and any("myohmy", 'y'.`==`):
echo "works"
else:
echo "does not work"