/*
* Copyright 2010 Pablo Arrighi, Alex Concha, Miguel Lezama for version 1.
* Copyright 2013 Pablo Arrighi, Miguel Lezama, Kevin Mazet for version 2.
*
* This file is part of GOOL.
*
* GOOL is free software: you can redistribute it and/or modify it under the terms of the GNU
* General Public License as published by the Free Software Foundation, version 3.
*
* GOOL 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 3 for more details.
*
* You should have received a copy of the GNU General Public License along with GOOL,
* in the file COPYING.txt. If not, see <http://www.gnu.org/licenses/>.
*/
package gool.generator.common;
import gool.ast.core.ArrayAccess;
import gool.ast.core.ArrayNew;
import gool.ast.core.Assign;
import gool.ast.core.BinaryOperation;
import gool.ast.core.Block;
import gool.ast.core.CastExpression;
import gool.ast.core.ClassDef;
import gool.ast.core.ClassFree;
import gool.ast.core.Comment;
import gool.ast.core.CompoundAssign;
import gool.ast.core.Constant;
import gool.ast.core.Constructor;
import gool.ast.core.ExpressionUnknown;
import gool.ast.core.Field;
import gool.ast.core.FieldAccess;
import gool.ast.core.For;
import gool.ast.core.GoolCall;
import gool.ast.core.Identifier;
import gool.ast.core.If;
import gool.ast.core.ListMethCall;
import gool.ast.core.MapEntryMethCall;
import gool.ast.core.MapMethCall;
import gool.ast.core.MemberSelect;
import gool.ast.core.Meth;
import gool.ast.core.MethCall;
import gool.ast.core.Modifier;
import gool.ast.core.NewInstance;
import gool.ast.core.Operator;
import gool.ast.core.Package;
import gool.ast.core.RecognizedDependency;
import gool.ast.core.Return;
import gool.ast.core.Statement;
import gool.ast.core.This;
import gool.ast.core.ThisCall;
import gool.ast.core.TypeDependency;
import gool.ast.core.UnImplemented;
import gool.ast.core.UnaryOperation;
import gool.ast.core.UnrecognizedDependency;
import gool.ast.core.VarAccess;
import gool.ast.core.VarDeclaration;
import gool.ast.core.While;
import gool.ast.type.TypeArray;
import gool.ast.type.TypeByte;
import gool.ast.type.TypeChar;
import gool.ast.type.TypeClass;
import gool.ast.type.TypeGoolLibraryClass;
import gool.ast.type.TypeMethod;
import gool.ast.type.TypeNone;
import gool.ast.type.TypeNull;
import gool.ast.type.TypePackage;
import gool.ast.type.TypeString;
import gool.ast.type.TypeUnknown;
import gool.ast.type.TypeVar;
import gool.ast.type.TypeVoid;
import gool.generator.GeneratorHelper;
import gool.recognizer.common.RecognizerMatcher;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
/**
* Basic code generator. It does what is common to the code generation of all
* output object oriented languages.
*/
public abstract class CommonCodeGenerator implements CodeGenerator {
/**
* String used to produce one level of indentation. Can be overwritten in
* the constructor of the concrete generator.
*/
protected String indentation = "\t";
/**
* <pre>
* Produce indented code in a manner similar to printf but with custom conversions.
* %% a single "%"
* %s Print an argument as a string, without indentation or newlines added.
* Similar to the corresponding flag of <i>String.format</i>.
* %<i>n</i> (where <i>n</i> is a digit)
* Print an argument as a bloc indented <i>n</i> times from the current indentation level.
* Newlines are inserted before and after the bloc.
* %-<i>n</i> (where <i>n</i> is a digit)
* <i>n</i> times the indentation string, does not consumes a argument.
* %-0 becomes a empty string (it does nothing but is still parsed)
*
* @param format
* the format string
* @param arguments
* the objects to format, each one corresponding to a % code
* @return the formated string
*/
protected String formatIndented(String format, Object... arguments) {
StringBuilder sb = new StringBuilder(format);
int pos = sb.indexOf("%");
int arg = 0;
while (pos != -1) {
if (sb.charAt(pos + 1) == '%') {
sb = sb.replace(pos, pos + 2, "%");
} else if (sb.charAt(pos + 1) == 's') {
sb = sb.replace(pos, pos + 2, arguments[arg].toString());
pos += arguments[arg].toString().length() - 1;
arg++;
} else if (Character.isDigit(sb.charAt(pos + 1))) {
String replacement = ("\n" + arguments[arg].toString()
.replaceFirst("\\s*\\z", ""))
.replace(
"\n",
"\n"
+ StringUtils.repeat(
indentation,
Character.digit(
sb.charAt(pos + 1), 10)))
+ "\n";
sb = sb.replace(pos, pos + 2, replacement);
pos += replacement.length() - 1;
arg++;
} else if (sb.charAt(pos + 1) == '-'
&& Character.isDigit(sb.charAt(pos + 2))) {
String replacement = StringUtils.repeat(indentation,
Character.digit(sb.charAt(pos + 2), 10));
sb = sb.replace(pos, pos + 3, replacement);
pos += replacement.length();
}
pos = sb.indexOf("%", pos);
}
return sb.toString();
}
@Override
public String getCode(Identifier identifier) {
return identifier.getName();
}
@Override
public String getCode(ArrayAccess arrayAccess) {
return String.format("%s[%s]", arrayAccess.getExpression(),
arrayAccess.getIndex());
}
@Override
public String getCode(ArrayNew arrayNew) {
return String.format("new %s[%s]", arrayNew.getType(),
StringUtils.join(arrayNew.getDimesExpressions(), ", "));
}
/**
* Produces code for an assign statement.
*
* @param assign
* the assign statement.
* @return the formatted assign statement.
*/
@Override
public String getCode(Assign assign) {
return assign.getLValue() + " = " + assign.getValue();
}
@Override
public String getCode(CompoundAssign compoundAssign) {
return String
.format("%s %s=%s %s",
compoundAssign.getLValue(),
compoundAssign.getTextualoperator(),
compoundAssign.getOperator().equals(Operator.UNKNOWN) ? "/* Unrecognized by GOOL, passed on */"
: "", compoundAssign.getValue());
}
/**
* Produces code for a binary operation.
*
* @param binaryOp
* a binary operation.
* @return the formatted binary operation.
*/
@Override
public String getCode(BinaryOperation binaryOp) {
return String
.format("(%s %s%s %s)",
binaryOp.getLeft(),
binaryOp.getTextualoperator(),
binaryOp.getOperator().equals(Operator.UNKNOWN) ? "/* Unrecognized by GOOL, passed on */"
: "", binaryOp.getRight());
}
/**
* Produces code for a block of statements.
*
* @param block
* the block of statements.
* @return the formatted block of statements.
*/
@Override
public String getCode(Block block) {
StringBuilder result = new StringBuilder();
for (Statement statement : block.getStatements()) {
result.append(statement);
if (!(statement instanceof Block)) {
result.append(";").append("\n");
}
}
return result.toString();
}
/**
* Produces code for a cast expression.
*
* @param cast
* the cast expression.
* @return the formatted cast expression.
*/
@Override
public String getCode(CastExpression cast) {
return String.format("((%s) (%s))", cast.getType(),
cast.getExpression());
}
/**
* Generates code for a GOOL comment block.
*
* @param comment
* the comment to be generated.
*/
@Override
public String getCode(Comment comment) {
return String.format("/*\n%s\n*/", comment.getValue());
}
/**
* Produces code for a constant.
*
* @param constant
* a constant value.
* @return the formatted constant value.
*/
@Override
public String getCode(Constant constant) {
if (constant.getType() instanceof TypeArray) {
StringBuffer sb = new StringBuffer();
int size = Array.getLength(constant.getValue());
boolean escape = ((TypeArray) constant.getType()).getElementType() instanceof TypeString;
sb.append("{");
for (int i = 0; i < size; i++) {
if (escape) {
return "\""
+ StringEscapeUtils.escapeJava(Array.get(
constant.getValue(), i).toString()) + "\"";
} else {
sb.append(Array.get(constant.getValue(), i));
}
sb.append(",");
}
sb.append("}");
return sb.toString();
} else if (constant.getType() == TypeString.INSTANCE) {
return "\""
+ StringEscapeUtils.escapeJava(constant.getValue()
.toString()) + "\"";
} else if (constant.getType() == TypeChar.INSTANCE) {
return "'"
+ StringEscapeUtils.escapeJava(constant.getValue()
.toString()) + "'";
}
return constant.getValue().toString();
}
@Override
public String getCode(Constructor cons) {
return getCode((Meth) cons);
}
/**
* Produces code for a field, i.e. a class attribute declaration.
*
* @param field
* the abstract GOOL field
* @return the string corresponding to such a declaration in the concrete
* target language
*/
@Override
public String getCode(Field field) {
String out = String.format("%s %s %s", getCode(field.getModifiers()),
field.getType(), field.getName());
if (field.getType().toString().equals("noprint"))
return "";
if (field.getDefaultValue() != null) {
// Notice that this will call a toString() on the field.defaultValue
// Which will become a JavaGenerator.getCode(defaultValue)
// Hence this seemingly simple statement
// Is in fact a recursive descent on the abstract GOOL tree.
out = String.format("%s = %s", out, field.getDefaultValue());
}
return out;
}
@Override
public String getCode(FieldAccess sfa) {
return sfa.getTarget() + "." + sfa.getMember();
}
@Override
public String getCode(For forInstruction) {
return formatIndented("for (%s ; %s ; %s) {%1}",
forInstruction.getInitializer(), forInstruction.getCondition(),
forInstruction.getUpdater(), forInstruction.getWhileStatement());
}
@Override
public String getCode(GoolCall goolCall) {
throw new IllegalStateException(String.format(
"Invalid unimplemented Gool Method: (%s).",
goolCall.getMethod()));
}
/**
* Produces code for an if statement.
*
* @param pif
* the if statement.
* @return the formatted if statement.
*/
@Override
public String getCode(If pif) {
String out = formatIndented("if (%s) {%1}", pif.getCondition(),
pif.getThenStatement());
if (pif.getElseStatement() != null) {
if (pif.getElseStatement() instanceof If)
out += formatIndented(" else %s", pif.getElseStatement());
else
out += formatIndented(" else {%1}", pif.getElseStatement());
}
return out;
}
@Override
public String getCode(Collection<Modifier> modifiers) {
StringBuilder sb = new StringBuilder();
for (Modifier modifier : modifiers) {
sb.append(getCode(modifier)).append(" ");
}
return sb.toString().trim();
}
@Override
public String getCode(ListMethCall lmc) {
return "===ListMethCall====";
}
@Override
public String getCode(MapEntryMethCall mapEntryMethCall) {
throw new IllegalStateException("Unsupported MapEntryMethCall: "
+ mapEntryMethCall.getExpression());
}
@Override
public String getCode(MapMethCall mapMethCall) {
throw new IllegalStateException(String.format(
"Invalid method call over maps (%s).",
mapMethCall.getExpression()));
}
@Override
public String getCode(MemberSelect memberSelect) {
return String.format("%s.%s", memberSelect.getTarget(),
memberSelect.getIdentifier());
}
@Override
public String getCode(Meth meth) {
return String.format("%s %s %s (%s)", getCode(meth.getModifiers()),
meth.getType(), meth.getName(),
StringUtils.join(meth.getParams(), ", "));
}
/**
* Produces code for a method invocation.
*
* @param methodCall
* the method to be invoked.
* @return the formatted method invocation.
*/
@Override
public String getCode(MethCall methodCall) {
String target = methodCall.getTarget().toString();
String goolMethod = methodCall.getGoolLibraryMethod();
if(goolMethod!=null){
//here, get matched output method name with the GeneratorMatcher
String methodName=GeneratorMatcher.matchGoolMethod(goolMethod);
if(methodName!=null)
target=target.substring(0, target.lastIndexOf(".")+1)+methodName;
}
return String.format("%s (%s)", target,
StringUtils.join(methodCall.getParameters(), ", "));
}
@Override
public String getCode(Modifier modifier) {
return modifier.name().toLowerCase();
}
/**
* Produces code for an object instantiation. This is different from
* ClassNew in the sense that it includes a variable declaration and
* assignment in the same line.
*
* @param newInstance
* the object instantiation.
* @return the formatted object instantiation.
*/
@Override
public String getCode(NewInstance newInstance) {
return String.format(
"%s = new %s( %s )",
newInstance.getVariable(),
newInstance.getVariable().getType().toString()
.replaceAll("\\*$", ""),
StringUtils.join(newInstance.getParameters(), ", "));
}
/**
* Produces code for a return statement.
*
* @param returnExpr
* the return statement.
* @return the formatted return statement.
*/
@Override
public String getCode(Return returnExpr) {
return String.format("return (%s)", returnExpr.getExpression());
}
/**
* Produces code for the reference to the current object.
*
* @param pthis
* the reference to the current object.
* @return the formatted self reference expression.
*/
@Override
public String getCode(This pthis) {
return "this";
}
@Override
public String getCode(TypeByte typeByte) {
return "byte";
}
/**
* @param typeClass
* the class to be formatted.
* @return the formatted class type.
*/
@Override
public String getCode(TypeClass typeClass) {
String code = "";
if (typeClass.getPackageName() != null) {
code += typeClass.getPackageName() + ".";
}
code += typeClass.getName();
return code;
}
@Override
public String getCode(TypeDependency typeDependency) {
return typeDependency.getType().toString();
}
/**
* Produces code for the pseudo-type.
*
* @param type
* a pseudo-type.
* @return an empty string.
*/
@Override
public String getCode(TypeNone type) {
return "";
}
@Override
public String getCode(TypeNull typeNull) {
return "null";
}
/**
* Produces code for a type that does not return anything.
*
* @param typeVoid
* the void type
* @return the formatted void type.
*/
@Override
public String getCode(TypeVoid typeVoid) {
return "void";
}
@Override
public String getCode(UnaryOperation unaryOperation) {
switch (unaryOperation.getOperator()) {
case POSTFIX_DECREMENT:
case POSTFIX_INCREMENT:
return String
.format("(%s)%s%s",
unaryOperation.getExpression(),
unaryOperation.getTextualoperator(),
unaryOperation.getOperator().equals(
Operator.UNKNOWN) ? "/* Unrecognized by GOOL, passed on */"
: "");
default:
return String
.format("%s%s(%s)",
unaryOperation.getTextualoperator(),
unaryOperation.getOperator().equals(
Operator.UNKNOWN) ? "/* Unrecognized by GOOL, passed on */"
: "", unaryOperation.getExpression());
}
}
/**
* Produces code for a variable declaration.
*
* @param varDec
* the variable to be declared.
* @return the formatted variable declaration.
*/
@Override
public String getCode(VarDeclaration varDec) {
String initialValue = "";
if (varDec.getInitialValue() != null) {
initialValue = " = " + varDec.getInitialValue();
}
return String.format("%s %s%s", varDec.getType(), varDec.getName(),
initialValue);
}
@Override
public String getCode(VarAccess varAccess) {
return varAccess.getDec().getName();
}
@Override
public String getCode(While whilee) {
return formatIndented("while (%s) {%1}", whilee.getCondition(),
whilee.getWhileStatement());
}
@Override
public String getCode(TypeArray typeArray) {
return String.format("%s[]", typeArray.getElementType());
}
@Override
public String getCode(ThisCall thisCall) {
return String.format("this (%s)",
GeneratorHelper.joinParams(thisCall.getParameters()));
}
@Override
public String getCode(TypeUnknown typeUnknown) {
return String.format("%s", typeUnknown.getTextualtype())
+ " /* Unrecognized by GOOL, passed on */";
}
@Override
public String getCode(ExpressionUnknown unknownExpression) {
return String.format("%s /* Unrecognized by GOOL, passed on */",
unknownExpression.getTextual());
}
@Override
public String getCode(ClassFree classFree) {
return "free /* Not Implemented, passed on by GOOL */";
}
@Override
public String getCode(Platform platform) {
return platform.getName();
}
@Override
public String getCode(ClassDef classDef) {
return String.format("%s.%s", classDef.getPackageName(),
classDef.getName());
}
@Override
public String getCode(Package _package) {
return _package.getName();
}
@Override
public String getCode(TypePackage typePackage) {
return typePackage.getTextualtype();
}
@Override
public String getCode(TypeVar typeVar) {
// For now if one wants to print the type of a TypeVar, this returns
// just the name of the TypeVar.
return typeVar.getTextualtype();
}
@Override
public String getCode(TypeMethod typeMethod) {
// For now if one wants to print the type of a Method, this returns just
// the name of the method.
return typeMethod.getTextualtype();
}
@Override
public String getCode(TypeGoolLibraryClass typeMatchedGoolClass) {
String res = GeneratorMatcher.matchGoolClass(typeMatchedGoolClass
.getGoolclassname());
if (res == null)
return typeMatchedGoolClass.getGoolclassname()
+ " /* Ungenerated by GOOL, passed on. */";
else {
return res;
}
}
public String getCode(UnrecognizedDependency unrecognizedDependency) {
return "/* "+ unrecognizedDependency.getName()
+ " unrecognized by GOOL, passed on. */";
}
public String getCode(UnImplemented unImplemented) {
return "/* "+ unImplemented.getCommentUnImplemented()
+ " unimplemented by GOOL, passed on : "
+ unImplemented.getCodeUnImplemented()
+ " */";
}
}