/*
* Copyright 2015 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.template.soy.jbcsrc;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.base.Optional;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
/**
* A local variable representation.
*
* <p>This does nothing to enforce required constraints, e.g.:
*
* <ul>
* <li>This does not ensure that {@link #start()} and {@link #end()} are valid and exist in the
* method.
* <li>This does not ensure that the {@link #index} is otherwise unused and that only one variable
* is active at a time with the index.
* </ul>
*
* <p>Note: This class does not attempt to make use of the convenience methods on generator adapter
* such as {@link CodeBuilder#newLocal(Type)} or {@link CodeBuilder#loadArg(int)} that make it
* easier to work with local variables (and calculating local variable indexes). Instead we push
* this responsibility onto our caller. This is because CodeBuilder doesn't make it possible to
* generate local variable debugging tables in this case (e.g. there is no way to map a method
* parameter index to a local variable index).
*/
final class LocalVariable extends Expression {
// TODO(lukes): the fact that you need to specify the start and end labels during construction
// ends up being awkward... Due to the fact that it is unclear who is responsible for actually
// visiting the labels. Maybe this object should be label agnostic and the labels should just be
// parameters to tableEntry?
static LocalVariable createThisVar(TypeInfo owner, Label start, Label end) {
return new LocalVariable("this", owner.type(), 0, start, end, Feature.NON_NULLABLE);
}
static LocalVariable createLocal(String name, int index, Type type, Label start, Label end) {
checkArgument(!name.equals("this"));
return new LocalVariable(name, type, index, start, end);
}
private final String variableName;
private final int index;
private final Label start;
private final Label end;
private LocalVariable(
String variableName, Type type, int index, Label start, Label end, Feature... features) {
super(type, Feature.CHEAP /* locals are always cheap */, features);
this.variableName = checkNotNull(variableName);
this.index = index;
this.start = checkNotNull(start);
this.end = checkNotNull(end);
}
/** The name of the variable, ends up in debugging tables. */
String variableName() {
return variableName;
}
int index() {
return index;
}
/** A label defining the earliest point at which this variable is defined. */
Label start() {
return start;
}
/** A label defining the latest point at which this variable is defined. */
Label end() {
return end;
}
@Override
LocalVariable asCheap() {
return this;
}
@Override
LocalVariable asNonNullable() {
if (isNonNullable()) {
return this;
}
return new LocalVariable(variableName, resultType(), index, start, end, Feature.NON_NULLABLE);
}
/**
* Write a local variable table entry for this variable. This informs debuggers about variable
* names, types and lifetime.
*/
void tableEntry(CodeBuilder mv) {
mv.visitLocalVariable(
variableName(),
resultType().getDescriptor(),
null, // no generic signature
start(),
end(),
index());
}
@Override
void doGen(CodeBuilder mv) {
mv.visitVarInsn(resultType().getOpcode(Opcodes.ILOAD), index());
}
/**
* Return a {@link Statement} that stores the value of the given expression into this variable.
*/
Statement store(final Expression expr) {
return store(expr, Optional.<Label>absent());
}
/**
* Return a {@link Statement} that stores the value of the given expression into this variable.
*
* @param expr The expression to store
* @param firstVarInstruction A label to use to mark the store instruction
*/
Statement store(final Expression expr, Label firstVarInstruction) {
return store(expr, Optional.<Label>of(firstVarInstruction));
}
/** Writes the value at the top of the stack to the local variable. */
private Statement store(final Expression expr, final Optional<Label> firstVarInstruction) {
expr.checkAssignableTo(resultType());
return new Statement() {
@Override
void doGen(CodeBuilder adapter) {
expr.gen(adapter);
if (firstVarInstruction.isPresent()) {
adapter.mark(firstVarInstruction.get());
}
adapter.visitVarInsn(resultType().getOpcode(Opcodes.ISTORE), index());
}
};
}
}