logo

class

compiler::SimpleLiteralExpr

sys::Obj
  compiler::Node
    compiler::Expr
      compiler::SimpleLiteralExpr
//
// Copyright (c) 2006, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   19 Jul 06  Brian Frank  Creation
//

**
** Expr
**
abstract class Expr : Node
{

//////////////////////////////////////////////////////////////////////////
// Construction
//////////////////////////////////////////////////////////////////////////

  new make(Location location, ExprId id)
    : super(location)
  {
    this.id = id
  }

//////////////////////////////////////////////////////////////////////////
// Expr
//////////////////////////////////////////////////////////////////////////

  **
  ** Return this expression as an Int literal usable in a tableswitch,
  ** or null if this Expr doesn't represent a constant Int.  Expressions
  ** which work as table switch cases: int literals and enum constants
  **
  virtual Int asTableSwitchCase() { return null }

  **
  ** Return if this expression matches the expected type.
  **
  Bool fits(CType expected)
  {
    // sanity check that this expression has been typed
    if (ctype == null) throw NullErr.make("null ctype: ${this}")
    actual := ctype

    // void is never any type
    if (actual.isVoid) return false

    // null can be used for any type (everything is boxed)
    if (id === ExprId.nullLiteral) return true

    // route to CType.fits
    return actual.fits(expected)
  }

  **
  ** Return if this expression can be used as the
  ** left hand side of an assignment expression.
  **
  virtual Bool isAssignable()
  {
    return false
  }

  **
  ** Is this a boolean conditional (boolOr/boolAnd)
  **
  virtual Bool isCond()
  {
    return false
  }

  **
  ** Does this expression make up a complete statement
  **
  virtual Bool isStmt()
  {
    return false
  }

  **
  ** Assignments to instance fields require a temporary local variable.
  **
  virtual Bool assignRequiresTempVar()
  {
    return false
  }

  **
  ** Map the list of expressions into their list of types
  **
  static CType[] ctypes(Expr[] exprs)
  {
    return (CType[])exprs.map(CType[,]) |Expr e->Obj| { return e.ctype }
  }

  **
  ** Given a list of Expr instances, find the common base type
  ** they all share.  This method does not take into account
  ** the null literal.  It is used for type inference for lists
  ** and maps.
  **
  static CType commonType(CNamespace ns, Expr[] exprs)
  {
    exprs = exprs.exclude |Expr e->Bool| { return e.id == ExprId.nullLiteral }
    return CType.common(ns, ctypes(exprs))
  }

  **
  ** Return this expression as an ExprStmt
  **
  ExprStmt toStmt()
  {
    return ExprStmt.make(this)
  }

  **
  ** Return this expression as serialization text or
  ** throw exception if not serializable.
  **
  virtual Str serialize()
  {
    throw CompilerErr.make("'$id' not serializable", location)
  }

//////////////////////////////////////////////////////////////////////////
// Tree
//////////////////////////////////////////////////////////////////////////

  Expr walk(Visitor v)
  {
    walkChildren(v)
    return v.visitExpr(this)
  }

  virtual Void walkChildren(Visitor v)
  {
  }

  static Expr walkExpr(Visitor v, Expr expr)
  {
    if (expr == null) return null
    return expr.walk(v)
  }

  static Expr[] walkExprs(Visitor v, Expr[] exprs)
  {
    for (i:=0; i<exprs.size; ++i)
    {
      expr := exprs[i]
      if (expr != null)
      {
        replace := expr.walk(v)
        if (expr !== replace)
          exprs[i] = replace
      }
    }
    return exprs
  }

//////////////////////////////////////////////////////////////////////////
// Debug
//////////////////////////////////////////////////////////////////////////

  override abstract Str toStr()

  override Void print(AstWriter out)
  {
    out.w(toStr)
  }

//////////////////////////////////////////////////////////////////////////
// Fields
//////////////////////////////////////////////////////////////////////////

  readonly ExprId id      // expression type identifier
  CType ctype             // type expression resolves to
  Bool leave := true      // leave this expression on the stack
}

**************************************************************************
** LiteralExpr
**************************************************************************

