/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.ochafik.lang.jnaerator;
import com.ochafik.io.IOUtils;
import com.ochafik.lang.jnaerator.parser.DeclarationsHolder;
import com.ochafik.lang.jnaerator.parser.Function;
import com.ochafik.lang.jnaerator.parser.Identifier;
import com.ochafik.lang.jnaerator.parser.Struct;
import com.ochafik.lang.jnaerator.parser.VariablesDeclaration;
import java.io.IOException;
import com.ochafik.lang.jnaerator.parser.Arg;
import com.ochafik.lang.jnaerator.parser.Declarator;
import static com.ochafik.lang.jnaerator.parser.ElementsHelper.*;
import com.ochafik.lang.jnaerator.parser.Expression;
import com.ochafik.lang.jnaerator.parser.Include;
import com.ochafik.lang.jnaerator.parser.ModifierType;
import com.ochafik.lang.jnaerator.parser.SourceFile;
import com.ochafik.lang.jnaerator.parser.TypeRef;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static com.ochafik.lang.jnaerator.GYPUtils.*;
import com.ochafik.lang.jnaerator.TypeConversion.JavaPrim;
import com.ochafik.lang.jnaerator.parser.Declaration;
import com.ochafik.lang.jnaerator.parser.Define;
import com.ochafik.lang.jnaerator.parser.Element;
import com.ochafik.lang.jnaerator.parser.ElementsHelper;
import com.ochafik.lang.jnaerator.parser.Expression.BinaryOperator;
import com.ochafik.lang.jnaerator.parser.Expression.MemberRefStyle;
import com.ochafik.lang.jnaerator.parser.Expression.UnaryOperator;
import com.ochafik.lang.jnaerator.parser.Identifier.QualificationSeparator;
import com.ochafik.lang.jnaerator.parser.Statement;
import com.ochafik.lang.jnaerator.parser.Statement.Block;
import com.ochafik.lang.jnaerator.parser.Statement.If;
import com.ochafik.lang.jnaerator.parser.Statement.Return;
import com.nativelibs4java.jalico.Pair;
import java.io.File;
import org.bridj.demangling.Demangler;
/**
*
* @author ochafik
*/
public class NodeJSDeclarationsConverter extends DeclarationsConverter {
public NodeJSDeclarationsConverter(Result result) {
super(result);
}
@Override
protected void configureCallbackStruct(Struct callbackStruct) {
//throw new UnsupportedOperationException("Not supported yet.");
}
public void convertCallback(TypeRef.FunctionSignature functionSignature, Signatures signatures, DeclarationsHolder out, Identifier callerLibraryName) {
}
// public void convertConstants(String library, List<Define> defines, Element sourcesRoot, final Signatures signatures, final DeclarationsHolder out, final Identifier libraryClassName) {
// }
@Override
protected void outputNSString(String name, String value, DeclarationsHolder out, Signatures signatures, Element... elementsToTakeCommentsFrom) {
}
@Override
protected void convertConstant(String name, JavaPrim prim, TypeRef mutatedType, Expression defaultValue, VariablesDeclaration v, Declarator decl, DeclarationsHolder out, Signatures signatures, Identifier libraryClassName) {
convertConstant(name, defaultValue, out, signatures);
}
Expression.Constant.Type getValueConstantType(Expression value) {
if (value instanceof Expression.Cast) {
return getValueConstantType(((Expression.Cast) value).getTarget());
}
if (value instanceof Expression.BinaryOp) {
return getValueConstantType(((Expression.BinaryOp) value).getFirstOperand());
}
if (value instanceof Expression.Constant) {
return ((Expression.Constant) value).getType();
}
throw new UnsupportedConversionException(value, "Unknown value type");
}
@Override
protected void convertDefine(Define define, DeclarationsHolder out, Signatures signatures, Identifier libraryClassName) {
Expression value = define.getValue();
convertConstant(define.getName(), value, out, signatures);
}
protected void convertConstant(String name, Expression value, DeclarationsHolder out, Signatures signatures) {
Block initBlock = getInitMethodBody(out);
Expression expr;
Expression.Constant.Type valueType = getValueConstantType(value);
switch (valueType) {
case Int:
case UInt:
case IntegerString:
case Long:
case ULong:
case LongString:
case Short:
case Byte:
expr = methodCall(ident("v8", "Integer", "New"), value.clone());
break;
case Double:
case Float:
expr = methodCall(ident("v8", "Number", "New"), value.clone());
break;
case Bool:
expr = methodCall(ident("v8", "Boolean", "New"), value.clone());
break;
case Char:
case String:
expr = methodCall(ident("v8", "String", "New"), new Expression.Constant(valueType, value + "", null));
break;
case Null:
expr = methodCall(ident("v8", "Null"));
break;
default:
throw new UnsupportedConversionException(value, "Constant type not supported yet");
}
initBlock.addStatement(stat(methodCall(varRef(initTarget), MemberRefStyle.Arrow, "Set",
methodCall(ident("v8", "String", "NewSymbol"), expr(name)),
expr)));
}
Block getInitMethodBody(DeclarationsHolder decls) {
for (Declaration decl : decls.getDeclarations()) {
if (decl instanceof Function) {
return ((Function) decl).getBody();
}
}
throw new RuntimeException("Init method not found in " + decls);
}
static Identifier ident(String... components) {
return ElementsHelper.ident(Identifier.QualificationSeparator.Colons, components);
}
static Expression newV8String(String s) {
return methodCall(ident("v8", "String", "New"), expr(s));
}
static Expression arrowMethodCall(Expression target, String name, Expression... args) {
return methodCall(target, Expression.MemberRefStyle.Arrow, name, args);
}
static final String argsName = "_arguments_";
static final String returnValueName = "_return_";
static final String scopeName = "_scope_";
static final String initTarget = "_target_";
static final String dummyNodeBufferFreeCallbackName = "_dummy_node_buffer_free_callback_";
private enum NodeType {
Pointer, String, Number, Boolean, VAList, Unknown, Void
};
private TypeRef resolveTypeDef(TypeRef typeRef) {
TypeRef tr = result.typeConverter.normalizeTypeRef(typeRef);
return tr;
}
private NodeType getNodeType(TypeRef tr) {
if (tr instanceof TypeRef.TargettedTypeRef) {
TypeRef.TargettedTypeRef ttr = (TypeRef.TargettedTypeRef) tr;
if (ttr.getTarget().toString().matches("(const\\s+)?char")) {
return NodeType.String;
} else {
return NodeType.Pointer;
}
} else if (tr instanceof TypeRef.SimpleTypeRef) {
TypeRef.SimpleTypeRef str = (TypeRef.SimpleTypeRef) tr;
String n = str + "";//strstr.getName().toString();
if (n.matches("bool|BOOL")) {
return NodeType.Boolean;
} else if (n.matches("void")) {
return NodeType.Void;
} else if (n.equals("va_list") || n.equals("__builtin_va_list")) {
return NodeType.VAList;
} else {
return NodeType.Number;
}
} else if (tr instanceof TypeRef.FunctionSignature) {
return NodeType.Pointer;
}
return NodeType.Unknown;
}
@Override
protected void convertFunction(Function function, Signatures signatures, boolean callback,
DeclarationsHolder declarations, DeclarationsHolder implementations, Identifier libraryClassName,
String sig, Identifier functionName, String library, int iConstructor) {
assert implementations == declarations || declarations == null;
String methodName = library + "_" + (function.getParentElement() instanceof Struct ? ((Struct) function.getParentElement()).getTag() + "_" : "") + function.getName();
Function method = new Function(Function.Type.CppMethod, ident(methodName), typeRef(v8Ident("Handle", v8Ident("Value"))));
method.addArg(new Arg(argsName, new TypeRef.Pointer(typeRef(v8Ident("Arguments")), Declarator.PointerStyle.Reference).addModifiers(ModifierType.Const)));
Block body = new Block();
//if (args.Length() != 4) {
//return THROW_ERROR_EXCEPTION("ffi_call() requires 4 arguments!");
List<Arg> args = function.getArgs();
int argCount = args.size();
body.addStatement(
new If(
expr(methodCall(varRef(argsName), "Length"), BinaryOperator.IsDifferent, expr(argCount)),
new Return(
methodCall(v8Ident("ThrowException"),
newV8String(functionName + "() requires " + argCount + " arguments!")))));
List<Expression> params = new ArrayList<Expression>();
for (int iArg = 0; iArg < argCount; iArg++) {
Arg arg = args.get(iArg);
if (arg.isVarArg()) {
throw new UnsupportedConversionException(function, "varargs not supported yet");
}
TypeRef tr = resolveTypeDef(arg.getValueType());
NodeType argNodeType = getNodeType(tr);
Expression typeTest = null;
String typeErrorMessage = null;
Expression argExpr = new Expression.ArrayAccess(varRef(argsName), expr(iArg));
Expression argDeclExpr = null;
TypeRef argType = null;
Expression argUsageExpr = null;
switch (argNodeType) {
case String:
typeTest = arrowMethodCall(argExpr, "IsString");
typeErrorMessage = "expected a String";
argDeclExpr = arrowMethodCall(argExpr.clone(), "ToString");
argType = typeRef(v8Ident("Handle", v8Ident("String")));
argUsageExpr = cast(transformTypeForCast(arg.getValueType()),
expr(UnaryOperator.Dereference,
methodCall(ident("v8", "String", "AsciiValue"),
varRef(arg.getName()))));
break;
case Pointer:
typeTest = expr(
methodCall(argExpr.clone(), MemberRefStyle.Arrow, "IsNull"),
BinaryOperator.Or,
methodCall(ident("node", "Buffer", "HasInstance"), argExpr));
typeErrorMessage = "expected a Buffer";
argDeclExpr =
new Expression.ConditionalExpression(
methodCall(argExpr.clone(), MemberRefStyle.Arrow, "IsNull"),
varRef("NULL"),
methodCall(ident("node", "Buffer", "Data"),
methodCall(argExpr.clone(), Expression.MemberRefStyle.Dot, templateIdent(ident("As"), varRef(v8Ident("Object"))))));
argType = new TypeRef.Pointer(typeRef(ident("char")), Declarator.PointerStyle.Pointer);
argUsageExpr = cast(transformTypeForCast(arg.getValueType()), varRef(arg.getName()));
break;
case Boolean:
case Number:
String t = argNodeType.name();
typeTest = arrowMethodCall(argExpr, "Is" + t);
typeErrorMessage = "expected a " + t;
argDeclExpr = arrowMethodCall(argExpr.clone(), "To" + t);
argType = typeRef(v8Ident("Handle", v8Ident(t)));
argUsageExpr = cast(arg.getValueType().clone(), arrowMethodCall(expr(UnaryOperator.Dereference, varRef(arg.getName())), "Value"));
break;
case VAList:
throw new UnsupportedConversionException(function, "va_list not supported yet");
case Unknown:
case Void:
throw new UnsupportedConversionException(arg, "Cannot convert arguments of type " + tr);
}
if (typeTest != null) {
body.addStatement(
new If(
expr(UnaryOperator.Not, typeTest),
new Return(
methodCall(v8Ident("ThrowException"),
methodCall(ident("v8", "Exception", "TypeError"),
newV8String("Invalid value for argument '" + arg.getName() + "' at index " + iArg + (typeErrorMessage == null ? "" : ": " + typeErrorMessage)))))));
}
if (argDeclExpr != null) {
body.addStatement(stat(new VariablesDeclaration(argType, new Declarator.DirectDeclarator(arg.getName(), argDeclExpr))));
}
if (argUsageExpr != null) {
params.add(argUsageExpr);
}
}
TypeRef retTr = resolveTypeDef(function.getValueType());
NodeType retNodeType = getNodeType(retTr);
body.addStatement(new VariablesDeclaration(typeRef(v8Ident("HandleScope")), new Declarator.DirectDeclarator(scopeName)));
Expression call = methodCall(function.getName(), params.toArray(new Expression[params.size()]));
if (retNodeType == NodeType.Void) {
body.addStatement(stat(call));
} else {
VariablesDeclaration retDecl = new VariablesDeclaration(function.getValueType().clone(), new Declarator.DirectDeclarator(returnValueName, call));
body.addStatement(retDecl);
}
Expression retExpr;
switch (retNodeType) {
case Number:
body.addStatement(new Return(scopeClose(methodCall(ident("v8", "Number", "New"), varRef(returnValueName)))));
break;
case Boolean:
body.addStatement(new Return(scopeClose(methodCall(ident("v8", "Boolean", "New"), varRef(returnValueName)))));
break;
case Pointer:
// TODO: add pointer validity info.
body.addStatement(
new If(
varRef(returnValueName),
new Return(
scopeClose(
memberRef(
methodCall(ident("node", "Buffer", "New"),
cast(new TypeRef.Pointer(typeRef("char"), Declarator.PointerStyle.Pointer), varRef(returnValueName)),
expr(1),
varRef(dummyNodeBufferFreeCallbackName),
varRef("NULL")),
Expression.MemberRefStyle.Arrow,
"handle_"))),
new Return(scopeClose(methodCall(ident("v8", "Null"))))));
break;
case Void:
body.addStatement(new Return(scopeClose(methodCall(v8Ident("Undefined")))));
break;
default:
throw new UnsupportedConversionException(function, "Return type not handled: " + retTr + " (" + retNodeType + ")");
}
method.setBody(body);
implementations.addDeclaration(method);
Block initBlock = getInitMethodBody(implementations);
initBlock.addStatement(stat(methodCall("NODE_SET_METHOD", varRef(initTarget), expr(functionName.toString()), varRef(methodName))));
}
private Expression scopeClose(Expression content) {
return methodCall(varRef(scopeName), Expression.MemberRefStyle.Dot, "Close", content);
}
private TypeRef transformTypeForCast(TypeRef tr) {
if (tr instanceof TypeRef.TargettedTypeRef) {
TypeRef.TargettedTypeRef ttr = (TypeRef.TargettedTypeRef) tr;
return new TypeRef.Pointer(transformTypeForCast(ttr.getTarget()), Declarator.PointerStyle.Pointer);
}
return tr.clone();
}
@Override
protected Struct createFakePointerClass(Identifier fakePointer) {
return null;
//throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void convertEnum(com.ochafik.lang.jnaerator.parser.Enum e, Signatures signatures, DeclarationsHolder out, Identifier libraryClassName) {
//throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public Struct convertStruct(Struct struct, Signatures signatures, Identifier callerLibraryClass, String callerLibrary, boolean onlyFields) throws IOException {
return null;
//throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public boolean convertVariablesDeclaration(VariablesDeclaration v, Signatures signatures, DeclarationsHolder out, int[] iChild, boolean isGlobal, Identifier holderName, Identifier callerLibraryClass, String callerLibrary) {
throw new UnsupportedOperationException("Not supported yet.");
}
public static Identifier v8Ident(String name, Identifier... args) {
Expression[] exprArgs = new Expression[args.length];
for (int i = 0; i < args.length; i++) {
exprArgs[i] = expr(typeRef(args[i]));
}
return templateIdent(ident("v8", name), exprArgs);
}
@Override
public void generateLibraryFiles(SourceFiles sourceFiles, Result result, JNAeratorConfig config) throws IOException {
List<Object> gypTargets = new ArrayList();
for (String library : result.libraries) {
if (library == null) {
continue; // to handle code defined in macro-expanded expressions
}
boolean isFramework = config.frameworks.contains(library);
String moduleName = library;
String sourcePath = "src/" + library + "_module.cc";
final List<String> sources = new ArrayList<String>(),
dependencies = new ArrayList<String>();
// TODO: list sources in fillLibraryMapping
sources.add(sourcePath);
//out.println("///\n/// This file was autogenerated by JNAerator (http://jnaerator.googlecode.com/), \n/// a tool written by Olivier Chafik (http://ochafik.com/).\n///");
SourceFile sourceFile = new SourceFile();
sourceFile.setElementFile(sourcePath);
for (String inc : new String[]{"v8.h", "node.h", "node_buffer.h"}) {
sourceFile.addDeclaration(new Include(Include.Type.CInclude, inc));
}
if (!isFramework) {
List<File> librarySourceFiles = config.sourceFilesByLibrary.get(library);
if (librarySourceFiles != null) {
for (File file : librarySourceFiles) {
sourceFile.addDeclaration(new Include(Include.Type.CInclude, file.toString()));
}
}
}
for (String otherFramework : config.frameworks) {
sourceFile.addDeclaration(new Include(Include.Type.ObjCImport, otherFramework + "/" + otherFramework + ".h"));
}
String initFunctionName = library + "_init";
Function initMethod = new Function(Function.Type.CppMethod, ident(initFunctionName), typeRef(void.class));
initMethod.addArg(new Arg(initTarget, typeRef(v8Ident("Handle", v8Ident("Object")))));
initMethod.setBody(new Block());
// Hack: add init method here so that convertFunction finds it, then (re)move it to the end.
sourceFile.addDeclaration(initMethod);
Function dummyNodeBufferFreeCallback =
new Function(
Function.Type.CFunction,
ident(dummyNodeBufferFreeCallbackName),
typeRef(void.class),
new Arg("data", new TypeRef.Pointer(typeRef("char"), Declarator.PointerStyle.Pointer)),
new Arg("hint", new TypeRef.Pointer(typeRef("void"), Declarator.PointerStyle.Pointer)));
dummyNodeBufferFreeCallback.setBody(new Block());
sourceFile.addDeclaration(dummyNodeBufferFreeCallback);
fillLibraryMapping(result, sourceFiles, sourceFile, sourceFile, library, null, null);
initMethod.replaceBy(null);
sourceFile.addDeclaration(initMethod);
sourceFile.addDeclaration(decl(stat(methodCall("NODE_MODULE", varRef(ident(library)), varRef(ident(initFunctionName))))));
writeLibraryInterface(result, sourceFiles, sourceFile, library, null);
Map<String, Object> gypTarget = map();
gypTarget.put("target_name", library);
gypTarget.put("sources", sources);
gypTarget.put("include_dirs", config.preprocessorConfig.explicitIncludes);
gypTarget.put("dependencies", dependencies);
Map<String, Object> linkSettings = map();
linkSettings.put("libraries",
isFramework
? Arrays.asList("-framework", library)
: Arrays.asList("-l" + library));
List<Object> defines = new ArrayList<Object>();
for (Map.Entry<String, String> e : config.preprocessorConfig.explicitMacros.entrySet()) {
String k = e.getKey(), v = e.getValue();
defines.add(v == null || v.length() == 0 ? k : k + "=" + v);
}
gypTarget.put("defines", defines);
gypTarget.put("link_settings", linkSettings);
gypTargets.add(gypTarget);
}
Map<String, Object> gypContents = map();
gypContents.put("targets", gypTargets);
final PrintWriter gypOut = result.classOutputter.getSourceWriter("binding.gyp");
gypOut.print(toGYP(gypContents));
gypOut.close();
}
}