about summary refs log blame commit diff stats
path: root/apps/crenshaw2-1b.subx
blob: 0d17a320769aaaaa8df9473f8ab7cab16689269f (plain) (tree)
<
#
#
#           The Nimrod Compiler
#        (c) Copyright 2011 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

# This file implements the evaluator for Nimrod code.
# The evaluator is very slow, but simple. Since this
# is used mainly for evaluating macros and some other
# stuff at compile time, performance is not that
# important.

import 
  strutils, magicsys, lists, options, ast, astalgo, trees, treetab, nimsets, 
  msgs, os, condsyms, idents, renderer, types, passes, semfold

type 
  PStackFrame* = ref TStackFrame
  TStackFrame*{.final.} = object 
    mapping*: TIdNodeTable    # mapping from symbols to nodes
    prc*: PSym                # current prc; proc that is evaluated
    call*: PNode
    next*: PStackFrame        # for stacking
    params*: TNodeSeq         # parameters passed to the proc
  
  TEvalContext* = object of passes.TPassContext
    module*: PSym
    tos*: PStackFrame         # top of stack
    lastException*: PNode
    optEval*: bool            # evaluation done for optimization purposes
  
  PEvalContext* = ref TEvalContext

  TEvalFlag = enum 
    efNone, efLValue
  TEvalFlags = set[TEvalFlag]

proc eval*(c: PEvalContext, n: PNode): PNode
  # eval never returns nil! This simplifies the code a lot and
  # makes it faster too.
proc evalConstExpr*(module: PSym, e: PNode): PNode

const
  evalMaxIterations = 500_000 # max iterations of all loops
  evalMaxRecDepth = 10_000    # max recursion depth for evaluation

# Much better: use a timeout! -> Wether code compiles depends on the machine
# the compiler runs on then! Bad idea!

proc newStackFrame*(): PStackFrame = 
  new(result)
  initIdNodeTable(result.mapping)
  result.params = @[]

proc newEvalContext*(module: PSym, filename: string, 
                     optEval: bool): PEvalContext = 
  new(result)
  result.module = module
  result.optEval = optEval

proc pushStackFrame*(c: PEvalContext, t: PStackFrame) {.inline.} = 
  t.next = c.tos
  c.tos = t

proc popStackFrame*(c: PEvalContext) {.inline.} = 
  if (c.tos == nil): InternalError("popStackFrame")
  c.tos = c.tos.next

