# # # The Nim Compiler # (c) Copyright 2014 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # ## This module implements the C code generator. import ast, astalgo, strutils, hashes, trees, platform, magicsys, extccomp, options, intsets, nversion, nimsets, msgs, crc, bitsets, idents, lists, types, ccgutils, os, times, ropes, math, passes, rodread, wordrecg, treetab, cgmeth, condsyms, rodutils, renderer, idgen, cgendata, ccgmerge, semfold, aliases, lowerings, semparallel when options.hasTinyCBackend: import tccgen # implementation var generatedHeader: BModule proc ropeff(cformat, llvmformat: string, args: varargs[PRope]): PRope = if gCmd == cmdCompileToLLVM: result = ropef(llvmformat, args) else: result = ropef(cformat, args) proc appff(dest: var PRope, cformat, llvmformat: string, args: varargs[PRope]) = if gCmd == cmdCompileToLLVM: appf(dest, llvmformat, args) else: appf(dest, cformat, args) proc addForwardedProc(m: BModule, prc: PSym) = m.forwardedProcs.add(prc) inc(gForwardedProcsCounter) proc getCgenModule(s: PSym): BModule = result = if s.position >= 0 and s.position < gModules.len: gModules[s.position] else: nil proc findPendingModule(m: BModule, s: PSym): BModule = var ms = getModule(s) result = gModules[ms.position] proc emitLazily(s: PSym): bool {.inline.} = result = optDeadCodeElim in gGlobalOptions or sfDeadCodeElim in getModule(s).flags proc initLoc(result: var TLoc, k: TLocKind, typ: PType, s: TStorageLoc) = result.k = k result.s = s result.t = getUniqueType(typ) result.r = nil #result.a = - 1 result.flags = {} proc fillLoc(a: var TLoc, k: TLocKind, typ: PType, r: PRope, s: TStorageLoc) = # fills the loc if it is not already initialized if a.k == locNone: a.k = k a.t = getUniqueType(typ) #a.a = - 1 a.s = s if a.r == nil: a.r = r proc isSimpleConst(typ: PType): bool = let t = skipTypes(typ, abstractVar) result = t.kind notin {tyTuple, tyObject, tyArray, tyArrayConstr, tySet, tySequence} and not (t.kind == tyProc and t.callConv == ccClosure) proc useStringh(m: BModule) = if not m.includesStringh: m.includesStringh = true discard lists.includeStr(m.headerFiles, "") proc useHeader(m: BModule, sym: PSym) = if lfHeader in sym.loc.flags: assert(sym.annex != nil) discard lists.includeStr(m.headerFiles, getStr(sym.annex.path)) proc cgsym(m: BModule, name: string): PRope proc ropecg(m: BModule, frmt: TFormatStr, args: varargs[PRope]): PRope = var i = 0 var length = len(frmt) result = nil var num = 0 while i < length: if frmt[i] == '$': inc(i) # skip '$' case frmt[i] of '$': app(result, "$") inc(i) of '#': inc(i) app(result, args[num]) inc(num) of '0'..'9': var j = 0 while true: j = (j * 10) + ord(frmt[i]) - ord('0') inc(i) if i >= length or not (frmt[i] in {'0'..'9'}): break num = j if j > high(args) + 1: internalError("ropes: invalid format string $" & $j) app(result, args[j-1]) of 'n': if optLineDir notin gOptions: app(result, rnl) inc(i) of 'N': app(result, rnl) inc(i) else: internalError("ropes: invalid format string $" & frmt[i]) elif frmt[i] == '#' and frmt[i+1] in IdentStartChars: inc(i) var j = i while frmt[j] in IdentChars: inc(j) var ident = substr(frmt, i, j-1) i = j app(result, cgsym(m, ident)) elif frmt[i] == '#' and frmt[i+1] == '$': inc(i, 2) var j = 0 while frmt[i] in Digits: j = (j * 10) + ord(frmt[i]) - ord('0') inc(i) app(result, cgsym(m, args[j-1].ropeToStr)) var start = i while i < length: if frmt[i] != '$' and frmt[i] != '#': inc(i) else: break if i - 1 >= start: app(result, substr(frmt, start, i - 1)) const compileTimeRopeFmt = false when compileTimeRopeFmt: import macros type TFmtFragmentKind = enum ffSym, ffLit, ffParam type TFragment = object case kind: TFmtFragmentKind of ffSym, ffLit: value: string of ffParam: intValue: int iterator fmtStringFragments(s: string): tuple[kind: TFmtFragmentKind, value: string, intValue: int] = # This is a bit less featured version of the ropecg's algorithm # (be careful when replacing ropecg calls) var i = 0 length = s.len while i < length: var start = i case s[i] of '$': let n = s[i+1] case n of '$': inc i, 2 of '0'..'9': # XXX: use the new case object construction syntax when it's ready yield (kind: ffParam, value: "", intValue: n.ord - ord('1')) inc i, 2 start = i else: inc i of '#': inc i var j = i while s[i] in IdentChars: inc i yield (kind: ffSym, value: substr(s, j, i-1), intValue: 0) start = i else: discard while i < length: if s[i] != '$' and s[i] != '#': inc i else: break if i - 1 >= start: yield (kind: ffLit, value: substr(s, start, i-1), intValue: 0) macro rfmt(m: BModule, fmt: static[string], args: varargs[PRope]): expr = ## Experimental optimized rope-formatting operator ## The run-time code it produces will be very fast, but will it speed up ## the compilation of nimrod itself or will the macro execution time ## offset the gains? result = newCall(bindSym"ropeConcat") for frag in fmtStringFragments(fmt): case frag.kind of ffSym: result.add(newCall(bindSym"cgsym", m, newStrLitNode(frag.value))) of ffLit: result.add(newCall(bindSym"~", newStrLitNode(frag.value))) of ffParam: result.add(args[frag.intValue]) else: template rfmt(m: BModule, fmt: string, args: varargs[PRope]): expr = ropecg(m, fmt, args) proc appcg(m: BModule, c: var PRope, frmt: TFormatStr, args: varargs[PRope]) = app(c, ropecg(m, frmt, args)) proc appcg(m: BModule, s: TCFileSection, frmt: TFormatStr, args: varargs[PRope]) = app(m.s[s], ropecg(m, frmt, args)) proc appcg(p: BProc, s: TCProcSection, frmt: TFormatStr, args: varargs[PRope]) = app(p.s(s), ropecg(p.module, frmt, args)) var indent = "\t".toRope proc indentLine(p: BProc, r: PRope): PRope = result = r for i in countup(0, p.blocks.len-1): prepend(result, indent) proc line(p: BProc, s: TCProcSection, r: PRope) = app(p.s(s), indentLine(p, r)) proc line(p: BProc, s: TCProcSection, r: string) = app(p.s(s), indentLine(p, r.toRope)) proc lineF(p: BProc, s: TCProcSection, frmt: TFormatStr, args: varargs[PRope]) = app(p.s(s), indentLine(p, ropef(frmt, args))) proc lineCg(p: BProc, s: TCProcSection, frmt: TFormatStr, args: varargs[PRope]) = app(p.s(s), indentLine(p, ropecg(p.module, frmt, args))) when compileTimeRopeFmt: template linefmt(p: BProc, s: TCProcSection, frmt: TFormatStr, args: varargs[PRope]) = line(p, s, rfmt(p.module, frmt, args)) else: proc linefmt(p: BProc, s: TCProcSection, frmt: TFormatStr, args: varargs[PRope]) = app(p.s(s), indentLine(p, ropecg(p.module, frmt, args))) proc appLineCg(p: BProc, r: var PRope, frmt: TFormatStr, args: varargs[PRope]) = app(r, indentLine(p, ropecg(p.module, frmt, args))) proc lineFF(p: BProc, s: TCProcSection, cformat, llvmformat: string, args: varargs[PRope]) = if gCmd == cmdCompileToLLVM: lineF(p, s, llvmformat, args) else: lineF(p, s, cformat, args) proc safeLineNm(info: TLineInfo): int = result = toLinenumber(info) if result < 0: result = 0 # negative numbers are not allowed in #line proc genCLineDir(r: var PRope, filename: string, line: int) = assert line >= 0 if optLineDir in gOptions: appff(r, "$N#line $2 $1$N", "; line $2 \"$1\"$n", [toRope(makeSingleLineCString(filename)), toRope(line)]) proc genCLineDir(r: var PRope, info: TLineInfo) = genCLineDir(r, info.toFullPath, info.safeLineNm) proc genLineDir(p: BProc, t: PNode) = var line = t.info.safeLineNm if optEmbedOrigSrc in gGlobalOptions: app(p.s(cpsStmts), con(~"//", t.info.sourceLine, rnl)) genCLineDir(p.s(cpsStmts), t.info.toFullPath, line) if ({optStackTrace, optEndb} * p.options == {optStackTrace, optEndb}) and (p.prc
#
#
#            Nimrod's Runtime Library
#        (c) Copyright 2010 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## A higher level `mySQL`:idx: database wrapper. The same interface is 
## implemented for other databases too.