**
** LiteralExpr puts an Bool, Int, Float, Str, Duration, Uri,
** or null constant onto the stack.
**
class LiteralExpr : Expr
{
  static LiteralExpr makeFor(Location loc, CNamespace ns, Obj val)
  {
    switch (val.type)
    {
      case Bool.type:
        return val == true ?
          make(loc, ExprId.trueLiteral, ns.boolType, true) :
          make(loc, ExprId.falseLiteral, ns.boolType, false)
      case Str.type:
        return make(loc, ExprId.strLiteral, ns.strType, val)
      default:
        throw Err.make("Unsupported literal type $val.type")
    }
  }

  new make(Location location, ExprId id, CType ctype, Obj val)
    : super(location, id)
  {
    this.ctype = ctype
    this.val   = val
  }

  override Int asTableSwitchCase()
  {
    return val as Int
  }

  override Str serialize()
  {
    switch (id)
    {
      case ExprId.falseLiteral:    return "false"
      case ExprId.trueLiteral:     return "true"
      case ExprId.intLiteral:      return val.toStr
      case ExprId.floatLiteral:    return val.toStr
      case ExprId.strLiteral:      return val.toStr.toCode
      case ExprId.uriLiteral:      return val.toStr.toCode('`')
      case ExprId.typeLiteral:     return val->signature + ".type"
      case ExprId.durationLiteral: return val.toStr
      default:                     return super.serialize
    }
  }

  override Str toStr()
  {
    switch (id)
    {
      case ExprId.nullLiteral: return "null"
      case ExprId.strLiteral:  return "\"" + val.toStr.replace("\n", "\\n") + "\""
      case ExprId.typeLiteral: return "${val}.type"
      case ExprId.uriLiteral:  return "`$val`"
      default: return val.toStr
    }
  }

  Obj val // Bool, Int, Float, Str (for Str/Uri), Duration, CType, or null
}

**************************************************************************
** RangeLiteralExpr
**************************************************************************

**
** RangeLiteralExpr creates a Range instance
**
class RangeLiteralExpr : Expr
{
  new make(Location location, CType ctype)
    : super(location, ExprId.rangeLiteral)
  {
    this.ctype = ctype
  }

  override Void walkChildren(Visitor v)
  {
    start = start.walk(v)
    end   = end.walk(v)
  }

  override Str toStr()
  {
    if (exclusive)
      return "${start}...${end}"
    else
      return "${start}..${end}"
  }

  Expr start
  Expr end
  Bool exclusive
}

**************************************************************************
** ListLiteralExpr
**************************************************************************

**
** ListLiteralExpr creates a List instance
**
class ListLiteralExpr : Expr
{
  new make(Location location, ListType explicitType := null)
    : super(location, ExprId.listLiteral)
  {
    this.explicitType = explicitType
  }

  new makeFor(Location location, CType ctype, Expr[] vals)
    : super.make(location, ExprId.listLiteral)
  {
    this.ctype = ctype
    this.vals  = vals
  }

  override Void walkChildren(Visitor v)
  {
    vals = walkExprs(v, vals)
  }

  override Str serialize()
  {
    return format |Expr e->Str| { return e.serialize }
  }

  override Str toStr()
  {
    return format |Expr e->Str| { return e.toStr }
  }

  Str format(|Expr e->Str| f)
  {
    s := StrBuf.make
    if (explicitType != null) s.add(explicitType.v)
    s.add("[")
    if (vals.isEmpty) s.add(",")
    else vals.each |Expr v, Int i|
    {
      if (i > 0) s.add(",")
      s.add(f(v))
    }
    s.add("]")
    return s.toStr
  }

  ListType explicitType
  Expr[] vals := Expr[,]
}

**************************************************************************
** MapLiteralExpr
**************************************************************************

**
** MapLiteralExpr creates a List instance
**
class MapLiteralExpr : Expr
{
  new make(Location location, MapType explicitType := null)
    : super(location, ExprId.mapLiteral)
  {
    this.explicitType = explicitType
  }

  override Void walkChildren(Visitor v)
  {
    keys = walkExprs(v, keys)
    vals = walkExprs(v, vals)
  }

  override Str serialize()
  {
    return format |Expr e->Str| { return e.serialize }
  }

