summary refs log tree commit diff stats
path: root/compiler/canonicalizer.nim
blob: 1cf6171c1cfc1f31bf7d2d823dc5103f06ec50e3 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
#
#
#           The Nim Compiler
#        (c) Copyright 2015 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## This module implements the canonalization for the various caching mechanisms.

import strutils, db_sqlite, md5

var db: DbConn

# We *hash* the relevant information into 128 bit hashes. This should be good
# enough to prevent any collisions.

type
  TUid = distinct MD5Digest

# For name mangling we encode these hashes via a variant of base64 (called
# 'base64a') and prepend the *primary* identifier to ease the debugging pain.
# So a signature like:
#
#   proc gABI(c: PCtx; n: PNode; opc: TOpcode; a, b: TRegister; imm: BiggestInt)
#
# is mangled into:
#   gABI_MTdmOWY5MTQ1MDcyNGQ3ZA
#
# This is a good compromise between correctness and brevity. ;-)

const
  cb64 = [
    "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N",
    "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
    "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n",
    "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
    "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
    "_A", "_B"]

proc toBase64a(s: cstring, len: int): string =
  ## encodes `s` into base64 representation. After `lineLen` characters, a
  ## `newline` is added.
  result = newStringOfCap(((len + 2) div 3) * 4)
  var i = 0
  while i < s.len - 2:
    let a = ord(s[i])
    let b = ord(s[i+1])
    let c = ord(s[i+2])
    result.add cb64[a shr 2]
    result.add cb64[((a and 3) shl 4) or ((b and 0xF0) shr 4)]
    result.add cb64[((b and 0x0F) shl 2) or ((c and 0xC0) shr 6)]
    result.add cb64[c and 0x3F]
    inc(i, 3)
  if i < s.len-1:
    let a = ord(s[i])
    let b = ord(s[i+1])
    result.add cb64[a shr 2]
    result.add cb64[((a and 3) shl 4) or ((b and 0xF0) shr 4)]
    result.add cb64[((b and 0x0F) shl 2)]
  elif i < s.len:
    let a = ord(s[i])
    result.add cb64[a shr 2]
    result.add cb64[(a and 3) shl 4]

proc toBase64a(u: TUid): string = toBase64a(cast[cstring](u), sizeof(u))

proc `&=`(c: var MD5Context, s: string) = md5Update(c, s, s.len)

proc hashSym(c: var MD5Context, s: PSym) =
  if sfAnon in s.flags or s.kind == skGenericParam:
    c &= ":anon"
  else:
    var it = s.owner
    while it != nil:
      hashSym(c, it)
      c &= "."
      it = s.owner
    c &= s.name.s

proc hashTree(c: var MD5Context, n: PNode) =
  if n == nil:
    c &= "\255"
    return
  var k = n.kind
  md5Update(c, cast[cstring](addr(k)), 1)
  # we really must not hash line information. 'n.typ' is debatable but
  # shouldn't be necessary for now and avoids potential infinite recursions.
  case n.kind
  of nkEmpty, nkNilLit, nkType: discard
  of nkIdent:
    c &= n.ident.s
  of nkSym:
    hashSym(c, n.sym)
  of nkCharLit..nkUInt64Lit:
    var v = n.intVal
    md5Update(c, cast[cstring](addr(v)), sizeof(v))
  of nkFloatLit..nkFloat64Lit:
    var v = n.floatVal
    md5Update(c, cast[cstring](addr(v)), sizeof(v))
  of nkStrLit..nkTripleStrLit:
    c &= n.strVal
  else:
    for i in 0..<n.len: hashTree(c, n.sons[i])

proc hashType(c: var MD5Context, t: PType) =
  # modelled after 'typeToString'
  if t == nil:
    c &= "\254"
    return

  var k = t.kind
  md5Update(c, cast[cstring](addr(k)), 1)

  if t.sym != nil and sfAnon notin t.sym.flags:
    # t.n for literals, but not for e.g. objects!
    if t.kind in {tyFloat, tyInt}: c.hashNode(t.n)
    c.hashSym(t.sym)

  case t.kind
  of tyGenericBody, tyGenericInst, tyGenericInvocation:
    for i in 0 ..< len(t)-ord(t.kind != tyGenericInvocation):
      c.hashType t.sons[i]
  of tyUserTypeClass:
    internalAssert t.sym != nil and t.sym.owner != nil
    c &= t.sym.owner.name.s
  of tyUserTypeClassInst:
    let body = t.base
    c.hashSym body.sym
    for i in 1 .. len(t) - 2:
      c.hashType t.sons[i]
  of tyFromExpr:
    c.hashTree(t.n)
  of tyArray:
    c.hashTree(t.sons[0].n)
    c.hashType(t.sons[1])
  of tyTuple:
    if t.n != nil:
      assert(len(t.n) == len(t))
      for i in 0 ..< len(t.n):
        assert(t.n.sons[i].kind == nkSym)
        c &= t.n.sons[i].sym.name.s
        c &= ":"
        c.hashType(t.sons[i])
        c &= ","
    else:
      for i in 0 ..< len(t): c.hashType t.sons[i]
  of tyRange:
    c.hashTree(t.n)
    c.hashType(t.sons[0])
  of tyProc:
    c &= (if tfIterator in t.flags: "iterator " else: "proc ")
    for i in 0..<t.len: c.hashType(t.sons[i])
    md5Update(c, cast[cstring](addr(t.callConv)), 1)

    if tfNoSideEffect in t.flags: c &= ".noSideEffect"
    if tfThread in t.flags: c &= ".thread"
  else:
    for i in 0..<t.len: c.hashType(t.sons[i])
  if tfNotNil in t.flags: c &= "not nil"