proc evalAux(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode

proc stackTraceAux(x: PStackFrame) =
  if x != nil:
    stackTraceAux(x.next)
    var info = if x.call != nil: x.call.info else: UnknownLineInfo()
    # we now use the same format as in system/except.nim
    var s = toFilename(info)
    var line = toLineNumber(info)
    if line > 0:
      add(s, '(')
      add(s, $line)
      add(s, ')')
    if x.prc != nil:
      for k in 1..max(1, 25-s.len): add(s, ' ')
      add(s, x.prc.name.s)
    MsgWriteln(s)

proc stackTrace(c: PEvalContext, n: PNode, msg: TMsgKind, arg = "") = 
  MsgWriteln("stack trace: (most recent call last)")
  stackTraceAux(c.tos)
  Fatal(n.info, msg, arg)

proc isSpecial(n: PNode): bool {.inline.} = 
  result = (n.kind == nkExceptBranch) 
  # or (n.kind == nkEmpty)
  # XXX this does not work yet! Better to compile too much than to compile to
  # few programs

proc myreset(n: PNode) {.inline.} =
  when defined(system.reset): reset(n[])

proc evalIf(c: PEvalContext, n: PNode): PNode = 
  var i = 0
  var length = sonsLen(n)
  while (i < length) and (sonsLen(n.sons[i]) >= 2): 
    result = evalAux(c, n.sons[i].sons[0], {})
    if isSpecial(result): return 
    if (result.kind == nkIntLit) and (result.intVal != 0): 
      return evalAux(c, n.sons[i].sons[1], {})
    inc(i)
  if (i < length) and (sonsLen(n.sons[i]) < 2): 
    result = evalAux(c, n.sons[i].sons[0], {})
  else: 
    result = emptyNode
  
proc evalCase(c: PEvalContext, n: PNode): PNode = 
  result = evalAux(c, n.sons[0], {})
  if isSpecial(result): return 
  var res = result
  result = emptyNode
  for i in countup(1, sonsLen(n) - 1): 
    if n.sons[i].kind == nkOfBranch: 
      for j in countup(0, sonsLen(n.sons[i]) - 2): 
        if overlap(res, n.sons[i].sons[j]): 
          return evalAux(c, lastSon(n.sons[i]), {})
    else: 
      result = evalAux(c, lastSon(n.sons[i]), {})

var 
  gWhileCounter: int # Use a counter to prevent endless loops!
                     # We make this counter global, because otherwise
                     # nested loops could make the compiler extremely slow.
  gNestedEvals: int  # count the recursive calls to ``evalAux`` to prevent
                     # endless recursion

proc evalWhile(c: PEvalContext, n: PNode): PNode = 
  while true: 
    result = evalAux(c, n.sons[0], {})
    if isSpecial(result): return 
    if getOrdValue(result) == 0: break 
    result = evalAux(c, n.sons[1], {})
    case result.kind
    of nkBreakStmt: 
      if result.sons[0].kind == nkEmpty: 
        result = emptyNode    # consume ``break`` token
      # Bugfix (see tmacro2): but break in any case!
      break 
    of nkExceptBranch, nkReturnToken: break 
    else: nil
    dec(gWhileCounter)
    if gWhileCounter <= 0: 
      stackTrace(c, n, errTooManyIterations)
      break 

proc evalBlock(c: PEvalContext, n: PNode): PNode = 
  result = evalAux(c, n.sons[1], {})
  if result.kind == nkBreakStmt: 
    if result.sons[0] != nil: 
      assert(result.sons[0].kind == nkSym)
      if n.sons[0].kind != nkEmpty: 
        assert(n.sons[0].kind == nkSym)
        if result.sons[0].sym.id == n.sons[0].sym.id: result = emptyNode
    else: 
      result = emptyNode      # consume ``break`` token
  
proc evalFinally(c: PEvalContext, n, exc: PNode): PNode = 
  var finallyNode = lastSon(n)
  if finallyNode.kind == nkFinally: 
    result = evalAux(c, finallyNode, {})
    if result.kind != nkExceptBranch: result = exc
  else: 
    result = exc
  
proc evalTry(c: PEvalContext, n: PNode): PNode = 
  result = evalAux(c, n.sons[0], {})
  case result.kind
  of nkBreakStmt, nkReturnToken: 
    nil
  of nkExceptBranch: 
    if sonsLen(result) >= 1: 
      # creating a nkExceptBranch without sons means that it could not be
      # evaluated
      var exc = result
      var i = 1
      var length = sonsLen(n)
      while (i < length) and (n.sons[i].kind == nkExceptBranch): 
        var blen = sonsLen(n.sons[i])
        if blen == 1: 
          # general except section:
          result = evalAux(c, n.sons[i].sons[0], {})
          exc = result
          break 
        else: 
          for j in countup(0, blen - 2): 
            assert(n.sons[i].sons[j].kind == nkType)
            if exc.typ.id == n.sons[i].sons[j].typ.id: 
              result = evalAux(c, n.sons[i].sons[blen - 1], {})
              exc = result
              break 
        inc(i)
      result = evalFinally(c, n, exc)
  else: result = evalFinally(c, n, emptyNode)
  
proc getNullValue(typ: PType, info: TLineInfo): PNode
proc getNullValueAux(obj: PNode, result: PNode) = 
  case obj.kind
  of nkRecList:
    for i in countup(0, sonsLen(obj) - 1): getNullValueAux(obj.sons[i], result)
  of nkRecCase:
    getNullValueAux(obj.sons[0], result)
    for i in countup(1, sonsLen(obj) - 1): 
      getNullValueAux(lastSon(obj.sons[i]), result)
  of nkSym: 
    var s = obj.sym
    var p = newNodeIT(nkExprColonExpr, result.info, s.typ)
    addSon(p, newSymNode(s, result.info))
    addSon(p, getNullValue(s.typ, result.info))
    addSon(result, p)
  else: InternalError(result.info, "getNullValueAux")
  
proc getNullValue(typ: PType, info: TLineInfo): PNode = 
  var t = skipTypes(typ, abstractRange)
  result = emptyNode
  case t.kind
  of tyBool, tyChar, tyInt..tyInt64: 
    result = newNodeIT(nkIntLit, info, t)
  of tyFloat..tyFloat128: 
    result = newNodeIt(nkFloatLit, info, t)
  of tyVar, tyPointer, tyPtr, tyRef, tyCString, tySequence, tyString, tyExpr, 
     tyStmt, tyTypeDesc: 
    result = newNodeIT(nkNilLit, info, t)
  of tyObject: 
    result = newNodeIT(nkPar, info, t)
    getNullValueAux(t.n, result)
  of tyArray, tyArrayConstr: 
    result = newNodeIT(nkBracket, info, t)
    for i in countup(0, int(lengthOrd(t)) - 1): 
      addSon(result, getNullValue(elemType(t), info))
  of tyTuple: 
    result = newNodeIT(nkPar, info, t)
    for i in countup(0, sonsLen(t) - 1): 
      var p = newNodeIT(nkExprColonExpr, info, t.sons[i])
      var field = if t.n != nil: t.n.sons[i].sym else: newSym(
        skField, getIdent(":tmp" & $i), t.owner)
      addSon(p, newSymNode(field, info))
      addSon(p, getNullValue(t.sons[i], info))
      addSon(result, p)
  of tySet:
    result = newNodeIT(nkCurly, info, t)    
  else: InternalError("getNullValue")
  
proc evalVar(c: PEvalContext, n: PNode): PNode = 
  for i in countup(0, sonsLen(n) - 1): 
    var a = n.sons[i]
    if a.kind == nkCommentStmt: continue 
    assert(a.kind == nkIdentDefs)
    assert(a.sons[0].kind == nkSym)
    var v = a.sons[0].sym
    if a.sons[2].kind != nkEmpty: 
      result = evalAux(c, a.sons[2], {})
      if isSpecial(result): return 
    else: 
      result = getNullValue(a.sons[0].typ, a.sons[0].info)
    IdNodeTablePut(c.tos.mapping, v, result)
  result = emptyNode

proc evalCall(c: PEvalContext, n: PNode): PNode = 
  result = evalAux(c, n.sons[0], {})
  if isSpecial(result): return 
  var prc = result
  # bind the actual params to the local parameter of a new binding
  var d = newStackFrame()
  d.call = n
  if prc.kind == nkSym: 
    d.prc = prc.sym
    if not (prc.sym.kind in {skProc, skConverter}): 
      InternalError(n.info, "evalCall")
  setlen(d.params, sonsLen(n))
  for i in countup(1, sonsLen(n) - 1): 
    result = evalAux(c, n.sons[i], {})
    if isSpecial(result): return 
    d.params[i] = result
  if n.typ != nil: d.params[0] = getNullValue(n.typ, n.info)
  pushStackFrame(c, d)
  result = evalAux(c, prc, {})
  if result.kind == nkExceptBranch: return 
  if n.typ != nil: result = d.params[0]
  popStackFrame(c)

proc aliasNeeded(n: PNode, flags: TEvalFlags): bool = 
  result = efLValue in flags or n.typ == nil or 
    n.typ.kind in {tyExpr, tyStmt, tyTypeDesc}

proc evalVariable(c: PStackFrame, sym: PSym, flags: TEvalFlags): PNode = 
  # We need to return a node to the actual value,
  # which can be modified.
  var x = c
  while x != nil: 
    if sfResult in sym.flags: 
      result = x.params[0]
      if result == nil: result = emptyNode
      return
    result = IdNodeTableGet(x.mapping, sym)
    if result != nil and not aliasNeeded(result, flags): 
      result = copyTree(result)
    if result != 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 
  myreset(x)
  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")
  myreset(a)
  a.kind = nkRefTy
  a.info = n.info
  a.typ = t
  a.sons = nil
  addSon(a, getNullValue(t.sons[0], n.info))
  result = emptyNode

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")
  myreset(a)
  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
  var a = newNodeIT(nkStrLit, n.info, n.typ)
  a.strVal = ""
  for i in countup(1, sonsLen(n) - 1): 
    result = evalAux(c, n.sons[i], {})
    if isSpecial(result): return 
    a.strVal.add(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
                   
                                      
              
                                
                      
                                                                                                                                                                  
                                        
                   
                                     
              
                                
                      
                                                                                                                                                                  

                                     
                   

                               
              
                         
                      
                                                                                                                                                                  
                                                                     
                                   

                                                                                                                                                                         
                                      
                   
                                                  
                  
              
                                          
                      
                                                                                                                                                                  

                                     
                   
                                      
              
                            
                      
                                                                                                                                                                  
                               
                   
                  


                                      
              
                           
                                                         
                      
                                                                                                                                                                  
                                                               
                   

                                                                                

                                                                                                                                                                      
              
                                    
                      
                                                                                                                                                                  
                      

                                                                                                                                                                  
             



                                                      
                                                                                          
                

                                                                                                                                                                       
                         
                   
                            
                                                                                                                                                                      
              
                         
                      
                                                                                                                                                                  
                 
                   

                                                                                                                                                                      
              
                         
                      
                                                                                                                                                                  
                             
                   
                                
                                                                                                                                                                      
              
                         
                      
                                                                                                                                                                  
                 
                   
                    
                                                                                                                                                                     
              
                        
                                      
                   
                

                                                                                                                                                                       
             
 
                                             
                                    
                

                                                                                                                                                                       
                      

                                 
                   
                                                                                                                                                                     
              
                                      
                      


                                                                                                                                                                         
              
                         
                 
                

                                                                                                                                                                       
             
 
                                    
                



                                                                                                                                                                       
                               
                                                                                                                                                                        
                                      
                               
                                                                                                                                                                        
                                      
                           
                           
               
                

                                                                                                                                                                       
             


       

                                       
 
                            
# Port of https://github.com/akkartik/crenshaw/blob/master/tutor2.1.pas
# which corresponds to the section "single digits" in https://compilers.iecc.com/crenshaw/tutor2.txt
# except that we support hex numbers of multiple digits.
#
# To run:
#   $ ./bootstrap translate init.linux 0*.subx apps/crenshaw2-1b.subx -o apps/crenshaw2-1b
#   $ echo '1a'  |./bootstrap run apps/crenshaw2-1b
# Expected output:
#   # syscall(exit, 1a)
#   bb/copy-to-ebx  3/imm32
#   b8/copy-to-eax  1/imm32/exit
#   cd/syscall  0x80/imm8
#
# To run the generated output:
#   $ echo '1a'  |./bootstrap run apps/crenshaw2-1b > z1.subx
#   $ ./bootstrap translate init.linux z1.subx -o z1
#   $ ./bootstrap run z1
#   $ echo $?
#   26  # 0x1a in decimal
#
# Stdin must contain just a single hex digit. Other input will print an error:
#   $ echo 'xyz'  |./bootstrap run apps/crenshaw2-1b
#   Error: integer expected
#
# Names in this file sometimes follow Crenshaw's original rather than my usual
# naming conventions.

== code
#   instruction                     effective address                                                   register    displacement    immediate
# . op          subop               mod             rm32          base        index         scale       r32
# . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes

Entry:  # run tests if necessary, call 'compile' if not
    # . prologue
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp

    # initialize heap
    # . Heap = new-segment(Heap-size)
    # . . push args
    68/push  Heap/imm32
    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Heap-size/disp32                  # push *Heap-size
    # . . call
    e8/call  new-segment/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp

    # - if argc > 1 and argv[1] == "test", then return run_tests()
    # if (argc <= 1) goto run-main
    81          7/subop/compare     1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0/disp8         1/imm32           # compare *ebp
    7e/jump-if-<=  $run-main/disp8
    # if (!kernel-string-equal?(argv[1], "test")) goto run-main
    # . eax = kernel-string-equal?(argv[1], "test")
    # . . push args
    68/push  "test"/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
    # . . call
    e8/call  kernel-string-equal?/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . if (eax == false) goto run-main
    3d/compare-eax-and  0/imm32/false
    74/jump-if-=  $run-main/disp8
    # run-tests()
    e8/call  run-tests/disp32
    # syscall(exit, *Num-test-failures)
    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/ebx   Num-test-failures/disp32          # copy *Num-test-failures to ebx
    eb/jump  $main:end/disp8
$run-main:
    # - otherwise read a program from stdin and emit its translation to stdout
    # . compile(Stdin, 1/stdout, 2/stderr, 0)
    # . . push args
    68/push  0/imm32/exit-descriptor
    68/push  2/imm32/stderr
    68/push  1/imm32/stdout
    68/push  Stdin/imm32
    # . . call
    e8/call  compile/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
    # syscall(exit, 0)
    bb/copy-to-ebx  0/imm32
$main:end:
    e8/call  syscall_exit/disp32

# the main entry point
compile:  # in: (addr buffered-file), out: fd or (addr stream byte), err: fd or (addr stream byte), ed: (addr exit-descriptor)
    # . prologue
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # . save registers
    50/push-eax
    51/push-ecx
    # prime the pump
    # . Look = get-char(in)
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8      .                    # push *(ebp+8)
    # . . call
    e8/call  get-char/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # var num/ecx: (stream byte 7)
    # Numbers can be 32 bits or 8 hex bytes long. One of them will be in 'Look', so we need space for 7 bytes.
    # Sizing the stream just right buys us overflow-handling for free inside 'get-num'.
    # Add 12 bytes for 'read', 'write' and 'size' fields, for a total of 19 bytes, or 0x13 in hex.
    # The stack pointer is no longer aligned, so dump_stack() can be misleading past this point.
    81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0x13/imm32        # subtract from esp
    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
    # initialize the stream
    # . num->size = 7
    c7          0/subop/copy        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           8/disp8         7/imm32           # copy to *(ecx+8)
    # . clear-stream(num)
    # . . push args
    51/push-ecx
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # read a digit from 'in' into 'num'
    # . get-num(in, num, err, ed)
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x14/disp8      .                 # push *(ebp+20)
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
    51/push-ecx/num
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8      .                    # push *(ebp+8)
    # . . call
    e8/call  get-num/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
    # render 'num' into the following template on 'out':
    #   bb/copy-to-ebx  _num_
    #   b8/copy-to-eax  1/imm32/exit
    #   cd/syscall  0x80/imm8
    #
    # . write(out, "bb/copy-to-ebx  ")
    # . . push args
    68/push  "bb/copy-to-ebx  "/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . write-stream(out, num)
    # . . push args
    51/push-ecx/num
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
    # . . call
    e8/call  write-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . write(out, Newline)
    # . . push args
    68/push  Newline/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . write(out, "b8/copy-to-eax  1/imm32/exit\n")
    # . . push args
    68/push  "b8/copy-to-eax  1/imm32/exit\n"/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . write(out, "cd/syscall  0x80/imm8\n")
    # . . push args
    68/push  "cd/syscall  0x80/imm8\n"/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
$compile:end:
    # . restore registers
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    5d/pop-to-ebp
    c3/return

# Read a sequence of digits into 'out'. Abort if there are none, or if there is
# no space in 'out'.
# Input comes from the global variable 'Look' (first byte) and the argument
# 'in' (rest). We leave the next byte from 'in' into 'Look' on exit.
get-num:  # in: (addr buffered-file), out: (addr stream byte), err: fd or (addr stream byte), ed: (addr exit-descriptor)
    # pseudocode:
    #   if (!is-digit?(Look)) expected(ed, err, "integer")
    #   do
    #     if out->write >= out->size
    #       write(err, "Error: too many digits in number\n")
    #       stop(ed, 1)
    #     out->data[out->write] = LSB(Look)
    #     ++out->write
    #     Look = get-char(in)
    #   while is-digit?(Look)
    # This is complicated because I don't want to hard-code the error strategy in
    # a general helper like write-byte-buffered. Maybe I should just create a
    # local helper.
    #
    # within the loop we'll try to keep things in registers:
    #   in: esi
    #   out: edi
    #   out->write: ecx (cached copy; need to keep in sync)
    #   out->size: edx
    #   temporaries: eax, ebx
    # We can't allocate Look to a register because it gets written implicitly in
    # get-char in each iteration of the loop. (Thereby demonstrating that it's
    # not the right interface for us. But we'll keep it just to follow Crenshaw.)
    #
    # . prologue
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # - if (is-digit?(Look)) expected(ed, err, "integer")
    # . eax = is-digit?(Look)
    # . . push args
    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Look/disp32     .                 # push *Look
    # . . call
    e8/call  is-digit?/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . if (eax == false)
    3d/compare-eax-and  0/imm32/false
    75/jump-if-!=  $get-num:main/disp8
    # . expected(ed, err, "integer")
    # . . push args
    68/push  "integer"/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x14/disp8      .                 # push *(ebp+20)
    # . . call
    e8/call  expected/disp32  # never returns
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
$get-num:main:
    # - otherwise read a digit
    # . save registers
    50/push-eax
    51/push-ecx
    52/push-edx
    53/push-ebx
    56/push-esi
    57/push-edi
    # read necessary variables to registers
    # esi = in
    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
    # edi = out
    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   0xc/disp8       .                 # copy *(ebp+12) to edi
    # ecx = out->write
    8b/copy                         0/mod/indirect  7/rm32/edi    .           .             .           1/r32/ecx   .               .                 # copy *edi to ecx
    # edx = out->size
    8b/copy                         1/mod/*+disp8   7/rm32/edi    .           .             .           2/r32/edx   8/disp8         .                 # copy *(edi+8) to edx
$get-num:loop:
    # if (out->write >= out->size) error
    39/compare                      3/mod/direct    2/rm32/edx    .           .             .           1/r32/ecx   .               .                 # compare edx with ecx
    7d/jump-if-<  $get-num:loop-stage2/disp8
    # . error(ed, err, msg)  # TODO: show full number
    # . . push args
    68/push  "get-num: too many digits in number"/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x14/disp8      .                 # push *(ebp+20)
    # . . call
    e8/call  error/disp32  # never returns
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
$get-num:loop-stage2:
    # out->data[out->write] = LSB(Look)
    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    7/base/edi  1/index/ecx   .           3/r32/ebx   0xc/disp8       .                 # copy edi+ecx+12 to ebx
    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/eax   Look/disp32     .                 # copy *Look to eax
    88/copy-byte                    0/mod/indirect  3/rm32/ebx    .           .             .           0/r32/AL    .               .                 # copy byte at AL to *ebx
    # ++out->write
    41/increment-ecx
    # Look = get-char(in)
    # . . push args
    56/push-esi
    # . . call
    e8/call  get-char/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # if (is-digit?(Look)) loop
    # . eax = is-digit?(Look)
    # . . push args
    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Look/disp32     .                 # push *Look
    # . . call
    e8/call  is-digit?/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . if (eax != false) loop
    3d/compare-eax-and  0/imm32/false
    0f 85/jump-if-!=  $get-num:loop/disp32
$get-num:loop-end:
    # persist necessary variables from registers
    89/copy                         0/mod/indirect  7/rm32/edi    .           .             .           1/r32/ecx   .               .                 # copy ecx to *edi
$get-num:end:
    # . restore registers
    5f/pop-to-edi
    5e/pop-to-esi
    5b/pop-to-ebx
    5a/pop-to-edx
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    5d/pop-to-ebp
    c3/return

test-get-num-reads-single-digit:
    # - check that get-num returns first character if it's a digit
    # This test uses exit-descriptors. Use ebp for setting up local variables.
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # clear all streams
    # . clear-stream(_test-stream)
    # . . push args
    68/push  _test-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . clear-stream($_test-buffered-file->buffer)
    # . . push args
    68/push  $_test-buffered-file->buffer/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . clear-stream(_test-output-stream)
    # . . push args
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . clear-stream(_test-error-stream)
    # . . push args
    68/push  _test-error-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # initialize 'in'
    # . write(_test-stream, "3")
    # . . push args
    68/push  "3"/imm32
    68/push  _test-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # initialize exit-descriptor 'ed' for the call to 'get-num' below
    # . var ed/eax: exit-descriptor
    81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # subtract from esp
    89/copy                         3/mod/direct    0/rm32/eax    .           .             .           4/r32/esp   .               .                 # copy esp to eax
    # . tailor-exit-descriptor(ed, 16)
    # . . push args
    68/push  0x10/imm32/nbytes-of-args-for-get-num
    50/push-eax/ed
    # . . call
    e8/call  tailor-exit-descriptor/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # prime the pump
    # . get-char(_test-buffered-file)
    # . . push args
    68/push  _test-buffered-file/imm32
    # . . call
    e8/call  get-char/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # get-num(in, out, err, ed)
    # . . push args
    50/push-eax/ed
    68/push  _test-error-stream/imm32
    68/push  _test-output-stream/imm32
    68/push  _test-buffered-file/imm32
    # . . call
    e8/call  get-num/disp32
    # registers except esp may be clobbered at this point
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
    # check-ints-equal(*_test-output-stream->data, '3', msg)
    # . . push args
    68/push  "F - test-get-num-reads-single-digit"/imm32
    68/push  0x33/imm32
    b8/copy-to-eax  _test-output-stream/imm32
    ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           0xc/disp8       .                 # push *(eax+12)
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # . reclaim locals
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    5d/pop-to-ebp
    c3/return

test-get-num-aborts-on-non-digit-in-Look:
    # - check that get-num returns first character if it's a digit
    # This test uses exit-descriptors. Use ebp for setting up local variables.
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # clear all streams
    # . clear-stream(_test-stream)
    # . . push args
    68/push  _test-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . clear-stream($_test-buffered-file->buffer)
    # . . push args
    68/push  $_test-buffered-file->buffer/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . clear-stream(_test-output-stream)
    # . . push args
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . clear-stream(_test-error-stream)
    # . . push args
    68/push  _test-error-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # initialize 'in'
    # . write(_test-stream, "3")
    # . . push args
    68/push  "3"/imm32
    68/push  _test-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # initialize exit-descriptor 'ed' for the call to 'get-num' below
    # . var ed/eax: exit-descriptor
    81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # subtract from esp
    89/copy                         3/mod/direct    0/rm32/eax    .           .             .           4/r32/esp   .               .                 # copy esp to eax
    # . tailor-exit-descriptor(ed, 16)
    # . . push args
    68/push  0x10/imm32/nbytes-of-args-for-get-num
    50/push-eax/ed
    # . . call
    e8/call  tailor-exit-descriptor/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # *don't* prime the pump
    # get-num(in, out, err, ed)
    # . . push args
    50/push-eax/ed
    68/push  _test-error-stream/imm32
    68/push  _test-output-stream/imm32
    68/push  _test-buffered-file/imm32
    # . . call
    e8/call  get-num/disp32
    # registers except esp may be clobbered at this point
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
    # check that get-num tried to call exit(1)
    # . check-ints-equal(ed->value, 2, msg)  # i.e. stop was called with value 1
    # . . push args
    68/push  "F - test-get-num-aborts-on-non-digit-in-Look"/imm32
    68/push  2/imm32
    # . . push ed->value
    ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # . reclaim locals
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    5d/pop-to-ebp
    c3/return

test-get-num-reads-multiple-digits:
    # - check that get-num returns all initial digits until it encounters a non-digit
    # This test uses exit-descriptors. Use ebp for setting up local variables.
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # clear all streams
    # . clear-stream(_test-stream)
    # . . push args
    68/push  _test-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . clear-stream($_test-buffered-file->buffer)
    # . . push args
    68/push  $_test-buffered-file->buffer/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . clear-stream(_test-output-stream)
    # . . push args
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . clear-stream(_test-error-stream)
    # . . push args
    68/push  _test-error-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # initialize 'in'
    # . write(_test-stream, "3456 x")
    # . . push args
    68/push  "3456"/imm32
    68/push  _test-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # initialize exit-descriptor 'ed' for the call to 'get-num' below
    # . var ed/eax: exit-descriptor
    81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # subtract from esp
    89/copy                         3/mod/direct    0/rm32/eax    .           .             .           4/r32/esp   .               .                 # copy esp to eax
    # . tailor-exit-descriptor(ed, 16)
    # . . push args
    68/push  0x10/imm32/nbytes-of-args-for-get-num
    50/push-eax/ed
    # . . call
    e8/call  tailor-exit-descriptor/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # prime the pump
    # . get-char(_test-buffered-file)
    # . . push args
    68/push  _test-buffered-file/imm32
    # . . call
    e8/call  get-char/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # get-num(in, out, err, ed)
    # . . push args
    50/push-eax/ed
    68/push  _test-error-stream/imm32
    68/push  _test-output-stream/imm32
    68/push  _test-buffered-file/imm32
    # . . call
    e8/call  get-num/disp32
    # registers except esp may be clobbered at this point
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
    # check-ints-equal(*_test-output-stream->data, '3456', msg)
    # . . push args
    68/push  "F - test-get-num-reads-multiple-digits"/imm32
    68/push  0x36353433/imm32
    b8/copy-to-eax  _test-output-stream/imm32
    ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           0xc/disp8       .                 # push *(eax+12)
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # . reclaim locals
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    5d/pop-to-ebp
    c3/return

test-get-num-reads-multiple-digits-followed-by-nondigit:
    # - check that get-num returns all initial digits until it encounters a non-digit
    # This test uses exit-descriptors. Use ebp for setting up local variables.
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # clear all streams
    # . clear-stream(_test-stream)
    # . . push args
    68/push  _test-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . clear-stream($_test-buffered-file->buffer)
    # . . push args
    68/push  $_test-buffered-file->buffer/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . clear-stream(_test-output-stream)
    # . . push args
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . clear-stream(_test-error-stream)
    # . . push args
    68/push  _test-error-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # initialize 'in'
    # . write(_test-stream, "3456 x")
    # . . push args
    68/push  "3456 x"/imm32
    68/push  _test-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # initialize exit-descriptor 'ed' for the call to 'get-num' below
    # . var ed/eax: exit-descriptor
    81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # subtract from esp
    89/copy                         3/mod/direct    0/rm32/eax    .           .             .           4/r32/esp   .               .                 # copy esp to eax
    # . tailor-exit-descriptor(ed, 16)
    # . . push args
    68/push  0x10/imm32/nbytes-of-args-for-get-num
    50/push-eax/ed
    # . . call
    e8/call  tailor-exit-descriptor/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # prime the pump
    # . get-char(_test-buffered-file)
    # . . push args
    68/push  _test-buffered-file/imm32
    # . . call
    e8/call  get-char/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # get-num(in, out, err, ed)
    # . . push args
    50/push-eax/ed
    68/push  _test-error-stream/imm32
    68/push  _test-output-stream/imm32
    68/push  _test-buffered-file/imm32
    # . . call
    e8/call  get-num/disp32
    # registers except esp may be clobbered at this point
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
    # check-ints-equal(*_test-output-stream->data, '3456', msg)
    # . . push args
    68/push  "F - test-get-num-reads-multiple-digits-followed-by-nondigit"/imm32
    68/push  0x36353433/imm32
    b8/copy-to-eax  _test-output-stream/imm32
    ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           0xc/disp8       .                 # push *(eax+12)
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # . reclaim locals
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    5d/pop-to-ebp
    c3/return

## helpers

# write(f, "Error: "+s+" expected\n") then stop(ed, 1)
expected:  # ed: (addr exit-descriptor), f: fd or (addr stream byte), s: (addr array byte)
    # . prologue
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # write(f, "Error: ")
    # . . push args
    68/push  "Error: "/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # write(f, s)
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # write(f, " expected\n")
    # . . push args
    68/push  " expected\n"/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # stop(ed, 1)
    # . . push args
    68/push  1/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
    # . . call
    e8/call  stop/disp32
    # should never get past this point
$expected:dead-end:
    # . epilogue
    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    5d/pop-to-ebp
    c3/return

# read a byte from 'f', and save it in 'Look'
get-char:  # f: (addr buffered-file)
    # . prologue
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # . save registers
    50/push-eax
    # eax = read-byte-buffered(f)
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
    # . . call
    e8/call  read-byte-buffered/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # save eax to Look
    89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/eax   Look/disp32     .                 # copy eax to *Look
$get-char:end:
    # . restore registers
    58/pop-to-eax
    # . epilogue
    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    5d/pop-to-ebp
    c3/return

is-digit?:  # c: int -> eax: boolean
    # . prologue
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # eax = false
    b8/copy-to-eax  0/imm32
    # if (c < '0') return false
    81          7/subop/compare     1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         0x30/imm32        # compare *(ebp+8)
    7c/jump-if-<  $is-digit?:end/disp8
    # if (c > '9') return false
    81          7/subop/compare     1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         0x39/imm32        # compare *(ebp+8)
    7f/jump-if->  $is-digit?:end/disp8
    # otherwise return true
    b8/copy-to-eax  1/imm32
$is-digit?:end:
    # . epilogue
    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    5d/pop-to-ebp
    c3/return

== data

Look:  # (char with some extra padding)
    0/imm32

# . . vim:nowrap:textwidth=0