  override Str toStr()
  {
    return format |Expr e->Str| { return e.toStr }
  }

  Str format(|Expr e->Str| f)
  {
    s := StrBuf.make
    if (explicitType != null) s.add(explicitType)
    s.add("[")
    if (vals.isEmpty) s.add(":")
    else
    {
      keys.size.times |Int i|
      {
        if (i > 0) s.add(",")
        s.add(f(keys[i])).add(":").add(f(vals[i]))
      }
    }
    s.add("]")
    return s.toStr
  }

  MapType explicitType
  Expr[] keys := Expr[,]
  Expr[] vals := Expr[,]
}

**************************************************************************
** SimpleLiteralExpr
**************************************************************************

**
** SimpleLiteralExpr is convenience for 'ctype.parse(arg)'
**
class SimpleLiteralExpr : Expr
{
  new make(Location location, CType ctype, Expr arg)
    : super(location, ExprId.simpleLiteral)
  {
    this.ctype = ctype
    this.arg   = arg
  }

  override Void walkChildren(Visitor v)
  {
    arg = arg.walk(v)
  }

  override Str serialize()
  {
    return "${ctype}(${arg.serialize})"
  }

  override Str toStr()
  {
    return "${ctype}($arg)"
  }

  Expr arg            // str representation
  CMethod method      // parse method (resolved in CheckErrors)
}

**************************************************************************
** UnaryExpr
**************************************************************************

**
** UnaryExpr is used for unary expressions including !, +.
** Note that - is mapped to negate() as a shortcut method.
**
class UnaryExpr : Expr
{
  new make(Location location, ExprId id, Token opToken, Expr operand)
    : super(location, id)
  {
    this.opToken = opToken
    this.operand = operand
  }

  override Void walkChildren(Visitor v)
  {
    operand = operand.walk(v)
  }

  override Str toStr()
  {
    if (id == ExprId.cmpNull)
      return operand.toStr + " == null"
    else if (id == ExprId.cmpNotNull)
      return operand.toStr + " != null"
    else
      return opToken.toStr + operand.toStr
  }

  Token opToken   // operator token type (Token.bang, etc)
  Expr operand    // operand expression

}

**************************************************************************
** BinaryExpr
**************************************************************************

**
** BinaryExpr is used for binary expressions with a left hand side and a
** right hand side including assignment.  Note that many common binary
** operations are actually modeled as ShortcutExpr to enable method based
** operator overloading.
**
class BinaryExpr : Expr
{
  new make(Expr lhs, Token opToken, Expr rhs)
    : super(lhs.location, opToken.toExprId)
  {
    this.lhs = lhs
    this.opToken = opToken
    this.rhs = rhs
  }

  new makeAssign(Expr lhs, Expr rhs, Bool leave := false)
    : this.make(lhs, Token.assign, rhs)
  {
    this.ctype = lhs.ctype
    this.leave = leave
  }

  override Bool isStmt() { return id === ExprId.assign }

  override Void walkChildren(Visitor v)
  {
    lhs = lhs.walk(v)
    rhs = rhs.walk(v)
  }

  override Str serialize()
  {
    if (id === ExprId.assign)
      return "${lhs.serialize}=${rhs.serialize}"
    else
      return super.serialize
  }

  override Str toStr()
  {
    return "($lhs $opToken $rhs)"
  }

  Token opToken      // operator token type (Token.and, etc)
  Expr lhs           // left hand side
  Expr rhs           // right hand side
  MethodVar tempVar  // temp local var to store field assignment leaves

}

**************************************************************************
** CondExpr
**************************************************************************

**
** CondExpr is used for || and && short-circuit boolean conditionals.
**
class CondExpr : Expr
{
  new make(Expr first, Token opToken)
    : super(first.location, opToken.toExprId)
  {
    this.opToken = opToken
    this.operands = [first]
  }

  override Bool isCond() { return true }

  override Void walkChildren(Visitor v)
  {
    operands = walkExprs(v, operands)
  }

  override Str toStr()
  {
    return operands.join(" $opToken ")
  }

  Token opToken      // operator token type (Token.and, etc)
  Expr[] operands    // list of operands

}

