package fr.ensimag.deca.tree;
import java.io.PrintStream;
import org.apache.commons.lang.Validate;
import fr.ensimag.deca.DecacCompiler;
import fr.ensimag.deca.context.ClassDefinition;
import fr.ensimag.deca.context.ContextualError;
import fr.ensimag.deca.context.VariableDefinition;
import fr.ensimag.deca.context.ExpDefinition;
import fr.ensimag.deca.context.MethodDefinition;
import fr.ensimag.deca.context.EnvironmentExp;
import fr.ensimag.deca.context.Signature;
import fr.ensimag.deca.context.Type;
import fr.ensimag.deca.tools.IndentPrintStream;
import fr.ensimag.deca.tools.SymbolTable.Symbol;
import fr.ensimag.ima.pseudocode.Label;
import fr.ensimag.ima.pseudocode.Register;
import fr.ensimag.ima.pseudocode.RegisterOffset;
import fr.ensimag.ima.pseudocode.ImmediateString;
import fr.ensimag.ima.pseudocode.instructions.ADDSP;
import fr.ensimag.ima.pseudocode.instructions.BOV;
import fr.ensimag.ima.pseudocode.instructions.RTS;
import fr.ensimag.ima.pseudocode.instructions.SUBSP;
import fr.ensimag.ima.pseudocode.instructions.TSTO;
import fr.ensimag.ima.pseudocode.instructions.WNL;
import fr.ensimag.ima.pseudocode.instructions.WSTR;
import fr.ensimag.ima.pseudocode.instructions.ERROR;
/**
* Declaration of a method
*
* @author gl57
* @date 01/01/2026
*/
public class DeclMethod extends AbstractDeclMethod {
private final AbstractIdentifier returnType;
private final AbstractIdentifier methodName;
private final ListDeclParam params;
private final MethodBody body; // null if ASM
public DeclMethod(AbstractIdentifier returnType, AbstractIdentifier methodName,
ListDeclParam params, MethodBody body) {
Validate.notNull(returnType);
Validate.notNull(methodName);
Validate.notNull(params);
this.returnType = returnType;
this.methodName = methodName;
this.params = params;
this.body = body;
}
public AbstractIdentifier getReturnType() {
return returnType;
}
public AbstractIdentifier getMethodName() {
return methodName;
}
public ListDeclParam getParams() {
return params;
}
public MethodBody getBody() {
return body;
}
@Override
protected void verifyDeclMethod(DecacCompiler compiler,
ClassDefinition currentClass) throws ContextualError {
Type typeDeRetour = returnType.verifyType(compiler);
EnvironmentExp localEnv = new EnvironmentExp(null);
Signature signature = getParams().verifyListParam(compiler, localEnv, currentClass);
Symbol methodSymb = methodName.getName();
EnvironmentExp environnementMembres = currentClass.getMembers();
ExpDefinition existingDef = environnementMembres.get(methodSymb);
if (existingDef != null) {
if (!existingDef.isMethod()) {
throw new ContextualError("Method '" + methodSymb + "' conflicts with non-method definition", methodName.getLocation());
}
MethodDefinition superMethod = existingDef.asMethodDefinition("", methodName.getLocation());
Signature superSig = superMethod.getSignature();
Type superReturnType = superMethod.getType();
if (!signaturesEqual(signature, superSig)) {
throw new ContextualError("Method '" + methodSymb + "' has different signature than superclass", methodName.getLocation());
}
if (!typeDeRetour.isSubTypeOf(superReturnType, compiler.environmentType)) {
throw new ContextualError("Method '" + methodSymb + "' return type must be a subtype of superclass return type",methodName.getLocation());
}
}
int methodIndex = currentClass.getNumberOfMethods();
MethodDefinition methodDef = new MethodDefinition(typeDeRetour, getLocation(), signature, methodIndex);
try {
environnementMembres.declare(methodSymb, methodDef);
} catch (EnvironmentExp.DoubleDefException e) {
throw new ContextualError("Method '" + methodSymb + "' is already declared in this class", methodName.getLocation());
// on décore le methodName avec la définition de la méthode
}
methodName.setDefinition(methodDef);
methodName.setType(typeDeRetour);
currentClass.incNumberOfMethods();
}
private boolean signaturesEqual(Signature sig1, Signature sig2) {
if (sig1.size() != sig2.size()) {
return false;
}
for (int i = 0; i < sig1.size(); i++) {
if (!sig1.paramNumber(i).sameType(sig2.paramNumber(i))) {
return false;
}
}
return true;
}
@Override
protected void verifyMethodBody(DecacCompiler compiler,
ClassDefinition currentClass) throws ContextualError {
if (body == null) {
// si il n'y a pas de body, on fait rien
return;
}
// Même logique que pour verifyDeclMethod
// on vérifie le type de retour
Type typeDeRetour = returnType.verifyType(compiler);
EnvironmentExp environnementMembres = currentClass.getMembers();
// Création un nouvel environnement pour la méthode
EnvironmentExp methodEnv = new EnvironmentExp(environnementMembres);
for (DeclParam param : getParams().getList()) {
Type paramType = param.getType().verifyType(compiler);
// Pas possible d'avoir un paramètre void
if (paramType.isVoid()) {
throw new ContextualError("Parameter cannot be declared void", param.getType().getLocation());
}
// on crée une définition de variable pour le paramètre
VariableDefinition paramDef = new VariableDefinition(paramType, param.getLocation());
// on déclare le paramètre dans l'environnement de la méthode
try {
methodEnv.declare(param.getName().getName(), paramDef);
} catch (EnvironmentExp.DoubleDefException e) {
throw new ContextualError("Duplicate parameter: " + param.getName().getName(), param.getName().getLocation());
}
param.getName().setDefinition(paramDef);
}
body.verifyMethodBody(compiler, methodEnv, currentClass, typeDeRetour);
}
void codeGenMethod(DecacCompiler compiler, ClassDefinition currentClass) {
MethodDefinition methodDef = methodName.getMethodDefinition();
Label methodLabel = methodDef.getLabel();
if (methodLabel == null) {
methodLabel = new Label("code." + currentClass.getType().getName().getName()
+ "." + methodName.getName().getName());
methodDef.setLabel(methodLabel);
}
Label endLabel = new Label("fin." + currentClass.getType().getName().getName()
+ "." + methodName.getName().getName());
compiler.addLabel(methodLabel);
compiler.enterMethod(endLabel);
int nVar = 0;
if (body != null) {
nVar = body.getDeclVariables().getList().size();
}
if (nVar > 0) {
compiler.addInstruction(new TSTO(nVar));
compiler.addInstruction(new BOV(new Label("stack_overflow_error")));
compiler.addInstruction(new ADDSP(nVar));
}
int paramOffset = -3;
for (DeclParam param : params.getList()) {
VariableDefinition def = param.getName().getVariableDefinition();
def.setOperand(new RegisterOffset(paramOffset, Register.LB));
paramOffset--;
}
if (body != null) {
body.codeGenMethodBody(compiler, currentClass, returnType.getType());
}
if (!returnType.getType().isVoid()) {
compiler.addInstruction(new WSTR(new ImmediateString(
"Error: method " + currentClass.getType().getName().getName()
+ "." + methodName.getName().getName() + " ended without return")));
compiler.addInstruction(new WNL());
compiler.addInstruction(new ERROR());
}
compiler.addLabel(endLabel);
if (nVar > 0) {
compiler.addInstruction(new SUBSP(nVar));
}
compiler.addInstruction(new RTS());
compiler.exitMethod();
}
@Override
public void decompile(IndentPrintStream s) {
returnType.decompile(s);
s.print(" ");
methodName.decompile(s);
s.print("(");
params.decompile(s);
s.print(") ");
if (body != null) {
body.decompile(s);
} else {
s.print("asm { ... }");
}
}
@Override
protected void iterChildren(TreeFunction f) {
returnType.iter(f);
methodName.iter(f);
params.iter(f);
if (body != null) {
body.iter(f);
}
}
@Override
protected void prettyPrintChildren(PrintStream s, String prefix) {
returnType.prettyPrint(s, prefix, false);
methodName.prettyPrint(s, prefix, false);
params.prettyPrint(s, prefix, false);
if (body != null) {
body.prettyPrint(s, prefix, true);
}
}
}