import strutils, mysql

type
  TDbConn* = PMySQL    ## encapsulates a database connection
  TRow* = seq[string]  ## a row of a dataset
  EDb* = object of EIO ## exception that is raised if a database error occurs

  TSqlQuery* = distinct string ## an SQL query string
 
proc dbError(db: TDbConn) {.noreturn.} = 
  ## raises an EDb exception.
  var e: ref EDb
  new(e)
  e.msg = $mysql.error(db)
  raise e

proc dbError*(msg: string) {.noreturn.} = 
  ## raises an EDb exception with message `msg`.
  var e: ref EDb
  new(e)
  e.msg = msg
  raise e

when false:
  proc dbQueryOpt*(db: TDbConn, query: string, args: openarray[string]) =
    var stmt = mysql_stmt_init(db)
    if stmt == nil: dbError(db)
    if mysql_stmt_prepare(stmt, query, len(query)) != 0: 
      dbError(db)
    var 
      binding: seq[MYSQL_BIND]
    discard mysql_stmt_close(stmt)

proc dbQuote(s: string): string =
  result = "'"
  for c in items(s):
    if c == '\'': add(result, "''")
    else: add(result, c)
  add(result, '\'')

proc dbFormat(formatstr: TSqlQuery, args: openarray[string]): string =
  result = ""
  var a = 0
  for c in items(string(formatstr)):
    if c == '?':
      add(result, dbQuote(args[a]))
      inc(a)
    else: 
      add(result, c)
  