**************************************************************************
** NameExpr
**************************************************************************

**
** NameExpr is the base class for an identifier expression which has
** an optional base expression.  NameExpr is the base class for
** UnknownVarExpr and CallExpr which are resolved via CallResolver
**
abstract class NameExpr : Expr
{
  new make(Location location, ExprId id, Expr target, Str name)
    : super(location, id)
  {
    this.target = target
    this.name   = name
  }

  override Void walkChildren(Visitor v)
  {
    target = walkExpr(v, target)
  }

  override Str toStr()
  {
    if (target != null)
      return "${target}.${name}"
    else
      return name
  }

  Expr target   // base target expression or null
  Str name      // name of variable (local/field/method)
}

**************************************************************************
** UnknownVarExpr
**************************************************************************

**
** UnknownVarExpr is a place holder in the AST for a variable until
** we can figure out what it references: local or slot.  We also use
** this class for storage operators before they are resolved to a field.
**
class UnknownVarExpr : NameExpr
{
  new make(Location location, Expr target, Str name)
    : super(location, ExprId.unknownVar, target, name)
  {
  }

  new makeStorage(Location location, Expr target, Str name)
    : super.make(location, ExprId.storage, target, name)
  {
  }
}

**************************************************************************
** CallExpr
**************************************************************************

**
** CallExpr is a method call.
**
class CallExpr : NameExpr
{
  new make(Location location, Expr target := null, Str name := null, ExprId id := ExprId.call)
    : super(location, id, target, name)
  {
    args = Expr[,]
    isDynamic = false
    isCtorChain = false
  }

  new makeWithMethod(Location location, Expr target, CMethod method, Expr[] args := null)
    : this.make(location, target, method.name, ExprId.call)
  {
    this.method = method

    if (args != null)
      this.args = args

    if (method.isCtor)
      ctype = method.parent
    else
      ctype = method.returnType
  }

  override Str toStr()
  {
    return toCallStr(true)
  }

  override Bool isStmt() { return true }

  override Void walkChildren(Visitor v)
  {
    target = walkExpr(v, target)
    args = walkExprs(v, args)
  }

  override Void print(AstWriter out)
  {
    out.w(toCallStr(false))
    if (args.size > 0 && args.last is ClosureExpr)
      args.last.print(out)
  }

  private Str toCallStr(Bool isToStr)
  {
    s := StrBuf.make

    if (target != null)
      s.add(target).add(isDynamic ? "->" : ".")
    else if (method != null && (method.isStatic || method.isCtor))
      s.add(method.parent.qname).add(".")

    s.add(name).add("(")
    if (args.last is ClosureExpr)
    {
      s.add(args[0..-2].join(", ")).add(") ");
      if (isToStr) s.add(args.last)
    }
    else
    {
      s.add(args.join(", ")).add(")")
    }
    return s.toStr
  }

  Expr[] args         // Expr[] arguments to pass
  Bool isDynamic      // true if this is a -> dynamic call
  Bool isCtorChain    // true if this is MethodDef.ctorChain call
  CMethod method      // resolved method
}

**************************************************************************
** ShortcutExpr
**************************************************************************

**
** ShortcutExpr is used for operator expressions which are a shortcut
** to a method call:
**   a + b    => a.plus(b)
**   a - b    => a.minus(b)
**   a * b    => a.star(b)
**   a / b    => a.slash(b)
**   a % b    => a.percent(b)
**   a[b]     => a.get(b)
**   a[b] = c => a.set(b, c)
**   a[b]     => a.slice(b) if b is Range
**   a[b] = c => a.splice(b, c) if b is Range
**   a << b   => a.lshift(b)
**   a >> b   => a.rshift(b)
**   a & b    => a.amp(b)
**   a | b    => a.pipe(b)
**   a ^ b    => a.caret(b)
**   ~a       => a.tilde()
**   -a       => a.negate()
**   ++a, a++ => a.increment()
**   --a, a-- => a.decrement()
**   a == b   => a.equals(b)
**   a != b   => ! a.equals(b)
**   a <=>    => a.compare(b)
**   a > b    => a.compare(b) > 0
**   a >= b   => a.compare(b) >= 0
**   a < b    => a.compare(b) < 0
**   a <= b   => a.compare(b) <= 0
**
class ShortcutExpr : CallExpr
{
  new makeUnary(Location loc, Token opToken, Expr operand)
    : super.make(loc, null, null, ExprId.shortcut)
  {
    this.op      = opToken.toShortcutOp(1)
    this.opToken = opToken
    this.name    = op.methodName
    this.target  = operand
  }

