//
// 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 }
**
** Get this expression's type as a string for error reporting.
**
Str toTypeStr()
{
if (id == ExprId.nullLiteral) return "null"
return ctype.toStr
}
**
** 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.
** If you override this to true, then you must make sure
** the expr is popped in CodeAsm.
**
virtual Bool isStmt()
{
return false
}
**
** Was this expression generated by the compiler (not necessarily
** everything auto-generated has this flag true, but we set in
** cases where error checking needs to be handled special)
**
virtual Bool synthetic()
{
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)
{
hasNull := false
exprs = exprs.exclude |Expr e->Bool|
{
if (e.id !== ExprId.nullLiteral) return false
hasNull = true
return true
}
t := CType.common(ns, ctypes(exprs))
if (hasNull) t = t.toNullable
return t
}
**
** 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("'$id' not serializable", location)
}
**
** Set this expression to not be left on the stack.
**
Expr noLeave()
{
// if the expression is prefixed with a synthetic cast by
// CallResolver, it is unnecessary at the top level and must
// be stripped
result := this
if (result.id === ExprId.coerce)
{
coerce := (TypeCheckExpr)result
if (coerce.synthetic) result = coerce.target
}
result.leave = false
return result
}
//////////////////////////////////////////////////////////////////////////
// Doc
//////////////////////////////////////////////////////////////////////////
**
** Get this expression as a string suitable for documentation.
**
Str toDocStr()
{
// not perfect, but better than what we had previously which
// was nothing; we might want to grab the actual text from the
// actual source file - but with the current design we've freed
// the buffer by the time the tokens are passed to the parser
try
{
// remove extra parens with binary ops
s := toStr
if (s[0] == '(' && s[-1] == ')') s = s[1..-2]
if (s.contains("{"))
{
// with blocks
s = s.replace("with.", "")
}
else
{
// hide implicit assignments
if (s.contains("=")) s = s[s.index("=")+1..-1].trim
}
// remove extra parens with binary ops
if (s[0] == '(' && s[-1] == ')') s = s[1..-2]
// hide storage operator
s = s.replace(".@", ".")
// hide safe nav construction
s = s.replace(".?(", "(")
// use unqualified names
while (true)
{
qcolon := s.index("::")
if (qcolon == null) break
i := qcolon-1
for (; i>=0; --i) if (!s[i].isAlphaNum && s[i] != '_') break
s = (i < 0) ? s[qcolon+2..-1] : s[0..i] + s[qcolon+2..-1]
}
if (s.size > 40) s = "..."
return s
}
catch (Err e)
{
e.trace
return toStr
}
}
//////////////////////////////////////////////////////////////////////////
// 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 { protected set } // 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#:
return val == true ?
make(loc, ExprId.trueLiteral, ns.boolType, true) :
make(loc, ExprId.falseLiteral, ns.boolType, false)
case Str#:
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
if (val == null && !ctype.isNullable)
throw Err("null literal must typed as nullable!")
}
new makeNullLiteral(Location location, CNamespace ns)
: this.make(location, ExprId.nullLiteral, ns.objType.toNullable, null)
{
}
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 + "f"
case ExprId.decimalLiteral: return val.toStr + "d"
case ExprId.strLiteral: return val.toStr.toCode
case ExprId.uriLiteral: return val.toStr.toCode('`')
case ExprId.typeLiteral: return "${val->signature}#"
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}#"
case ExprId.uriLiteral: return "`$val`"
default: return val.toStr
}
}
Obj? val // Bool, Int, Float, Str (for Str/Uri), Duration, CType, or null
}
**************************************************************************
** LiteralExpr
**************************************************************************
**
** SlotLiteralExpr
**
class SlotLiteralExpr : Expr
{
new make(Location loc, CType parent, Str name)
: super(loc, ExprId.slotLiteral)
{
this.parent = parent
this.name = name
}
override Str serialize()
{
return "$parent.signature#name"
}
override Str toStr()
{
return "$parent.signature#name"
}
CType parent
Str name
CSlot slot
}
**************************************************************************
** 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[,]
}
**************************************************************************
** 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
this.isSafe = false
}
override Void walkChildren(Visitor v)
{
target = walkExpr(v, target)
}
override Str toStr()
{
if (target != null)
return target.toStr + (isSafe ? "?." : ".") + name
else
return name
}
Expr? target // base target expression or null
Str? name // name of variable (local/field/method)
Bool isSafe // if ?. operator
}
**************************************************************************
** 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
isSafe = 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 }
virtual Bool isCompare() { return false }
override Void walkChildren(Visitor v)
{
target = walkExpr(v, target)
args = walkExprs(v, args)
}
override Str serialize()
{
// only serialize a true Type("xx") expr which maps to Type.fromStr
if (id != ExprId.construction || method.name != "fromStr")
return super.serialize
argSer := args.join(",") |Expr e->Str| { return e.serialize }
return "$target($argSer)"
}
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(isSafe ? "?" : "").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
Bool noParens // was this call accessed without parens
CMethod? method // resolved method
override Bool synthetic := false
}
**************************************************************************
** 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.mult(b)
** a / b => a.div(b)
** a % b => a.mod(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.and(b)
** a | b => a.or(b)
** a ^ b => a.xor(b)
** ~a => a.inverse()
** -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 isCompare()
{
return op === ShortcutOp.eq || op === ShortcutOp.cmp
}
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 : NameExpr
{
new make(Location location, Expr? target := null, CField? field := null, Bool useAccessor := true)
: super(location, ExprId.field, target, null)
{
this.useAccessor = useAccessor
this.isSafe = false
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#:
ifield := field as ReflectField
return ((Enum)ifield.f.get).ordinal
case FieldDef#:
fieldDef := field as FieldDef
enumDef := fieldDef.parentDef.enumDef(field.name)
if (enumDef != null) return enumDef.ordinal
default:
throw CompilerErr("Invalid field for tableswitch: " + field.type, location)
}
}
return null
}
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
}
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, coerce
**
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 coerce(Expr target, CType to)
: super.make(target.location, ExprId.coerce)
{
if (to.isGenericParameter) to = to.ns.objType // TODO: not sure about this
this.target = target
this.from = target.ctype
this.check = to
this.ctype = to
this.synthetic = true
}
override Void walkChildren(Visitor v)
{
target = walkExpr(v, target)
}
override Bool isStmt()
{
return id === ExprId.coerce && target.isStmt
}
override Str serialize()
{
if (id == ExprId.coerce)
return target.serialize
else
return super.serialize
}
Str opStr()
{
switch (id)
{
case ExprId.isExpr: return "is"
case ExprId.isnotExpr: return "isnot"
case ExprId.asExpr: return "as"
default: throw Err.make(id.toStr)
}
}
override Str toStr()
{
switch (id)
{
case ExprId.isExpr: return "($target is $check)"
case ExprId.isnotExpr: return "($target isnot $check)"
case ExprId.asExpr: return "($target as $check)"
case ExprId.coerce: return "(($check)$target)"
default: throw Err.make(id.toStr)
}
}
** From type if coerce
CType? from { get { return @from ?: target.ctype } }
Expr target
CType check // to type if coerce
override Bool synthetic := false
}
**************************************************************************
** 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 = WithSubExpr[,]
}
override Void walkChildren(Visitor v)
{
base = base.walk(v)
ctype = base.ctype
subs = (WithSubExpr[])walkExprs(v, subs)
}
override Bool isStmt() { return true }
Bool isCtorWithBlock()
{
return (base.id == ExprId.call || base.id == ExprId.construction) && 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
WithSubExpr[] subs // sub-expressions applied to base
}
**
** WithSubExpr wraps each sub-expr within a with-block.
**
class WithSubExpr : Expr
{
new make(WithBlockExpr withBlock, Expr expr)
: super(expr.location, ExprId.withSub)
{
this.withBlock = withBlock
this.expr = expr
}
override Void walkChildren(Visitor v)
{
expr = expr.walk(v)
}
override Bool isStmt() { return expr.isStmt }
override Str serialize() { return expr.serialize }
override Str toStr() { return expr.toStr }
WithBlockExpr withBlock
Expr expr
CMethod? add // if 'with.add(expr)'
}
**
** 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, WithSubExpr? withSub := null)
: super(withBlock.location, ExprId.withBase)
{
this.ctype = withBlock.ctype
this.withBlock = withBlock
this.withSub = withSub
}
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
WithSubExpr? withSub
}
**************************************************************************
** 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,
SlotDef enclosingSlot, ClosureExpr enclosingClosure,
FuncType signature, Str name)
: super(location, ExprId.closure)
{
this.ctype = signature
this.enclosingType = enclosingType
this.enclosingSlot = enclosingSlot
this.enclosingClosure = enclosingClosure
this.signature = signature
this.name = name
this.code = code
this.usesCvars = false
}
once CField outerThisField()
{
if (enclosingSlot.isStatic) throw Err.make("Internal error: $location.toLocationStr")
return ClosureVars.makeOuterThisField(this)
}
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
SlotDef enclosingSlot // enclosing method or field initializer
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,
decimalLiteral,
strLiteral,
durationLiteral,
uriLiteral,
typeLiteral,
slotLiteral, // SlotLiteralExpr
rangeLiteral, // RangeLiteralExpr
listLiteral, // ListLiteralExpr
mapLiteral, // MapLiteralExpr
boolNot, // UnaryExpr
cmpNull,
cmpNotNull,
elvis,
assign, // BinaryExpr
same,
notSame,
boolOr, // CondExpr
boolAnd,
isExpr, // TypeCheckExpr
isnotExpr,
asExpr,
coerce,
call, // CallExpr
construction,
shortcut, // ShortcutExpr (has ShortcutOp)
field, // FieldExpr
localVar, // LocalVarExpr
thisExpr, // ThisExpr
superExpr, // SuperExpr
staticTarget, // StaticTargetExpr
unknownVar, // UnknownVarExpr
storage,
ternary, // TernaryExpr
withBlock, // WithBlockExpr
withSub, // WithSubExpr
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),
mult(2),
div(2),
mod(2),
lshift(2),
rshift(2),
and(2),
or(2),
xor(2),
inverse(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
}