/* Copyright (c) 2009-2013 Olivier Chafik, All Rights Reserved This file is part of JNAerator (http://jnaerator.googlecode.com/). JNAerator is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. JNAerator 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with JNAerator. If not, see <http://www.gnu.org/licenses/>. */ package com.ochafik.lang.jnaerator; import com.ochafik.lang.jnaerator.BridJTypeConversion.NL4JConversion; import java.util.Collections; import java.util.Collection; import java.io.PrintStream; import com.ochafik.lang.jnaerator.parser.Identifier.SimpleIdentifier; import org.bridj.BridJ; import java.util.Map; import java.util.LinkedHashMap; import java.util.Set; import java.util.HashSet; import java.util.List; import java.util.ArrayList; import java.util.Arrays; import com.ochafik.lang.jnaerator.parser.*; import static com.ochafik.lang.jnaerator.parser.Statement.*; import com.ochafik.lang.jnaerator.parser.StoredDeclarations.*; import com.ochafik.lang.jnaerator.parser.TypeRef.*; import com.ochafik.lang.jnaerator.parser.Expression.*; import com.ochafik.lang.jnaerator.parser.Declarator.*; import com.nativelibs4java.jalico.Pair; import com.ochafik.util.string.StringUtils; import static com.ochafik.lang.jnaerator.parser.ElementsHelper.*; import static com.ochafik.lang.jnaerator.TypeConversion.*; import static com.ochafik.lang.jnaerator.MatchingUtils.*; /* mvn -o compile exec:java -Dexec.mainClass=com.ochafik.lang.jnaerator.JNAerator */ public class BridJer { Result result; public BridJer(Result result) { this.result = result; } String primName(TypeRef tr) { tr = tr.clone(); tr.setModifiers(Collections.EMPTY_LIST); return tr.toString(); } String primCapName(TypeRef tr) { return StringUtils.capitalize(primName(tr)); } Expression staticPtrMethod(String name, Expression... args) { return methodCall(expr(typeRef(ptrClass())), name, args); } int iFile = 0; public Pair<Element, List<Declaration>> convertToJava(Element element, final Identifier libraryClassName) { //element = element.clone(); try { PrintStream out = new PrintStream("jnaerator-" + (iFile++) + ".out"); out.println(element); out.close(); } catch (Exception ex) { ex.printStackTrace(); } final List<Declaration> extraDeclarationsOut = new ArrayList<Declaration>(); final ReferencedElements ref = findReferencedElements(element); // Second pass : replacement ! element.accept(new Scanner() { @Override public void visitVariableRef(VariableRef vr) { super.visitVariableRef(vr); String identStr = vr.getName() + ""; if ("NULL".equals(identStr)) { vr.replaceBy(nullExpr()); } } int nConstants = 0; @Override public void visitConstant(Constant c) { if (c.getValue() instanceof String) { //Struct s = c.findParentOfType(Struct.class); Class charClass; String ptrMethodName; if (c.getType() == Constant.Type.LongString) { charClass = Short.class; ptrMethodName = "pointerToWideCString"; } else { charClass = Byte.class; ptrMethodName = "pointerToCString"; } String fieldName = "strConstant" + (++nConstants); VariablesDeclaration staticConstField = new VariablesDeclaration(typeRef(ident(ptrClass(), expr(typeRef(charClass)))), new DirectDeclarator(fieldName, staticPtrMethod(ptrMethodName, c.clone()))); staticConstField.addModifiers(ModifierType.Static, ModifierType.Private, ModifierType.Final); //s.addDeclaration(staticConstField); extraDeclarationsOut.add(staticConstField); c.replaceBy(varRef(fieldName)); return; } super.visitConstant(c); } // // static Map<String, String> primToCapNames = new LinkedHashMap<String, String>(); // static { // primToCapNames.put("int", "Int"); // primToCapNames.put("long", "Long"); // primToCapNames.put("short", "Short"); // primToCapNames.put("byte", "Byte"); // primToCapNames.put("double", "Double"); // primToCapNames.put("float", "Float"); // primToCapNames.put("char", "Char"); // primToCapNames.put("boolean", "Boolean"); // } void replaceMalloc(TypeRef pointedType, Element toReplace, Expression sizeExpression) { // TODO handle casts and sizeof expressions ! Pair<TypeRef, Expression> typeAndSize = recognizeSizeOfMult(sizeExpression); if (typeAndSize != null && (pointedType == null || pointedType.equals(typeAndSize.getFirst()))) { String tStr = String.valueOf(typeAndSize.getFirst()); try { JavaPrim prim = JavaPrim.getJavaPrim(tStr); if (prim != null && prim.isPrimitive) { String cap = StringUtils.capitalize(tStr); toReplace.replaceBy(staticPtrMethod("allocate" + cap + "s", typeAndSize.getValue())); return; } } catch (Throwable th) { // Do nothing } } toReplace.replaceBy(staticPtrMethod("allocateBytes", sizeExpression)); } <T extends Element> void visitAllButConstants(List<T> elements) { if (elements == null) { return; } for (T element : elements) { if (element == null || (element instanceof Constant)) { continue; } element.accept(this); } } List<Expression> getArgs(List<Pair<String, Expression>> args) { if (args == null) { return null; } List<Expression> ret = new ArrayList<Expression>(args.size()); for (Pair<String, Expression> p : args) { ret.add(p.getValue()); } return ret; } @Override public void visitDelete(Delete d) { d.replaceBy(stat(methodCall(d.getValue(), "release"))); } @Override public void visitFunctionCall(FunctionCall fc) { super.visit(fc.getTarget()); super.visit(fc.getFunction()); List<Expression> arguments = getArgs(fc.getArguments()); //super.visitFunctionCall(fc); if (fc.getTarget() == null && (fc.getFunction() instanceof VariableRef)) { Identifier ident = ((VariableRef) fc.getFunction()).getName(); if ((ident instanceof SimpleIdentifier) && ((SimpleIdentifier) ident).getTemplateArguments().isEmpty()) { String name = ident.toString(); int nArgs = arguments == null ? 0 : arguments.size(); Expression arg1 = nArgs > 0 ? arguments.get(0) : null, arg2 = nArgs > 1 ? arguments.get(1) : null, arg3 = nArgs > 2 ? arguments.get(2) : null; if ("printf".equals(name)) { visitAllButConstants(arguments); fc.replaceBy(methodCall(memberRef(expr(typeRef(System.class)), "out"), "println", formatStr(arg1, arguments, 1))); return; } else if ("sprintf".equals(name)) { visitAllButConstants(arguments); fc.replaceBy(methodCall(arg1, "setCString", formatStr(arg2, arguments, 2))); return; } switch (nArgs) { case 1: if ("malloc".equals(name)) { visit(arguments); replaceMalloc(null, fc, arg1); return; } else if ("free".equals(name)) { visit(arguments); fc.replaceBy(methodCall(arg1, "release")); return; } break; case 3: if ("memset".equals(name)) { visit(arguments); Expression value = arg2, num = arg3; if ("0".equals(value + "")) { fc.replaceBy(methodCall(arg1, "clearBytes", expr(0), num)); } else { fc.replaceBy(methodCall(arg1, "clearBytes", expr(0), num, value)); } return; } else if ("memcpy".equals(name)) { visit(arguments); Expression dest = arg1, source = arg2, num = arg3; fc.replaceBy(methodCall(source, "copyBytesAtOffsetTo", expr(0), dest, expr(0), num)); return; } else if ("memmov".equals(name)) { visit(arguments); Expression dest = arg1, source = arg2, num = arg3; fc.replaceBy(methodCall(source, "moveBytesAtOffsetTo", expr(0), dest, expr(0), num)); return; } else if ("memcmp".equals(name)) { visit(arguments); Expression ptr1 = arg1, ptr2 = arg2, num = arg3; fc.replaceBy(methodCall(ptr1, "compareBytes", ptr2, num)); return; } break; } } } visit(arguments); } Expression formatStr(Expression str, List<Expression> arguments, int argsToSkip) { if (arguments.isEmpty()) { return str; } List<Expression> fmtArgs = new ArrayList<Expression>(); if (!(str instanceof Constant)) { str = methodCall(str, "getCString"); } fmtArgs.add(str); for (int i = argsToSkip, nArgs = arguments.size(); i < nArgs; i++) { fmtArgs.add(arguments.get(i)); } return methodCall(expr(typeRef(String.class)), "format", fmtArgs.toArray(new Expression[fmtArgs.size()])); } @Override public void visitIf(If ifStat) { super.visitIf(ifStat); Expression cond = ifStat.getCondition(); if (!(cond instanceof BinaryOp)) // TODO use typing rather than this hack to detect implicit boolean conversion in if statements (and not only there) { cond.replaceBy(expr(cond, BinaryOperator.IsDifferent, expr(0))); } } @Override public void visitUnaryOp(UnaryOp unaryOp) { super.visitUnaryOp(unaryOp); switch (unaryOp.getOperator()) { case Dereference: unaryOp.replaceBy(methodCall(unaryOp.getOperand(), "get")); break; case Reference: // TODO handle special case of referenced primitives, for instance unaryOp.replaceBy(methodCall(unaryOp.getOperand(), "getReference")); break; } } @Override public void visitCast(Cast cast) { super.visitCast(cast); if (cast.getType() instanceof TargettedTypeRef) { cast.replaceBy(methodCall(cast.getTarget(), "as", result.typeConverter.typeLiteral(((TargettedTypeRef) cast.getType()).getTarget()))); } } @Override public void visitArrayAccess(ArrayAccess arrayAccess) { super.visitArrayAccess(arrayAccess); arrayAccess.replaceBy(methodCall(arrayAccess.getTarget(), "get", arrayAccess.getIndex())); } @Override public void visitMemberRef(MemberRef memberRef) { // TODO restrict to struct/class fields // TODO handle global variables with explicit namespaces... if (!(memberRef.getParentElement() instanceof FunctionCall)) { if (memberRef.getName() != null) { switch (memberRef.getMemberRefStyle()) { case Colons: memberRef.setMemberRefStyle(MemberRefStyle.Dot); break; case Arrow: case Dot: Expression rep = methodCall(memberRef.getTarget(), memberRef.getName().toString()); if (memberRef.getMemberRefStyle() == MemberRefStyle.Arrow) { rep = methodCall(rep, "get"); } memberRef.replaceBy(rep); rep.accept(this); return; } } } super.visitMemberRef(memberRef); } @Override public void visitVariablesDeclaration(VariablesDeclaration v) { super.visitVariablesDeclaration(v); if (v.getDeclarators().size() == 1) { Declarator decl = v.getDeclarators().get(0); //DirectDeclarator decl = (DirectDeclarator); TypeRef vt = v.getValueType(); MutableByDeclarator mt = decl instanceof DirectDeclarator ? vt : decl.mutateTypeKeepingParent(vt); if (mt instanceof TypeRef) { TypeRef mutatedType = (TypeRef) mt; if (decl.getDefaultValue() == null) { TypeRef actualType = mutatedType; boolean referenced = ref.varDeclTypeRefsTransformedToPointers.contains(v) && (mutatedType instanceof Pointer); if (referenced) { actualType = ((Pointer) mutatedType).getTarget(); } if (result.symbols.isClassType(actualType)) { decl.setDefaultValue(referenced ? staticPtrMethod("allocate", result.typeConverter.typeLiteral(actualType)) : new Expression.New(actualType.clone())); Expression vr = varRef(new SimpleIdentifier(decl.resolveName())); Statement deleteStat = stat(methodCall(expr(typeRef(BridJ.class)), "delete", referenced ? methodCall(vr, "get") : vr)); deleteStat.setCommentAfter("// object would end up being deleted by the GC later, but in C++ it would be deleted here."); Statement.Block parentBlock = (Statement.Block) v.getParentElement(); List<Statement> stats = new ArrayList<Statement>(parentBlock.getStatements()); for (int i = stats.size(); i-- != 0;) { Statement stat = stats.get(i); if (!(stat instanceof Statement.Return) && !deleteStatements.contains(stat)) { stats.add(i + 1, deleteStat); deleteStatements.add(deleteStat); break; } } parentBlock.setStatements(stats); } }// else { NL4JConversion conv = ((BridJTypeConversion) result.typeConverter).convertTypeToNL4J( mutatedType, libraryClassName,//callerLibraryName, null,//thisField("io"), null,//varRef(name), -1,//fieldIndex, -1//bits ); v.setValueType(conv.getTypeRef(false)); if (conv.arrayLengths != null && (mutatedType instanceof TargettedTypeRef) && decl.getDefaultValue() == null) { v.setDeclarators(Arrays.asList((Declarator) new DirectDeclarator(decl.resolveName(), newAllocateArray(((TargettedTypeRef) mutatedType).getTarget(), conv.arrayLengths)))); } //} } } } Set<Element> deleteStatements = new HashSet<Element>(); @Override public void visitAssignmentOp(AssignmentOp assignment) { BinaryOperator binOp = assignment.getOperator().getCorrespondingBinaryOp(); Expression value = assignment.getValue(); value.setParenthesisIfNeeded(); if (assignment.getTarget() instanceof UnaryOp) { UnaryOp uop = (UnaryOp) assignment.getTarget(); if (uop.getOperator() == UnaryOperator.Dereference) { visit(uop.getOperand()); visit(assignment.getValue()); Expression target = uop.getOperand(); if (binOp != null) { value = expr(methodCall(target.clone(), "get"), binOp, value); } assignment.replaceBy(methodCall(target, "set", value)); return; } } if (assignment.getTarget() instanceof ArrayAccess) { ArrayAccess aa = (ArrayAccess) assignment.getTarget(); visit(aa.getTarget()); visit(aa.getIndex()); visit(assignment.getValue()); Expression target = aa.getTarget(); Expression index = aa.getIndex(); if (binOp != null) { value = expr(methodCall(target.clone(), "get", index.clone()), binOp, value); } assignment.replaceBy(methodCall(target, "set", index, value)); return; } if (assignment.getTarget() instanceof MemberRef) { MemberRef mr = (MemberRef) assignment.getTarget(); boolean isArrow = mr.getMemberRefStyle() == MemberRefStyle.Arrow; if (isArrow || mr.getMemberRefStyle() == MemberRefStyle.Dot) { Expression target = mr.getTarget(); String name = mr.getName().toString(); if (binOp != null) { value = expr(methodCall(target.clone(), name), binOp, value); } if (isArrow) { target = methodCall(target, "get"); } assignment.replaceBy(methodCall(target, name, value)); return; } } super.visitAssignmentOp(assignment); } @Override public void visitNew(New new1) { super.visitNew(new1); if (new1.getConstruction() == null) { new1.replaceBy(staticPtrMethod("allocate" + primCapName(new1.getType()))); } } public void notSup(Element x, String msg) throws UnsupportedConversionException { throw new UnsupportedConversionException(x, msg); } /* @Override public void visitExpressionStatement(ExpressionStatement expressionStatement) { super.visitExpressionStatement(expressionStatement); if (expressionStatement.getExpression() instanceof UnaryOp) { UnaryOp uop = (UnaryOp)expressionStatement.getExpression(); Expression target = uop.getOperand(); switch (uop.getOperator()) { case PostIncr: case PreIncr: expressionStatement.replaceBy(expr(target)); } } }*/ Expression newAllocateArray(TypeRef tr, Collection<Expression> sizes) { return staticPtrMethod( "allocate" + primCapName(tr) + "s", sizes.toArray(new Expression[sizes.size()])); } @Override public void visitNewArray(NewArray newArray) { super.visitNewArray(newArray); if (newArray.getType() instanceof Primitive) { if (newArray.getDimensions().size() > 3) { notSup(newArray, "TODO only dimensions 1 to 3 are supported for primitive array creations !"); } newArray.replaceBy(newAllocateArray(newArray.getType(), newArray.getDimensions())); } else { if (newArray.getDimensions().size() != 1) { notSup(newArray, "TODO only dimension 1 is supported for reference array creations !"); } newArray.replaceBy( staticPtrMethod( "allocateArray", result.typeConverter.typeLiteral(newArray.getType()), newArray.getDimensions().get(0))); } } @Override public void visitPointer(Pointer pointer) { super.visitPointer(pointer); } /* @Override protected void visitTargettedTypeRef(TargettedTypeRef targettedTypeRef) { super.visitTargettedTypeRef(targettedTypeRef); if (targettedTypeRef.getTarget() != null) targettedTypeRef.replaceBy(pointerToTypeRef(targettedTypeRef.getTarget().clone())); }*/ }); return new Pair<Element, List<Declaration>>(element, extraDeclarationsOut); } TypeRef getWrapperType(TypeRef tr) { JavaPrim prim = result.typeConverter.getPrimitive(tr); if (prim != null) { return typeRef(prim.wrapperType); } return tr; } TypeRef pointerToTypeRef(TypeRef targetType) { Identifier id = ident(ptrClass()); id.resolveLastSimpleIdentifier().addTemplateArgument(expr(targetType)); return typeRef(id); } Class ptrClass() { return result.config.runtime.pointerClass; } static class ReferencedElements { final Set<Pair<Element, Integer>> referencedElements = new HashSet<Pair<Element, Integer>>(); final Set<Element> varDeclTypeRefsTransformedToPointers = new HashSet<Element>(); } private ReferencedElements findReferencedElements(Element element) { final ReferencedElements ret = new ReferencedElements(); element.accept(new Scanner() { @Override public void visitUnaryOp(UnaryOp unaryOp) { super.visitUnaryOp(unaryOp); if (unaryOp.getOperator() == UnaryOperator.Reference) { if (unaryOp.getOperand() instanceof VariableRef) { VariableRef vr = (VariableRef) unaryOp.getOperand(); Element e = result.symbols.getVariable(vr.getName()); if (e != null) { ret.referencedElements.add(new Pair<Element, Integer>(e, e.getId())); } } } } }); final Map<Integer, String> referencedElementsChangedNames = new LinkedHashMap<Integer, String>(); for (Pair<Element, Integer> kv : ret.referencedElements) { Element e = kv.getKey(); //int id = kv if (e instanceof DirectDeclarator) { DirectDeclarator decl = (DirectDeclarator) e; String name = decl.getName(); String changedName = "p" + StringUtils.capitalize(name); referencedElementsChangedNames.put(e.getId(), changedName); decl.setName(changedName); VariablesDeclaration vd = (VariablesDeclaration) decl.getParentElement(); TypeRef tr = vd.getValueType(); //TypeRef wrapper = getWrapperType(tr); //vd.setValueType(pointerToTypeRef(wrapper)); vd.setValueType(new Pointer(tr, PointerStyle.Pointer)); ret.varDeclTypeRefsTransformedToPointers.add(vd); Expression defVal = decl.getDefaultValue(); if (defVal != null) { decl.setDefaultValue(staticPtrMethod("pointerTo" + primCapName(tr), defVal)); } else { decl.setDefaultValue(staticPtrMethod("allocate" + primCapName(tr))); } } } // First pass to detect referenced variables (no replacement here) : element.accept(new Scanner() { @Override public void visitIdentifier(Identifier identifier) { super.visitIdentifier(identifier); Element e = result.symbols.getVariable(identifier); if (e != null && isReferenced(e)) { String changedName = referencedElementsChangedNames.get(e.getId()); if (changedName != null) { Identifier replacedIdentifier = ident(changedName); identifier.replaceBy(replacedIdentifier); ret.referencedElements.add(new Pair<Element, Integer>(replacedIdentifier, replacedIdentifier.getId())); } } } boolean isReferenced(Element e) { if (e == null) { return false; } return ret.referencedElements.contains(new Pair<Element, Integer>(e, e.getId())); } @Override public void visitVariableRef(VariableRef vr) { super.visitVariableRef(vr); Identifier ident = vr.getName(); if (isReferenced(ident)) { vr.replaceBy(methodCall(varRef(ident), "get")); } } @Override public void visitUnaryOp(UnaryOp unaryOp) { if (unaryOp.getOperator() == UnaryOperator.Reference) { if (unaryOp.getOperand() instanceof VariableRef) { VariableRef vr = (VariableRef) unaryOp.getOperand(); Identifier ident = vr.getName(); Element e = result.symbols.getVariable(ident); if ((e != null || isReferenced(e)) || isReferenced(ident)) { String changedName = referencedElementsChangedNames.get(e.getId()); if (changedName != null) { Element rep = varRef(changedName); unaryOp.replaceBy(rep); visit(rep); return; } } } } super.visitUnaryOp(unaryOp); } }); return ret; } }