  new makeBinary(Expr lhs, Token opToken, Expr rhs)
    : super.make(lhs.location, null, null, ExprId.shortcut)
  {
    this.op      = opToken.toShortcutOp(2)
    this.opToken = opToken
    this.name    = op.methodName
    this.target  = lhs
    this.args.add(rhs)
  }

  new makeGet(Location loc, Expr target, Expr index)
    : super.make(loc, null, null, ExprId.shortcut)
  {
    this.op      = ShortcutOp.get
    this.opToken = Token.lbracket
    this.name    = op.methodName
    this.target  = target
    this.args.add(index)
  }

  new makeFrom(ShortcutExpr from)
    : super.make(from.location, null, null, ExprId.shortcut)
  {
    this.op      = from.op
    this.opToken = from.opToken
    this.name    = from.name
    this.target  = from.target
    this.args    = from.args
    this.isPostfixLeave = from.isPostfixLeave
  }

  override Bool assignRequiresTempVar()
  {
    return isAssignable
  }

  override Bool isAssignable()
  {
    return op == ShortcutOp.get
  }

  override Bool isStmt() { return isAssign || op === ShortcutOp.set }

  Bool isAssign() { return opToken.isAssign }

  Bool isStrConcat()
  {
    return opToken == Token.plus && (target.ctype.isStr || args.first.ctype.isStr)
  }

  override Str toStr()
  {
    if (op == ShortcutOp.get) return "${target}[$args.first]"
    if (op == ShortcutOp.increment) return isPostfixLeave ? "${target}++" : "++${target}"
    if (op == ShortcutOp.decrement) return isPostfixLeave ? "${target}--" : "--${target}"
    if (isAssign) return "${target} ${opToken} ${args.first}"
    if (op.degree == 1) return "${opToken}${target}"
    if (op.degree == 2) return "(${target} ${opToken} ${args.first})"
    return super.toStr
  }

  override Void print(AstWriter out)
  {
    out.w(toStr())
  }

  ShortcutOp op
  Token opToken
  Bool isPostfixLeave := false  // x++ or x-- (must have Expr.leave set too)
  MethodVar tempVar    // temp local var to store += to field/indexed
}

**
** IndexedAssignExpr is a subclass of ShortcutExpr used
** in situations like x[y] += z where we need keep of two
** extra scratch variables and the get's matching set method.
** Note this class models the top x[y] += z, NOT the get target
** which is x[y].
**
** In this example, IndexedAssignExpr shortcuts Int.plus and
** its target shortcuts List.get:
**   x := [2]
**   x[0] += 3
**
class IndexedAssignExpr : ShortcutExpr
{
  new makeFrom(ShortcutExpr from)
    : super.makeFrom(from)
  {
  }

  MethodVar scratchA
  MethodVar scratchB
  CMethod setMethod
}

**************************************************************************
** FieldExpr
**************************************************************************

**
** FieldExpr is used for a field variable access.
**
class FieldExpr : Expr
{
  new make(Location location, Expr target := null, CField field := null, Bool useAccessor := true)
    : super(location, ExprId.field)
  {
    this.target = target
    this.useAccessor = useAccessor
    if (field != null)
    {
      this.name  = field.name
      this.field = field
      this.ctype = field.fieldType
    }
  }

  override Bool isAssignable() { return true }

  override Bool assignRequiresTempVar() { return !field.isStatic }

  override Int asTableSwitchCase()
  {
    // TODO - this should probably be tightened up if we switch to const
    if (field.isStatic && field.parent.isEnum && ctype.isEnum)
    {
      switch (field.type)
      {
        case ReflectField.type:
          ifield := field as ReflectField
          return ((Enum)ifield.f.get).ordinal
        case FieldDef.type:
          fieldDef := field as FieldDef
          enumDef := fieldDef.parentDef.enumDef(field.name)
          if (enumDef != null) return enumDef.ordinal
        default:
          throw CompilerErr.make("Invalid field for tableswitch: " + field.type, location)
      }
    }
    return null
  }

