//
// 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(Loc loc, ExprId id)
: super(loc)
{
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() { null }
**
** Get this expression's type as a string for error reporting.
**
Str toTypeStr()
{
if (id == ExprId.nullLiteral) return "null"
return ctype.toStr
}
**
** If this expression performs assignment, then return
** the target of that assignment. Otherwise return null.
**
virtual Obj? assignTarget() { null }
**
** Return if this expression can be used as the
** left hand side of an assignment expression.
**
virtual Bool isAssignable() { false }
**
** Is this a boolean conditional (boolOr/boolAnd)
**
virtual Bool isCond() { 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() { 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() { false }
**
** If this an assignment expression, then return the
** result of calling the given function with the LHS.
** Otherwise return false.
**
virtual Bool isDefiniteAssign(|Expr lhs->Bool| f) { false }
**
** Return if this expression is guaranteed to sometimes
** return a null result (safe invoke, as, etc)
**
virtual Bool isAlwaysNullable() { false }
**
** Assignments to instance fields require a temporary local variable.
**
virtual Bool assignRequiresTempVar() { false }
**
** Return if this expression represents the same variable or
** field as that. This is used for self assignment checks.
**
virtual Bool sameVarAs(Expr that) { false }
**
** Map the list of expressions into their list of types
**
static CType[] ctypes(Expr[] exprs)
{
return exprs.map |Expr e->CType| { 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(this)
}
**
** Return this expression as serialization text or
** throw exception if not serializable.
**
virtual Str serialize()
{
throw CompilerErr("'$id' not serializable", loc)
}
**
** Make an Expr which will serialize the given literal.
**
static Expr makeForLiteral(Loc loc, CNamespace ns, Obj val)
{
switch (val.typeof)
{
case Bool#:
return val == true ?
LiteralExpr(loc, ExprId.trueLiteral, ns.boolType, true) :
LiteralExpr(loc, ExprId.falseLiteral, ns.boolType, false)
case Str#:
return LiteralExpr(loc, ExprId.strLiteral, ns.strType, val)
case DateTime#:
return CallExpr(loc, null, "fromStr", ExprId.construction)
{
method = ns.resolveSlot("sys::DateTime.fromStr")
ctype = method.parent
args = [makeForLiteral(loc, ns, val.toStr)]
}
default:
throw Err("Unsupported literal type $val.typeof")
}
}
**
** 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.
** This string must not contain a newline or it will break the
** DocApiParser.
**
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
{
// literals
if (this is LiteralExpr)
{
s := serialize
if (s.size > 40) s = "..."
return s
}
// if this is cast, return base
if (this is TypeCheckExpr)
return ((TypeCheckExpr)this).target.toDocStr
// if we access an internal slot then don't expose in public docs
CSlot? slot := null
if (this is CallExpr) slot = ((CallExpr)this).method
else if (this is FieldExpr) slot = ((FieldExpr)this).field
if (slot != null && (slot.isPrivate || slot.isInternal)) return null
// remove extra parens with binary ops
s := toStr
if (s[0] == '(' && s[-1] == ')') s = s[1..-2]
// 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.endsWith("()")) 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
//////////////////////////////////////////////////////////////////////////
const 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
{
new make(Loc loc, ExprId id, CType ctype, Obj? val)
: super(loc, id)
{
this.ctype = ctype
this.val = val
if (val == null && !ctype.isNullable)
throw Err("null literal must typed as nullable!")
}
new makeNull(Loc loc, CNamespace ns)
: this.make(loc, ExprId.nullLiteral, ns.objType.toNullable, null) {}
new makeTrue(Loc loc, CNamespace ns)
: this.make(loc, ExprId.trueLiteral, ns.boolType, true) {}
new makeFalse(Loc loc, CNamespace ns)
: this.make(loc, ExprId.falseLiteral, ns.boolType, false) {}
new makeStr(Loc loc, CNamespace ns, Str val)
: this.make(loc, ExprId.strLiteral, ns.strType, val) {}
static LiteralExpr makeDefaultLiteral(Loc loc, CNamespace ns, CType ctype)
{
if (!ctype.isNullable())
{
if (ctype.isBool()) return make(loc, ExprId.falseLiteral, ctype, false)
if (ctype.isInt()) return make(loc, ExprId.intLiteral, ctype, 0)
if (ctype.isFloat()) return make(loc, ExprId.floatLiteral, ctype, 0f)
}
return makeNull(loc, ns)
}
override Bool isAlwaysNullable() { id === ExprId.nullLiteral }
override Int? asTableSwitchCase()
{
return val as Int
}
override Str serialize()
{
switch (id)
{
case ExprId.nullLiteral: return "null"
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
}
**************************************************************************
** LocaleLiteralExpr
**************************************************************************
**
** LocaleLiteralExpr: podName::key=defVal
**
class LocaleLiteralExpr: Expr
{
new make(Loc loc, Str pattern)
: super(loc, ExprId.localeLiteral)
{
this.pattern = pattern
this.key = pattern
eq := pattern.index("=")
if (eq != null)
{
this.key = pattern[0..<eq]
this.def = pattern[eq+1..-1]
}
colons := key.index("::")
if (colons != null)
{
this.podName = key[0..<colons]
this.key = key[colons+2..-1]
}
}
override Str toStr() { "<${pattern}>" }
Str pattern
Str key
Str? podName
Str? def
}
**************************************************************************
** SlotLiteralExpr
**************************************************************************
**
** SlotLiteralExpr
**
class SlotLiteralExpr : Expr
{
new make(Loc loc, CType parent, Str name)
: super(loc, ExprId.slotLiteral)
{
this.parent = parent
this.name = name
}
override Str serialize() { "$parent.signature#${name}" }
override Str toStr() { "$parent.signature#${name}" }
CType parent
Str name
CSlot? slot
}
**************************************************************************
** RangeLiteralExpr
**************************************************************************
**
** RangeLiteralExpr creates a Range instance
**
class RangeLiteralExpr : Expr
{
new make(Loc loc, CType ctype, Expr start, Expr end, Bool exclusive)
: super(loc, ExprId.rangeLiteral)
{
this.ctype = ctype
this.start = start
this.end = end
this.exclusive = exclusive
}
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(Loc loc, ListType? explicitType := null)
: super(loc, ExprId.listLiteral)
{
this.explicitType = explicitType
}
new makeFor(Loc loc, CType ctype, Expr[] vals)
: super.make(loc, 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| { e.serialize }
}
override Str toStr()
{
return format |Expr e->Str| { 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(Loc loc, MapType? explicitType := null)
: super(loc, 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| { e.serialize }
}
override Str toStr()
{
return format |Expr e->Str| { 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(Loc loc, ExprId id, Token opToken, Expr operand)
: super(loc, 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.loc, 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 Obj? assignTarget() { id === ExprId.assign ? lhs : null }
override Bool isStmt() { id === ExprId.assign }
override Bool isDefiniteAssign(|Expr lhs->Bool| f)
{
if (id === ExprId.assign && f(lhs)) return true
return rhs.isDefiniteAssign(f)
}
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.loc, opToken.toExprId)
{
this.opToken = opToken
this.operands = [first]
}
override Bool isCond() { 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(Loc loc, ExprId id, Expr? target, Str? name)
: super(loc, id)
{
this.target = target
this.name = name
this.isSafe = false
}
override Bool isAlwaysNullable() { isSafe }
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(Loc loc, Expr? target, Str name, ExprId id := ExprId.unknownVar)
: super(loc, id, target, name)
{
}
}
**************************************************************************
** CallExpr
**************************************************************************
**
** CallExpr is a method call.
**
class CallExpr : NameExpr
{
new make(Loc loc, Expr? target := null, Str? name := null, ExprId id := ExprId.call)
: super(loc, id, target, name)
{
args = Expr[,]
isDynamic = false
isSafe = false
isCtorChain = false
}
new makeWithMethod(Loc loc, Expr? target, CMethod method, Expr[]? args := null)
: this.make(loc, target, method.name, ExprId.call)
{
this.method = method
this.ctype = method.isCtor ? method.parent : method.returnType
if (args != null) this.args = args
}
override Str toStr()
{
return toCallStr(true)
}
override Bool isDefiniteAssign(|Expr lhs->Bool| f)
{
if (target != null && target.isDefiniteAssign(f)) return true
return args.any |Expr arg->Bool| { arg.isDefiniteAssign(f) }
}
override Bool isStmt()
{
// stand alone constructor is not a valid stmt
if (method.isCtor) return false
// with block applied to stand alone constructor is not valid stmt
if (method.name == "with" && target is CallExpr && ((CallExpr)target).method.isCtor)
return false
// consider any other call a stand alone stmt
return true
}
virtual Bool isCompare() { 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| { e.serialize }
return "$method.parent($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
Bool isCallOp // was this 'target()' (instead of 'target.name()')
Bool isItAdd // if using comma operator
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 => 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(Loc 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.loc, 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(Loc 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.loc, 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() { isAssignable }
override Obj? assignTarget() { isAssign ? target : null }
override Bool isAssignable() { op === ShortcutOp.get }
override Bool isCompare() { op === ShortcutOp.eq || op === ShortcutOp.cmp }
override Bool isStmt() { isAssign || op === ShortcutOp.set }
Bool isAssign() { opToken.isAssign || opToken.isIncrementOrDecrement }
Bool isStrConcat() { opToken == Token.plus && args.size == 1 && target.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(Loc loc, Expr? target := null, CField? field := null, Bool useAccessor := true)
: super(loc, 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() { true }
override Bool assignRequiresTempVar() { !field.isStatic }
override Bool sameVarAs(Expr that)
{
x := that as FieldExpr
if (x == null) return false
return field == x.field &&
target != null &&
x.target != null &&
target.sameVarAs(x.target)
}
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.typeof)
{
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
case FField#:
ffield := field as FField
attr := ffield.attr(FConst.EnumOrdinalAttr)
if (attr != null) return attr.u2
default:
throw Err("Invalid field for tableswitch: $field.typeof $loc.toLocStr")
}
}
return null
}
override Str serialize()
{
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 (isSafe) s.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(Loc loc, MethodVar? var, ExprId id := ExprId.localVar)
: super(loc, id)
{
if (var != null)
{
this.var = var
this.ctype = var.ctype
}
}
static LocalVarExpr makeNoUnwrap(Loc loc, MethodVar var)
{
self := make(loc, var, ExprId.localVar)
self.unwrap = false
return self
}
override Bool isAssignable() { true }
override Bool assignRequiresTempVar() { var.usedInClosure }
override Bool sameVarAs(Expr that)
{
x := that as LocalVarExpr
if (x == null) return false
if (var?.usedInClosure != x?.var?.usedInClosure) return false
return register == x.register
}
virtual Int register() { var.register }
override Str toStr()
{
if (var == null) return "???"
return var.name
}
MethodVar? var // bound variable
Bool unwrap := true // if hoisted onto heap with wrapper
}
**************************************************************************
** ThisExpr
**************************************************************************
**
** ThisExpr models the "this" keyword to access the implicit this
** local variable always stored in register zero.
**
class ThisExpr : LocalVarExpr
{
new make(Loc loc, CType? ctype := null)
: super(loc, null, ExprId.thisExpr)
{
this.ctype = ctype
}
override Bool isAssignable() { false }
override Int register() { 0 }
override Str toStr() { "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(Loc loc, CType? explicitType := null)
: super(loc, null, ExprId.superExpr)
{
this.explicitType = explicitType
}
override Bool isAssignable() { false }
override Int register() { 0 }
override Str toStr()
{
if (explicitType != null)
return "${explicitType}.super"
else
return "super"
}
CType? explicitType // if "named super"
}
**************************************************************************
** ItExpr
**************************************************************************
**
** ItExpr models the "it" keyword to access the implicit
** target of an it-block.
**
class ItExpr : LocalVarExpr
{
new make(Loc loc, CType? ctype := null)
: super(loc, null, ExprId.itExpr)
{
this.ctype = ctype
}
override Bool isAssignable() { false }
override Int register() { 1 } // Void doCall(Type it)
override Str toStr() { "it" }
}
**************************************************************************
** 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(Loc loc, CType ctype)
: super(loc, ExprId.staticTarget)
{
this.ctype = ctype
}
override Bool sameVarAs(Expr that)
{
that.id === ExprId.staticTarget && ctype == that.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(Loc loc, ExprId id, Expr target, CType check)
: super(loc, id)
{
this.target = target
this.check = check
this.ctype = check
}
new coerce(Expr target, CType to)
: super.make(target.loc, ExprId.coerce)
{
if (to.isGenericParameter) to = to.raw
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 Bool isAlwaysNullable() { id === ExprId.asExpr }
override Bool isDefiniteAssign(|Expr lhs->Bool| f) { target.isDefiniteAssign(f) }
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(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(id.toStr)
}
}
** From type if coerce
CType? from { get { &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.loc, 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
}
**************************************************************************
** ComplexLiteral
**************************************************************************
**
** ComplexLiteral is used to model a serialized complex object
** declared in facets. It is only used in facets, in all other
** code complex literals are parsed as it-block ClosureExprs.
**
class ComplexLiteral : Expr
{
new make(Loc loc, CType ctype)
: super(loc, ExprId.complexLiteral)
{
this.ctype = ctype
this.names = Str[,]
this.vals = Expr[,]
}
override Void walkChildren(Visitor v)
{
vals = walkExprs(v, vals)
}
override Str toStr() { doToStr |expr| { expr.toStr } }
override Str serialize() { doToStr |expr| { expr.serialize } }
Str doToStr(|Expr->Str| f)
{
s := StrBuf()
s.add("$ctype {")
names.each |Str n, Int i| { s.add("$n = ${f(vals[i])};") }
s.add("}")
return s.toStr
}
Str[] names
Expr[] vals
}
**************************************************************************
** 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(Loc loc, TypeDef enclosingType,
SlotDef enclosingSlot, ClosureExpr? enclosingClosure,
FuncType signature, Str name)
: super(loc, ExprId.closure)
{
this.ctype = signature
this.enclosingType = enclosingType
this.enclosingSlot = enclosingSlot
this.enclosingClosure = enclosingClosure
this.signature = signature
this.name = name
}
once CField outerThisField()
{
if (enclosingSlot.isStatic) throw Err("Internal error: $loc.toLocStr")
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)
}
}
override Bool isDefiniteAssign(|Expr lhs->Bool| f)
{
// at this point, we have moved code into doCall method
if (doCall == null) return false
return doCall.code.isDefiniteAssign(f)
}
Expr toWith(Expr target)
{
if (target.ctype != null) setInferredSignature(FuncType.makeItBlock(target.ctype))
x := CallExpr.makeWithMethod(loc, target, enclosingType.ns.objWith, Expr[this])
// TODO: this coercion should be added automatically later in the pipeline
if (target.ctype == null) return x
return TypeCheckExpr.coerce(x, target.ctype)
}
Void setInferredSignature(FuncType t)
{
// bail if we didn't expect an inferred the signature
// or haven't gotten to InitClosures yet
if (!signature.inferredSignature || cls == null) return
// between the explicit signature and the inferred
// signature, take the most specific types; this is where
// we take care of functions with generic parameters like V
t = t.toArity(((FuncType)cls.base).arity)
t = signature.mostSpecific(t)
// sanity check
if (t.usesThis)
throw Err("Inferring signature with un-parameterized this type: $t")
// update my signature and the doCall signature
signature = t
ctype = t
if (doCall != null)
{
// update parameter types
doCall.paramDefs.each |ParamDef p, Int i|
{
if (i < signature.params.size)
p.paramType = signature.params[i]
}
// update return, we might have to translate an single
// expression statement into a return statement
if (doCall.ret.isVoid && !t.ret.isVoid)
{
doCall.ret = t.ret
collapseExprAndReturn(doCall)
collapseExprAndReturn(call)
}
}
// if an itBlock, set type of it
if (isItBlock) itType = t.params.first
// update base type of Func subclass
cls.base = t
}
Void collapseExprAndReturn(MethodDef m)
{
code := m.code.stmts
if (code.size != 2) return
if (code[0].id !== StmtId.expr) return
if (code[1].id !== StmtId.returnStmt) return
if (!((ReturnStmt)code.last).isSynthetic) return
expr := ((ExprStmt)code.first).expr
code.set(0, ReturnStmt.makeSynthetic(expr.loc, expr))
code.removeAt(1)
}
// Parse
TypeDef enclosingType // enclosing class
SlotDef enclosingSlot // enclosing method or field initializer
ClosureExpr? enclosingClosure // if nested closure
FuncType signature // function signature
Block? code // moved into a MethodDef in InitClosures
Str name // anonymous class name
Bool isItBlock // does closure have implicit it scope
// InitClosures
CallExpr? substitute // expression to substitute during assembly
TypeDef? cls // anonymous class which implements the closure
MethodDef? call // anonymous class's call() with code
MethodDef? doCall // anonymous class's doCall() with code
// ResolveExpr
[Str:MethodVar]? enclosingVars // my parent methods vars in scope
Bool setsConst // sets one or more const fields (CheckErrors)
CType? itType // type of implicit it
}
**************************************************************************
** ClosureExpr
**************************************************************************
**
** DslExpr is an embedded Domain Specific Language which
** is parsed by a DslPlugin.
**
class DslExpr : Expr
{
new make(Loc loc, CType anchorType, Loc srcLoc, Str src)
: super(loc, ExprId.dsl)
{
this.anchorType = anchorType
this.src = src
this.srcLoc = srcLoc
}
override Str toStr()
{
return "$anchorType <|$src|>"
}
override Void print(AstWriter out)
{
out.w(toStr)
}
CType anchorType // anchorType <|src|>
Str src // anchorType <|src|>
Loc srcLoc // location of first char of src
Int leadingTabs // number of leading tabs on original Fantom line
Int leadingSpaces // number of leading non-tab chars on original Fantom line
}
**************************************************************************
** ThrowExpr
**************************************************************************
**
** ThrowExpr models throw as an expr versus a statement
** for use inside ternary/elvis operations.
**
class ThrowExpr : Expr
{
new make(Loc loc, Expr exception)
: super(loc, ExprId.throwExpr)
{
this.exception = exception
}
override Void walkChildren(Visitor v)
{
exception = exception.walk(v)
}
override Str toStr() { "throw $exception" }
Expr exception // exception to throw
}
**************************************************************************
** ExprId
**************************************************************************
**
** ExprId uniquely identifies the type of expr
**
enum class ExprId
{
nullLiteral, // LiteralExpr
trueLiteral,
falseLiteral,
intLiteral,
floatLiteral,
decimalLiteral,
strLiteral,
durationLiteral,
uriLiteral,
typeLiteral,
localeLiteral, // LocaleLiteralExpr
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
itExpr, // ItExpr
staticTarget, // StaticTargetExpr
unknownVar, // UnknownVarExpr
storage,
ternary, // TernaryExpr
complexLiteral, // ComplexLiteral
closure, // ClosureExpr
dsl, // DslExpr
throwExpr // ThrowExpr
}
**************************************************************************
** ShortcutId
**************************************************************************
**
** ShortcutOp is a sub-id for ExprId.shortcut which identifies the
** an shortuct operation and it's method call
**
enum class ShortcutOp
{
plus(2, "+"),
minus(2, "-"),
mult(2, "*"),
div(2, "/"),
mod(2, "%"),
negate(1, "-"),
increment(1, "++"),
decrement(1, "--"),
eq(2, "==", "equals"),
cmp(2, "<=>", "compare"),
get(2, "[]"),
set(3, "[]="),
add(2, ",")
private new make(Int degree, Str symbol, Str? methodName := null)
{
this.degree = degree
this.symbol = symbol
this.methodName = methodName == null ? name : methodName
this.isOperator = methodName == null
}
static ShortcutOp? fromPrefix(Str prefix) { prefixes[prefix] }
private static const Str:ShortcutOp prefixes
static
{
m := Str:ShortcutOp[:]
vals.each |val| { m[val.methodName] = val }
prefixes = m
}
Str formatErr(CType lhs, CType rhs)
{
if (this === get) return "$lhs [ $rhs ]"
if (this === set) return "$lhs [ $rhs ]="
return "$lhs $symbol $rhs"
}
const Int degree
const Str methodName
const Bool isOperator
const Str symbol
}