summary refs log tree commit diff stats
path: root/tests/compilerapi
Commit message (Collapse)AuthorAgeFilesLines
* attempt to make travis green for 'koch testinstall'Andreas Rumpf2018-09-181-1/+8
|
* compiler API: final cleanups; improve security by diabling 'gorge' and friendsAndreas Rumpf2018-05-292-1/+14
|
* rewrote nimeval.nim; added tcompilerapi example to show how the compiler can ↵Andreas Rumpf2018-05-293-0/+46
be used as an API
/a> 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
discard """
output: '''
main started: a=10, b=inner-b, c=10, d=some-d, x=16, z=20
exiting: a=12, b=overriden-b, c=100, msg=bye bye, x=16
'''
"""

import macros, tables

template scopeHolder =
  0 # scope revision number

type
  BindingsSet = Table[string, NimNode]

proc actualBody(n: NimNode): NimNode =
  # skip over the double StmtList node introduced in `mergeScopes`
  result = n.body
  if result.kind == nnkStmtList and result[0].kind == nnkStmtList:
    result = result[0]

iterator bindings(n: NimNode, skip = 0): (string, NimNode) =
  for i in skip ..< n.len:
    let child = n[i]
    if child.kind in {nnkAsgn, nnkExprEqExpr}:
      let name = $child[0]
      let value = child[1]
      yield (name, value)

proc scopeRevision(scopeHolder: NimNode): int =
  # get the revision number from a scopeHolder sym
  assert scopeHolder.kind == nnkSym
  var revisionNode = scopeHolder.getImpl.actualBody[0]
  result = int(revisionNode.intVal)

proc lastScopeHolder(scopeHolders: NimNode): NimNode =
  # get the most recent scopeHolder from a symChoice node
  if scopeHolders.kind in {nnkClosedSymChoice, nnkOpenSymChoice}:
    var bestScopeRev = 0
    assert scopeHolders.len > 0
    for scope in scopeHolders:
      let rev = scope.scopeRevision
      if result == nil or rev > bestScopeRev:
        result = scope
        bestScopeRev = rev
  else:
    result = scopeHolders

  assert result.kind == nnkSym

macro mergeScopes(scopeHolders: typed, newBindings: untyped): untyped =
  var
    bestScope = scopeHolders.lastScopeHolder
    bestScopeRev = bestScope.scopeRevision

  var finalBindings = initTable[string, NimNode]()
  for k, v in bindings(bestScope.getImpl.actualBody, skip = 1):
    finalBindings[k] = v

  for k, v in bindings(newBindings):
    finalBindings[k] = v

  var newScopeDefinition = newStmtList(newLit(bestScopeRev + 1))

  for k, v in finalBindings:
    newScopeDefinition.add newAssignment(newIdentNode(k), v)

  result = quote:
    template scopeHolder = `newScopeDefinition`

template scope(newBindings: untyped) {.dirty.} =
  mergeScopes(bindSym"scopeHolder", newBindings)

type
  TextLogRecord = object
    line: string

  StdoutLogRecord = object

template setProperty(r: var TextLogRecord, key: string, val: string, isFirst: bool) =
  if not first: r.line.add ", "
  r.line.add key
  r.line.add "="
  r.line.add val

template setEventName(r: var StdoutLogRecord, name: string) =
  stdout.write(name & ": ")

template setProperty(r: var StdoutLogRecord, key: string, val: auto, isFirst: bool) =
  when not isFirst: stdout.write ", "
  stdout.write key
  stdout.write "="
  stdout.write $val

template flushRecord(r: var StdoutLogRecord) =
  stdout.write "\n"
  stdout.flushFile

macro logImpl(scopeHolders: typed,
              logStmtProps: varargs[untyped]): untyped =
  let lexicalScope = scopeHolders.lastScopeHolder.getImpl.actualBody
  var finalBindings = initOrderedTable[string, NimNode]()

  for k, v in bindings(lexicalScope, skip = 1):
    finalBindings[k] = v

  for k, v in bindings(logStmtProps, skip = 1):
    finalBindings[k] = v

  finalBindings.sort(system.cmp)

  let eventName = logStmtProps[0]
  assert eventName.kind in {nnkStrLit}
  let record = genSym(nskVar, "record")

  result = quote:
    var `record`: StdoutLogRecord
    setEventName(`record`, `eventName`)

  var isFirst = true
  for k, v in finalBindings:
    result.add newCall(newIdentNode"setProperty",
                       record, newLit(k), v, newLit(isFirst))
    isFirst = false

  result.add newCall(newIdentNode"flushRecord", record)

template log(props: varargs[untyped]) {.dirty.} =
  logImpl(bindSym"scopeHolder", props)

scope:
  a = 12
  b = "original-b"

scope:
  x = 16
  b = "overriden-b"

scope:
  c = 100

proc main =
  scope:
    c = 10

  scope:
    z = 20

  log("main started", a = 10, b = "inner-b", d = "some-d")

main()

log("exiting", msg = "bye bye")