  override Void walkChildren(Visitor v)
  {
    target = walkExpr(v, target)
  }

  override Str serialize()
  {
    if (target != null && target.id === ExprId.withBase)
      return "$name"

    if (field.isStatic)
    {
      if (field.parent.isFloat)
      {
        switch (name)
        {
          case "nan":    return "sys::Float(\"NaN\")"
          case "posInf": return "sys::Float(\"INF\")"
          case "negInf": return "sys::Float(\"-INF\")"
        }
      }

      if (field.isEnum)
        return "${field.parent.qname}(\"$name\")"
    }

    return super.serialize
  }

  override Str toStr()
  {
    s := StrBuf.make
    if (target != null) s.add(target).add(".");
    if (!useAccessor) s.add("@")
    s.add(name)
    return s.toStr
  }

  Expr target         // target of field access
  Str name            // unresolved name of field to get/set
  CField field        // resolved field
  Bool useAccessor    // false if access using '@' storage operator
}

**************************************************************************
** LocalVarExpr
**************************************************************************

**
** LocalVarExpr is used to access a local variable stored in a register.
**
class LocalVarExpr : Expr
{
  new make(Location location, MethodVar var, ExprId id := ExprId.localVar)
    : super(location, id)
  {
    if (var != null)
    {
      this.var = var
      this.ctype = var.ctype
    }
  }

  override Bool isAssignable() { return true }

  override Bool assignRequiresTempVar() { return var.usedInClosure }

  virtual Int register() { return var.register }

  override Str toStr()
  {
    if (var == null) return "???"
    return var.name
  }

  MethodVar var   // bound variable

  // used to mark a local var access that should not be
  // pulled out into cvars, even if var.usedInClosure is true
  Bool noRemapToCvars := false
}

**************************************************************************
** ThisExpr
**************************************************************************

**
** ThisExpr models the "this" keyword to access the implicit this
** local variable always stored in register zero.
**
class ThisExpr : LocalVarExpr
{
  new make(Location location, CType ctype := null)
    : super(location, null, ExprId.thisExpr)
  {
    this.ctype = ctype
  }

  override Bool isAssignable() { return false }

  override Int register() { return 0 }

  override Str toStr()
  {
    return "this"
  }
}

**************************************************************************
** SuperExpr
**************************************************************************

**
** SuperExpr is used to access super class slots.  It always references
** the implicit this local variable stored in register zero, but the
** super class's slot definitions.
**
class SuperExpr : LocalVarExpr
{
  new make(Location location, CType explicitType := null)
    : super(location, null, ExprId.superExpr)
  {
    this.explicitType = explicitType
  }

  override Bool isAssignable() { return false }

  override Int register() { return 0 }

  override Str toStr()
  {
    if (explicitType != null)
      return "${explicitType}.super"
    else
      return "super"
  }

  CType explicitType   // if "named super"
}

**************************************************************************
** StaticTargetExpr
**************************************************************************

**
** StaticTargetExpr wraps a type reference as an Expr for use as
** a target in a static field access or method call
**
class StaticTargetExpr : Expr
{
  new make(Location location, CType ctype)
    : super(location, ExprId.staticTarget)
  {
    this.ctype = ctype
  }

  override Str toStr()
  {
    return ctype.signature
  }
}

**************************************************************************
** TypeCheckExpr
**************************************************************************

**
** TypeCheckExpr is an expression which is composed of an arbitrary
** expression and a type - is, as, & casts
**
class TypeCheckExpr : Expr
{
  new make(Location location, ExprId id, Expr target, CType check)
    : super(location, id)
  {
    this.target = target
    this.check  = check
    this.ctype  = check
  }

  new cast(Expr target, CType to)
    : super.make(target.location, ExprId.cast)
  {
    this.target = target
    this.check  = to
    this.ctype  = to
  }