proc canonConst(n: PNode): TUid =
  var c: MD5Context
  md5Init(c)
  c.hashTree(n)
  c.hashType(n.typ)
  md5Final(c, MD5Digest(result))

proc canonSym(s: PSym): TUid =
  var c: MD5Context
  md5Init(c)
  c.hashSym(s)
  md5Final(c, MD5Digest(result))

proc pushType(w: PRodWriter, t: PType) =
  # check so that the stack does not grow too large:
  if iiTableGet(w.index.tab, t.id) == InvalidKey:
    w.tstack.add(t)

proc pushSym(w: PRodWriter, s: PSym) =
  # check so that the stack does not grow too large:
  if iiTableGet(w.index.tab, s.id) == InvalidKey:
    w.sstack.add(s)

proc encodeNode(w: PRodWriter, fInfo: TLineInfo, n: PNode,
                result: var string) =
  if n == nil:
    # nil nodes have to be stored too:
    result.add("()")
    return
  result.add('(')
  encodeVInt(ord(n.kind), result)
  # we do not write comments for now
  # Line information takes easily 20% or more of the filesize! Therefore we
  # omit line information if it is the same as the father's line information:
  if fInfo.fileIndex != n.info.fileIndex:
    result.add('?')
    encodeVInt(n.info.col, result)
    result.add(',')
    encodeVInt(n.info.line, result)
    result.add(',')
    encodeVInt(fileIdx(w, toFilename(n.info)), result)
  elif fInfo.line != n.info.line:
    result.add('?')
    encodeVInt(n.info.col, result)
    result.add(',')
    encodeVInt(n.info.line, result)
  elif fInfo.col != n.info.col:
    result.add('?')
    encodeVInt(n.info.col, result)
  var f = n.flags * PersistentNodeFlags
  if f != {}:
    result.add('$')
    encodeVInt(cast[int32](f), result)
  if n.typ != nil:
    result.add('^')
    encodeVInt(n.typ.id, result)
    pushType(w, n.typ)
  case n.kind
  of nkCharLit..nkInt64Lit:
    if n.intVal != 0:
      result.add('!')
      encodeVBiggestInt(n.intVal, result)
  of nkFloatLit..nkFloat64Lit:
    if n.floatVal != 0.0:
      result.add('!')
      encodeStr($n.floatVal, result)
  of nkStrLit..nkTripleStrLit:
    if n.strVal != "":
      result.add('!')
      encodeStr(n.strVal, result)
  of nkIdent:
    result.add('!')
    encodeStr(n.ident.s, result)
  of nkSym:
    result.add('!')
    encodeVInt(n.sym.id, result)
    pushSym(w, n.sym)
  else:
    for i in 0 ..< len(n):
      encodeNode(w, n.info, n.sons[i], result)
  add(result, ')')

proc encodeLoc(w: PRodWriter, loc: TLoc, result: var string) =
  var oldLen = result.len
  result.add('<')
  if loc.k != low(loc.k): encodeVInt(ord(loc.k), result)
  if loc.s != low(loc.s):
    add(result, '*')
    encodeVInt(ord(loc.s), result)
  if loc.flags != {}:
    add(result, '$')
    encodeVInt(cast[int32](loc.flags), result)
  if loc.t != nil:
    add(result, '^')
    encodeVInt(cast[int32](loc.t.id), result)
    pushType(w, loc.t)
  if loc.r != nil:
    add(result, '!')
    encodeStr($loc.r, result)
  if loc.a != 0:
    add(result, '?')
    encodeVInt(loc.a, result)
  if oldLen + 1 == result.len:
    # no data was necessary, so remove the '<' again:
    setLen(result, oldLen)
  else:
    add(result, '>')

proc encodeType(w: PRodWriter, t: PType, result: var string) =
  if t == nil:
    # nil nodes have to be stored too:
    result.add("[]")
    return
  # we need no surrounding [] here because the type is in a line of its own
  if t.kind == tyForward: internalError("encodeType: tyForward")
  # for the new rodfile viewer we use a preceding [ so that the data section
  # can easily be disambiguated:
  add(result, '[')
  encodeVInt(ord(t.kind), result)
  add(result, '+')
  encodeVInt(t.id, result)
  if t.n != nil:
    encodeNode(w, unknownLineInfo(), t.n, result)
  if t.flags != {}:
    add(result, '$')
    encodeVInt(cast[int32](t.flags), result)
  if t.callConv != low(t.callConv):
    add(result, '?')
    encodeVInt(ord(t.callConv), result)
  if t.owner != nil:
    add(result, '*')
    encodeVInt(t.owner.id, result)
    pushSym(w, t.owner)
  if t.sym != nil:
    add(result, '&')
    encodeVInt(t.sym.id, result)
    pushSym(w, t.sym)
  if t.size != - 1:
    add(result, '/')
    encodeVBiggestInt(t.size, result)
  if t.align != - 1:
    add(result, '=')
    encodeVInt(t.align, result)
  encodeLoc(w, t.loc, result)
  for i in 0 ..< len(t):
    if t.sons[i] == nil:
      add(result, "^()")
    else:
      add(result, '^')
      encodeVInt(t.sons[i].id, result)
      pushType(w, t.sons[i])

