/*
* 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.checkNotNull;
import static com.google.template.soy.jbcsrc.BytecodeUtils.THROWABLE_TYPE;
import com.google.template.soy.base.SourceLocation;
import java.io.IOException;
import java.util.Arrays;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.Method;
/**
* A statement models a section of code. Unlike {@linkplain Expression expressions}, statements may
* have side effects. For example, the Soy code {@code {'foo'}} could be implemented as a statement
* that writes to an output stream.
*
* <p>The generated code should satisfy the invariant that the runtime stack and local variables are
* are unchanged by the generated code (i.e. the frame map at the start of the statement is
* identical to the frame map at the end of the statement).
*/
abstract class Statement extends BytecodeProducer {
private static final Type[] IO_EXCEPTION_ARRAY = new Type[] {Type.getType(IOException.class)};
static final Statement NULL_STATEMENT =
new Statement() {
@Override
void doGen(CodeBuilder adapter) {}
};
static final Statement RETURN =
new Statement() {
@Override
void doGen(CodeBuilder adapter) {
adapter.returnValue();
}
};
/**
* Generates a statement that returns the value produced by the given expression.
*
* <p>This does not validate that the return type is appropriate. It is our callers responsibility
* to do that.
*/
static Statement returnExpression(final Expression expression) {
// TODO(lukes): it would be nice to do a checkType operation here to make sure that expression
// is compatible with the return type of the method, but i don't know how to get that
// information here (reasonably). So it is the caller's responsibility.
return new Statement() {
@Override
void doGen(CodeBuilder adapter) {
expression.gen(adapter);
adapter.returnValue();
}
};
}
/**
* Generates a statement that throws the throwable produced by the given expression.
*
* <p>This does not validate that the throwable is compatible with the methods throws clause.
*/
static Statement throwExpression(final Expression expression) {
expression.checkAssignableTo(THROWABLE_TYPE);
return new Statement() {
@Override
void doGen(CodeBuilder adapter) {
expression.gen(adapter);
adapter.throwException();
}
};
}
/** Returns a statement that concatenates all the provided statements. */
static Statement concat(Statement... statements) {
return concat(Arrays.asList(statements));
}
/** Returns a statement that concatenates all the provided statements. */
static Statement concat(final Iterable<? extends Statement> statements) {
checkNotNull(statements);
return new Statement() {
@Override
void doGen(CodeBuilder adapter) {
for (Statement statement : statements) {
statement.gen(adapter);
}
}
};
}
Statement() {
super();
}
Statement(SourceLocation location) {
super(location);
}
/**
* Writes this statement to the {@link ClassVisitor} as a method.
*
* @param access The access modifiers of the method
* @param method The method signature
* @param visitor The class visitor to write it to
*/
final void writeMethod(int access, Method method, ClassVisitor visitor) {
writeMethodTo(new CodeBuilder(access, method, null, visitor));
}
/**
* Writes this statement to the {@link ClassVisitor} as a method.
*
* @param access The access modifiers of the method
* @param method The method signature
* @param visitor The class visitor to write it to
*/
final void writeIOExceptionMethod(int access, Method method, ClassVisitor visitor) {
writeMethodTo(new CodeBuilder(access, method, IO_EXCEPTION_ARRAY, visitor));
}
/** Writes this statement as the complete method body to {@code ga}. */
private final void writeMethodTo(CodeBuilder builder) {
builder.visitCode();
gen(builder);
try {
builder.endMethod();
} catch (Throwable t) {
// ASM fails in bizarre ways, attach a trace of the thing we tried to generate to the
// exception.
throw new RuntimeException("Failed to generate method:\n" + this, t);
}
}
/**
* Returns a new statement identical to this one but with the given label applied at the start of
* the statement.
*/
final Statement labelStart(final Label label) {
return new Statement() {
@Override
void doGen(CodeBuilder adapter) {
adapter.mark(label);
Statement.this.gen(adapter);
}
};
}
/** Returns a new {@link Statement} with the source location attached. */
final Statement withSourceLocation(SourceLocation location) {
checkNotNull(location);
return new Statement(location) {
@Override
void doGen(CodeBuilder adapter) {
Statement.this.gen(adapter);
}
};
}
@Override
public String toString() {
return "Statement:\n" + trace();
}
}