package org.develnext.jphp.core.compiler.jvm.statement;
import org.develnext.jphp.core.compiler.common.misc.StackItem;
import org.develnext.jphp.core.compiler.jvm.Constants;
import org.develnext.jphp.core.compiler.jvm.misc.JumpItem;
import org.develnext.jphp.core.compiler.jvm.misc.LocalVariable;
import org.develnext.jphp.core.compiler.jvm.node.MethodNodeImpl;
import org.develnext.jphp.core.tokenizer.TokenMeta;
import org.develnext.jphp.core.tokenizer.token.expr.value.NameToken;
import org.develnext.jphp.core.tokenizer.token.expr.value.StaticAccessExprToken;
import org.develnext.jphp.core.tokenizer.token.stmt.*;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*;
import php.runtime.Memory;
import php.runtime.common.Messages;
import php.runtime.env.Environment;
import php.runtime.env.TraceInfo;
import php.runtime.exceptions.support.ErrorType;
import php.runtime.lang.IObject;
import php.runtime.memory.ArrayMemory;
import php.runtime.memory.ObjectMemory;
import php.runtime.memory.helper.ClassConstantMemory;
import php.runtime.memory.helper.ConstantMemory;
import php.runtime.reflection.ClassEntity;
import php.runtime.reflection.DocumentComment;
import php.runtime.reflection.MethodEntity;
import php.runtime.reflection.ParameterEntity;
import php.runtime.reflection.helper.GeneratorEntity;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.NEW;
public class MethodStmtCompiler extends StmtCompiler<MethodEntity> {
public final ClassStmtCompiler clazz;
public MethodStmtToken statement;
public final MethodNode node;
protected int statementIndex = 0;
private Stack<StackItem> stack = new Stack<StackItem>();
private final List<JumpItem> jumpStack = new ArrayList<JumpItem>();
final Stack<TryCatchItem> tryStack = new Stack<TryCatchItem>();
final List<BodyStmtToken> finallyBlocks = new ArrayList<>();
private int stackLevel = 0;
private int stackSize = 0;
private int stackMaxSize = 0;
private Map<String, LabelNode> gotoLabels;
private Map<String, LocalVariable> localVariables;
protected String realName;
private boolean external = false;
private long methodId;
private LabelNode labelStart;
private Map<Class<?>, AtomicInteger> statementIndexes = new HashMap<Class<?>, AtomicInteger>();
private Set<Integer> lineTickHandled = new HashSet<>();
private GeneratorEntity generatorEntity;
public MethodStmtCompiler(ClassStmtCompiler clazz, MethodNode node){
super(clazz.getCompiler());
this.clazz = clazz;
this.statement = null;
this.node = node;
this.localVariables = new LinkedHashMap<>();
entity = new MethodEntity(getCompiler().getContext());
entity.setClazz(clazz.entity);
entity.setName(node.name);
realName = entity.getName();
methodId = compiler.getScope().nextMethodIndex();
}
public MethodStmtCompiler(ClassStmtCompiler clazz, MethodStmtToken statement) {
super(clazz.getCompiler());
this.clazz = clazz;
this.statement = statement;
this.node = new MethodNodeImpl();
this.localVariables = new LinkedHashMap<>();
entity = new MethodEntity(getCompiler().getContext());
entity.setClazz(clazz.entity);
if (statement != null)
entity.setName(statement.getName().getName());
realName = entity.getName();
methodId = compiler.getScope().nextMethodIndex();
}
public long getMethodId() {
return methodId;
}
public long getClassId(){
return clazz.entity.getId();
}
public String getRealName() {
return realName;
}
public void setRealName(String realName) {
this.realName = realName;
}
public boolean isExternal() {
return external;
}
public void setExternal(boolean external) {
this.external = external;
}
public GeneratorEntity getGeneratorEntity() {
return generatorEntity;
}
public void setGeneratorEntity(GeneratorEntity generatorEntity) {
this.generatorEntity = generatorEntity;
}
public Stack<TryCatchItem> getTryStack() {
return tryStack;
}
public Map<String, LocalVariable> getLocalVariables() {
return localVariables;
}
public boolean registerTickTrigger(int line) {
return lineTickHandled.add(line);
}
public int nextStatementIndex(Class<?> clazz){
AtomicInteger atomic = statementIndexes.get(clazz);
if (atomic == null)
statementIndexes.put(clazz, atomic = new AtomicInteger());
return atomic.getAndIncrement();
}
public int prevStatementIndex(Class<?> clazz){
AtomicInteger atomic = statementIndexes.get(clazz);
if (atomic == null)
statementIndexes.put(clazz, atomic = new AtomicInteger());
return atomic.getAndDecrement();
}
public void pushJump(LabelNode breakLabel, LabelNode continueLabel, int stackSize){
jumpStack.add(new JumpItem(breakLabel, continueLabel, stackSize));
}
public void pushJump(LabelNode breakLabel, LabelNode continueLabel){
pushJump(breakLabel, continueLabel, 0);
}
public JumpItem getJump(int level){
if (jumpStack.size() - level < 0)
return null;
if (jumpStack.size() - level >= jumpStack.size())
return null;
return jumpStack.get(jumpStack.size() - level);
}
public int getJumpStackSize(int level){
int size = 0;
for(int i = jumpStack.size(); i >= 0 && jumpStack.size() - i < level; i--){
JumpItem item = getJump(i);
size += item.stackSize;
}
return size;
}
public void popJump(){
jumpStack.remove(jumpStack.size() - 1);
}
void push(StackItem item) {
if (item.getLevel() == -1)
item.setLevel(stackLevel++);
stack.push(item);
stackSize += item.size;
if (stackMaxSize < stackSize)
stackMaxSize = stackSize;
}
int getStackSize(){
return stackSize;
}
int getStackCount(){
return stack.size();
}
StackItem pop(){
StackItem item = stack.pop();
stackSize -= item.size;
return item;
}
void popAll(){
stackSize = 0;
stack.clear();
}
StackItem peek(){
return stack.peek();
}
public LabelNode getGotoLabel(String name) {
return gotoLabels == null ? null : gotoLabels.get(name.toLowerCase());
}
public LabelNode getOrCreateGotoLabel(String name) {
name = name.toLowerCase();
if (gotoLabels == null)
gotoLabels = new HashMap<String, LabelNode>();
LabelNode label = gotoLabels.get(name);
if (label == null)
gotoLabels.put(name, label = new LabelNode());
return label;
}
public LocalVariable addLocalVariable(String variable, LabelNode label, Class clazz){
LocalVariable result;
localVariables.put(
variable,
result = new LocalVariable(variable, localVariables.size(), label, clazz)
);
return result;
}
public LocalVariable getOrAddLocalVariable(String variable, LabelNode label, Class clazz){
LocalVariable local = getLocalVariable(variable);
if (local == null){
local = addLocalVariable(variable, label, clazz);
} else {
if (local.getClazz() != clazz)
throw new RuntimeException(
"Invalid class for " + variable + " variable, " + local.getClazz() + " != " + clazz.getClass()
);
}
return local;
}
public LocalVariable addLocalVariable(String variable, LabelNode label){
return addLocalVariable(variable, label, Memory.class);
}
public LocalVariable getLocalVariable(String variable){
return localVariables.get(variable);
}
void writeHeader(){
int access = 0;
if (statement != null){
if (compiler.getScope().isDebugMode()) {
statement.setDynamicLocal(true);
}
switch (statement.getModifier()){
case PRIVATE: access += Opcodes.ACC_PRIVATE; break;
case PROTECTED: access += Opcodes.ACC_PROTECTED; break;
case PUBLIC: access += Opcodes.ACC_PUBLIC; break;
}
if (statement.isStatic()) access += Opcodes.ACC_STATIC;
//if (statement.isAbstract()) access += Opcodes.ACC_ABSTRACT;
if (statement.isFinal()) access += Opcodes.ACC_FINAL;
node.access = access;
node.name = clazz.isSystem() || entity == null ? statement.getName().getName() : entity.getInternalName();
node.desc = Type.getMethodDescriptor(
Type.getType(Memory.class),
Type.getType(Environment.class),
Type.getType(Memory[].class)
);
if (external){
node.desc = Type.getMethodDescriptor(
Type.getType(Memory.class),
Type.getType(Environment.class),
Type.getType(Memory[].class),
Type.getType(ArrayMemory.class)
);
}
}
if (statement != null){
LabelNode label = labelStart = writeLabel(node, statement.getMeta().getStartLine());
if (!statement.isStatic())
addLocalVariable("~this", label, Object.class);
ExpressionStmtCompiler expressionCompiler = new ExpressionStmtCompiler(this, null);
addLocalVariable("~env", label, Environment.class); // Environment env
LocalVariable args = addLocalVariable("~args", label, Memory[].class); // Memory[] arguments
if (statement.isDynamicLocal()){
if (external)
addLocalVariable("~passedLocal", label, ArrayMemory.class);
LocalVariable local = addLocalVariable("~local", label, ArrayMemory.class);
if (external){
expressionCompiler.writeVarLoad("~passedLocal");
expressionCompiler.writeSysStaticCall(ArrayMemory.class, "valueOfRef", ArrayMemory.class, ArrayMemory.class);
expressionCompiler.setStackPeekAsImmutable();
expressionCompiler.writeVarStore(local, false, true);
} else {
expressionCompiler.writePushConstNull();
expressionCompiler.writeSysStaticCall(ArrayMemory.class, "valueOfRef", ArrayMemory.class, ArrayMemory.class);
expressionCompiler.setStackPeekAsImmutable();
expressionCompiler.writeVarStore(local, false, true);
}
}
if (statement.getUses() != null && !statement.getUses().isEmpty()){
int i = 0;
expressionCompiler.writeVarLoad("~this");
expressionCompiler.writeGetDynamic("uses", Memory[].class);
for (ArgumentStmtToken argument : statement.getUses()) {
LocalVariable local;
if (statement.isDynamicLocal()) {
expressionCompiler.writeDefineVariable(argument.getName());
local = getLocalVariable(argument.getName().getName());
} else {
local = addLocalVariable(argument.getName().getName(), label, Memory.class);
}
if (argument.isReference()){
local.setReference(true);
statement.variable(argument.getName()).setUnstable(true);
}
expressionCompiler.writePushDup();
expressionCompiler.writePushGetFromArray(i, Memory.class);
if (statement.isDynamicLocal()) {
expressionCompiler.writeVarAssign(local, argument.getName(), false, false);
} else {
expressionCompiler.writeVarStore(local, false, false);
}
local.pushLevel();
i++;
}
expressionCompiler.writePopAll(1);
}
int i = 0;
for(ArgumentStmtToken argument : statement.getArguments()){
if (argument.isReference()){
statement.variable(argument.getName())
.setReference(true)
.setUnstable(true);
}
LabelNode next = new LabelNode();
expressionCompiler.writeDefineVariable(argument.getName());
LocalVariable local = getLocalVariable(argument.getName().getName());
if (local != null) {
expressionCompiler.writeVarLoad(args);
expressionCompiler.writePushGetFromArray(i, Memory.class);
expressionCompiler.writeVarAssign(local, argument.getName(), true, false);
// if length <= i then undefined
node.instructions.add(new JumpInsnNode(Opcodes.IFNONNULL, next));
expressionCompiler.stackPop();
if (argument.getValue() == null)
expressionCompiler.writePushNull();
else
expressionCompiler.writeExpression(argument.getValue(), true, false);
expressionCompiler.writeVarAssign(local, argument.getName(), false, false);
node.instructions.add(next);
local.pushLevel();
}
i++;
}
} else {
LabelNode label = labelStart = writeLabel(node, clazz.statement.getMeta().getStartLine());
}
}
@SuppressWarnings("unchecked")
void writeFooter(){
LabelNode endL = new LabelNode();
node.instructions.add(endL);
for(LocalVariable variable : localVariables.values()){
String description = Type.getDescriptor(variable.getClazz() == null ? Object.class : variable.getClazz());
if (variable.name.equals("~this")) {
//if (variable.getClazz() != Memory.class && !clazz.statement.isTrait()) {
description = "L" + clazz.entity.getCompiledInternalName() + ";";
//}
}
node.localVariables.add(new LocalVariableNode(
variable.name,
description,
null,
variable.label == null ? labelStart : variable.label,
variable.getEndLabel() == null ? endL : variable.getEndLabel(),
variable.index
));
}
//node.maxStack = this.stackMaxSize; !!! we don't need this, see: ClassWriter.COMPUTE_FRAMES
//node.maxLocals = this.localVariables.size();
}
@Override
public MethodEntity compile() {
if (statement != null){
if (external)
statement.setDynamicLocal(true);
if (statement.getDocComment() != null)
entity.setDocComment(new DocumentComment(statement.getDocComment().getComment()));
entity.setAbstract(statement.isAbstract());
entity.setAbstractable(statement.getBody() == null);
entity.setFinal(statement.isFinal());
entity.setStatic(statement.isStatic());
entity.setModifier(statement.getModifier());
entity.setReturnReference(statement.isReturnReference());
entity.setTrace(statement.toTraceInfo(compiler.getContext()));
entity.setImmutable(statement.getArguments().isEmpty());
entity.setGeneratorEntity(generatorEntity);
if (clazz.isSystem())
entity.setInternalName(entity.getName());
else
entity.setInternalName(entity.getName() + "$" + clazz.entity.nextMethodIndex());
ParameterEntity[] parameters = new ParameterEntity[statement.getArguments().size()];
int i = 0;
for(ArgumentStmtToken argument : statement.getArguments()){
parameters[i] = new ParameterEntity(compiler.getContext());
ParameterEntity parameter = parameters[i];
parameter.setReference(argument.isReference());
parameter.setName(argument.getName().getName());
parameter.setTrace(argument.toTraceInfo(compiler.getContext()));
parameter.setMutable(
statement.isDynamicLocal() || statement.variable(argument.getName()).isMutable()
);
parameter.setUsed(
!statement.isUnusedVariable(argument.getName())
);
parameter.setVariadic(argument.isVariadic());
parameter.setType(argument.getHintType());
if (argument.getHintTypeClass() != null) {
parameter.setTypeClass(argument.getHintTypeClass().getName());
}
ExpressionStmtCompiler expressionStmtCompiler = new ExpressionStmtCompiler(compiler);
ExprStmtToken value = argument.getValue();
if (value != null) {
Memory defaultValue = expressionStmtCompiler.writeExpression(value, true, true, false);
// try detect constant
if (value.isSingle()) {
if (value.getSingle() instanceof NameToken){
parameter.setDefaultValueConstName(((NameToken) value.getSingle()).getName());
if (defaultValue == null) {
defaultValue = (new ConstantMemory(((NameToken) value.getSingle()).getName()));
parameter.setMutable(true);
}
} else if (value.getSingle() instanceof StaticAccessExprToken){
StaticAccessExprToken access = (StaticAccessExprToken)value.getSingle();
if (access.getClazz() instanceof NameToken && access.getField() instanceof NameToken){
if (defaultValue == null)
defaultValue = (new ClassConstantMemory(
((NameToken) access.getClazz()).getName(),
((NameToken) access.getField()).getName()
));
parameter.setDefaultValueConstName(
((NameToken) access.getClazz()).getName() + "::" +
((NameToken) access.getField()).getName()
);
parameter.setMutable(true);
}
}
}
if (defaultValue == null)
compiler.getEnvironment().error(
argument.toTraceInfo(compiler.getContext()), ErrorType.E_COMPILE_ERROR,
Messages.ERR_EXPECTED_CONST_VALUE, "$" + argument.getName().getName()
);
parameter.setDefaultValue(defaultValue);
}
i++;
}
entity.setParameters(parameters);
}
if (statement != null && clazz.statement.isInterface()){
if (!statement.isInterfacable()){
compiler.getEnvironment().error(entity.getTrace(),
Messages.ERR_INTERFACE_FUNCTION_CANNOT_CONTAIN_BODY.fetch(entity.getSignatureString(false))
);
}
if (statement.isAbstract() || statement.isFinal()){
compiler.getEnvironment().error(entity.getTrace(),
Messages.ERR_ACCESS_TYPE_FOR_INTERFACE_METHOD.fetch(entity.getSignatureString(false))
);
}
} else {
writeHeader();
if (statement.isGenerator()) {
entity.setEmpty(false);
entity.setImmutable(false);
GeneratorStmtCompiler generatorStmtCompiler = new GeneratorStmtCompiler(compiler, statement);
entity.setGeneratorEntity(generatorStmtCompiler.compile());
ExpressionStmtCompiler expr = new ExpressionStmtCompiler(this, null);
expr.makeUnknown(new TypeInsnNode(NEW, entity.getGeneratorEntity().getInternalName()));
expr.stackPush(Memory.Type.REFERENCE);
expr.writePushDup();
// env
expr.writePushEnv();
// classEntity
expr.writePushDup();
expr.writePushConstString(compiler.getModule().getInternalName());
expr.writePushConstInt((int) entity.getGeneratorEntity().getId());
expr.writeSysDynamicCall(Environment.class, "__getGenerator", ClassEntity.class, String.class, Integer.TYPE);
// self
expr.writePushThis();
// uses
expr.writeVarLoad("~args");
expr.writeSysCall(
entity.getGeneratorEntity().getInternalName(), INVOKESPECIAL, Constants.INIT_METHOD, void.class,
Environment.class, ClassEntity.class, Memory.class, Memory[].class
);
expr.writeSysStaticCall(ObjectMemory.class, "valueOf", Memory.class, IObject.class);
expr.makeUnknown(new InsnNode(Opcodes.ARETURN));
expr.stackPop();
} else {
ExpressionStmtCompiler expr = new ExpressionStmtCompiler(this, null);
entity.setEmpty(true);
if (statement != null && statement.getBody() != null) {
expr.writeDefineVariables(statement.getLocal());
expr.write(statement.getBody());
if (!statement.getBody().getInstructions().isEmpty()) {
entity.setEmpty(false);
}
}
if (generatorEntity != null) {
expr.writeVarLoad("~this");
expr.writePushConstBoolean(false);
expr.writeSysDynamicCall(null, "_setValid", void.class, Boolean.TYPE);
}
ReturnStmtToken token = new ReturnStmtToken(new TokenMeta("", 0, 0, 0, 0));
token.setValue(null);
expr.getCompiler(ReturnStmtToken.class).write(token);
}
writeFooter();
}
return entity;
}
public static class TryCatchItem {
private final TryStmtToken token;
private final LabelNode returnLabel;
public TryCatchItem(TryStmtToken token, LabelNode returnLabel) {
this.token = token;
this.returnLabel = returnLabel;
}
public TryStmtToken getToken() {
return token;
}
public LabelNode getReturnLabel() {
return returnLabel;
}
}
}