proc encodeLib(w: PRodWriter, lib: PLib, info: TLineInfo, result: var string) =
  add(result, '|')
  encodeVInt(ord(lib.kind), result)
  add(result, '|')
  encodeStr($lib.name, result)
  add(result, '|')
  encodeNode(w, info, lib.path, result)

proc encodeSym(w: PRodWriter, s: PSym, result: var string) =
  if s == nil:
    # nil nodes have to be stored too:
    result.add("{}")
    return
  # we need no surrounding {} here because the symbol is in a line of its own
  encodeVInt(ord(s.kind), result)
  result.add('+')
  encodeVInt(s.id, result)
  result.add('&')
  encodeStr(s.name.s, result)
  if s.typ != nil:
    result.add('^')
    encodeVInt(s.typ.id, result)
    pushType(w, s.typ)
  result.add('?')
  if s.info.col != -1'i16: encodeVInt(s.info.col, result)
  result.add(',')
  if s.info.line != -1'i16: encodeVInt(s.info.line, result)
  result.add(',')
  encodeVInt(fileIdx(w, toFilename(s.info)), result)
  if s.owner != nil:
    result.add('*')
    encodeVInt(s.owner.id, result)
    pushSym(w, s.owner)
  if s.flags != {}:
    result.add('$')
    encodeVInt(cast[int32](s.flags), result)
  if s.magic != mNone:
    result.add('@')
    encodeVInt(ord(s.magic), result)
  if s.options != w.options:
    result.add('!')
    encodeVInt(cast[int32](s.options), result)
  if s.position != 0:
    result.add('%')
    encodeVInt(s.position, result)
  if s.offset != - 1:
    result.add('`')
    encodeVInt(s.offset, result)
  encodeLoc(w, s.loc, result)
  if s.annex != nil: encodeLib(w, s.annex, s.info, result)
  if s.constraint != nil:
    add(result, '#')
    encodeNode(w, unknownLineInfo(), s.constraint, result)
  # lazy loading will soon reload the ast lazily, so the ast needs to be
  # the last entry of a symbol:
  if s.ast != nil:
    # we used to attempt to save space here by only storing a dummy AST if
    # it is not necessary, but Nim's heavy compile-time evaluation features
    # make that unfeasible nowadays:
    encodeNode(w, s.info, s.ast, result)


proc createDb() =
  db.exec(sql"""
    create table if not exists Module(
      id integer primary key,
      name varchar(256) not null,
      fullpath varchar(256) not null,
      interfHash varchar(256) not null,
      fullHash varchar(256) not null,

      created timestamp not null default (DATETIME('now'))
    );""")

  db.exec(sql"""
    create table if not exists Backend(
      id integer primary key,
      strongdeps varchar(max) not null,
      weakdeps varchar(max) not null,
      header varchar(max) not null,
      code varchar(max) not null
    )

    create table if not exists Symbol(
      id integer primary key,
      module integer not null,
      backend integer not null,
      name varchar(max) not null,
      data varchar(max) not null,
      created timestamp not null default (DATETIME('now')),

      foreign key (module) references Module(id),
      foreign key (backend) references Backend(id)
    );""")

  db.exec(sql"""
    create table if not exists Type(
      id integer primary key,
      module integer not null,
      name varchar(max) not null,
      data varchar(max) not null,
      created timestamp not null default (DATETIME('now')),

      foreign key (module) references module(id)
    );""")
