// // 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 } ** ** 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. ** 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 { // 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 = 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 { 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) {} 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 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 } ************************************************************************** ** 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 } ************************************************************************** ** SymbolExpr ************************************************************************** ** ** SymbolExpr ** class SymbolExpr: Expr { new make(Loc loc, Str? podName, Str name) : super(loc, ExprId.symbolLiteral) { this.podName = podName this.name = name } new makeFor(Loc loc, CSymbol symbol) : this.make(loc, symbol.pod.name, symbol.name) { this.ctype = symbol.ns.symbolType this.symbol = symbol } override Str serialize() { "@$symbol.qname" } override Str toStr() { podName == null ? "@$name" : "@$podName::$name" } Str qname() { symbol.qname } Str? podName Str name CSymbol? symbol } ************************************************************************** ** 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| { 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(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| { 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(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() { return 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() { 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(Loc loc, ExprId id, Expr? target, Str? name) : super(loc, 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(Loc loc, Expr? target, Str name) : super(loc, ExprId.unknownVar, target, name) { } new makeStorage(Loc loc, Expr? target, Str name) : super.make(loc, ExprId.storage, 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() { 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 "$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 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(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() { return isAssignable } override Obj? assignTarget() { isAssign ? target : null } 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(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() { return true } override Bool assignRequiresTempVar() { return !field.isStatic } override Bool sameVarAs(Expr that) { x := that as FieldExpr if (x == null) return false return field == x.field && 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 default: throw CompilerErr("Invalid field for tableswitch: " + field.typeof, loc) } } 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 (!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 } } new makeNoUnwrap(Loc loc, MethodVar var) : super.make(loc, ExprId.localVar) { this.var = var this.ctype = var.ctype this.unwrap = false } 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() { return 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 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.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(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 { 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.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) } } 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.first.id !== StmtId.expr) 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 ExprId { nullLiteral, // LiteralExpr trueLiteral, falseLiteral, intLiteral, floatLiteral, decimalLiteral, strLiteral, durationLiteral, uriLiteral, typeLiteral, slotLiteral, // SlotLiteralExpr symbolLiteral, // SymbolExpr 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 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(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 }