  override Void walkChildren(Visitor v)
  {
    target = walkExpr(v, target)
  }

  override Str toStr()
  {
    switch (id)
    {
      case ExprId.isExpr: return "($target is $check)"
      case ExprId.asExpr: return "($target as $check)"
      case ExprId.cast:   return "($check)$target"
      default:            throw Err.make(id.toStr)
    }
  }

  Expr target
  CType check
}

**************************************************************************
** TernaryExpr
**************************************************************************

**
** TernaryExpr is used for the ternary expression <cond> ? <true> : <false>
**
class TernaryExpr : Expr
{
  new make(Expr condition, Expr trueExpr, Expr falseExpr)
    : super(condition.location, ExprId.ternary)
  {
    this.condition = condition
    this.trueExpr  = trueExpr
    this.falseExpr = falseExpr
  }

  override Void walkChildren(Visitor v)
  {
    condition = condition.walk(v)
    trueExpr  = trueExpr.walk(v)
    falseExpr = falseExpr.walk(v)
  }

  override Str toStr()
  {
    return "$condition ? $trueExpr : $falseExpr"
  }

  Expr condition     // boolean test
  Expr trueExpr      // result of expression if condition is true
  Expr falseExpr     // result of expression if condition is false
}

**************************************************************************
** WithBlockExpr
**************************************************************************

**
** WithBlockExpr is used enclose a series of sub-expressions
** against a base expression:
**   base { a = b; c() }
** Translates to:
**   temp := base
**   temp.a = b
**   temp.c()
**
class WithBlockExpr : Expr
{
  new make(Expr base)
    : super(base.location, ExprId.withBlock)
  {
    this.base = base
    this.subs = Expr[,]
  }

  override Void walkChildren(Visitor v)
  {
    base  = base.walk(v)
    ctype = base.ctype
    subs  = walkExprs(v, subs)
  }

  override Bool isStmt() { return true }

  Bool isCtorWithBlock()
  {
    return base.id == ExprId.call && base->method->isCtor
  }

  override Str serialize()
  {
    if (base.id != ExprId.call || base->method->isCtor != true ||
        base->name != "make" || base->args->size != 0)
      return super.serialize

    s := StrBuf.make
    s.add("${base->target}{")
    subs.each |Expr sub| { s.add("$sub.serialize;") }
    s.add("}")
    return s.toStr
  }

  override Str toStr()
  {
    s := StrBuf.make
    s.add("$base { ")
    subs.each |Expr sub| { s.add("$sub; ") }
    s.add("}")
    return s.toStr
  }

  Expr base      // base expression
  Expr[] subs    // sub-expressions applied to base
}

**
** WithBaseExpr is a place holder used as the target of
** sub-expressions within a with block typed to the with base.
**
class WithBaseExpr : Expr
{
  new make(WithBlockExpr withBlock)
    : super(withBlock.location, ExprId.withBase)
  {
    this.withBlock = withBlock
  }

  Bool isCtorWithBlock()
  {
    return withBlock.isCtorWithBlock
  }

  override Str toStr()
  {
    return "with"
  }

  override Void walkChildren(Visitor v)
  {
    // this node never has children, but whenever the
    // tree is walked update its ctype from the withBlock
    ctype = withBlock.ctype
  }

  WithBlockExpr withBlock
}

**************************************************************************
** CurryExpr
**************************************************************************

**
** CurryExpr is used to "curry" a function into another
** function thru partially evaluation
**
class CurryExpr : Expr
{
  new make(Location location, Expr operand)
    : super(location, ExprId.curry)
  {
    this.operand = operand
  }

  override Void walkChildren(Visitor v)
  {
    operand = operand.walk(v)
  }

  override Str toStr()
  {
    return "&$operand"
  }

  Expr operand
}

**************************************************************************
** ClosureExpr
**************************************************************************

