/*
* 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.csharp;
import gool.ast.core.BinaryOperation;
import gool.ast.core.Catch;
import gool.ast.core.ClassDef;
import gool.ast.core.ClassNew;
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.Field;
import gool.ast.core.Finally;
import gool.ast.core.MainMeth;
import gool.ast.core.Meth;
import gool.ast.core.Modifier;
import gool.ast.core.Operator;
import gool.ast.core.Package;
import gool.ast.core.ParentCall;
import gool.ast.core.RecognizedDependency;
import gool.ast.core.Throw;
import gool.ast.core.ToStringCall;
import gool.ast.core.Try;
import gool.ast.core.TypeDependency;
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.TypeBool;
import gool.ast.type.TypeByte;
import gool.ast.type.TypeChar;
import gool.ast.type.TypeClass;
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.TypeObject;
import gool.ast.type.TypeString;
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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
/**
* It generates specific C# code for certain GOOL nodes.
*/
public class CSharpGenerator extends CommonCodeGenerator /*implements
CodeGeneratorNoVelocity*/ {
@Override
public String getCode(TypeBool typeBool) {
return "bool";
}
@Override
public String getCode(BinaryOperation binaryOp) {
if (!(binaryOp.getLeft() instanceof Constant)
&& binaryOp.getOperator() == Operator.EQUAL) {
return String.format("%s.Equals(%s)", binaryOp.getLeft(),
binaryOp.getRight());
} else {
return super.getCode(binaryOp);
}
}
@Override
public String getCode(TypeInt typeInt) {
return "int";
}
@Override
public String getCode(TypeByte t) {
return "byte";
}
@Override
public String getCode(TypeChar typeChar) {
return "char";
}
/**
* Produces code for an object instantiation.
*
* @param classNew
* the object instantiation node.
* @return the formatted object instantiation.
*/
public String getCode(ClassNew classNew) {
return String.format("new %s( %s )", classNew.getType(),
GeneratorHelper.joinParams(classNew.getParameters()));
}
@Override
public String getCode(TypeString typeString) {
return "string";
}
@Override
public String getCode(TypeVoid typeVoid) {
return "void";
}
@Override
public String getCode(ListAddCall lac) {
String method = lac.getParameters().size() > 1 ? "Insert" : "Add";
return String.format("%s.%s(%s)", lac.getExpression(), method,
GeneratorHelper.joinParams(lac.getParameters()));
}
@Override
public String getCode(ListRemoveCall lrc) {
return String.format("%s.Remove(%s)", lrc.getExpression(),
GeneratorHelper.joinParams(lrc.getParameters()));
}
@Override
public String getCode(ListRemoveAtCall lrc) {
return String.format("%s.RemoveAt(%s)", lrc.getExpression(),
GeneratorHelper.joinParams(lrc.getParameters()));
}
@Override
public String getCode(ListContainsCall lcc) {
return String.format("%s.Contains(%s)", lcc.getExpression(),
GeneratorHelper.joinParams(lcc.getParameters()));
}
@Override
public String getCode(ListGetCall lgc) {
return String.format("%s[%s]", lgc.getExpression(),
GeneratorHelper.joinParams(lgc.getParameters()));
}
@Override
public String getCode(ListSizeCall lsc) {
return String.format("%s.Count", lsc.getExpression());
}
@Override
public String getCode(ListIsEmptyCall liec) {
return String.format("%s.Count == 0", liec.getExpression());
}
@Override
public String getCode(ListGetIteratorCall lgic) {
return String.format("%s.GetIterator()", lgic.getExpression());
}
@Override
public String getCode(TypeDependency typeDependency) {
if (typeDependency.getType() instanceof TypeList) {
if (((TypeList) typeDependency.getType()).getElementType() == null) {
return "System.Collections";
}
return "System.Collections.Generic";
}
if (typeDependency.getType() instanceof TypeMap) {
return "System.Collections.Generic";
}
if (typeDependency.getType() instanceof TypeEntry) {
return "System.Collections.Generic";
}
if (typeDependency.getType() instanceof TypeString) {
return "noprint";
}
if (typeDependency.getType() instanceof TypeBool) {
return "noprint";
}
if (typeDependency.getType() instanceof TypeInt) {
return "noprint";
}
if (typeDependency.getType() instanceof TypeClass) {
return "noprint";
}
return super.getCode(typeDependency);
}
@Override
public String getCode(ClassDef classDef) {
if (classDef.getPpackage() == null) {
return "noprint";
}
return classDef.getPackageName();
}
@Override
public String getCode(TypeObject typeObject) {
return "object";
}
@Override
public String getCode(TypeList typeList) {
if (typeList.getElementType() == null) {
return "ArrayList";
}
return "List<" + typeList.getElementType().toString() + ">";
}
@Override
public String getCode(TypeDecimal typeReal) {
return "double";
}
@Override
public String getCode(SystemOutPrintCall systemOutPrintCall) {
return String.format("Console.WriteLine(%s)",
GeneratorHelper.joinParams(systemOutPrintCall.getParameters()));
}
@Override
public String getCode(ParentCall parentCall) {
String out = "base(";
if (parentCall.getParameters() != null) {
out += GeneratorHelper.joinParams(parentCall.getParameters());
}
out += ")";
return out;
}
@Override
public String getCode(SystemOutDependency systemOutDependency) {
return "System";
}
@Override
public String getCode(Meth meth) {
// #join ( $method.Modifiers " ") #if($method.isInherited())override#end
// $method.Type $method.Name ( #params( $method.getParams() ) )
String name = replaceMethodName(meth, meth.getName());
customizeModifiers(meth);
return String.format("%s %s %s(%s)", getCode(meth.getModifiers()),
meth.getType(), name,
GeneratorHelper.joinParams(meth.getParams()));
}
private void customizeModifiers(Meth meth) {
if (!meth.getModifiers().contains(Modifier.ABSTRACT)
&& !meth.getModifiers().contains(Modifier.STATIC)
&& !meth.isConstructor()) {
if (meth.isInherited()) {
meth.getModifiers().add(Modifier.OVERRIDE);
} else if (!meth.getClassDef().isInterface()
&& meth.getModifiers().contains(Modifier.PUBLIC)) {
meth.getModifiers().add(Modifier.VIRTUAL);
}
}
}
/*
* Override properly the ToString, Equals, GetHashCode method in C#
*/
private String replaceMethodName(Meth meth, String name) {
if (meth.getModifiers().contains(Modifier.PUBLIC) && meth.isInherited()) {
if (name.equals("toString") && meth.getParams().isEmpty()) {
return "ToString";
} else if (name.equals("equals")
&& meth.getParams().size() == 1
&& meth.getParams().get(0).getType()
.equals(TypeObject.INSTANCE)) {
return "Equals";
} else if (name.equals("hashCode") && meth.getParams().isEmpty()) {
return "GetHashCode";
}
}
return name;
}
@Override
public String getCode(Modifier modifier) {
if (modifier == Modifier.FINAL) {
return "";
}
return super.getCode(modifier);
}
@Override
public String getCode(MainMeth mainMeth) {
return "public static void Main(string[] args)";
}
@Override
public String getCode(Constructor cons) {
// #if($method.isConstructor())
// #if(!$method.ParentCalls.isEmpty()):#end
// #foreach( $parentCall in $method.ParentCalls )
// $parentCall
// #end
// #end
return super.getCode(cons)
+ ((cons.getInitCalls().size() > 0) ? " : "
+ StringUtils.join(cons.getInitCalls(), " ") : "");
}
@Override
public String getCode(MapSizeCall mapSizeCall) {
return String.format("%s.Count", mapSizeCall.getExpression());
}
@Override
public String getCode(MapRemoveCall mapRemoveCall) {
return String.format("%s.Remove(%s)", mapRemoveCall.getExpression(),
GeneratorHelper.joinParams(mapRemoveCall.getParameters()));
}
@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(MapIsEmptyCall mapIsEmptyCall) {
return String.format("(%s.Count == 0)", mapIsEmptyCall.getExpression());
}
@Override
public String getCode(MapGetIteratorCall mapGetIteratorCall) {
return String.format("(%s.GetIterator() == 0)",
mapGetIteratorCall.getExpression());
}
@Override
public String getCode(MapGetCall mapGetCall) {
return String.format("%s[%s]", mapGetCall.getExpression(),
GeneratorHelper.joinParams(mapGetCall.getParameters()));
}
@Override
public String getCode(MapContainsKeyCall mapContainsKeyCall) {
return String.format("%s.ContainsKey(%s)",
mapContainsKeyCall.getExpression(),
GeneratorHelper.joinParams(mapContainsKeyCall.getParameters()));
}
@Override
public String getCode(TypeMap typeMap) {
return String.format("Dictionary<%s, %s>", typeMap.getKeyType(),
typeMap.getElementType());
}
@Override
public String getCode(EnhancedForLoop enhancedForLoop) {
return String.format("foreach(%s in %s){%s}",
enhancedForLoop.getVarDec(), enhancedForLoop.getExpression(),
enhancedForLoop.getStatements());
}
private static Map<String, Dependency> customDependencies = new HashMap<String, Dependency>();
public String getCode(CustomDependency customDependency) {
if (!customDependencies.containsKey(customDependency.getName())) {
throw new IllegalArgumentException(
String.format(
"There is no equivalent type in C# for the GOOL type '%s'.",
customDependency.getName()));
}
Dependency dependency = customDependencies.get(customDependency
.getName());
if (dependency instanceof ClassDef) { // It is already a package.
// Return only the package. C# does not support individual class
// importation.
return ((ClassDef) dependency).getPackageName();
}
return dependency.toString();
}
public void addCustomDependency(String key, Dependency value) {
customDependencies.put(key, value);
}
@Override
public String getCode(TypeEntry typeEntry) {
return String.format("KeyValuePair<%s, %s>", typeEntry.getKeyType(),
typeEntry.getElementType());
}
@Override
public String getCode(MapEntryGetKeyCall mapEntryGetKeyCall) {
return String.format("%s.Key", mapEntryGetKeyCall.getExpression());
}
@Override
public String getCode(MapEntryGetValueCall mapEntryGetKeyCall) {
return String.format("%s.Value", mapEntryGetKeyCall.getExpression());
}
@Override
public String getCode(EqualsCall equalsCall) {
return String.format("%s.Equals(%s)", equalsCall.getTarget(),
GeneratorHelper.joinParams(equalsCall.getParameters()));
}
@Override
public String getCode(ToStringCall tsc) {
return String.format("%s.ToString()", tsc.getTarget());
}
@Override
public String getCode(Package _package) {
return _package.getName();
}
@Override
public String getCode(SystemCommandDependency systemCommandDependency) {
// TODO Auto-generated method stub
return null;
}
//@Override
public String printClass(ClassDef classDef) {
StringBuilder sb = new StringBuilder(String.format(
"// Platform: %s\n\n", classDef.getPlatform()));
// BUG: yield a stack overflow
Set<String> dependencies = GeneratorHelper.printDependencies(classDef);
if (!dependencies.isEmpty()) {
for (String dependency : dependencies)
sb = sb.append(String.format("using %s;\n", dependency));
sb = sb.append("\n");
}
if (classDef.getPpackage() != null)
sb = sb.append(String.format("namespace %s;\n\n",
classDef.getPackageName()));
// print the class prototype
sb = sb.append(String.format("%s %s %s",
StringUtils.join(classDef.getModifiers(), ' '),
classDef.isInterface() ? "interface" : "class",
classDef.getName()));
if (classDef.getParentClass() != null)
sb = sb.append(String.format(" : %s", classDef.getParentClass()));
if (!classDef.getInterfaces().isEmpty())
sb = sb.append(String.format(" : %s",
StringUtils.join(classDef.getInterfaces(), ", ")));
sb = sb.append(" {\n\n");
// print the fields
for (Field field : classDef.getFields())
sb = sb.append(formatIndented("%-1%s;\n\n", field));
// print the methods
for (Meth meth : classDef.getMethods()) {
// TODO: deal with constructors ?
if (classDef.isInterface())
sb = sb.append(formatIndented("%-1%s;\n\n", meth.getHeader()));
else
sb = sb.append(formatIndented("%-1%s {%2%-1}\n\n",
meth.getHeader(), meth.getBlock()));
}
return sb.toString() + "}";
}
@Override
public String getCode(Throw throwStatement) {
return String.format("throw %s", throwStatement.getExpression());
}
@Override
public String getCode(Catch catchStatement) {
return formatIndented("catch (%s %s)\n{%1}\n", catchStatement
.getParameter().getType(), catchStatement.getParameter()
.getName(), catchStatement.getBlock());
}
@Override
public String getCode(Try tryStatement) {
String ret = formatIndented("try\n{%1}", tryStatement.getBlock());
for (Catch c : tryStatement.getCatches()) {
ret += "\n" + c;
}
if (!tryStatement.getFinilyBlock().getStatements().isEmpty())
ret += formatIndented(" finally\n{%1}",
tryStatement.getFinilyBlock());
return ret;
}
@Override
public String getCode(TypeException typeException) {
switch (typeException.getKind()) {
case GLOBAL:
return "Exception";
case ARITHMETIC:
return "ArithmeticException";
case CAST:
return "InvalidCastException";
case ARGUMENT:
return "ArgumentException";
case NULLREFERENCE:
return "NullReferenceException";
case SECURITY:
return "SecurityException";
case UNSUPORTED:
return "InvalidOperationException";
case DEFAULT:
return "Exception";
case ACCESS:
return "AccessViolationException";
default:
return typeException.getName();
}
}
public String getCode(RecognizedDependency recognizedDependency) {
List<String> imports = GeneratorMatcher.matchImports(recognizedDependency.getName());
if(imports.isEmpty())
return "/* import "+recognizedDependency.getName()+" not generated by GOOL, passed on. */";
String result = "";
for (String Import : imports) {
if (Import.startsWith("+"))
Import = Import.substring(1);
result += "using " + Import + ";\n";
}
return result;
}
}