proc TryExec*(db: TDbConn, query: TSqlQuery, args: openarray[string]): bool =
  ## tries to execute the query and returns true if successful, false otherwise.
  var q = dbFormat(query, args)
  return mysql.RealQuery(db, q, q.len) == 0'i32

proc Exec*(db: TDbConn, query: TSqlQuery, args: openarray[string]) =
  ## executes the query and raises EDB if not successful.
  var q = dbFormat(query, args)
  if mysql.RealQuery(db, q, q.len) != 0'i32: dbError(db)
    
proc newRow(L: int): TRow = 
  newSeq(result, L)
  for i in 0..L-1: result[i] = ""
  
proc properFreeResult(sqlres: mysql.PRES, row: cstringArray) =  
  if row != nil:
    while mysql.FetchRow(sqlres) != nil: nil
  mysql.FreeResult(sqlres)
  
iterator FastRows*(db: TDbConn, query: TSqlQuery,
                   args: openarray[string]): TRow =
  ## executes the query and iterates over the result dataset. This is very 
  ## fast, but potenially dangerous: If the for-loop-body executes another
  ## query, the results can be undefined. For MySQL this is the case!.
  Exec(db, query, args)
  var sqlres = mysql.UseResult(db)
  if sqlres != nil:
    var L = int(mysql.NumFields(sqlres))
    var result = newRow(L)
    var row: cstringArray
    while true:
      row = mysql.FetchRow(sqlres)
      if row == nil: break
      for i in 0..L-1: 
        setLen(result[i], 0)
        add(result[i], row[i])
      yield result
    properFreeResult(sqlres, row)

proc GetAllRows*(db: TDbConn, query: TSqlQuery, 
                 args: openarray[string]): seq[TRow] =
  ## executes the query and returns the whole result dataset.
  result = @[]
  Exec(db, query, args)
  var sqlres = mysql.UseResult(db)
  if sqlres != nil:
    var L = int(mysql.NumFields(sqlres))
    var row: cstringArray
    var j = 0
    while true:
      row = mysql.FetchRow(sqlres)
      if row == nil: break
      setLen(result, j+1)
      newSeq(result[j], L)
      for i in 0..L-1: result[j][i] = $row[i]
      inc(j)
    mysql.FreeResult(sqlres)

iterator Rows*(db: TDbConn, query: TSqlQuery, 
               args: openarray[string]): TRow =
  ## same as `FastRows`, but slower and safe.
  for r in items(GetAllRows(db, query, args)): yield r

proc GetValue*(db: TDbConn, query: TSqlQuery, 
               args: openarray[string]): string = 
  ## executes the query and returns the result dataset's the first column 
  ## of the first row. Returns "" if the dataset contains no rows. This uses
  ## `FastRows`, so it inherits its fragile behaviour.
  result = ""
  for row in FastRows(db, query, args): 
    result = row[0]
    break

proc TryInsertID*(db: TDbConn, query: TSqlQuery, 
                  args: openarray[string]): int64 =
  ## executes the query (typically "INSERT") and returns the 
  ## generated ID for the row or -1 in case of an error.
  var q = dbFormat(query, args)
  if mysql.RealQuery(db, q, q.len) != 0'i32: 
    result = -1'i64
  else:
    result = mysql.InsertId(db)
  
proc InsertID*(db: TDbConn, query: TSqlQuery, args: openArray[string]): int64 = 
  ## executes the query (typically "INSERT") and returns the 
  ## generated ID for the row.
  result = TryInsertID(db, query, args)
  if result < 0: dbError(db)

proc ExecAffectedRows*(db: TDbConn, query: TSqlQuery, 
                       args: openArray[string]): int64 = 
  ## runs the query (typically "UPDATE") and returns the
  ## number of affected rows
  Exec(db, query, args)
  result