">nil: return x = x.next result = emptyNode proc evalArrayAccess(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode = result = evalAux(c, n.sons[0], flags) if isSpecial(result): return var x = result result = evalAux(c, n.sons[1], {}) if isSpecial(result): return var idx = getOrdValue(result) result = emptyNode case x.kind of nkPar: if (idx >= 0) and (idx < sonsLen(x)): result = x.sons[int(idx)] if result.kind == nkExprColonExpr: result = result.sons[1] else: stackTrace(c, n, errIndexOutOfBounds) if not aliasNeeded(result, flags): result = copyTree(result) of nkBracket, nkMetaNode: if (idx >= 0) and (idx < sonsLen(x)): result = x.sons[int(idx)] else: stackTrace(c, n, errIndexOutOfBounds) if not aliasNeeded(result, flags): result = copyTree(result) of nkStrLit..nkTripleStrLit: if efLValue in flags: InternalError(n.info, "cannot evaluate write access to char") result = newNodeIT(nkCharLit, x.info, getSysType(tyChar)) if (idx >= 0) and (idx < len(x.strVal)): result.intVal = ord(x.strVal[int(idx) + 0]) elif idx == len(x.strVal): nil else: stackTrace(c, n, errIndexOutOfBounds) else: stackTrace(c, n, errNilAccess) proc evalFieldAccess(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode = # a real field access; proc calls have already been transformed # XXX: field checks! result = evalAux(c, n.sons[0], flags) if isSpecial(result): return var x = result if x.kind != nkPar: InternalError(n.info, "evalFieldAccess") var field = n.sons[1].sym for i in countup(0, sonsLen(x) - 1): var it = x.sons[i] if it.kind != nkExprColonExpr: InternalError(it.info, "evalFieldAccess") if it.sons[0].sym.name.id == field.name.id: result = x.sons[i].sons[1] if not aliasNeeded(result, flags): result = copyTree(result) return stackTrace(c, n, errFieldXNotFound, field.name.s) result = emptyNode proc evalAsgn(c: PEvalContext, n: PNode): PNode = result = evalAux(c, n.sons[0], {efLValue}) if isSpecial(result): return var x = result result = evalAux(c, n.sons[1], {}) if isSpecial(result): return x.kind = result.kind x.typ = result.typ case x.kind of nkCharLit..nkInt64Lit: x.intVal = result.intVal of nkFloatLit..nkFloat64Lit: x.floatVal = result.floatVal of nkStrLit..nkTripleStrLit: x.strVal = result.strVal else: if not (x.kind in {nkEmpty..nkNilLit}): discardSons(x) for i in countup(0, sonsLen(result) - 1): addSon(x, result.sons[i]) result = emptyNode assert result.kind == nkEmpty proc evalSwap(c: PEvalContext, n: PNode): PNode = result = evalAux(c, n.sons[0], {efLValue}) if isSpecial(result): return var x = result result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return if (x.kind != result.kind): stackTrace(c, n, errCannotInterpretNodeX, $n.kind) else: case x.kind of nkCharLit..nkInt64Lit: var tmpi = x.intVal x.intVal = result.intVal result.intVal = tmpi of nkFloatLit..nkFloat64Lit: var tmpf = x.floatVal x.floatVal = result.floatVal result.floatVal = tmpf of nkStrLit..nkTripleStrLit: var tmps = x.strVal x.strVal = result.strVal result.strVal = tmps else: var tmpn = copyTree(x) discardSons(x) for i in countup(0, sonsLen(result) - 1): addSon(x, result.sons[i]) discardSons(result) for i in countup(0, sonsLen(tmpn) - 1): addSon(result, tmpn.sons[i]) result = emptyNode proc evalSym(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode = case n.sym.kind of skProc, skConverter, skMacro: result = n.sym.ast.sons[codePos] of skVar, skForVar, skTemp: result = evalVariable(c.tos, n.sym, flags) of skParam: # XXX what about LValue? result = c.tos.params[n.sym.position + 1] of skConst: result = n.sym.ast else: stackTrace(c, n, errCannotInterpretNodeX, $n.sym.kind) result = emptyNode if result == nil: stackTrace(c, n, errCannotInterpretNodeX, n.sym.name.s) proc evalIncDec(c: PEvalContext, n: PNode, sign: biggestInt): PNode = result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return var a = result result = evalAux(c, n.sons[2], {}) if isSpecial(result): return var b = result case a.kind of nkCharLit..nkInt64Lit: a.intval = a.intVal + sign * getOrdValue(b) else: internalError(n.info, "evalIncDec") result = emptyNode proc getStrValue(n: PNode): string = case n.kind of nkStrLit..nkTripleStrLit: result = n.strVal else: InternalError(n.info, "getStrValue") result = "" proc evalEcho(c: PEvalContext, n: PNode): PNode = for i in countup(1, sonsLen(n) - 1): result = evalAux(c, n.sons[i], {}) if isSpecial(result): return Write(stdout, getStrValue(result)) writeln(stdout, "") result = emptyNode proc evalExit(c: PEvalContext, n: PNode): PNode = result = evalAux(c, n.sons[1], {}) if isSpecial(result): return Message(n.info, hintQuitCalled) quit(int(getOrdValue(result))) proc evalOr(c: PEvalContext, n: PNode): PNode = result = evalAux(c, n.sons[1], {}) if isSpecial(result): return if result.kind != nkIntLit: InternalError(n.info, "evalOr") if result.intVal == 0: result = evalAux(c, n.sons[2], {}) proc evalAnd(c: PEvalContext, n: PNode): PNode = result = evalAux(c, n.sons[1], {}) if isSpecial(result): return if result.kind != nkIntLit: InternalError(n.info, "evalAnd") if result.intVal != 0: result = evalAux(c, n.sons[2], {}) proc evalNoOpt(c: PEvalContext, n: PNode): PNode = result = newNodeI(nkExceptBranch, n.info) # creating a nkExceptBranch without sons # means that it could not be evaluated proc evalNew(c: PEvalContext, n: PNode): PNode = if c.optEval: return evalNoOpt(c, n) # we ignore the finalizer for now and most likely forever :-) result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return var a = result var t = skipTypes(n.sons[1].typ, abstractVar) if a.kind == nkEmpty: InternalError(n.info, "first parameter is empty") # changing the node kind is ugly and suggests deep problems: a.kind = nkRefTy a.info = n.info a.typ = t a.sons = nil addSon(a, getNullValue(t.sons[0], n.info)) result = emptyNode when false: var t = skipTypes(n.sons[1].typ, abstractVar) result = newNodeIT(nkRefTy, n.info, t) addSon(result, getNullValue(t.sons[0], n.info)) proc evalDeref(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode = result = evalAux(c, n.sons[0], {efLValue}) if isSpecial(result): return case result.kind of nkNilLit: stackTrace(c, n, errNilAccess) of nkRefTy: # XXX efLValue? result = result.sons[0] else: InternalError(n.info, "evalDeref " & $result.kind) proc evalAddr(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode = result = evalAux(c, n.sons[0], {efLValue}) if isSpecial(result): return var a = result var t = newType(tyPtr, c.module) addSon(t, a.typ) result = newNodeIT(nkRefTy, n.info, t) addSon(result, a) proc evalConv(c: PEvalContext, n: PNode): PNode = result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return var a = result result = foldConv(n, a) if result == nil: # foldConv() cannot deal with everything that we want to do here: result = a proc evalCheckedFieldAccess(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode = result = evalAux(c, n.sons[0], flags) proc evalUpConv(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode = result = evalAux(c, n.sons[0], flags) if isSpecial(result): return var dest = skipTypes(n.typ, abstractPtrs) var src = skipTypes(result.typ, abstractPtrs) if inheritanceDiff(src, dest) > 0: stackTrace(c, n, errInvalidConversionFromTypeX, typeToString(src)) proc evalRangeChck(c: PEvalContext, n: PNode): PNode = result = evalAux(c, n.sons[0], {}) if isSpecial(result): return var x = result result = evalAux(c, n.sons[1], {}) if isSpecial(result): return var a = result result = evalAux(c, n.sons[2], {}) if isSpecial(result): return var b = result if leValueConv(a, x) and leValueConv(x, b): result = x # a <= x and x <= b result.typ = n.typ else: stackTrace(c, n, errGenerated, msgKindToString(errIllegalConvFromXtoY) % [ typeToString(n.sons[0].typ), typeToString(n.typ)]) proc evalConvStrToCStr(c: PEvalContext, n: PNode): PNode = result = evalAux(c, n.sons[0], {}) if isSpecial(result): return result.typ = n.typ proc evalConvCStrToStr(c: PEvalContext, n: PNode): PNode = result = evalAux(c, n.sons[0], {}) if isSpecial(result): return result.typ = n.typ proc evalRaise(c: PEvalContext, n: PNode): PNode = if n.sons[0].kind != nkEmpty: result = evalAux(c, n.sons[0], {}) if isSpecial(result): return var a = result result = newNodeIT(nkExceptBranch, n.info, a.typ) addSon(result, a) c.lastException = result elif c.lastException != nil: result = c.lastException else: stackTrace(c, n, errExceptionAlreadyHandled) result = newNodeIT(nkExceptBranch, n.info, nil) addSon(result, ast.emptyNode) proc evalReturn(c: PEvalContext, n: PNode): PNode = if n.sons[0].kind != nkEmpty: result = evalAsgn(c, n.sons[0]) if isSpecial(result): return result = newNodeIT(nkReturnToken, n.info, nil) proc evalProc(c: PEvalContext, n: PNode): PNode = if n.sons[genericParamsPos].kind == nkEmpty: if (resultPos < sonsLen(n)) and (n.sons[resultPos].kind != nkEmpty): var v = n.sons[resultPos].sym result = getNullValue(v.typ, n.info) IdNodeTablePut(c.tos.mapping, v, result) result = evalAux(c, n.sons[codePos], {}) if result.kind == nkReturnToken: result = IdNodeTableGet(c.tos.mapping, v) else: result = evalAux(c, n.sons[codePos], {}) if result.kind == nkReturnToken: result = emptyNode else: result = emptyNode proc evalHigh(c: PEvalContext, n: PNode): PNode = result = evalAux(c, n.sons[1], {}) if isSpecial(result): return case skipTypes(n.sons[1].typ, abstractVar).kind of tyOpenArray, tySequence: result = newIntNodeT(sonsLen(result), n) of tyString: result = newIntNodeT(len(result.strVal) - 1, n) else: InternalError(n.info, "evalHigh") proc evalIs(c: PEvalContext, n: PNode): PNode = result = evalAux(c, n.sons[1], {}) if isSpecial(result): return result = newIntNodeT(ord(inheritanceDiff(result.typ, n.sons[2].typ) >= 0), n) proc evalSetLengthStr(c: PEvalContext, n: PNode): PNode = result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return var a = result result = evalAux(c, n.sons[2], {}) if isSpecial(result): return var b = result case a.kind of nkStrLit..nkTripleStrLit: var newLen = int(getOrdValue(b)) setlen(a.strVal, newLen) else: InternalError(n.info, "evalSetLengthStr") result = emptyNode proc evalSetLengthSeq(c: PEvalContext, n: PNode): PNode = result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return var a = result result = evalAux(c, n.sons[2], {}) if isSpecial(result): return var b = result if a.kind != nkBracket: InternalError(n.info, "evalSetLengthSeq") var newLen = int(getOrdValue(b)) var oldLen = sonsLen(a) setlen(a.sons, newLen) for i in countup(oldLen, newLen - 1): a.sons[i] = getNullValue(skipTypes(n.sons[1].typ, abstractVar), n.info) result = emptyNode proc evalNewSeq(c: PEvalContext, n: PNode): PNode = result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return var a = result result = evalAux(c, n.sons[2], {}) if isSpecial(result): return var b = result var t = skipTypes(n.sons[1].typ, abstractVar) if a.kind == nkEmpty: InternalError(n.info, "first parameter is empty") # changing the node kind is ugly and suggests deep problems: a.kind = nkBracket a.info = n.info a.typ = t a.sons = nil var L = int(getOrdValue(b)) newSeq(a.sons, L) for i in countup(0, L-1): a.sons[i] = getNullValue(t.sons[0], n.info) result = emptyNode proc evalAssert(c: PEvalContext, n: PNode): PNode = result = evalAux(c, n.sons[1], {}) if isSpecial(result): return if getOrdValue(result) != 0: result = emptyNode else: stackTrace(c, n, errAssertionFailed) proc evalIncl(c: PEvalContext, n: PNode): PNode = result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return var a = result result = evalAux(c, n.sons[2], {}) if isSpecial(result): return var b = result if not inSet(a, b): addSon(a, copyTree(b)) result = emptyNode proc evalExcl(c: PEvalContext, n: PNode): PNode = result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return var a = result result = evalAux(c, n.sons[2], {}) if isSpecial(result): return var b = newNodeIT(nkCurly, n.info, n.sons[1].typ) addSon(b, result) var r = diffSets(a, b) discardSons(a) for i in countup(0, sonsLen(r) - 1): addSon(a, r.sons[i]) result = emptyNode proc evalAppendStrCh(c: PEvalContext, n: PNode): PNode = result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return var a = result result = evalAux(c, n.sons[2], {}) if isSpecial(result): return var b = result case a.kind of nkStrLit..nkTripleStrLit: add(a.strVal, chr(int(getOrdValue(b)))) else: InternalError(n.info, "evalAppendStrCh") result = emptyNode proc evalConStrStr(c: PEvalContext, n: PNode): PNode = # we cannot use ``evalOp`` for this as we can here have more than 2 arguments result = evalAux(c, n.sons[1], {}) if isSpecial(result): return var a = result for i in countup(2, sonsLen(n) - 1): result = evalAux(c, n.sons[i], {}) if isSpecial(result): return a.strVal = getStrValue(a) & getStrValue(result) result = a proc evalAppendStrStr(c: PEvalContext, n: PNode): PNode = result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return var a = result result = evalAux(c, n.sons[2], {}) if isSpecial(result): return var b = result case a.kind of nkStrLit..nkTripleStrLit: a.strVal = a.strVal & getStrValue(b) else: InternalError(n.info, "evalAppendStrStr") result = emptyNode proc evalAppendSeqElem(c: PEvalContext, n: PNode): PNode = result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return var a = result result = evalAux(c, n.sons[2], {}) if isSpecial(result): return var b = result if a.kind == nkBracket: addSon(a, copyTree(b)) else: InternalError(n.info, "evalAppendSeqElem") result = emptyNode proc evalRepr(c: PEvalContext, n: PNode): PNode = result = evalAux(c, n.sons[1], {}) if isSpecial(result): return result = newStrNodeT(renderTree(result, {renderNoComments}), n) proc isEmpty(n: PNode): bool = result = (n != nil) and (n.kind == nkEmpty) proc evalMagicOrCall(c: PEvalContext, n: PNode): PNode = var m = getMagic(n) case m of mNone: result = evalCall(c, n) of mIs: result = evalIs(c, n) of mSizeOf: internalError(n.info, "sizeof() should have been evaluated") of mHigh: result = evalHigh(c, n) of mAssert: result = evalAssert(c, n) of mExit: result = evalExit(c, n) of mNew, mNewFinalize: result = evalNew(c, n) of mNewSeq: result = evalNewSeq(c, n) of mSwap: result = evalSwap(c, n) of mInc: result = evalIncDec(c, n, 1) of ast.mDec: result = evalIncDec(c, n, - 1) of mEcho: result = evalEcho(c, n) of mSetLengthStr: result = evalSetLengthStr(c, n) of mSetLengthSeq: result = evalSetLengthSeq(c, n) of mIncl: result = evalIncl(c, n) of mExcl: result = evalExcl(c, n) of mAnd: result = evalAnd(c, n) of mOr: result = evalOr(c, n) of mAppendStrCh: result = evalAppendStrCh(c, n) of mAppendStrStr: result = evalAppendStrStr(c, n) of mAppendSeqElem: result = evalAppendSeqElem(c, n) of mNLen: result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return var a = result result = newNodeIT(nkIntLit, n.info, n.typ) case a.kind of nkEmpty..nkNilLit: nil else: result.intVal = sonsLen(a) of mNChild: result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return var a = result result = evalAux(c, n.sons[2], {efLValue}) if isSpecial(result): return var k = getOrdValue(result) if not (a.kind in {nkEmpty..nkNilLit}) and (k >= 0) and (k < sonsLen(a)): result = a.sons[int(k)] if result == nil: result = newNode(nkEmpty) else: stackTrace(c, n, errIndexOutOfBounds) result = emptyNode of mNSetChild: result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return var a = result result = evalAux(c, n.sons[2], {efLValue}) if isSpecial(result): return var b = result result = evalAux(c, n.sons[3], {efLValue}) if isSpecial(result): return var k = getOrdValue(b) if (k >= 0) and (k < sonsLen(a)) and not (a.kind in {nkEmpty..nkNilLit}): a.sons[int(k)] = result else: stackTrace(c, n, errIndexOutOfBounds) result = emptyNode of mNAdd: result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return var a = result result = evalAux(c, n.sons[2], {efLValue}) if isSpecial(result): return addSon(a, result) result = emptyNode of mNAddMultiple: result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return var a = result result = evalAux(c, n.sons[2], {efLValue}) if isSpecial(result): return for i in countup(0, sonsLen(result) - 1): addSon(a, result.sons[i]) result = emptyNode of mNDel: result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return var a = result result = evalAux(c, n.sons[2], {efLValue}) if isSpecial(result): return var b = result result = evalAux(c, n.sons[3], {efLValue}) if isSpecial(result): return for i in countup(0, int(getOrdValue(result)) - 1): delSon(a, int(getOrdValue(b))) result = emptyNode of mNKind: result = evalAux(c, n.sons[1], {}) if isSpecial(result): return var a = result result = newNodeIT(nkIntLit, n.info, n.typ) result.intVal = ord(a.kind) of mNIntVal: result = evalAux(c, n.sons[1], {}) if isSpecial(result): return var a = result result = newNodeIT(nkIntLit, n.info, n.typ) case a.kind of nkCharLit..nkInt64Lit: result.intVal = a.intVal else: InternalError(n.info, "no int value") of mNFloatVal: result = evalAux(c, n.sons[1], {}) if isSpecial(result): return var a = result result = newNodeIT(nkFloatLit, n.info, n.typ) case a.kind of nkFloatLit..nkFloat64Lit: result.floatVal = a.floatVal else: InternalError(n.info, "no float value") of mNSymbol: result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return if result.kind != nkSym: InternalError(n.info, "no symbol") of mNIdent: result = evalAux(c, n.sons[1], {}) if isSpecial(result): return if result.kind != nkIdent: InternalError(n.info, "no symbol") of mNGetType: result = evalAux(c, n.sons[1], {}) of mNStrVal: result = evalAux(c, n.sons[1], {}) if isSpecial(result): return var a = result result = newNodeIT(nkStrLit, n.info, n.typ) case a.kind of nkStrLit..nkTripleStrLit: result.strVal = a.strVal else: InternalError(n.info, "no string value") of mNSetIntVal: result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return var a = result result = evalAux(c, n.sons[2], {}) if isSpecial(result): return a.intVal = result.intVal # XXX: exception handling? result = emptyNode of mNSetFloatVal: result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return var a = result result = evalAux(c, n.sons[2], {}) if isSpecial(result): return a.floatVal = result.floatVal # XXX: exception handling? result = emptyNode of mNSetSymbol: result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return var a = result result = evalAux(c, n.sons[2], {efLValue}) if isSpecial(result): return a.sym = result.sym # XXX: exception handling? result = emptyNode of mNSetIdent: result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return var a = result result = evalAux(c, n.sons[2], {efLValue}) if isSpecial(result): return a.ident = result.ident # XXX: exception handling? result = emptyNode of mNSetType: result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return var a = result result = evalAux(c, n.sons[2], {efLValue}) if isSpecial(result): return a.typ = result.typ # XXX: exception handling? result = emptyNode of mNSetStrVal: result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return var a = result result = evalAux(c, n.sons[2], {}) if isSpecial(result): return a.strVal = result.strVal # XXX: exception handling? result = emptyNode of mNNewNimNode: result = evalAux(c, n.sons[1], {}) if isSpecial(result): return var k = getOrdValue(result) result = evalAux(c, n.sons[2], {efLValue}) if result.kind == nkExceptBranch: return var a = result if k < 0 or k > ord(high(TNodeKind)): internalError(n.info, "request to create a NimNode with invalid kind") result = newNodeI(TNodeKind(int(k)), if a.kind == nkNilLit: n.info else: a.info) of mNCopyNimNode: result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return result = copyNode(result) of mNCopyNimTree: result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return result = copyTree(result) of mStrToIdent: result = evalAux(c, n.sons[1], {}) if isSpecial(result): return if not (result.kind in {nkStrLit..nkTripleStrLit}): InternalError(n.info, "no string node") var a = result result = newNodeIT(nkIdent, n.info, n.typ) result.ident = getIdent(a.strVal) of mIdentToStr: result = evalAux(c, n.sons[1], {}) if isSpecial(result): return if result.kind != nkIdent: InternalError(n.info, "no ident node") var a = result result = newNodeIT(nkStrLit, n.info, n.typ) result.strVal = a.ident.s of mEqIdent: result = evalAux(c, n.sons[1], {}) if isSpecial(result): return var a = result result = evalAux(c, n.sons[2], {}) if isSpecial(result): return var b = result result = newNodeIT(nkIntLit, n.info, n.typ) if (a.kind == nkIdent) and (b.kind == nkIdent): if a.ident.id == b.ident.id: result.intVal = 1 of mEqNimrodNode: result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return var a = result result = evalAux(c, n.sons[2], {efLValue}) if isSpecial(result): return var b = result result = newNodeIT(nkIntLit, n.info, n.typ) if (a == b) or (b.kind in {nkNilLit, nkEmpty}) and (a.kind in {nkNilLit, nkEmpty}): result.intVal = 1 of mNHint: result = evalAux(c, n.sons[1], {}) if isSpecial(result): return Message(n.info, hintUser, getStrValue(result)) result = emptyNode of mNWarning: result = evalAux(c, n.sons[1], {}) if isSpecial(result): return Message(n.info, warnUser, getStrValue(result)) result = emptyNode of mNError: result = evalAux(c, n.sons[1], {}) if isSpecial(result): return stackTrace(c, n, errUser, getStrValue(result)) result = emptyNode of mConStrStr: result = evalConStrStr(c, n) of mRepr: result = evalRepr(c, n) of mNewString: result = evalAux(c, n.sons[1], {}) if isSpecial(result): return var a = result result = newNodeIT(nkStrLit, n.info, n.typ) result.strVal = newString(int(getOrdValue(a))) else: result = evalAux(c, n.sons[1], {}) if isSpecial(result): return var a = result var b: PNode = nil var cc: PNode = nil if sonsLen(n) > 2: result = evalAux(c, n.sons[2], {}) if isSpecial(result): return b = result if sonsLen(n) > 3: result = evalAux(c, n.sons[3], {}) if isSpecial(result): return cc = result if isEmpty(a) or isEmpty(b) or isEmpty(cc): result = emptyNode else: result = evalOp(m, n, a, b, cc) proc evalAux(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode = result = emptyNode dec(gNestedEvals) if gNestedEvals <= 0: stackTrace(c, n, errTooManyIterations) case n.kind # atoms: of nkEmpty: result = n of nkSym: result = evalSym(c, n, flags) of nkType..nkNilLit: result = copyNode(n) # end of atoms of nkCall, nkHiddenCallConv, nkMacroStmt, nkCommand, nkCallStrLit: result = evalMagicOrCall(c, n) of nkCurly, nkBracket, nkRange: # flags need to be passed here for mNAddMultiple :-( # XXX this is not correct in every case! var a = copyNode(n) for i in countup(0, sonsLen(n) - 1): result = evalAux(c, n.sons[i], flags) if isSpecial(result): return addSon(a, result) result = a of nkPar: var a = copyTree(n) for i in countup(0, sonsLen(n) - 1): result = evalAux(c, n.sons[i].sons[1], flags) if isSpecial(result): return a.sons[i].sons[1] = result result = a of nkBracketExpr: result = evalArrayAccess(c, n, flags) of nkDotExpr: result = evalFieldAccess(c, n, flags) of nkDerefExpr, nkHiddenDeref: result = evalDeref(c, n, flags) of nkAddr, nkHiddenAddr: result = evalAddr(c, n, flags) of nkHiddenStdConv, nkHiddenSubConv, nkConv: result = evalConv(c, n) of nkAsgn, nkFastAsgn: result = evalAsgn(c, n) of nkWhenStmt, nkIfStmt, nkIfExpr: result = evalIf(c, n) of nkWhileStmt: result = evalWhile(c, n) of nkCaseStmt: result = evalCase(c, n) of nkVarSection: result = evalVar(c, n) of nkTryStmt: result = evalTry(c, n) of nkRaiseStmt: result = evalRaise(c, n) of nkReturnStmt: result = evalReturn(c, n) of nkBreakStmt, nkReturnToken: result = n of nkBlockExpr, nkBlockStmt: result = evalBlock(c, n) of nkDiscardStmt: result = evalAux(c, n.sons[0], {}) of nkCheckedFieldExpr: result = evalCheckedFieldAccess(c, n, flags) of nkObjDownConv: result = evalAux(c, n.sons[0], flags) of nkObjUpConv: result = evalUpConv(c, n, flags) of nkChckRangeF, nkChckRange64, nkChckRange: result = evalRangeChck(c, n) of nkStringToCString: result = evalConvStrToCStr(c, n) of nkCStringToString: result = evalConvCStrToStr(c, n) of nkPassAsOpenArray: result = evalAux(c, n.sons[0], flags) of nkStmtListExpr, nkStmtList, nkModule: for i in countup(0, sonsLen(n) - 1): result = evalAux(c, n.sons[i], flags) case result.kind of nkExceptBranch, nkReturnToken, nkBreakStmt: break else: nil of nkProcDef, nkMethodDef, nkMacroDef, nkCommentStmt, nkPragma, nkTypeSection, nkTemplateDef, nkConstSection, nkIteratorDef, nkConverterDef, nkIncludeStmt, nkImportStmt, nkFromStmt: nil of nkIdentDefs, nkCast, nkYieldStmt, nkAsmStmt, nkForStmt, nkPragmaExpr, nkLambda, nkContinueStmt, nkIdent: stackTrace(c, n, errCannotInterpretNodeX, $n.kind) else: InternalError(n.info, "evalAux: " & $n.kind) if result == nil: InternalError(n.info, "evalAux: returned nil " & $n.kind) inc(gNestedEvals) proc eval(c: PEvalContext, n: PNode): PNode = gWhileCounter = evalMaxIterations gNestedEvals = evalMaxRecDepth result = evalAux(c, n, {}) if (result.kind == nkExceptBranch) and (sonsLen(result) >= 1): stackTrace(c, n, errUnhandledExceptionX, typeToString(result.typ)) proc evalConstExpr(module: PSym, e: PNode): PNode = var p = newEvalContext(module, "", true) var s = newStackFrame() s.call = e pushStackFrame(p, s) result = eval(p, e) if result != nil and result.kind == nkExceptBranch: result = nil popStackFrame(p) proc myOpen(module: PSym, filename: string): PPassContext = var c = newEvalContext(module, filename, false) pushStackFrame(c, newStackFrame()) result = c proc myProcess(c: PPassContext, n: PNode): PNode = result = eval(PEvalContext(c), n) proc evalPass*(): TPass = initPass(result) result.open = myOpen result.close = myProcess result.process = myProcess