/**
* This file is licensed under the terms of the Modified BSD License.
*/
package abs.backend.erlang;
import java.io.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import abs.common.CompilerUtils;
import abs.backend.common.CodeStream;
import abs.backend.erlang.ErlUtil.Mask;
import abs.frontend.ast.*;
import com.google.common.collect.Iterables;
import java.nio.charset.Charset;
import org.apache.commons.io.output.WriterOutputStream;
/**
* Generates the Erlang module for one class
*
* @author Georg Göri
*
*/
public class ClassGenerator {
private final CodeStream ecs;
private final ClassDecl classDecl;
private final String modName;
private final boolean hasFields;
public ClassGenerator(ErlApp ea, ClassDecl classDecl) throws IOException {
this.classDecl = classDecl;
modName = ErlUtil.getName(classDecl);
ecs = ea.createSourceFile(modName);
hasFields = classDecl.getParams().hasChildren() || classDecl.getFields().hasChildren();
try {
generateHeader();
generateExports();
generateConstructor();
generateRecoverHandler();
generateMethods();
generateDataAccess();
} finally {
ecs.close();
}
}
private void generateHeader() {
ecs.pf("-module(%s).", modName);
ecs.println("-include_lib(\"../include/abs_types.hrl\").");
if (hasFields) {
ecs.println("-behaviour(object).");
}
}
private void generateMethods() {
for (MethodImpl m : classDecl.getMethodList()) {
ecs.pf(" %%%% %s:%s", m.getFileName(), m.getStartLine());
MethodSig ms = m.getMethodSig();
ecs.pf(" %%%% %s:%s", m.getFileName(), m.getStartLine());
ErlUtil.functionHeader(ecs, "m_" + ms.getName(), generatorClassMatcher(), ms.getParamList());
ecs.print("put(vars, #{");
boolean first = true;
for (ParamDecl p : ms.getParamList()) {
if (first == true) first = false;
else ecs.print(",");
// Same name construction as
// ErlUtil.functionHeader(CodeStream, String, List<String>, Mask)
ecs.format(" '%s' => %s", p.getName(), ErlUtil.absParamDeclToErlVarName(p));
}
ecs.println(" }),");
ecs.println("try");
ecs.incIndent();
Vars vars = new Vars();
m.getBlock().generateErlangCode(ecs, vars);
ecs.println();
ecs.decIndent().println("catch");
ecs.incIndent();
ecs.println("_:Exception ->");
if (classDecl.hasRecoverBranch()) {
ecs.incIndent();
ecs.println("Recovered = try 'recover'(O, Exception) catch _:RecoverError -> io:format(standard_error, \"Recovery block for ~s in class " + classDecl.qualifiedName() + " failed with exception ~s~n\", [builtin:toString(Cog, Exception), builtin:toString(Cog, RecoverError)]), false end,");
ecs.println("case Recovered of");
ecs.incIndent().println("true -> exit(Exception);");
ecs.println("false ->");
ecs.incIndent();
ecs.println("io:format(standard_error, \"Uncaught ~s in method " + ms.getName() + " not handled successfully by recovery block, killing object ~s~n\", [builtin:toString(Cog, Exception), builtin:toString(Cog, O)]),");
ecs.println("object:die(O, Exception), exit(Exception)");
ecs.decIndent().println("end");
ecs.decIndent();
} else {
ecs.incIndent();
ecs.println("io:format(standard_error, \"Uncaught ~s in method " + ms.getName() + " and no recovery block in class definition, killing object ~s~n\", [builtin:toString(Cog, Exception), builtin:toString(Cog, O)]),");
ecs.println("object:die(O, Exception), exit(Exception)");
ecs.decIndent();
}
ecs.decIndent().println("end.");
ecs.decIndent();
}
}
private void generateConstructor() {
ErlUtil.functionHeaderParamsAsList(ecs, "init", generatorClassMatcher(), classDecl.getParamList(), Mask.none);
ecs.println("put(vars, #{}),");
Vars vars = Vars.n();
for (ParamDecl p : classDecl.getParamList()) {
ecs.pf("set(O,'%s',%s),", p.getName(), "P_" + p.getName());
}
for (FieldDecl p : classDecl.getFields()) {
ErlUtil.emitLocationInformation(ecs, p.getModel(), p.getFileName(),
p.getStartLine(), p.getEndLine());
if (p.hasInitExp()) {
ecs.format("set(O,'%s',", p.getName());
p.getInitExp().generateErlangCode(ecs, vars);
ecs.println("),");
}
}
if (classDecl.getInitBlock() != null) {
classDecl.getInitBlock().generateErlangCode(ecs, vars);
ecs.println(",");
}
if (classDecl.isActiveClass()) {
ecs.println("cog:process_is_blocked_for_gc(Cog, self()),");
ecs.print("cog:add_sync(Cog,active_object_task,O,");
ecs.print(vars.toStack());
ecs.println("),");
ecs.println("cog:process_is_runnable(Cog,self()),");
ecs.print("task:wait_for_token(Cog,");
ecs.print(vars.toStack());
ecs.println("),");
}
ecs.println("O.");
ecs.decIndent();
}
private void generateRecoverHandler() {
if (classDecl.hasRecoverBranch()) {
Vars vars = new Vars();
Vars safe = vars.pass();
// Build var scopes and statmemnts for each branch
java.util.List<Vars> branches_vars = new java.util.LinkedList<Vars>();
java.util.List<String> branches = new java.util.LinkedList<String>();
for (CaseBranchStmt b : classDecl.getRecoverBranchs()) {
Vars v = vars.pass();
StringWriter sw = new StringWriter();
CodeStream buffer = new CodeStream(new WriterOutputStream(sw, Charset.forName("UTF-8")),"");
b.getLeft().generateErlangCode(ecs, buffer, v);
buffer.setIndent(ecs.getIndent());
buffer.println("->");
buffer.incIndent();
b.getRight().generateErlangCode(buffer, v);
buffer.println(",");
buffer.print("true");
buffer.decIndent();
buffer.close();
branches_vars.add(v);
branches.add(sw.toString());
vars.updateTemp(v);
}
ErlUtil.functionHeader(ecs, "recover", ErlUtil.Mask.none, generatorClassMatcher(), "Exception");
ecs.println("Result=case Exception of ");
ecs.incIndent();
// Now print statments and mergelines for each branch.
java.util.List<String> mergeLines = vars.merge(branches_vars);
Iterator<String> ib = branches.iterator();
Iterator<String> im = mergeLines.iterator();
while (ib.hasNext()) {
ecs.print(ib.next());
ecs.incIndent();
ecs.print(im.next());
ecs.println(";");
ecs.decIndent();
}
ecs.println("_ -> false");
ecs.decIndent();
ecs.print("end");
ecs.println(".");
ecs.decIndent();
}
}
private String generatorClassMatcher() {
return String.format("O=#object{class=%s=C,ref=Ref,cog=Cog=#cog{ref=CogRef,dc=DC}}", modName);
}
private void generateDataAccess() {
ErlUtil.functionHeader(ecs, "set", Mask.none,
String.format("O=#object{class=%s=C,ref=Ref,cog=Cog}", modName), "Var", "Val");
ecs.println("gen_fsm:send_event(Ref,{O,set,Var,Val}).");
ecs.decIndent();
ecs.println();
ErlUtil.functionHeader(ecs, "get", Mask.none, generatorClassMatcher(), "Var");
ecs.println("gen_fsm:sync_send_event(Ref,{O,get,Var}).");
ecs.decIndent();
ecs.println();
ecs.print("-record(state,{");
boolean first = true;
for (TypedVarOrFieldDecl f : Iterables.concat(classDecl.getParams(), classDecl.getFields())) {
if (!first)
ecs.print(",");
first = false;
ecs.format("'%s'=null", f.getName());
}
ecs.println("}).");
ErlUtil.functionHeader(ecs, "init_internal");
ecs.println("#state{}.");
ecs.decIndent();
ecs.println();
for (TypedVarOrFieldDecl f : Iterables.concat(classDecl.getParams(), classDecl.getFields())) {
ecs.pf(" %%%% %s:%s", f.getFileName(), f.getStartLine());
ErlUtil.functionHeader(ecs, "get_val_internal", Mask.none, String.format("#state{'%s'=G}", f.getName()),
"'" + f.getName() + "'");
ecs.println("G;");
ecs.decIndent();
}
ErlUtil.functionHeader(ecs, "get_val_internal", Mask.none, "_", "_");
ecs.println("%% Invalid return value; handled by HTTP API when querying for non-existant field.");
ecs.println("%% Will never occur in generated code.");
ecs.println("none.");
ecs.decIndent();
ecs.println();
if (hasFields) {
first = true;
for (TypedVarOrFieldDecl f : Iterables.concat(classDecl.getParams(), classDecl.getFields())) {
if (!first) {
ecs.println(";");
ecs.decIndent();
}
first = false;
ecs.pf(" %%%% %s:%s", f.getFileName(), f.getStartLine());
ErlUtil.functionHeader(ecs, "set_val_internal", Mask.none, "S", "'" + f.getName() + "'", "V");
ecs.format("S#state{'%s'=V}", f.getName());
}
ecs.println(".");
ecs.decIndent();
ecs.println();
} else {
// Generate failing Dummy
ErlUtil.functionHeader(ecs, "set_val_internal", Mask.none, "S", "S", "S");
ecs.println("throw(badarg).");
ecs.decIndent();
}
ErlUtil.functionHeader(ecs, "get_all_state", Mask.none, "S");
ecs.println("[");
ecs.incIndent();
first = true;
for (TypedVarOrFieldDecl f : Iterables.concat(classDecl.getParams(), classDecl.getFields())) {
if (!first) ecs.print(", ");
first = false;
ecs.pf("{ '%s', S#state.%s }",
f.getName(), f.getName());
}
ecs.decIndent();
ecs.println("].");
}
private void generateExports() {
ecs.println("-export([get_val_internal/2,set_val_internal/3,init_internal/0,get_all_state/1]).");
ecs.println("-compile(export_all).");
ecs.println();
HashSet<MethodSig> callable_sigs = new HashSet<MethodSig>();
HashSet<InterfaceDecl> visited = new HashSet<InterfaceDecl>();
for (InterfaceTypeUse i : classDecl.getImplementedInterfaceUseList()) {
visited.add((InterfaceDecl)i.getDecl());
}
while (!visited.isEmpty()) {
InterfaceDecl id = visited.iterator().next();
visited.remove(id);
for (MethodSig ms : id.getBodyList()) {
if (ms.isHTTPCallable()) {
callable_sigs.add(ms);
}
}
for (InterfaceTypeUse i : id.getExtendedInterfaceUseList()) {
visited.add((InterfaceDecl)i.getDecl());
}
}
ecs.print("exported() -> #{ ");
boolean first = true;
for (MethodSig ms : callable_sigs) {
if (ms.isHTTPCallable()) {
if (!first) ecs.print(", ");
first = false;
ecs.print("<<\"" + ms.getName() + "\">> => { ");
ecs.print("'m_" + ms.getName() + "'");
ecs.print(", ");
ecs.print("<<\"" + ms.getReturnType().getType().getQualifiedName() + "\">>");
ecs.print(", ");
ecs.print("[ ");
boolean innerfirst = true;
for (ParamDecl p : ms.getParamList()) {
if (!innerfirst) ecs.print(", ");
innerfirst = false;
ecs.print("{ ");
ecs.print("<<\"" + p.getName() + "\">>");
ecs.print(", ");
ecs.print("<<\"" + p.getAccess().getType().getQualifiedName() + "\">>");
ecs.print(" }");
}
ecs.print("] ");
ecs.print("}");
}
}
ecs.println(" }.");
ecs.println();
}
}