/*
* Copyright (c) 1994, 2004, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.tools.tree;
import sun.tools.java.*;
import sun.tools.asm.Label;
import sun.tools.asm.Assembler;
import java.io.PrintStream;
import java.util.Hashtable;
WARNING: The contents of this source file are not part of any
supported API. Code that depends on them does so at its own risk:
they are subject to change or removal without notice.
/**
* WARNING: The contents of this source file are not part of any
* supported API. Code that depends on them does so at its own risk:
* they are subject to change or removal without notice.
*/
public
class Expression extends Node {
Type type;
Constructor
/**
* Constructor
*/
Expression(int op, long where, Type type) {
super(op, where);
this.type = type;
}
Type checking may assign a more complex implementation
to an innocuous-looking expression (like an identifier).
Return that implementation, or the original expression itself
if there is no special implementation.
This appears at present to be dead code, and is not called
from within javac. Access to the implementation generally
occurs within the same class, and thus uses the underlying
field directly.
/**
* Type checking may assign a more complex implementation
* to an innocuous-looking expression (like an identifier).
* Return that implementation, or the original expression itself
* if there is no special implementation.
* <p>
* This appears at present to be dead code, and is not called
* from within javac. Access to the implementation generally
* occurs within the same class, and thus uses the underlying
* field directly.
*/
public Expression getImplementation() {
return this;
}
public Type getType() {
return type;
}
Return the precedence of the operator
/**
* Return the precedence of the operator
*/
int precedence() {
return (op < opPrecedence.length) ? opPrecedence[op] : 100;
}
Order the expression based on precedence
/**
* Order the expression based on precedence
*/
public Expression order() {
return this;
}
Return true if constant, according to JLS 15.27.
A constant expression must inline away to a literal constant.
/**
* Return true if constant, according to JLS 15.27.
* A constant expression must inline away to a literal constant.
*/
public boolean isConstant() {
return false;
}
Return the constant value.
/**
* Return the constant value.
*/
public Object getValue() {
return null;
}
Check if the expression is known to be equal to a given value.
Returns false for any expression other than a literal constant,
thus should be called only after simplification (inlining) has
been performed.
/**
* Check if the expression is known to be equal to a given value.
* Returns false for any expression other than a literal constant,
* thus should be called only after simplification (inlining) has
* been performed.
*/
public boolean equals(int i) {
return false;
}
public boolean equals(boolean b) {
return false;
}
public boolean equals(Identifier id) {
return false;
}
public boolean equals(String s) {
return false;
}
Check if the expression must be a null reference.
/**
* Check if the expression must be a null reference.
*/
public boolean isNull() {
return false;
}
Check if the expression cannot be a null reference.
/**
* Check if the expression cannot be a null reference.
*/
public boolean isNonNull() {
return false;
}
Check if the expression is equal to its default static value
/**
* Check if the expression is equal to its default static value
*/
public boolean equalsDefault() {
return false;
}
Convert an expresion to a type
/**
* Convert an expresion to a type
*/
Type toType(Environment env, Context ctx) {
env.error(where, "invalid.type.expr");
return Type.tError;
}
/**
* Convert an expresion to a type in a context where a qualified
* type name is expected, e.g., in the prefix of a qualified type
* name.
*/
/*-----------------------------------------------------*
Type toQualifiedType(Environment env, Context ctx) {
env.error(where, "invalid.type.expr");
return Type.tError;
}
*-----------------------------------------------------*/
See if this expression fits in the given type.
This is useful because some larger numbers fit into
smaller types.
If it is an "int" constant expression, inline it, if necessary,
to examine its numerical value. See JLS 5.2 and 15.24.
/**
* See if this expression fits in the given type.
* This is useful because some larger numbers fit into
* smaller types.
* <p>
* If it is an "int" constant expression, inline it, if necessary,
* to examine its numerical value. See JLS 5.2 and 15.24.
*/
public boolean fitsType(Environment env, Context ctx, Type t) {
try {
if (env.isMoreSpecific(this.type, t)) {
return true;
}
if (this.type.isType(TC_INT) && this.isConstant() && ctx != null) {
// Tentative inlining is harmless for constant expressions.
Expression n = this.inlineValue(env, ctx);
if (n != this && n instanceof ConstantExpression) {
return n.fitsType(env, ctx, t);
}
}
return false;
} catch (ClassNotFound e) {
return false;
}
}
Deprecated: (for backward compatibility)
/** @deprecated (for backward compatibility) */
@Deprecated
public boolean fitsType(Environment env, Type t) {
return fitsType(env, (Context) null, t);
}
Check an expression
/**
* Check an expression
*/
public Vset checkValue(Environment env, Context ctx, Vset vset, Hashtable<Object, Object> exp) {
return vset;
}
public Vset checkInitializer(Environment env, Context ctx, Vset vset, Type t, Hashtable<Object, Object> exp) {
return checkValue(env, ctx, vset, exp);
}
public Vset check(Environment env, Context ctx, Vset vset, Hashtable<Object, Object> exp) {
throw new CompilerError("check failed");
}
public Vset checkLHS(Environment env, Context ctx,
Vset vset, Hashtable<Object, Object> exp) {
env.error(where, "invalid.lhs.assignment");
type = Type.tError;
return vset;
}
Return a FieldUpdater
object to be used in updating the value of the location denoted by this
, which must be an expression suitable for the left-hand side of an assignment. This is used for implementing assignments to private fields for which an access method is required. Returns null if no access method is needed, in which case the assignment is handled in the usual way, by direct access. Only simple assignment expressions are handled here Assignment operators and pre/post increment/decrement operators are are handled by 'getUpdater' below.
Called during the checking phase.
/**
* Return a {@code FieldUpdater} object to be used in updating the
* value of the location denoted by {@code this}, which must be an
* expression suitable for the left-hand side of an assignment.
* This is used for implementing assignments to private fields for which
* an access method is required. Returns null if no access method is
* needed, in which case the assignment is handled in the usual way, by
* direct access. Only simple assignment expressions are handled here
* Assignment operators and pre/post increment/decrement operators are
* are handled by 'getUpdater' below.
* <p>
* Called during the checking phase.
*/
public FieldUpdater getAssigner(Environment env, Context ctx) {
throw new CompilerError("getAssigner lhs");
}
Return a FieldUpdater
object to be used in updating the value of the location denoted by this
, which must be an expression suitable for the left-hand side of an assignment. This is used for implementing the assignment operators and the increment/decrement operators on private fields that require an access method, e.g., uplevel from an inner class. Returns null if no access method is needed.
Called during the checking phase.
/**
* Return a {@code FieldUpdater} object to be used in updating the value of the
* location denoted by {@code this}, which must be an expression suitable for the
* left-hand side of an assignment. This is used for implementing the assignment
* operators and the increment/decrement operators on private fields that require an
* access method, e.g., uplevel from an inner class. Returns null if no access method
* is needed.
* <p>
* Called during the checking phase.
*/
public FieldUpdater getUpdater(Environment env, Context ctx) {
throw new CompilerError("getUpdater lhs");
}
public Vset checkAssignOp(Environment env, Context ctx,
Vset vset, Hashtable<Object, Object> exp, Expression outside) {
if (outside instanceof IncDecExpression)
env.error(where, "invalid.arg", opNames[outside.op]);
else
env.error(where, "invalid.lhs.assignment");
type = Type.tError;
return vset;
}
Check something that might be an AmbiguousName (refman 6.5.2).
A string of dot-separated identifiers might be, in order of preference:
a variable name followed by fields or types
a type name followed by fields or types
a package name followed a type and then fields or types
If a type name is found, it rewrites itself as a TypeExpression
. If a node decides it can only be a package prefix, it sets its type to Type.tPackage
. The caller must detect this and act appropriately to verify the full package name. @arg loc the expression containing the ambiguous expression
/**
* Check something that might be an AmbiguousName (refman 6.5.2).
* A string of dot-separated identifiers might be, in order of preference:
* <nl>
* <li> a variable name followed by fields or types
* <li> a type name followed by fields or types
* <li> a package name followed a type and then fields or types
* </nl>
* If a type name is found, it rewrites itself as a {@code TypeExpression}.
* If a node decides it can only be a package prefix, it sets its
* type to {@code Type.tPackage}. The caller must detect this
* and act appropriately to verify the full package name.
* @arg loc the expression containing the ambiguous expression
*/
public Vset checkAmbigName(Environment env, Context ctx, Vset vset, Hashtable<Object, Object> exp,
UnaryExpression loc) {
return checkValue(env, ctx, vset, exp);
}
Check a condition. Return a ConditionVars(), which indicates when
which variables are set if the condition is true, and which are set if
the condition is false.
/**
* Check a condition. Return a ConditionVars(), which indicates when
* which variables are set if the condition is true, and which are set if
* the condition is false.
*/
public ConditionVars checkCondition(Environment env, Context ctx,
Vset vset, Hashtable<Object, Object> exp) {
ConditionVars cvars = new ConditionVars();
checkCondition(env, ctx, vset, exp, cvars);
return cvars;
}
/*
* Check a condition.
*
* cvars is modified so that
* cvar.vsTrue indicates variables with a known value if result = true
* cvars.vsFalse indicates variables with a known value if !result
*
* The default action is to simply call checkValue on the expression, and
* to see both vsTrue and vsFalse to the result.
*/
public void checkCondition(Environment env, Context ctx,
Vset vset, Hashtable<Object, Object> exp, ConditionVars cvars) {
cvars.vsTrue = cvars.vsFalse = checkValue(env, ctx, vset, exp);
// unshare side effects:
cvars.vsFalse = cvars.vsFalse.copy();
}
Evaluate.
Attempt to compute the value of an expression node. If all operands are
literal constants of the same kind (e.g., IntegerExpression nodes), a
new constant node of the proper type is returned representing the value
as computed at compile-time. Otherwise, the original node 'this' is
returned.
/**
* Evaluate.
*
* Attempt to compute the value of an expression node. If all operands are
* literal constants of the same kind (e.g., IntegerExpression nodes), a
* new constant node of the proper type is returned representing the value
* as computed at compile-time. Otherwise, the original node 'this' is
* returned.
*/
Expression eval() {
return this;
}
Simplify.
Attempt to simplify an expression node by returning a semantically-
equivalent expression that is presumably less costly to execute. There
is some overlap with the intent of 'eval', as compile-time evaluation of
conditional expressions and the short-circuit boolean operators is
performed here. Other simplifications include logical identities
involving logical negation and comparisons. If no simplification is
possible, the original node 'this' is returned. It is assumed that the
children of the node have previously been recursively simplified and
evaluated. A result of 'null' indicates that the expression may be
elided entirely.
/**
* Simplify.
*
* Attempt to simplify an expression node by returning a semantically-
* equivalent expression that is presumably less costly to execute. There
* is some overlap with the intent of 'eval', as compile-time evaluation of
* conditional expressions and the short-circuit boolean operators is
* performed here. Other simplifications include logical identities
* involving logical negation and comparisons. If no simplification is
* possible, the original node 'this' is returned. It is assumed that the
* children of the node have previously been recursively simplified and
* evaluated. A result of 'null' indicates that the expression may be
* elided entirely.
*/
Expression simplify() {
return this;
}
Inline.
Recursively simplify each child of an expression node, destructively
replacing the child with the simplified result. Also attempts to
simplify the current node 'this', and returns the simplified result.
The name 'inline' is somthing of a misnomer, as these methods are
responsible for compile-time expression simplification in general.
The 'eval' and 'simplify' methods apply to a single expression node
only -- it is 'inline' and 'inlineValue' that drive the simplification
of entire expressions.
/**
* Inline.
*
* Recursively simplify each child of an expression node, destructively
* replacing the child with the simplified result. Also attempts to
* simplify the current node 'this', and returns the simplified result.
*
* The name 'inline' is somthing of a misnomer, as these methods are
* responsible for compile-time expression simplification in general.
* The 'eval' and 'simplify' methods apply to a single expression node
* only -- it is 'inline' and 'inlineValue' that drive the simplification
* of entire expressions.
*/
public Expression inline(Environment env, Context ctx) {
return null;
}
public Expression inlineValue(Environment env, Context ctx) {
return this;
}
Attempt to evaluate this expression. If this expression
yields a value, append it to the StringBuffer `buffer'.
If this expression cannot be evaluated at this time (for
example if it contains a division by zero, a non-constant
subexpression, or a subexpression which "refuses" to evaluate)
then return `null' to indicate failure.
It is anticipated that this method will be called to evaluate
concatenations of compile-time constant strings. The call
originates from AddExpression#inlineValue().
See AddExpression#inlineValueSB() for detailed comments.
/**
* Attempt to evaluate this expression. If this expression
* yields a value, append it to the StringBuffer `buffer'.
* If this expression cannot be evaluated at this time (for
* example if it contains a division by zero, a non-constant
* subexpression, or a subexpression which "refuses" to evaluate)
* then return `null' to indicate failure.
*
* It is anticipated that this method will be called to evaluate
* concatenations of compile-time constant strings. The call
* originates from AddExpression#inlineValue().
*
* See AddExpression#inlineValueSB() for detailed comments.
*/
protected StringBuffer inlineValueSB(Environment env,
Context ctx,
StringBuffer buffer) {
Expression inlined = inlineValue(env, ctx);
Object val = inlined.getValue();
if (val == null && !inlined.isNull()){
// This (supposedly constant) expression refuses to yield
// a value. This can happen, in particular, when we are
// trying to evaluate a division by zero. It can also
// happen in cases where isConstant() is able to classify
// expressions as constant that the compiler's inlining
// mechanisms aren't able to evaluate; this is rare,
// and all such cases that we have found so far
// (e.g. 4082814, 4106244) have been plugged up.
//
// We return a null to indicate that we have failed to
// evaluate the concatenation.
return null;
}
// For boolean and character expressions, getValue() returns
// an Integer. We need to take care, when appending the result
// of getValue(), that we preserve the type.
// Fix for 4103959, 4102672.
if (type == Type.tChar) {
buffer.append((char)((Integer)val).intValue());
} else if (type == Type.tBoolean) {
buffer.append(((Integer)val).intValue() != 0);
} else {
buffer.append(val);
}
return buffer;
}
public Expression inlineLHS(Environment env, Context ctx) {
return null;
}
The cost of inlining this expression.
This cost controls the inlining of methods, and does not determine
the compile-time simplifications performed by 'inline' and friends.
/**
* The cost of inlining this expression.
* This cost controls the inlining of methods, and does not determine
* the compile-time simplifications performed by 'inline' and friends.
*/
public int costInline(int thresh, Environment env, Context ctx) {
return 1;
}
Code
/**
* Code
*/
void codeBranch(Environment env, Context ctx, Assembler asm, Label lbl, boolean whenTrue) {
if (type.isType(TC_BOOLEAN)) {
codeValue(env, ctx, asm);
asm.add(where, whenTrue ? opc_ifne : opc_ifeq, lbl, whenTrue);
} else {
throw new CompilerError("codeBranch " + opNames[op]);
}
}
public void codeValue(Environment env, Context ctx, Assembler asm) {
if (type.isType(TC_BOOLEAN)) {
Label l1 = new Label();
Label l2 = new Label();
codeBranch(env, ctx, asm, l1, true);
asm.add(true, where, opc_ldc, 0);
asm.add(true, where, opc_goto, l2);
asm.add(l1);
asm.add(true, where, opc_ldc, 1);
asm.add(l2);
} else {
throw new CompilerError("codeValue");
}
}
public void code(Environment env, Context ctx, Assembler asm) {
codeValue(env, ctx, asm);
switch (type.getTypeCode()) {
case TC_VOID:
break;
case TC_DOUBLE:
case TC_LONG:
asm.add(where, opc_pop2);
break;
default:
asm.add(where, opc_pop);
break;
}
}
int codeLValue(Environment env, Context ctx, Assembler asm) {
print(System.out);
throw new CompilerError("invalid lhs");
}
void codeLoad(Environment env, Context ctx, Assembler asm) {
print(System.out);
throw new CompilerError("invalid load");
}
void codeStore(Environment env, Context ctx, Assembler asm) {
print(System.out);
throw new CompilerError("invalid store");
}
Convert this expression to a string.
/**
* Convert this expression to a string.
*/
void ensureString(Environment env, Context ctx, Assembler asm)
throws ClassNotFound, AmbiguousMember
{
if (type == Type.tString && isNonNull()) {
return;
}
// Make sure it's a non-null string.
ClassDefinition sourceClass = ctx.field.getClassDefinition();
ClassDeclaration stClass = env.getClassDeclaration(Type.tString);
ClassDefinition stClsDef = stClass.getClassDefinition(env);
// FIX FOR 4071548
// We use 'String.valueOf' to do the conversion, in order to
// correctly handle null references and efficiently handle
// primitive types. For reference types, we force the argument
// to be interpreted as of 'Object' type, thus avoiding the
// the special-case overloading of 'valueOf' for character arrays.
// This special treatment would conflict with JLS 15.17.1.1.
if (type.inMask(TM_REFERENCE)) {
// Reference type
if (type != Type.tString) {
// Convert non-string object to string. If object is
// a string, we don't need to convert it, except in the
// case that it is null, which is handled below.
Type argType1[] = {Type.tObject};
MemberDefinition f1 =
stClsDef.matchMethod(env, sourceClass, idValueOf, argType1);
asm.add(where, opc_invokestatic, f1);
}
// FIX FOR 4030173
// If the argument was null, then value is "null", but if the
// argument was not null, 'toString' was called and could have
// returned null. We call 'valueOf' again to make sure that
// the result is a non-null string. See JLS 15.17.1.1. The
// approach taken here minimizes code size -- open code would
// be faster. The 'toString' method for an array class cannot
// be overridden, thus we know that it will never return null.
if (!type.inMask(TM_ARRAY|TM_NULL)) {
Type argType2[] = {Type.tString};
MemberDefinition f2 =
stClsDef.matchMethod(env, sourceClass, idValueOf, argType2);
asm.add(where, opc_invokestatic, f2);
}
} else {
// Primitive type
Type argType[] = {type};
MemberDefinition f =
stClsDef.matchMethod(env, sourceClass, idValueOf, argType);
asm.add(where, opc_invokestatic, f);
}
}
Convert this expression to a string and append it to the string
buffer on the top of the stack.
If the needBuffer argument is true, the string buffer needs to be
created, initialized, and pushed on the stack, first.
/**
* Convert this expression to a string and append it to the string
* buffer on the top of the stack.
* If the needBuffer argument is true, the string buffer needs to be
* created, initialized, and pushed on the stack, first.
*/
void codeAppend(Environment env, Context ctx, Assembler asm,
ClassDeclaration sbClass, boolean needBuffer)
throws ClassNotFound, AmbiguousMember
{
ClassDefinition sourceClass = ctx.field.getClassDefinition();
ClassDefinition sbClsDef = sbClass.getClassDefinition(env);
MemberDefinition f;
if (needBuffer) {
// need to create the string buffer
asm.add(where, opc_new, sbClass); // create the class
asm.add(where, opc_dup);
if (equals("")) {
// make an empty string buffer
f = sbClsDef.matchMethod(env, sourceClass, idInit);
} else {
// optimize by initializing the buffer with the string
codeValue(env, ctx, asm);
ensureString(env, ctx, asm);
Type argType[] = {Type.tString};
f = sbClsDef.matchMethod(env, sourceClass, idInit, argType);
}
asm.add(where, opc_invokespecial, f);
} else {
// append this item to the string buffer
codeValue(env, ctx, asm);
// FIX FOR 4071548
// 'StringBuffer.append' converts its argument as if by
// 'valueOf', treating character arrays specially. This
// violates JLS 15.17.1.1, which requires that concatenation
// convert non-primitive arguments using 'toString'. We force
// the treatment of all reference types as type 'Object', thus
// invoking an overloading of 'append' that has the required
// semantics.
Type argType[] =
{ (type.inMask(TM_REFERENCE) && type != Type.tString)
? Type.tObject
: type };
f = sbClsDef.matchMethod(env, sourceClass, idAppend, argType);
asm.add(where, opc_invokevirtual, f);
}
}
Code
/**
* Code
*/
void codeDup(Environment env, Context ctx, Assembler asm, int items, int depth) {
switch (items) {
case 0:
return;
case 1:
switch (depth) {
case 0:
asm.add(where, opc_dup);
return;
case 1:
asm.add(where, opc_dup_x1);
return;
case 2:
asm.add(where, opc_dup_x2);
return;
}
break;
case 2:
switch (depth) {
case 0:
asm.add(where, opc_dup2);
return;
case 1:
asm.add(where, opc_dup2_x1);
return;
case 2:
asm.add(where, opc_dup2_x2);
return;
}
break;
}
throw new CompilerError("can't dup: " + items + ", " + depth);
}
void codeConversion(Environment env, Context ctx, Assembler asm, Type f, Type t) {
int from = f.getTypeCode();
int to = t.getTypeCode();
switch (to) {
case TC_BOOLEAN:
if (from != TC_BOOLEAN) {
break;
}
return;
case TC_BYTE:
if (from != TC_BYTE) {
codeConversion(env, ctx, asm, f, Type.tInt);
asm.add(where, opc_i2b);
}
return;
case TC_CHAR:
if (from != TC_CHAR) {
codeConversion(env, ctx, asm, f, Type.tInt);
asm.add(where, opc_i2c);
}
return;
case TC_SHORT:
if (from != TC_SHORT) {
codeConversion(env, ctx, asm, f, Type.tInt);
asm.add(where, opc_i2s);
}
return;
case TC_INT:
switch (from) {
case TC_BYTE:
case TC_CHAR:
case TC_SHORT:
case TC_INT:
return;
case TC_LONG:
asm.add(where, opc_l2i);
return;
case TC_FLOAT:
asm.add(where, opc_f2i);
return;
case TC_DOUBLE:
asm.add(where, opc_d2i);
return;
}
break;
case TC_LONG:
switch (from) {
case TC_BYTE:
case TC_CHAR:
case TC_SHORT:
case TC_INT:
asm.add(where, opc_i2l);
return;
case TC_LONG:
return;
case TC_FLOAT:
asm.add(where, opc_f2l);
return;
case TC_DOUBLE:
asm.add(where, opc_d2l);
return;
}
break;
case TC_FLOAT:
switch (from) {
case TC_BYTE:
case TC_CHAR:
case TC_SHORT:
case TC_INT:
asm.add(where, opc_i2f);
return;
case TC_LONG:
asm.add(where, opc_l2f);
return;
case TC_FLOAT:
return;
case TC_DOUBLE:
asm.add(where, opc_d2f);
return;
}
break;
case TC_DOUBLE:
switch (from) {
case TC_BYTE:
case TC_CHAR:
case TC_SHORT:
case TC_INT:
asm.add(where, opc_i2d);
return;
case TC_LONG:
asm.add(where, opc_l2d);
return;
case TC_FLOAT:
asm.add(where, opc_f2d);
return;
case TC_DOUBLE:
return;
}
break;
case TC_CLASS:
switch (from) {
case TC_NULL:
return;
case TC_CLASS:
case TC_ARRAY:
try {
if (!env.implicitCast(f, t)) {
asm.add(where, opc_checkcast, env.getClassDeclaration(t));
}
} catch (ClassNotFound e) {
throw new CompilerError(e);
}
return;
}
break;
case TC_ARRAY:
switch (from) {
case TC_NULL:
return;
case TC_CLASS:
case TC_ARRAY:
try {
if (!env.implicitCast(f, t)) {
asm.add(where, opc_checkcast, t);
}
return;
} catch (ClassNotFound e) {
throw new CompilerError(e);
}
}
break;
}
throw new CompilerError("codeConversion: " + from + ", " + to);
}
Check if the first thing is a constructor invocation
/**
* Check if the first thing is a constructor invocation
*/
public Expression firstConstructor() {
return null;
}
Create a copy of the expression for method inlining
/**
* Create a copy of the expression for method inlining
*/
public Expression copyInline(Context ctx) {
return (Expression)clone();
}
Print
/**
* Print
*/
public void print(PrintStream out) {
out.print(opNames[op]);
}
}