**
** ClosureExpr is an "inlined anonymous method" which closes over it's
** lexical scope.  ClosureExpr is placed into the AST by the parser
** with the code field containing the method implementation.  In
** InitClosures we remap a ClosureExpr to an anonymous class TypeDef
** which extends Func.  The function implementation is moved to the
** anonymous class's doCall() method.  However we leave ClosureExpr
** in the AST in it's original location with a substitute expression.
** The substitute expr just creates an instance of the anonymous class.
** But by leaving the ClosureExpr in the tree, we can keep track of
** the original lexical scope of the closure.
**
class ClosureExpr : Expr
{
  new make(Location location, TypeDef enclosingType,
           MethodDef enclosingMethod, ClosureExpr enclosingClosure,
           FuncType signature, Str name)
    : super(location, ExprId.closure)
  {
    this.ctype            = signature
    this.enclosingType    = enclosingType
    this.enclosingMethod  = enclosingMethod
    this.enclosingClosure = enclosingClosure
    this.signature        = signature
    this.name             = name
    this.code             = code
    this.usesCvars        = false
  }

  readonly CField outerThisField
  {
    get
    {
      if (@outerThisField == null)
      {
        if (enclosingMethod.isStatic) throw Err.make("Internal error: $location.toLocationStr")
        @outerThisField = ClosureVars.makeOuterThisField(this)
      }
      return @outerThisField
    }
  }

  override Str toStr()
  {
    return "$signature { ... }"
  }

  override Void print(AstWriter out)
  {
    out.w(signature.toStr)
    if (substitute != null)
    {
      out.w(" { substitute: ")
      substitute.print(out)
      out.w(" }").nl
    }
    else
    {
      out.nl
      code.print(out)
    }
  }

  // Parse
  TypeDef enclosingType         // enclosing class
  MethodDef enclosingMethod     // enclosing method
  ClosureExpr enclosingClosure  // if nested closure
  FuncType signature            // parameter and return signature
  Block code                    // moved into a MethodDef in InitClosures
  Str name                      // anonymous class name

  // InitClosures
  CallExpr substitute           // expression to substitute during assembly
  TypeDef cls                   // anonymous class which implements the closure
  MethodDef doCall              // anonymous class's doCall() with code

  // ResolveExpr
  Str:MethodVar enclosingLocals // locals in scope
  Bool usesCvars                // does this guy use vars from outer scope
}

**************************************************************************
** ExprId
**************************************************************************

**
** ExprId uniquely identifies the type of expr
**
enum ExprId
{
  nullLiteral,      // LiteralExpr
  trueLiteral,
  falseLiteral,
  intLiteral,
  floatLiteral,
  strLiteral,
  durationLiteral,
  uriLiteral,
  typeLiteral,
  rangeLiteral,     // RangeLiteralExpr
  listLiteral,      // ListLiteralExpr
  mapLiteral,       // MapLiteralExpr
  simpleLiteral,    // SimpleLiteralExpr
  boolNot,          // UnaryExpr
  cmpNull,
  cmpNotNull,
  assign,           // BinaryExpr
  same,
  notSame,
  boolOr,           // CondExpr
  boolAnd,
  isExpr,           // TypeCheckExpr
  asExpr,
  cast,
  call,             // CallExpr
  shortcut,         // ShortcutExpr (has ShortcutOp)
  field,            // FieldExpr
  localVar,         // LocalVarExpr
  thisExpr,         // ThisExpr
  superExpr,        // SuperExpr
  staticTarget,     // StaticTargetExpr
  unknownVar,       // UnknownVarExpr
  storage,
  ternary,          // TernaryExpr
  withBlock,        // WithBlockExpr
  withBase,         // WithBaseExpr
  curry,            // CurryExpr
  closure           // ClosureExpr
}

**************************************************************************
** ShortcutId
**************************************************************************

**
** ShortcutOp is a sub-id for ExprId.shortcut which identifies the
** an shortuct operation and it's method call
**
enum ShortcutOp
{
  plus(2),
  minus(2),
  star(2),
  slash(2),
  percent(2),
  lshift(2),
  rshift(2),
  amp(2),
  pipe(2),
  caret(2),
  tilde(1),
  negate(1),
  increment(1),
  decrement(1),
  eq(2, "equals"),
  cmp(2, "compare"),
  get(2),
  set(2),
  slice(2)

  private new make(Int degree, Str methodName := null)
  {
    this.degree = degree
    this.methodName = methodName == null ? name : methodName
  }

  const Int degree
  const Str methodName
}