// Copyright (c) 2011, David J. Pearce (djp@ecs.vuw.ac.nz)
// All rights reserved.
//
// This software may be modified and distributed under the terms
// of the BSD license. See the LICENSE file for details.
package wyil.io;
import java.io.*;
import java.util.*;
import wybs.lang.Build;
import wyil.lang.*;
import wyil.lang.Constant;
import wyil.lang.Bytecode.AliasDeclaration;
import wyil.lang.Bytecode.Expr;
import wyil.lang.Bytecode.VariableAccess;
import wyil.lang.Bytecode.VariableDeclaration;
import wyil.lang.SyntaxTree.Location;
import wyil.lang.Type;
import wyil.lang.WyilFile.*;
/**
* Writes WYIL bytecodes in a textual from to a given file.
*
* <b>NOTE:</b> currently, this class is somewhat broken since it does not
* provide any way to specify the output directory. Rather, it simply puts the
* WYIL file in the same place as the Whiley file.
*
* @author David J. Pearce
*
*/
public final class WyilFilePrinter {
private PrintWriter out;
private boolean verbose = false;
public WyilFilePrinter(Build.Task builder) {
}
public WyilFilePrinter(PrintWriter writer) {
this.out = writer;
}
public WyilFilePrinter(OutputStream stream) {
this.out = new PrintWriter(new OutputStreamWriter(stream));
}
// ======================================================================
// Configuration Methods
// ======================================================================
public void setVerbose(boolean flag) {
this.verbose = flag;
}
// ======================================================================
// Apply Method
// ======================================================================
public void apply(WyilFile module) throws IOException {
out.println();
for(WyilFile.Constant cd : module.constants()) {
writeModifiers(cd.modifiers(),out);
out.println("constant " + cd.name() + " = " + cd.constant());
}
if(!module.constants().isEmpty()) {
out.println();
}
for (WyilFile.Type td : module.types()) {
Type t = td.type();
String t_str;
t_str = t.toString();
writeModifiers(td.modifiers(), out);
out.println("type " + td.name() + " : " + t_str);
for (Location<?> invariant : td.getInvariant()) {
out.print("where ");
writeExpression(invariant, out);
out.println();
}
out.println();
}
for(FunctionOrMethod md : module.functionOrMethods()) {
write(md,out);
out.println();
}
out.flush();
}
private void write(FunctionOrMethod method, PrintWriter out) {
//
if(verbose) {
writeLocationsAsComments(method.getTree(),out);
}
//
writeModifiers(method.modifiers(), out);
Type.FunctionOrMethod ft = method.type();
if (ft instanceof Type.Function) {
out.print("function ");
} else {
out.print("method ");
}
out.print(method.name());
writeParameters(ft.params(),out);
if (ft.returns().length != 0) {
out.print(" -> ");
writeParameters(ft.returns(),out);
}
//
for (Location<Expr> precondition : method.getPrecondition()) {
out.println();
out.print("requires ");
writeExpression(precondition, out);
}
for (Location<Expr> postcondition : method.getPostcondition()) {
out.println();
out.print("ensures ");
writeExpression(postcondition, out);
}
if (method.getBody() != null) {
out.println(": ");
writeBlock(0, method.getBody(), out);
}
}
private void writeLocationsAsComments(SyntaxTree tree, PrintWriter out) {
List<Location<?>> locations = tree.getLocations();
for(int i=0;i!=locations.size();++i) {
Location<?> loc = locations.get(i);
String id = String.format("%1$" + 3 + "s", "#" + i);
String type = String.format("%1$-" + 8 + "s", Arrays.toString(loc.getTypes()));
out.println("// " + id + " " + type + " " + loc.getBytecode());
}
}
private void writeParameters(Type[] parameters, PrintWriter out) {
out.print("(");
for (int i = 0; i != parameters.length; ++i) {
if (i != 0) {
out.print(", ");
}
out.print(parameters[i]);
}
out.print(")");
}
private void writeBlock(int indent, Location<Bytecode.Block> block, PrintWriter out) {
for (int i = 0; i != block.numberOfOperands(); ++i) {
writeStatement(indent, block.getOperand(i), out);
}
}
@SuppressWarnings("unchecked")
private void writeStatement(int indent, Location<?> c, PrintWriter out) {
tabIndent(indent+1,out);
switch(c.getOpcode()) {
case Bytecode.OPCODE_aliasdecl:
writeAliasDeclaration(indent, (Location<Bytecode.AliasDeclaration>) c, out);
break;
case Bytecode.OPCODE_assert:
writeAssert(indent, (Location<Bytecode.Assert>) c, out);
break;
case Bytecode.OPCODE_assume:
writeAssume(indent, (Location<Bytecode.Assume>) c, out);
break;
case Bytecode.OPCODE_assign:
writeAssign(indent, (Location<Bytecode.Assign>) c, out);
break;
case Bytecode.OPCODE_break:
writeBreak(indent, (Location<Bytecode.Break>) c, out);
break;
case Bytecode.OPCODE_continue:
writeContinue(indent, (Location<Bytecode.Continue>) c, out);
break;
case Bytecode.OPCODE_debug:
writeDebug(indent, (Location<Bytecode.Debug>) c, out);
break;
case Bytecode.OPCODE_dowhile:
writeDoWhile(indent, (Location<Bytecode.DoWhile>) c, out);
break;
case Bytecode.OPCODE_fail:
writeFail(indent, (Location<Bytecode.Fail>) c, out);
break;
case Bytecode.OPCODE_if:
case Bytecode.OPCODE_ifelse:
writeIf(indent, (Location<Bytecode.If>) c, out);
break;
case Bytecode.OPCODE_indirectinvoke:
writeIndirectInvoke(indent, (Location<Bytecode.IndirectInvoke>) c, out);
break;
case Bytecode.OPCODE_invoke:
writeInvoke(indent, (Location<Bytecode.Invoke>) c, out);
break;
case Bytecode.OPCODE_namedblock:
writeNamedBlock(indent, (Location<Bytecode.NamedBlock>) c, out);
break;
case Bytecode.OPCODE_while:
writeWhile(indent, (Location<Bytecode.While>) c, out);
break;
case Bytecode.OPCODE_return:
writeReturn(indent, (Location<Bytecode.Return>) c, out);
break;
case Bytecode.OPCODE_skip:
writeSkip(indent, (Location<Bytecode.Skip>) c, out);
break;
case Bytecode.OPCODE_switch:
writeSwitch(indent, (Location<Bytecode.Switch>) c, out);
break;
case Bytecode.OPCODE_vardecl:
case Bytecode.OPCODE_vardeclinit:
writeVariableDeclaration(indent, (Location<Bytecode.VariableDeclaration>) c, out);
break;
default:
throw new IllegalArgumentException("unknown bytecode encountered");
}
}
private void writeAliasDeclaration(int indent, Location<AliasDeclaration> loc, PrintWriter out) {
out.print("alias ");
out.print(loc.getType());
out.print(" ");
Location<VariableDeclaration> aliased = getVariableDeclaration(loc);
out.print(aliased.getBytecode().getName());
out.println();
}
private void writeAssert(int indent, Location<Bytecode.Assert> c, PrintWriter out) {
out.print("assert ");
writeExpression(c.getOperand(0),out);
out.println();
}
private void writeAssume(int indent, Location<Bytecode.Assume> c, PrintWriter out) {
out.print("assume ");
writeExpression(c.getOperand(0),out);
out.println();
}
private void writeAssign(int indent, Location<Bytecode.Assign> stmt, PrintWriter out) {
Location<?>[] lhs = stmt.getOperandGroup(SyntaxTree.LEFTHANDSIDE);
Location<?>[] rhs = stmt.getOperandGroup(SyntaxTree.RIGHTHANDSIDE);
if(lhs.length > 0) {
for(int i=0;i!=lhs.length;++i) {
if(i!=0) { out.print(", "); }
writeExpression(lhs[i],out);
}
out.print(" = ");
}
writeExpressions(rhs,out);
out.println();
}
private void writeBreak(int indent, Location<Bytecode.Break> b, PrintWriter out) {
out.println("break");
}
private void writeContinue(int indent, Location<Bytecode.Continue> b, PrintWriter out) {
out.println("continue");
}
private void writeDebug(int indent, Location<Bytecode.Debug> b, PrintWriter out) {
out.println("debug");
}
private void writeDoWhile(int indent, Location<Bytecode.DoWhile> b, PrintWriter out) {
Location<?>[] loopInvariant = b.getOperandGroup(0);
Location<?>[] modifiedOperands = b.getOperandGroup(1);
out.println("do:");
//
writeBlock(indent+1,b.getBlock(0),out);
tabIndent(indent+1,out);
out.print("while ");
writeExpression(b.getOperand(0),out);
out.print(" modifies ");
writeExpressions(modifiedOperands,out);
for(Location<?> invariant : loopInvariant) {
out.println();
tabIndent(indent+1,out);
out.print("where ");
writeExpression(invariant,out);
}
// FIXME: add invariants
out.println();
}
private void writeFail(int indent, Location<Bytecode.Fail> c, PrintWriter out) {
out.println("fail");
}
private void writeIf(int indent, Location<Bytecode.If> b, PrintWriter out) {
out.print("if ");
writeExpression(b.getOperand(0),out);
out.println(":");
writeBlock(indent+1,b.getBlock(0),out);
if(b.numberOfBlocks() > 1) {
tabIndent(indent+1,out);
out.println("else:");
writeBlock(indent+1,b.getBlock(1),out);
}
}
private void writeIndirectInvoke(int indent, Location<Bytecode.IndirectInvoke> stmt, PrintWriter out) {
Location<?>[] operands = stmt.getOperands();
writeExpression(operands[0],out);
out.print("(");
for(int i=1;i!=operands.length;++i) {
if(i!=1) {
out.print(", ");
}
writeExpression(operands[i],out);
}
out.println(")");
}
private void writeInvoke(int indent, Location<Bytecode.Invoke> stmt, PrintWriter out) {
out.print(stmt.getBytecode().name() + "(");
Location<?>[] operands = stmt.getOperands();
for(int i=0;i!=operands.length;++i) {
if(i!=0) {
out.print(", ");
}
writeExpression(operands[i],out);
}
out.println(")");
}
private void writeNamedBlock(int indent, Location<Bytecode.NamedBlock> b, PrintWriter out) {
out.print(b.getBytecode().getName());
out.println(":");
writeBlock(indent+1,b.getBlock(0),out);
}
private void writeWhile(int indent, Location<Bytecode.While> b, PrintWriter out) {
out.print("while ");
writeExpression(b.getOperand(0),out);
Location<?>[] loopInvariant = b.getOperandGroup(0);
Location<?>[] modifiedOperands = b.getOperandGroup(1);
out.print(" modifies ");
writeExpressions(modifiedOperands,out);
//
for(Location<?> invariant : loopInvariant) {
out.println();
tabIndent(indent+1,out);
out.print("where ");
writeExpression(invariant,out);
}
out.println(":");
writeBlock(indent+1,b.getBlock(0),out);
}
private void writeReturn(int indent, Location<Bytecode.Return> b, PrintWriter out) {
Location<?>[] operands = b.getOperands();
out.print("return");
if(operands.length > 0) {
out.print(" ");
writeExpressions(operands,out);
}
out.println();
}
private void writeSkip(int indent, Location<Bytecode.Skip> b, PrintWriter out) {
out.println("skip");
}
private void writeSwitch(int indent, Location<Bytecode.Switch> b, PrintWriter out) {
out.print("switch ");
writeExpression(b.getOperand(0), out);
out.println(":");
for (int i = 0; i != b.numberOfBlocks(); ++i) {
// FIXME: ugly
Bytecode.Case cAse = b.getBytecode().cases()[i];
Constant[] values = cAse.values();
tabIndent(indent + 2, out);
if (values.length == 0) {
out.println("default:");
} else {
out.print("case ");
for (int j = 0; j != values.length; ++j) {
if (j != 0) {
out.print(", ");
}
out.print(values[j]);
}
out.println(":");
}
writeBlock(indent + 2, b.getBlock(i), out);
}
}
private void writeVariableAccess(Location<VariableAccess> loc, PrintWriter out) {
Location<VariableDeclaration> vd = getVariableDeclaration(loc.getOperand(0));
out.print(vd.getBytecode().getName());
}
private void writeVariableDeclaration(int indent, Location<VariableDeclaration> loc, PrintWriter out) {
Location<?>[] operands = loc.getOperands();
out.print(loc.getType());
out.print(" ");
out.print(loc.getBytecode().getName());
if (operands.length > 0) {
out.print(" = ");
writeExpression(operands[0], out);
}
out.println();
}
/**
* Write a bracketed operand if necessary. Any operand whose human-readable
* representation can contain whitespace must have brackets around it.
*
* @param operand
* @param enclosing
* @param out
*/
private void writeBracketedExpression(Location<?> expr, PrintWriter out) {
boolean needsBrackets = needsBrackets(expr.getBytecode());
if (needsBrackets) {
out.print("(");
}
writeExpression(expr, out);
if (needsBrackets) {
out.print(")");
}
}
private void writeExpressions(Location<?>[] exprs, PrintWriter out) {
for (int i = 0; i != exprs.length; ++i) {
if (i != 0) {
out.print(", ");
}
writeExpression(exprs[i], out);
}
}
@SuppressWarnings("unchecked")
private void writeExpression(Location<?> expr, PrintWriter out) {
switch (expr.getOpcode()) {
case Bytecode.OPCODE_arraylength:
writeArrayLength((Location<Bytecode.Operator>) expr,out);
break;
case Bytecode.OPCODE_arrayindex:
writeArrayIndex((Location<Bytecode.Operator>) expr,out);
break;
case Bytecode.OPCODE_array:
writeArrayInitialiser((Location<Bytecode.Operator>) expr,out);
break;
case Bytecode.OPCODE_arraygen:
writeArrayGenerator((Location<Bytecode.Operator>) expr,out);
break;
case Bytecode.OPCODE_convert:
writeConvert((Location<Bytecode.Convert>) expr, out);
break;
case Bytecode.OPCODE_const:
writeConst((Location<Bytecode.Const>) expr, out);
break;
case Bytecode.OPCODE_fieldload:
writeFieldLoad((Location<Bytecode.FieldLoad>) expr, out);
break;
case Bytecode.OPCODE_indirectinvoke:
writeIndirectInvoke((Location<Bytecode.IndirectInvoke>) expr, out);
break;
case Bytecode.OPCODE_invoke:
writeInvoke((Location<Bytecode.Invoke>) expr, out);
break;
case Bytecode.OPCODE_lambda:
writeLambda((Location<Bytecode.Lambda>) expr, out);
break;
case Bytecode.OPCODE_record:
writeRecordConstructor((Location<Bytecode.Operator>) expr, out);
break;
case Bytecode.OPCODE_newobject:
writeNewObject((Location<Bytecode.Operator>) expr,out);
break;
case Bytecode.OPCODE_dereference:
case Bytecode.OPCODE_logicalnot:
case Bytecode.OPCODE_neg:
case Bytecode.OPCODE_bitwiseinvert:
writePrefixLocations((Location<Bytecode.Operator>) expr,out);
break;
case Bytecode.OPCODE_all:
case Bytecode.OPCODE_some:
writeQuantifier((Location<Bytecode.Quantifier>) expr, out);
break;
case Bytecode.OPCODE_add:
case Bytecode.OPCODE_sub:
case Bytecode.OPCODE_mul:
case Bytecode.OPCODE_div:
case Bytecode.OPCODE_rem:
case Bytecode.OPCODE_eq:
case Bytecode.OPCODE_ne:
case Bytecode.OPCODE_lt:
case Bytecode.OPCODE_le:
case Bytecode.OPCODE_gt:
case Bytecode.OPCODE_ge:
case Bytecode.OPCODE_logicaland:
case Bytecode.OPCODE_logicalor:
case Bytecode.OPCODE_bitwiseor:
case Bytecode.OPCODE_bitwisexor:
case Bytecode.OPCODE_bitwiseand:
case Bytecode.OPCODE_shl:
case Bytecode.OPCODE_shr:
case Bytecode.OPCODE_is:
writeInfixLocations((Location<Bytecode.Operator>) expr, out);
break;
case Bytecode.OPCODE_varmove:
case Bytecode.OPCODE_varcopy:
writeVariableAccess((Location<VariableAccess>) expr, out);
break;
default:
throw new IllegalArgumentException("unknown bytecode encountered: " + expr.getBytecode());
}
}
private void writeArrayLength(Location<Bytecode.Operator> expr, PrintWriter out) {
out.print("|");
writeExpression(expr.getOperand(0), out);
out.print("|");
}
private void writeArrayIndex(Location<Bytecode.Operator> expr, PrintWriter out) {
writeExpression(expr.getOperand(0), out);
out.print("[");
writeExpression(expr.getOperand(1), out);
out.print("]");
}
private void writeArrayInitialiser(Location<Bytecode.Operator> expr, PrintWriter out) {
Location<?>[] operands = expr.getOperands();
out.print("[");
for(int i=0;i!=operands.length;++i) {
if(i != 0) {
out.print(", ");
}
writeExpression(operands[i],out);
}
out.print("]");
}
private void writeArrayGenerator(Location<Bytecode.Operator> expr, PrintWriter out) {
out.print("[");
writeExpression(expr.getOperand(0), out);
out.print(" ; ");
writeExpression(expr.getOperand(1), out);
out.print("]");
}
private void writeConvert(Location<Bytecode.Convert> expr, PrintWriter out) {
out.print("(" + expr.getType() + ") ");
writeExpression(expr.getOperand(0),out);
}
private void writeConst(Location<Bytecode.Const> expr, PrintWriter out) {
out.print(expr.getBytecode().constant());
}
private void writeFieldLoad(Location<Bytecode.FieldLoad> expr, PrintWriter out) {
writeBracketedExpression(expr.getOperand(0),out);
out.print("." + expr.getBytecode().fieldName());
}
private void writeIndirectInvoke(Location<Bytecode.IndirectInvoke> expr, PrintWriter out) {
Location<?>[] operands = expr.getOperands();
writeExpression(operands[0],out);
out.print("(");
for(int i=1;i!=operands.length;++i) {
if(i!=1) {
out.print(", ");
}
writeExpression(operands[i],out);
}
out.print(")");
}
private void writeInvoke(Location<Bytecode.Invoke> expr, PrintWriter out) {
out.print(expr.getBytecode().name() + "(");
Location<?>[] operands = expr.getOperands();
for(int i=0;i!=operands.length;++i) {
if(i!=0) {
out.print(", ");
}
writeExpression(operands[i],out);
}
out.print(")");
}
@SuppressWarnings("unchecked")
private void writeLambda(Location<Bytecode.Lambda> expr, PrintWriter out) {
out.print("&[");
Location<?>[] environment = expr.getOperandGroup(SyntaxTree.ENVIRONMENT);
for (int i = 0; i != environment.length; ++i) {
Location<VariableDeclaration> var = (Location<VariableDeclaration>) environment[i];
if (i != 0) {
out.print(", ");
}
out.print(var.getType());
out.print(" ");
out.print(var.getBytecode().getName());
}
out.print("](");
Location<?>[] parameters = expr.getOperandGroup(SyntaxTree.PARAMETERS);
for (int i = 0; i != parameters.length; ++i) {
Location<VariableDeclaration> var = (Location<VariableDeclaration>) parameters[i];
if (i != 0) {
out.print(", ");
}
out.print(var.getType());
out.print(" ");
out.print(var.getBytecode().getName());
}
out.print(" -> ");
writeExpression(expr.getOperand(0), out);
out.print(")");
}
private void writeRecordConstructor(Location<Bytecode.Operator> expr, PrintWriter out) {
Type.EffectiveRecord t = (Type.EffectiveRecord) expr.getType();
String[] fields = t.getFieldNames();
Location<?>[] operands = expr.getOperands();
out.print("{");
for (int i = 0; i != operands.length; ++i) {
if (i != 0) {
out.print(", ");
}
out.print(fields[i]);
out.print(" ");
writeExpression(operands[i], out);
}
out.print("}");
}
private void writeNewObject(Location<Bytecode.Operator> expr, PrintWriter out) {
out.print("new ");
writeExpression(expr.getOperand(0), out);
}
private void writePrefixLocations(Location<Bytecode.Operator> expr, PrintWriter out) {
// Prefix operators
out.print(opcode(expr.getBytecode().kind()));
writeBracketedExpression(expr.getOperand(0),out);
}
private void writeInfixLocations(Location<Bytecode.Operator> c, PrintWriter out) {
writeBracketedExpression(c.getOperand(0),out);
out.print(" ");
out.print(opcode(c.getBytecode().kind()));
out.print(" ");
writeBracketedExpression(c.getOperand(1),out);
}
@SuppressWarnings("unchecked")
private void writeQuantifier(Location<Bytecode.Quantifier> c, PrintWriter out) {
out.print(quantifierKind(c));
out.print(" { ");
for (int i = 0; i != c.numberOfOperandGroups(); ++i) {
Location<?>[] range = c.getOperandGroup(i);
if (i != 0) {
out.print(", ");
}
Location<VariableDeclaration> v = (Location<VariableDeclaration>) range[SyntaxTree.VARIABLE];
out.print(v.getBytecode().getName());
out.print(" in ");
writeExpression(range[SyntaxTree.START], out);
out.print("..");
writeExpression(range[SyntaxTree.END], out);
}
out.print(" | ");
writeExpression(c.getOperand(SyntaxTree.CONDITION), out);
out.print(" } ");
}
private String quantifierKind(Location<Bytecode.Quantifier> c) {
switch(c.getOpcode()) {
case Bytecode.OPCODE_some:
return "some";
case Bytecode.OPCODE_all:
return "all";
}
throw new IllegalArgumentException();
}
private static void writeModifiers(List<Modifier> modifiers, PrintWriter out) {
for(Modifier m : modifiers) {
out.print(m.toString());
out.print(" ");
}
}
private boolean needsBrackets(Bytecode e) {
switch(e.getOpcode()) {
case Bytecode.OPCODE_convert:
case Bytecode.OPCODE_add:
case Bytecode.OPCODE_sub:
case Bytecode.OPCODE_mul:
case Bytecode.OPCODE_div:
case Bytecode.OPCODE_rem:
case Bytecode.OPCODE_eq:
case Bytecode.OPCODE_ne:
case Bytecode.OPCODE_lt:
case Bytecode.OPCODE_le:
case Bytecode.OPCODE_gt:
case Bytecode.OPCODE_ge:
case Bytecode.OPCODE_logicaland:
case Bytecode.OPCODE_logicalor:
case Bytecode.OPCODE_bitwiseor:
case Bytecode.OPCODE_bitwisexor:
case Bytecode.OPCODE_bitwiseand:
case Bytecode.OPCODE_shl:
case Bytecode.OPCODE_shr:
case Bytecode.OPCODE_is:
case Bytecode.OPCODE_newobject:
case Bytecode.OPCODE_dereference:
return true;
}
return false;
}
private static String opcode(Bytecode.OperatorKind k) {
switch(k) {
case NEG:
return "-";
case NOT:
return "!";
case BITWISEINVERT:
return "~";
case DEREFERENCE:
return "*";
// Binary
case ADD:
return "+";
case SUB:
return "-";
case MUL:
return "*";
case DIV:
return "/";
case REM:
return "%";
case EQ:
return "==";
case NEQ:
return "!=";
case LT:
return "<";
case LTEQ:
return "<=";
case GT:
return ">";
case GTEQ:
return ">=";
case AND:
return "&&";
case OR:
return "||";
case BITWISEOR:
return "|";
case BITWISEXOR:
return "^";
case BITWISEAND:
return "&";
case LEFTSHIFT:
return "<<";
case RIGHTSHIFT:
return ">>";
case IS:
return "is";
case NEW:
return "new";
default:
throw new IllegalArgumentException("unknown operator kind : " + k);
}
}
private static void tabIndent(int indent, PrintWriter out) {
indent = indent * 4;
for(int i=0;i<indent;++i) {
out.print(" ");
}
}
@SuppressWarnings("unchecked")
private Location<VariableDeclaration> getVariableDeclaration(Location<?> loc) {
switch (loc.getOpcode()) {
case Bytecode.OPCODE_vardecl:
case Bytecode.OPCODE_vardeclinit:
return (Location<VariableDeclaration>) loc;
case Bytecode.OPCODE_aliasdecl:
return getVariableDeclaration(loc.getOperand(0));
}
throw new IllegalArgumentException("invalid location provided: " + loc);
}
}