/*
* 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.python;
import gool.ast.core.ArrayNew;
import gool.ast.core.BinaryOperation;
import gool.ast.core.Block;
import gool.ast.core.CastExpression;
import gool.ast.core.Catch;
import gool.ast.core.ClassDef;
import gool.ast.core.ClassFree;
import gool.ast.core.ClassNew;
import gool.ast.core.Comment;
import gool.ast.core.CompoundAssign;
import gool.ast.core.Constant;
import gool.ast.core.Constructor;
import gool.ast.core.CustomDependency;
import gool.ast.core.Dependency;
import gool.ast.core.EnhancedForLoop;
import gool.ast.core.EqualsCall;
import gool.ast.core.Expression;
import gool.ast.core.ExpressionUnknown;
import gool.ast.core.Field;
import gool.ast.core.For;
import gool.ast.core.If;
import gool.ast.core.InitCall;
import gool.ast.core.MainMeth;
import gool.ast.core.MapEntryMethCall;
import gool.ast.core.MapMethCall;
import gool.ast.core.MemberSelect;
import gool.ast.core.Meth;
import gool.ast.core.Modifier;
import gool.ast.core.NewInstance;
import gool.ast.core.Operator;
import gool.ast.core.ParentCall;
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.Throw;
import gool.ast.core.ToStringCall;
import gool.ast.core.Try;
import gool.ast.core.TypeDependency;
import gool.ast.core.UnaryOperation;
import gool.ast.core.VarAccess;
import gool.ast.core.VarDeclaration;
import gool.ast.core.While;
import gool.ast.list.ListAddCall;
import gool.ast.list.ListContainsCall;
import gool.ast.list.ListGetCall;
import gool.ast.list.ListGetIteratorCall;
import gool.ast.list.ListIsEmptyCall;
import gool.ast.list.ListRemoveAtCall;
import gool.ast.list.ListRemoveCall;
import gool.ast.list.ListSizeCall;
import gool.ast.map.MapContainsKeyCall;
import gool.ast.map.MapEntryGetKeyCall;
import gool.ast.map.MapEntryGetValueCall;
import gool.ast.map.MapGetCall;
import gool.ast.map.MapGetIteratorCall;
import gool.ast.map.MapIsEmptyCall;
import gool.ast.map.MapPutCall;
import gool.ast.map.MapRemoveCall;
import gool.ast.map.MapSizeCall;
import gool.ast.system.SystemCommandDependency;
import gool.ast.system.SystemOutDependency;
import gool.ast.system.SystemOutPrintCall;
import gool.ast.type.IType;
import gool.ast.type.TypeArray;
import gool.ast.type.TypeBool;
import gool.ast.type.TypeByte;
import gool.ast.type.TypeChar;
import gool.ast.type.TypeDecimal;
import gool.ast.type.TypeEntry;
import gool.ast.type.TypeException;
import gool.ast.type.TypeInt;
import gool.ast.type.TypeList;
import gool.ast.type.TypeMap;
import gool.ast.type.TypeNone;
import gool.ast.type.TypeNull;
import gool.ast.type.TypeObject;
import gool.ast.type.TypeString;
import gool.ast.type.TypeUnknown;
import gool.ast.type.TypeVoid;
import gool.generator.GeneratorHelper;
import gool.generator.common.CodeGeneratorNoVelocity;
import gool.generator.common.CommonCodeGenerator;
import gool.generator.common.GeneratorMatcher;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import logger.Log;
import org.apache.commons.lang.StringUtils;
public class PythonGenerator extends CommonCodeGenerator implements
CodeGeneratorNoVelocity {
public PythonGenerator() {
super();
// As a tradition, Python is indented with 4 spaces
indentation = " ";
}
/**
* The class currently printed. Updated every time we start to print a new
* class.
*/
private ClassDef currentClass;
/**
* The class currently printed. Updated every time we start to print a new
* method.
*/
private Meth currentMeth;
/**
* Used to store comments. Used by the 'comment' and 'printWithComment'
* methods.
*/
private ArrayList<String> comments = new ArrayList<String>();
/**
* The local variables (and parameters) of the method currently printed. The
* names in this list are not prefixed with 'self.' Emptied every time we
* start to print a new method.
*/
private ArrayList<String> localIndentifiers = new ArrayList<String>();
/**
* Register a comment to be printed alongside the statement being parsed.
*
* @param newcomment
* string without '#' nor newline
*/
private void comment(String newcomment) {
String com = "# " + newcomment + "\n";
if (!comments.contains(com))
comments.add(com);
}
/**
* Print a statement with optional comments taken from the
* 'PytonGenerator.comments'. If only one comment is present, it is put at
* the end of the line, otherwise they are added before the statement.
* 'PytonGenerator.comments' is emptied.
*
* @param statement
* @return the corresponding python code
*/
private String printWithComment(Object statement) {
String sttmnt = statement.toString().replaceFirst("\\s*\\z", "");
if (comments.size() == 1 && !sttmnt.contains("\n")) {
sttmnt += " " + comments.get(0);
} else {
sttmnt = StringUtils.join(comments, "") + sttmnt + "\n";
}
comments.clear();
return sttmnt;
}
/**
* Holds the names of every method as they are outputed. Used to rename
* methods.
*/
private static Map<Meth, String> methodsNames = new HashMap<Meth, String>();
private static Map<String, Dependency> customDependencies = new HashMap<String, Dependency>();
private String getName(Meth meth) {
return methodsNames.get(meth);
}
@Override
public void addCustomDependency(String key, Dependency value) {
customDependencies.put(key, value);
}
@Override
public String getCode(ArrayNew arrayNew) {
// a newly declared array is a list of nulls
// or a list of list ... of nulls for multidimensional arrays
String ret = "None";
for (Expression e : arrayNew.getDimesExpressions())
ret = String.format("[%s]*%s", ret, e);
return "(" + ret + ")";
}
@Override
public String getCode(Block block) {
StringBuilder result = new StringBuilder();
for (Statement statement : block.getStatements()) {
if (statement.toString().contains(
"goolHelper.Util.Scanner(System.in"))
Log.e("mon stat :" + statement.toString());
result.append(printWithComment(statement));
}
return result.toString();
}
@Override
public String getCode(BinaryOperation binaryOp) {
String textualOp;
switch (binaryOp.getOperator()) {
case AND:
textualOp = "and";
break;
case OR:
textualOp = "or";
break;
case DIV:
if (binaryOp.getType().equals(TypeInt.INSTANCE))
// for compatibility with different versions of Python
textualOp = "//";
else
textualOp = "/";
break;
case PLUS:
if (binaryOp.getLeft().getType().getName().equals("str")
&& !binaryOp.getRight().getType().getName().equals("str")) {
return String.format("(%s %s str(%s))", binaryOp.getLeft(),
"+", binaryOp.getRight());
} else if (binaryOp.getRight().getType().getName().equals("str")
&& !binaryOp.getLeft().getType().getName().equals("str")) {
return String.format("(str(%s) %s %s)", binaryOp.getLeft(),
"+", binaryOp.getRight());
} else {
textualOp = binaryOp.getTextualoperator();
}
break;
default:
textualOp = binaryOp.getTextualoperator();
}
if (binaryOp.getOperator().equals(Operator.UNKNOWN))
comment("Unrecognized by GOOL, passed on: " + textualOp);
return String.format("(%s %s %s)", binaryOp.getLeft(), textualOp,
binaryOp.getRight());
}
@Override
public String getCode(Constant constant) {
if (constant.getType().equals(TypeBool.INSTANCE)) {
return String.valueOf(constant.getValue().toString()
.equalsIgnoreCase("true") ? "True" : "False");
} else {
String ret = super.getCode(constant);
// unicode strings have to be prefixed with a 'u'
if (constant.getType() == TypeString.INSTANCE
&& ret.contains("\\u")) {
return "u" + ret;
} else {
return ret;
}
}
}
/**
* Types to witch a cast is necessary. As Python is dynamically typed, it
* only need type casting for conversions.
*/
static private ArrayList<Class<? extends IType>> castableTypes = new ArrayList<Class<? extends IType>>();
static {
castableTypes.add(TypeArray.class);
castableTypes.add(TypeBool.class);
castableTypes.add(TypeByte.class);
castableTypes.add(TypeChar.class);
castableTypes.add(TypeInt.class);
castableTypes.add(TypeDecimal.class);
castableTypes.add(TypeList.class);
castableTypes.add(TypeMap.class);
castableTypes.add(TypeString.class);
}
@Override
public String getCode(CastExpression cast) {
if (castableTypes.contains(cast.getType().getClass())) {
return String
.format("%s(%s)", cast.getType(), cast.getExpression());
} else {
return cast.getExpression().toString();
}
}
@Override
public String getCode(ClassNew classNew) {
if (classNew.getName().equals("goolHelper.Util.Scanner"))
return String.format("%s()", classNew.getName());
return String.format("%s(%s)", classNew.getType(),
StringUtils.join(classNew.getParameters(), ", "));
}
@Override
public String getCode(Comment comment) {
// only works with comments that are alone on their line(s)
return comment.getValue().replaceAll("(^ *)([^ ])", "$1# $2");
}
@Override
public String getCode(EnhancedForLoop enhancedForLoop) {
// foreach-style loops define a local variable, we register it
localIndentifiers.add(enhancedForLoop.getVarDec().getName());
if (enhancedForLoop.getExpression().getType() instanceof TypeMap)
return formatIndented("for %s in %s.iteritems():%1",
enhancedForLoop.getVarDec().getName(),
enhancedForLoop.getExpression(),
enhancedForLoop.getStatements());
return formatIndented("for %s in %s:%1", enhancedForLoop.getVarDec()
.getName(), enhancedForLoop.getExpression(),
enhancedForLoop.getStatements());
}
@Override
public String getCode(EqualsCall equalsCall) {
return String.format("%s == %s", equalsCall.getTarget(), equalsCall
.getParameters().get(0));
}
@Override
public String getCode(Field field) {
String value;
if (field.getDefaultValue() != null) {
value = field.getDefaultValue().toString();
} else {
value = "None";
}
return printWithComment(String.format("%s = %s\n", field.getName(),
value));
}
@Override
public String getCode(For forr) {
// there is no 'for(;;)' construct in Python
return formatIndented(
"%swhile %s:%1",
printWithComment(forr.getInitializer()),
forr.getCondition(),
forr.getWhileStatement().toString()
+ printWithComment(forr.getUpdater()));
}
@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) {
// the concatenation with the next 'if' produces a 'elif'
out += formatIndented("el%s", pif.getElseStatement());
} else {
out += formatIndented("else:%1", pif.getElseStatement());
}
}
return out;
}
@Override
public String getCode(Collection<Modifier> modifiers) {
// there are no modifiers in Python
return "";
}
@Override
public String getCode(ListAddCall lac) {
switch (lac.getParameters().size()) {
case 1:
return String.format("%s.append(%s)", lac.getExpression(), lac
.getParameters().get(0));
case 2:
return String.format("%s.insert(%s, %s)", lac.getExpression(), lac
.getParameters().get(1), lac.getParameters().get(0));
default:
comment("Unrecognized by GOOL, passed on: add");
return String.format("%s.add(%s)", lac.getExpression(),
StringUtils.join(lac.getParameters(), ", "));
}
}
@Override
public String getCode(ListContainsCall lcc) {
return String.format("%s in %s", lcc.getParameters().get(0),
lcc.getExpression());
}
@Override
public String getCode(ListGetCall lgc) {
return String.format("%s[%s]", lgc.getExpression(), lgc.getParameters()
.get(0));
}
@Override
public String getCode(ListGetIteratorCall lgic) {
return String.format("iter(%s)", lgic.getExpression());
}
@Override
public String getCode(ListIsEmptyCall liec) {
// An empty list is a list whose boolean value is false.
// It is the official recommended 'pythonic' way to do it.
return String.format("(not %s)", liec.getExpression());
}
@Override
public String getCode(ListRemoveAtCall lrc) {
return String.format("%s.pop(%s)", lrc.getExpression(),
StringUtils.join(lrc.getParameters(), ", "));
}
@Override
public String getCode(ListRemoveCall lrc) {
if (!lrc.getType().getTypeArguments().contains("[Int]"))
return String.format("%s.remove(%s)", lrc.getExpression(),
StringUtils.join(lrc.getParameters(), ", "));
else
return String.format("%s.pop(%s)", lrc.getExpression(),
StringUtils.join(lrc.getParameters(), ", "));
}
@Override
public String getCode(ListSizeCall lsc) {
return String.format("len(%s)", lsc.getExpression());
}
@Override
public String getCode(MainMeth mainMeth) {
// the 'main' is not a method in python
return mainMeth.getBlock().toString();
}
@Override
public String getCode(MapContainsKeyCall mapContainsKeyCall) {
return String.format("%s in %s", mapContainsKeyCall.getParameters()
.get(0), mapContainsKeyCall.getExpression());
}
@Override
public String getCode(MapEntryGetKeyCall mapEntryGetKeyCall) {
// a map entry is simply a tuple of the form (key, value)
return String.format("%s[0]", mapEntryGetKeyCall.getExpression());
}
@Override
public String getCode(MapEntryGetValueCall mapEntryGetKeyCall) {
return String.format("%s[1]", mapEntryGetKeyCall.getExpression());
}
@Override
public String getCode(MapEntryMethCall mapEntryMethCall) {
// TODO Auto-generated method stub
return "";
}
@Override
public String getCode(MapGetCall mapGetCall) {
return String.format("%s[%s]", mapGetCall.getExpression(), mapGetCall
.getParameters().get(0));
}
@Override
public String getCode(MapGetIteratorCall mapGetIteratorCall) {
return String.format("iter(%s)", mapGetIteratorCall.getExpression());
}
@Override
public String getCode(MapIsEmptyCall mapIsEmptyCall) {
// c.f. getCode(ListIsEmptyCall)
return String.format("(not %s)", mapIsEmptyCall.getExpression());
}
@Override
public String getCode(MapMethCall mapMethCall) {
// TODO: unbalanced parenthesis in produced code
return String.format("%s[%s])", mapMethCall.getExpression(),
mapMethCall.getParameters().get(0));
}
@Override
public String getCode(MapPutCall mapPutCall) {
return String.format("%s[%s] = %s", mapPutCall.getExpression(),
mapPutCall.getParameters().get(0), mapPutCall.getParameters()
.get(1));
}
@Override
public String getCode(MapRemoveCall mapRemoveCall) {
// we don't use 'dict[key]' for compatibility with the java API
// if the key does'nt exists
return String.format("%s.pop(%s, None)", mapRemoveCall.getExpression(),
StringUtils.join(mapRemoveCall.getParameters(), ", "));
}
@Override
public String getCode(MapSizeCall mapSizeCall) {
return String.format("len(%s)", mapSizeCall.getExpression());
}
/**
* Helper method to produce the code of a method
*
* @param meth
* the method to output
* @param prefix
* code to add at the beginning of the method
* @return the Python code of the method
*/
private String printMeth(Meth meth, String prefix) {
currentMeth = meth;
// register parameters as local identifiers
localIndentifiers.clear();
for (VarDeclaration p : meth.getParams()) {
localIndentifiers.add(p.getName());
}
// all methods that aren't used by a wrapper have a header to
// allow inheritance when multiple methods have the same name
if (methodsNames.get(meth).equals(meth.getName())) {
prefix = formatIndented(
"if not goolHelper.test_args(args%s):\n%-1super(%s, self).%s(*args)\n%-1return\n",
printMethParamsTypes(meth, ", "), currentClass.getName(),
meth.isConstructor() ? "__init__" : meth.getName());
if (!meth.getParams().isEmpty()) {
prefix += printMethParamsNames(meth).substring(2);
if (meth.getParams().size() == 1)
prefix += ", = args\n";
else
prefix += " = args\n";
}
prefix += "# end of GOOL header\n";
}
if (meth.isConstructor()) {
for (InitCall init : ((Constructor) meth).getInitCalls()) {
// TODO: is'nt this already done at the start of the method?
for (VarDeclaration param : meth.getParams()) {
localIndentifiers.add(param.getName());
}
prefix += String.format("super(%s, self).__init__(%s)\n",
currentClass.getName(),
StringUtils.join(init.getParameters(), ", "));
}
}
// Python dosn't allow a empty block (it messes the indentation)
if (prefix == "" && meth.getBlock().getStatements().isEmpty())
prefix = "pass";
return formatIndented("%sdef %s(self%s):%1", meth.getModifiers()
.contains(Modifier.STATIC) ? "@classmethod\n" : "",
methodsNames.get(meth),
methodsNames.get(meth).equals(meth.getName()) ? ", *args"
: printMethParamsNames(meth), prefix + meth.getBlock());
}
/**
* Produces the comma-separated list of the names of the parameters of a
* method, with a leading comma.
*
* @param meth
* @return the corresponding string
*/
private String printMethParamsNames(Meth meth) {
String ret = "";
for (VarDeclaration p : meth.getParams()) {
ret += ", " + p.getName();
}
return ret;
}
/**
* Produces the comma-separated list of the types of the parameters of a
* method, with a leading comma.
*
* @param meth
* @return the corresponding string
*/
private String printMethParamsTypes(Meth meth, String separator) {
String ret = "";
for (VarDeclaration p : meth.getParams()) {
ret += separator + p.getType();
}
return ret;
}
@Override
public String getCode(Meth meth) {
return printMeth(meth, "");
}
@Override
public String getCode(VarAccess varAccess) {
String name = varAccess.getDec().getName();
// packages names are not modified
// is'nt there a better test?
if (varAccess.getType() == null
|| varAccess.getType().getName().isEmpty()) {
return name;
}
// this method seems to be called before dealing with any class
// so we have to check for 'null'
if (name.equals("super") && currentClass != null)
return String.format("super(%s, self)", currentClass.getName());
if (name.equals("this"))
return "self";
if (varAccess.getDec().getModifiers().contains(Modifier.PRIVATE)) {
name = name.replaceFirst("^__", "");
// we are cheating Python to access private members out of their
// parent class
if (currentMeth.isMainMethod())
name = String.format("_%s__%s", currentClass.getName(), name);
else
name = "__" + name;
}
if (localIndentifiers.contains(name)) {
return name;
} else {
if (currentMeth != null && currentMeth.isMainMethod())
return currentClass.getName() + "." + name;
else
return "self." + name;
}
}
@Override
public String getCode(MemberSelect memberSelect) {
String name = memberSelect.getIdentifier();
if (memberSelect.getDec().getModifiers().contains(Modifier.PRIVATE)) {
name = name.replaceFirst("^__", "");
// we are cheating Python to access private members out of their
// parent class
if (currentMeth != null && currentMeth.isMainMethod())
name = String.format("_%s__%s", currentClass.getName(), name);
else
name = "__" + name;
}
return String.format("%s.%s", memberSelect.getTarget(), name);
}
@Override
public String getCode(Modifier modifier) {
// there are no modifiers in Python
return "";
}
@Override
public String getCode(NewInstance newInstance) {
return String.format(
"%s = %s(%s)",
newInstance.getVariable(),
// why would there be any trailing '\'?
newInstance.getVariable().getType().toString()
.replaceFirst("\\*$", ""),
StringUtils.join(newInstance.getParameters(), ", "));
}
@Override
public String getCode(ParentCall parentCall) {
return String.format("super(%s, self).__init__(%s)", currentClass,
StringUtils.join(parentCall.getParameters(), ", "));
}
@Override
public String getCode(Return returnExpr) {
return String.format("return %s", returnExpr.getExpression());
}
@Override
public String getCode(SystemOutDependency systemOutDependency) {
return "noprint";
}
@Override
public String getCode(SystemOutPrintCall systemOutPrintCall) {
// TODO: what about the new 'print is a function' standard?
return String.format("print %s",
StringUtils.join(systemOutPrintCall.getParameters(), ","));
}
@Override
public String getCode(This pthis) {
return "self";
}
@Override
public String getCode(ThisCall thisCall) {
return String.format("self.__init__(%s)",
GeneratorHelper.joinParams(thisCall.getParameters()));
}
@Override
public String getCode(ToStringCall tsc) {
return String.format("str(%s)", tsc.getTarget());
}
@Override
public String getCode(TypeBool typeBool) {
return "bool";
}
@Override
public String getCode(TypeByte typeByte) {
return "bytearray";
}
@Override
public String getCode(TypeDecimal typeReal) {
return "float";
}
@Override
public String getCode(TypeDependency typeDependency) {
if (typeDependency.getType() instanceof TypeInt) {
return "noprint";
}
if (typeDependency.getType() instanceof TypeString) {
return "noprint";
}
if (typeDependency.getType() instanceof TypeList) {
return "noprint";
}
if (typeDependency.getType() instanceof TypeMap) {
return "noprint";
}
if (typeDependency.getType() instanceof TypeEntry)
return "noprint";
return super.getCode(typeDependency);
}
@Override
public String getCode(TypeEntry typeEntry) {
// it's just a tuple
return String.format("(%s, %s)", typeEntry.getKeyType(),
typeEntry.getElementType());
}
@Override
public String getCode(TypeInt typeInt) {
// should we use 'long' instead (infinite precision)?
return "int";
}
@Override
public String getCode(TypeList typeList) {
return "list";
}
@Override
public String getCode(TypeMap typeMap) {
return "dict";
}
@Override
public String getCode(TypeNone type) {
return "None";
}
@Override
public String getCode(TypeNull type) {
return "None";
}
@Override
public String getCode(TypeObject typeObject) {
return "object";
}
@Override
public String getCode(TypeString typeString) {
// does not support unicode is Python 2.x
return "str";
}
@Override
public String getCode(TypeVoid typeVoid) {
// TODO Auto-generated method stub
return "";
}
@Override
public String getCode(UnaryOperation unaryOperation) {
switch (unaryOperation.getOperator()) {
// TODO: Python is said to be 'call-by-assignment'.
// Increment and decrement helper functions do not update the variable.
// For now we only support incrementation and decrementation as
// statements
// case POSTFIX_INCREMENT:
// comment("GOOL warning: post-incrementation became pre-incrementation");
// // no break: follow to the next case
// case PREFIX_INCREMENT:
// return String.format("goolHelper.increment(%s)",
// unaryOperation.getExpression());
// case POSTFIX_DECREMENT:
// comment("GOOL warning: post-decrementation became pre-decrementation");
// // no break: follow to the next case
// case PREFIX_DECREMENT:
// return String.format("goolHelper.decrement(%s)",
// unaryOperation.getExpression());
case POSTFIX_INCREMENT:
case PREFIX_INCREMENT:
comment("GOOL warning: semantic may have changed");
return unaryOperation.getExpression() + " +=1";
case POSTFIX_DECREMENT:
case PREFIX_DECREMENT:
comment("GOOL warning: semantic may have changed");
return unaryOperation.getExpression() + " -=1";
case NOT:
return String.format("(not %s)", unaryOperation.getExpression());
case UNKNOWN:
comment("Unrecognized by GOOL, passed on: "
+ unaryOperation.getTextualoperator());
// no break: follow to the next case
default:
return String.format("%s %s", unaryOperation.getTextualoperator(),
unaryOperation.getExpression());
}
}
@Override
public String getCode(VarDeclaration varDec) {
// It's just an assignment. Even though it is not needed if there is no
// Initialization, we can't distinguish between no initialization and
// initialization to 'null'
// TODO: Can we make this distinction?
String value;
if (varDec.getInitialValue() != null) {
value = varDec.getInitialValue().toString();
} else {
value = "None";
}
// even if the declaration were not to be outputted, we would still need
// to register the local identifier
localIndentifiers.add(varDec.getName());
return String.format("%s = %s", varDec.getName(), value);
}
@Override
public String getCode(While whilee) {
return formatIndented("while %s:%1", whilee.getCondition(),
whilee.getWhileStatement());
}
@Override
public String getCode(TypeArray typeArray) {
// Should we use the 'array' module?
return "list";
}
@Override
public String getCode(CustomDependency customDependency) {
if (customDependency.getName().startsWith("java.io")) {
return "goolHelper.IO";
}
if (!customDependencies.containsKey(customDependency.getName())) {
Log.e(String.format("Custom dependencies: %s, Desired: %s",
customDependencies, customDependency.getName()));
throw new IllegalArgumentException(
String.format(
"There is no equivalent type in Python for the GOOL type '%s'.",
customDependency.getName()));
}
return customDependencies.get(customDependency.getName()).toString();
}
@Override
public String getCode(TypeUnknown typeUnknown) {
return "noprint";
}
@Override
public String getCode(CompoundAssign compoundAssign) {
String textualOp;
if (compoundAssign.getOperator() == Operator.DIV
&& compoundAssign.getType().equals(TypeInt.INSTANCE)) {
// for compatibility with different versions of Python
textualOp = "//";
} else {
textualOp = compoundAssign.getTextualoperator();
}
if (compoundAssign.getOperator().equals(Operator.UNKNOWN))
comment("Unrecognized by GOOL, passed on: " + textualOp);
return String.format("%s %s= %s", compoundAssign.getLValue(),
textualOp, compoundAssign.getValue());
}
@Override
public String getCode(ExpressionUnknown unknownExpression) {
comment("Unrecognized by GOOL, expression passed on");
return unknownExpression.getTextual();
}
@Override
public String getCode(ClassFree classFree) {
// TODO Auto-generated method stub
return "";
}
@Override
public String getCode(SystemCommandDependency systemCommandDependency) {
// a verifier car de partout à null et je ne sais pas ce que ça fait
return null;
}
@Override
public String printClass(ClassDef classDef) {
currentClass = classDef;
// every python script has to start with a hash-bang:
// do not change the "#!/usr/bin/env python" line!
StringBuilder code = new StringBuilder(
"#!/usr/bin/env python\n\nimport goolHelper\nimport goolHelper.IO\nimport goolHelper.Util\n\n");
// Printing the imports.
// This is not the best way to write it in Python, but doing it right
// would require a lot of renaming in other places.
Set<String> dependencies = GeneratorHelper.printDependencies(classDef);
for (String dependency : customDependencies.keySet()) {
if (!dependency.isEmpty() && !dependency.equals("noprint")) {
String name = dependency
.substring(dependency.lastIndexOf('.') + 1);
for (String dep : dependencies) {
if (dep.equals(name)) {
code = code.append(String
.format("from %s import *\n", (dependency
.startsWith(".") ? name : dependency)));
break;
}
}
}
}
/*
* As it is not a method, we have do do everything manually. The
* condition makes sure we do not execute the main when importing the
* file TODO: deal with command line arguments
*/
for (Meth meth : classDef.getMethods()) {
if (meth.isMainMethod()) {
localIndentifiers.clear();
currentMeth = meth;
code = code
.append(formatIndented(
"\nif __name__ == '__main__':\n%-1from %s import *\n# main program%1# end of main\n%-1exit()\n",
classDef.getName(), meth.getBlock()));
}
}
// We only produce new-style classes. Theses have to explicitly
// inherit from the 'object' class.
// Not needed in Python 3, but kept for compatibility.
code = code.append(String.format("\nclass %s(%s):\n", classDef
.getName(), (classDef.getParentClass() != null) ? classDef
.getParentClass().getName() : "object"));
String dynamicAttributs = "";
for (Field f : classDef.getFields()) {
// renaming private fields
if (f.getModifiers().contains(Modifier.PRIVATE)) {
f.setName("__" + f.getName());
}
// static fields are declared in the class, dynamic ones in the
// constructor
if (f.getModifiers().contains(Modifier.STATIC))
code = code.append(formatIndented("%1", f));
else
dynamicAttributs += String.format("self.%s", f);
}
// renaming private methods
for (Meth meth : classDef.getMethods()) {
if (meth.getModifiers().contains(Modifier.PRIVATE))
meth.setName("__" + meth.getName());
}
// dealing with multiple methods having the same name,
// which is forbidden in Python
List<Meth> meths = new ArrayList<Meth>();
for (Meth method : classDef.getMethods()) { // On parcourt les méthodes
// the main method will be printed outside of the class later
if (getName(method) == null) { // Si la méthode n'a pas encore été
// renommée
meths.clear();
for (Meth m : classDef.getMethods()) { // On récupère les
// méthodes de mêmes
// noms
if (m.getName().equals(method.getName())) {
meths.add(m);
}
}
if (meths.size() > 1) { // Si il y a plusieurs méthodes de même
// nom
code = code
.append(formatIndented("\n%-1# wrapper generated by GOOL\n"));
String block = "";
String newName = method.getName();
boolean first = true;
boolean someStatics = false;
boolean someDynamics = false;
for (Meth m2 : meths) {
if (m2.getModifiers().contains(Modifier.STATIC))
someStatics = true;
else
someDynamics = true;
int i = m2.getParams().size();
newName = String.format("__%s_%d", method.getName()
.replaceFirst("^_*", ""), i++);
while (methodsNames.containsValue(newName)) {
newName = String.format("__%s_%d", method.getName()
.replaceFirst("^_*", ""), i++);
}
methodsNames.put(m2, newName);
block += formatIndented(
"%sif goolHelper.test_args(args%s):\n%-1self.%s(*args)\n",
first ? "" : "el",
printMethParamsTypes(m2, ", "), newName);
first = false;
}
if (!method.getModifiers().contains(Modifier.PRIVATE)
&& !method.isConstructor()) {
block += formatIndented(
"else:\n%-1super(%s, self).%s(*args)",
classDef.getName(),
method.isConstructor() ? "__init__" : method
.getName());
}
// If all methods under the wrapper are statics of dynamics,
// the wrapper is of the same kind. If both exists, we can't
// properly deal with it so we output a warning in the code.
if (someStatics && someDynamics) {
code = code
.append(formatIndented("%-1# GOOL warning: static and dynamic methods under a same wrapper\n"
+ "%-1# impossible to call static methods\n"));
} else if (someStatics) {
// '@static' would be more accurate, but we would have
// to replace 'self' with the name of the class.
code = code.append(formatIndented("%-1@classmethod\n"));
}
String name = method.getName();
if (method.isConstructor()) {
name = "__init__";
// We add the declarations for dynamics attributes to
// the constructor if it is a wrapper.
block = dynamicAttributs + block;
}
// We have to manually print the header as it is not a
// method known to GOOL.
code = code.append(formatIndented(
"%-1def %s(self, *args):%2", name, block));
} else {
if (method.isConstructor()) {
methodsNames.put(method, "__init__");
} else {
methodsNames.put(method, method.getName());
}
}
}
}
for (Meth method : classDef.getMethods()) {
if (!method.isMainMethod()) {
// we add a comment for renamed methods
if (!methodsNames.get(method).equals(method.getName())
&& !methodsNames.get(method).equals("__init__")) {
code = code.append(formatIndented(
"\n%-1# used in wrapper '%s'",
method.isConstructor() ? "__init__" : method
.getName()));
}
// We add the declarations for dynamic attributes to the
// constructor
// if there is no wrapper for it.
if (method.isConstructor()
&& methodsNames.get(method).equals(method.getName())) {
code = code.append(formatIndented("%1",
printMeth(method, dynamicAttributs)));
} else {
code = code.append(formatIndented("%1", method));
}
}
}
return code.toString();
}
@Override
public String getCode(TypeChar typeChar) {
return "str";
}
public String getCode(Throw throwStatement) {
return String.format("raise %s", throwStatement.getExpression());
}
@Override
public String getCode(Catch catchStatement) {
localIndentifiers.add(catchStatement.getParameter().getName());
return formatIndented("except %s as %s:%1", catchStatement
.getParameter().getType(), catchStatement.getParameter()
.getName(), catchStatement.getBlock());
}
@Override
public String getCode(Try tryStatement) {
String retour = formatIndented("try:%1", tryStatement.getBlock());
for (Catch c : tryStatement.getCatches()) {
retour += c;
}
if (!tryStatement.getFinilyBlock().getStatements().isEmpty())
retour += formatIndented("finally:%1",
tryStatement.getFinilyBlock());
return retour;
}
@Override
public String getCode(TypeException typeException) {
// TODO: add more exceptions
switch (typeException.getKind()) {
case GLOBAL:
return "BaseException";
case ARITHMETIC:
return "ArithmeticError";
case COLLECTION:
return "LookupError";
case CAST:
return "ValueError";
case ARGUMENT:
return "AttributeError";
case ARRAY:
return "IndexError";
case TYPE:
return "TypeError";
case NULLREFERENCE:
// in Python, 'None' is an object but it does'nt have many methods
return "AttributeError";
default:
return typeException.getName();
}
}
public String getCode(RecognizedDependency recognizedDependency) {
//TODO: not implemented
String result = "RecognizedDependency not generated by GOOL, passd on.";
return result;
}
}