diff --git a/src/source/CppGenerator.scala b/src/source/CppGenerator.scala index 119636fd7..f794bb182 100644 --- a/src/source/CppGenerator.scala +++ b/src/source/CppGenerator.scala @@ -28,9 +28,9 @@ class CppGenerator(spec: Spec) extends Generator(spec) { val marshal = new CppMarshal(spec) - val writeCppFile = writeCppFileGeneric(spec.cppOutFolder.get, spec.cppNamespace, spec.cppFileIdentStyle, spec.cppIncludePrefix) _ + val writeCppFile = writeCppFileGeneric(spec.cppOutFolder.get, spec.cppNamespace, spec.cppFileIdentStyle, spec.cppIncludePrefix, spec.cppExt, spec.cppHeaderExt) _ def writeHppFile(name: String, origin: String, includes: Iterable[String], fwds: Iterable[String], f: IndentWriter => Unit, f2: IndentWriter => Unit = (w => {})) = - writeHppFileGeneric(spec.cppHeaderOutFolder.get, spec.cppNamespace, spec.cppFileIdentStyle)(name, origin, includes, fwds, f, f2) + writeHppFileGeneric(spec.cppHeaderOutFolder.get, spec.cppNamespace, spec.cppFileIdentStyle, spec.cppHeaderExt)(name, origin, includes, fwds, f, f2) class CppRefs(name: String) { var hpp = mutable.TreeSet[String]() @@ -67,7 +67,7 @@ class CppGenerator(spec: Spec) extends Generator(spec) { w.w(s"enum class $self : int").bracedSemi { for (o <- e.options) { writeDoc(w, o.doc) - w.wl(idCpp.enum(o.ident.name) + ",") + w.wl(idCpp.enum(o.ident.name) + (if(o == e.options.last) "" else ",")) } } }, diff --git a/src/source/CxCppGenerator.scala b/src/source/CxCppGenerator.scala new file mode 100644 index 000000000..660437e50 --- /dev/null +++ b/src/source/CxCppGenerator.scala @@ -0,0 +1,277 @@ +/** + * Copyright 2014 Dropbox, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package djinni + +import djinni.ast.Record.DerivingType +import djinni.ast._ +import djinni.generatorTools._ +import djinni.meta._ +import djinni.writer.IndentWriter + +import scala.collection.mutable + +class CxCppGenerator(spec: Spec) extends Generator(spec) { + + val cxcppMarshal = new CxCppMarshal(spec) + val cxMarshal = new CxMarshal(spec) + val cppMarshal = new CppMarshal(spec) + + val writeCxCppFile = writeCppFileGeneric(spec.cxcppOutFolder.get, spec.cxcppNamespace, spec.cppFileIdentStyle, spec.cxcppIncludePrefix, spec.cxcppExt, spec.cxcppHeaderExt) _ + def writeHxFile(name: String, origin: String, includes: Iterable[String], fwds: Iterable[String], f: IndentWriter => Unit, f2: IndentWriter => Unit = (w => {})) = + writeHppFileGeneric(spec.cxcppHeaderOutFolder.get, spec.cxcppNamespace, spec.cppFileIdentStyle, spec.cxcppHeaderExt)(name, origin, includes, fwds, f, f2) + + class CxCppRefs(name: String) { + var hx = mutable.TreeSet[String]() + var hxFwds = mutable.TreeSet[String]() + var cxcpp = mutable.TreeSet[String]() + + def find(ty: TypeRef) { find(ty.resolved) } + def find(tm: MExpr) { + tm.args.foreach(find) + find(tm.base) + } + def find(m: Meta) = for(r <- cxcppMarshal.references(m)) r match { + case ImportRef(arg) => hx.add("#include " + arg) + case DeclRef(decl, Some(spec.cxcppNamespace)) => hxFwds.add(decl) + case DeclRef(_, _) => + } + } + + override def generateEnum(origin: String, ident: Ident, doc: Doc, e: Enum) { + //nothing required? + } + + def generateHxConstants(w: IndentWriter, consts: Seq[Const]) = { + for (c <- consts) { + w.wl + writeDoc(w, c.doc) + w.wl(s"static ${cxcppMarshal.fieldType(c.ty)} const ${idCpp.const(c.ident)};") + } + } + + def generateCxCppConstants(w: IndentWriter, consts: Seq[Const], selfName: String) = { + def writeCxCppConst(w: IndentWriter, ty: TypeRef, v: Any): Unit = v match { + case l: Long => w.w(l.toString) + case d: Double if cxcppMarshal.fieldType(ty) == "float" => w.w(d.toString + "f") + case d: Double => w.w(d.toString) + case b: Boolean => w.w(if (b) "true" else "false") + case s: String => w.w(s) + case e: EnumValue => w.w(cxcppMarshal.typename(ty) + "::" + idCpp.enum(e.ty.name + "_" + e.name)) + case v: ConstRef => w.w(selfName + "::" + idCpp.const(v)) + case z: Map[_, _] => { // Value is record + val recordMdef = ty.resolved.base.asInstanceOf[MDef] + val record = recordMdef.body.asInstanceOf[Record] + val vMap = z.asInstanceOf[Map[String, Any]] + w.wl(cxcppMarshal.typename(ty) + "(") + w.increase() + // Use exact sequence + val skipFirst = SkipFirst() + for (f <- record.fields) { + skipFirst {w.wl(",")} + writeCxCppConst(w, f.ty, vMap.apply(f.ident.name)) + w.w(" /* " + idCpp.field(f.ident) + " */ ") + } + w.w(")") + w.decrease() + } + } + + val skipFirst = SkipFirst() + for (c <- consts) { + skipFirst{ w.wl } + w.w(s"${cxcppMarshal.fieldType(c.ty)} const $selfName::${idCpp.const(c.ident)} = ") + writeCxCppConst(w, c.ty, c.value) + w.wl(";") + } + } + + override def generateRecord(origin: String, ident: Ident, doc: Doc, params: Seq[TypeParam], r: Record) { + val refs = new CxCppRefs(ident.name) + for (c <- r.consts) + refs.find(c.ty) + for (f <- r.fields) + refs.find(f.ty) + + val cxName = ident.name + (if (r.ext.cx) "_base" else "") + val cxSelf = cxMarshal.fqTypename(ident, r) + val cppSelf = cppMarshal.fqTypename(ident, r) + + refs.hx.add("#include " + q(spec.cxcppIncludeCxPrefix + (if(r.ext.cx) "../" else "") + cxcppMarshal.headerName(ident)+ "." + spec.cxcppHeaderExt)) + refs.hx.add("#include " + q(spec.cxIncludePrefix + (if(r.ext.cx) "../" else "") + spec.cxFileIdentStyle(ident) + "." + spec.cxHeaderExt)) + refs.hx.add("#include " + q(spec.cxcppIncludeCppPrefix + (if(r.ext.cpp) "../" else "") + spec.cppFileIdentStyle(ident) + "." + spec.cppHeaderExt)) + + refs.cxcpp = refs.hx.clone() + refs.cxcpp.add("#include ") + + def checkMutable(tm: MExpr): Boolean = tm.base match { + case MOptional => checkMutable(tm.args.head) + case MString => true + case MBinary => true + case _ => false + } + + val self = cxcppMarshal.typename(ident, r) + + writeHxFile(cxcppMarshal.headerName(cxName), origin, refs.hx, refs.hxFwds, w => { + w.wl + w.wl(s"struct $self") + w.bracedSemi { + w.wl(s"using CppType = $cppSelf;") + w.wl(s"using CxType = $cxSelf^;"); + w.wl + w.wl(s"using Boxed = $self;") + w.wl + w.wl(s"static CppType toCpp(CxType cx);") + w.wl(s"static CxType fromCpp(const CppType& cpp);") + } + }) + + writeCxCppFile(cxcppMarshal.bodyName(self), origin, refs.cxcpp, w => { + w.wl(s"auto $self::toCpp(CxType cx) -> CppType") + w.braced { + w.wl("assert(cx);") + if(r.fields.isEmpty) w.wl("(void)cx; // Suppress warnings in relase builds for empty records") + writeAlignedCall(w, "return {", r.fields, "}", f => cxcppMarshal.toCpp(f.ty, "cx->" + idCx.field(f.ident))) + w.wl(";") + } + w.wl + w.wl(s"auto $self::fromCpp(const CppType& cpp) -> CxType") + w.braced { + if(r.fields.isEmpty) w.wl("(void)cpp; // Suppress warnings in relase builds for empty records") + writeAlignedCall(w, s"return ref new $cxSelf(", r.fields, ")", f=> cxcppMarshal.fromCpp(f.ty, "cpp." + idCpp.field(f.ident))) + w.wl(";") + } + }) + } + + override def generateInterface(origin: String, ident: Ident, doc: Doc, typeParams: Seq[TypeParam], i: Interface) { + val refs = new CxCppRefs(ident.name) + refs.hx.add("#include \""+spec.cppIncludePrefix + spec.cppFileIdentStyle(ident.name) + "." + spec.cppHeaderExt+"\"") + refs.hx.add("#include \""+spec.cxIncludePrefix + spec.cxFileIdentStyle(ident.name) + "." + spec.cxHeaderExt+"\"") + refs.hx.add("#include ") + i.methods.map(m => { + m.params.map(p => refs.find(p.ty)) + m.ret.foreach(refs.find) + }) + i.consts.map(c => { + refs.find(c.ty) + }) + refs.cxcpp = refs.hx.clone() + refs.cxcpp.add("#include \"CxWrapperCache.h\"") + + val self = cxcppMarshal.typename(ident, i) + val cxSelf = cxMarshal.fqTypename(ident, i) + val cppSelf = cppMarshal.fqTypename(ident, i) + val helperClass = cxcppMarshal.helperClass(ident) + + writeHxFile(cxcppMarshal.headerName(ident.name), origin, refs.hx, refs.hxFwds, w => { + w.wl + w.wl(s"class $self") + w.bracedSemi { + w.wlOutdent("public:") + w.wl(s"using CppType = std::shared_ptr<$cppSelf>;") + w.wl(s"using CxType = $cxSelf^;"); + w.wl + w.wl(s"using Boxed = $self;") + w.wl + w.wl(s"static CppType toCpp(CxType cx);") + w.wl(s"static CxType fromCpp(const CppType& cpp);") + if (i.ext.cx) { + w.wl + w.wlOutdent("private:") + w.wl(s"class CxProxy;") + } + + } + + }) + + writeCxCppFile(cxcppMarshal.bodyName(ident.name), origin, refs.cxcpp, w => { + + //only interface classes have proxy objects + if (i.ext.cx) { + w.wl(s"class $helperClass final : public $cppSelf, public ::djinni::CxWrapperCache::Handle").bracedSemi { + w.wlOutdent("public:") + w.wl("using Handle::Handle;") + w.wl(s"using CxType = $cxSelf^;") + w.wl + w.wl("CxProxy(Platform::Object^ cx) : ::djinni::CxWrapperCache::Handle{ cx } {}") + w.wl + //methods + for (m <- i.methods) { + w.wl + writeDoc(w, m.doc) + val ret = cppMarshal.fqReturnType(m.ret) + val params = m.params.map(p => cppMarshal.fqParamType(p.ty) + " " + idCpp.local(p.ident)) + if (m.static) { + w.wl(s"static $ret ${idCpp.method(m.ident)}${params.mkString("(", ", ", ")")}") + } else { + val constFlag = if (m.const) " const" else "" + w.wl(s"$ret ${idCpp.method(m.ident)}${params.mkString("(", ", ", ")")}$constFlag override") + } + w.braced { + val retCall = if(m.ret == None) "" else "auto r = " + val call = retCall + (if (!m.static) s"static_cast(Handle::get())->" else cppSelf + "::") + idCx.method(m.ident) + "(" + writeAlignedCall(w, call, m.params, ")", p => cxcppMarshal.fromCpp(p.ty, idCpp.local(p.ident.name))) + w.wl(";") + m.ret.fold()(r => w.wl(s"return ${cxcppMarshal.toCpp(r, "r")};")) + } + } + } + w.wl + w.wl(s"auto $self::toCpp(CxType cx) -> CppType") + w.braced { + w.wl("if (!cx)").braced { + w.wl("return nullptr;") + } + w.wl("return ::djinni::CxWrapperCache::getInstance()->get(cx);") + } + w.wl + w.wl(s"auto $self::fromCpp(const CppType& cpp) -> CxType") + w.braced { + w.braced { + w.wl("if (!cpp)").braced { + w.wl("return nullptr;") + } + w.wl("return static_cast(dynamic_cast(*cpp).Handle::get());") + } + } + } else { + w.wl(s"auto $self::toCpp(CxType cx) -> CppType") + w.braced { + w.wl("return cx->m_cppRef.get();") + } + w.wl + w.wl(s"auto $self::fromCpp(const CppType& cpp) -> CxType") + w.braced { + w.wl(s"return (CxType)::djinni::CppWrapperCache<$cppSelf>::getInstance()->get(cpp, [](const std::shared_ptr<$cppSelf>& p)").bracedEnd(");") { + w.wl(s"return ref new $cxSelf(p);") + } + + } + } + + }) + } + + + def writeCxCppTypeParams(w: IndentWriter, params: Seq[TypeParam]) { + if (params.isEmpty) return + w.wl("template " + params.map(p => "typename " + idCpp.typeParam(p.ident)).mkString("<", ", ", ">")) + } + +} diff --git a/src/source/CxCppMarshal.scala b/src/source/CxCppMarshal.scala new file mode 100644 index 000000000..04f4dc138 --- /dev/null +++ b/src/source/CxCppMarshal.scala @@ -0,0 +1,221 @@ +package djinni + +import djinni.ast._ +import djinni.generatorTools._ +import djinni.meta._ + +class CxCppMarshal(spec: Spec) extends Marshal(spec) { + private val cppMarshal = new CppMarshal(spec) + private val cxMarshal = new CxMarshal(spec) + + override def typename(tm: MExpr): String = toCxCppType(tm, None) + def typename(name: String, ty: TypeDef): String = ty match { + case e: Enum => idCx.enumType(name) + case i: Interface => idCx.ty(name) + case r: Record => idCx.ty(name) + } + + override def fqTypename(tm: MExpr): String = toCxCppType(tm, Some(spec.cxNamespace)) + def fqTypename(name: String, ty: TypeDef): String = ty match { + case e: Enum => withNs(Some(spec.cxNamespace), idCx.enumType(name)) + case i: Interface => withNs(Some(spec.cxNamespace), idCx.ty(name)) + case r: Record => withNs(Some(spec.cxNamespace), idCx.ty(name)) + } + + override def paramType(tm: MExpr): String = toCxCppParamType(tm) + override def fqParamType(tm: MExpr): String = toCxCppParamType(tm, Some(spec.cxNamespace)) + + override def returnType(ret: Option[TypeRef]): String = ret.fold("void")(toCxCppType(_, None)) + override def fqReturnType(ret: Option[TypeRef]): String = ret.fold("void")(toCxCppType(_, Some(spec.cxNamespace))) + + override def fieldType(tm: MExpr): String = typename(tm) + override def fqFieldType(tm: MExpr): String = fqTypename(tm) + + override def toCpp(tm: MExpr, expr: String): String = { + s"${ownClass(tm)}::toCpp($expr)" + } + override def fromCpp(tm: MExpr, expr: String): String = { + s"${ownClass(tm)}::fromCpp($expr)" + } + + def references(m: Meta): Seq[SymbolReference] = m match { + case o: MOpaque => + List(ImportRef(q(spec.cxBaseLibIncludePrefix + "Marshal.h"))) + case d: MDef => d.defType match { + case DEnum => + List(ImportRef(q(spec.cxBaseLibIncludePrefix + "Marshal.h"))) + case DInterface => + List(ImportRef(q(spec.cxcppIncludePrefix + headerName(d.name) + "." + spec.cxcppHeaderExt))) + case DRecord => + val r = d.body.asInstanceOf[Record] + val cxName = d.name + (if (r.ext.cx) "_base" else "") + List(ImportRef(q(spec.cxcppIncludePrefix + headerName(cxName) + "." + spec.cxcppHeaderExt))) + } + case p: MParam => List() + } + + def ownClass(name: String) = s"${idCpp.ty(name)}" + private def ownClass(tm: MExpr): String = ownName(tm) + ownTemplates(tm) + + private def ownName(tm: MExpr): String = tm.base match { + case d: MDef => d.defType match { + case DEnum => withNs(Some("djinni"), s"Enum<${cppMarshal.fqTypename(tm)}, ${cxMarshal.fqTypename(tm)}>") + case _ => withNs(Some(spec.cxcppNamespace), ownClass(d.name)) + } + case o => withNs(Some("djinni"), o match { + case p: MPrimitive => p.idlName match { + case "i8" => "I8" + case "i16" => "I16" + case "i32" => "I32" + case "i64" => "I64" + case "f32" => "F32" + case "f64" => "F64" + case "bool" => "Bool" + } + case MOptional => "Optional" + case MBinary => "Binary" + case MDate => "Date" + case MString => "String" + case MList => "List" + case MSet => "Set" + case MMap => "Map" + case d: MDef => throw new AssertionError("unreachable") + case p: MParam => throw new AssertionError("not applicable") + }) + } + + private def ownTemplates(tm: MExpr): String = { + def f() = if(tm.args.isEmpty) "" else tm.args.map(ownClass).mkString("<", ", ", ">") + tm.base match { + case MOptional => + assert(tm.args.size == 1) + val argHelperClass = ownClass(tm.args.head) + s"<${spec.cppOptionalTemplate}, $argHelperClass>" + case MList | MSet => + assert(tm.args.size == 1) + f + case MMap => + assert(tm.args.size == 2) + f + case _ => f + } + } + + def helperClass(name: String) = s"${idCx.ty(name)}::CxProxy" + private def helperClass(tm: MExpr): String = helperName(tm) + helperTemplates(tm) + + def headerName(ident: String): String = idCx.ty(ident) + "_convert" + def bodyName(ident: String): String = idCx.ty(ident) + "_convert" + + private def helperName(tm: MExpr): String = tm.base match { + case d: MDef => d.defType match { + case DEnum => withNs(Some("djinni"), s"Enum<${cppMarshal.fqTypename(tm)}, ${cxMarshal.fqTypename(tm)}>") + case _ => withNs(Some(spec.cxcppNamespace), helperClass(d.name)) + } + case o => withNs(Some("djinni"), o match { + case p: MPrimitive => p.idlName match { + case "i8" => "I8" + case "i16" => "I16" + case "i32" => "I32" + case "i64" => "I64" + case "f32" => "F32" + case "f64" => "F64" + case "bool" => "Bool" + } + case MOptional => "Optional" + case MBinary => "Binary" + case MDate => "Date" + case MString => "String" + case MList => "List" + case MSet => "Set" + case MMap => "Map" + case d: MDef => throw new AssertionError("unreachable") + case p: MParam => throw new AssertionError("not applicable") + }) + } + + private def helperTemplates(tm: MExpr): String = { + def f() = if(tm.args.isEmpty) "" else tm.args.map(helperClass).mkString("<", ", ", ">") + tm.base match { + case MOptional => + assert(tm.args.size == 1) + val argHelperClass = helperClass(tm.args.head) + s"<${spec.cppOptionalTemplate}, $argHelperClass>" + case MList | MSet => + assert(tm.args.size == 1) + f + case MMap => + assert(tm.args.size == 2) + f + case _ => f + } + } + + def include(ident: String): String = q(spec.cxcppIncludePrefix + spec.cxFileIdentStyle(ident) + "." + spec.cxcppHeaderExt) + + + def byValue(tm: MExpr): Boolean = tm.base match { + case p: MPrimitive => true + case d: MDef => d.defType match { + case DEnum => true + case _ => false + } + case e: MExtern => e.defType match { + case DInterface => false + case DEnum => true + case DRecord => e.cxcpp.byValue + } + case MOptional => byValue(tm.args.head) + case _ => false + } + + def byValue(td: TypeDecl): Boolean = td.body match { + case i: Interface => false + case r: Record => false + case e: Enum => true + } + + private def toCxCppType(ty: TypeRef, namespace: Option[String] = None): String = toCxCppType(ty.resolved, namespace) + private def toCxCppType(tm: MExpr, namespace: Option[String]): String = { + def base(m: Meta): String = m match { + case p: MPrimitive => p.cName + case MString => "std::string" + case MDate => "std::chrono::system_clock::time_point" + case MBinary => "std::vector" + case MOptional => spec.cppOptionalTemplate + case MList => "std::vector" + case MSet => "std::unordered_set" + case MMap => "std::unordered_map" + case d: MDef => + d.defType match { + case DEnum => withNs(namespace, idCpp.enumType(d.name)) + case DRecord => withNs(namespace, idCpp.ty(d.name)) + case DInterface => s"std::shared_ptr<${withNs(namespace, idCpp.ty(d.name))}>" + } + case p: MParam => idCpp.typeParam(p.name) + } + def expr(tm: MExpr): String = { + val args = if (tm.args.isEmpty) "" else tm.args.map(expr).mkString("<", ", ", ">") + base(tm.base) + args + } + expr(tm) + } + + // this can be used in c++ generation to know whether a const& should be applied to the parameter or not + private def toCxCppParamType(tm: MExpr, namespace: Option[String] = None): String = { + val cppType = toCxCppType(tm, namespace) + val refType = "const " + cppType + " &" + val valueType = cppType + + def toType(expr: MExpr): String = expr.base match { + case p: MPrimitive => valueType + case d: MDef => d.defType match { + case DEnum => valueType + case _ => refType + } + case MOptional => toType(expr.args.head) + case _ => refType + } + toType(tm) + } +} diff --git a/src/source/CxGenerator.scala b/src/source/CxGenerator.scala new file mode 100644 index 000000000..184645548 --- /dev/null +++ b/src/source/CxGenerator.scala @@ -0,0 +1,378 @@ +/** + * Copyright 2014 Dropbox, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package djinni + +import djinni.ast.Record.DerivingType +import djinni.ast._ +import djinni.generatorTools._ +import djinni.meta._ +import djinni.writer.IndentWriter + +import scala.collection.mutable + +class CxGenerator(spec: Spec) extends Generator(spec) { + + val cxMarshal = new CxMarshal(spec) + val cxcppMarshal = new CxCppMarshal(spec) + val cppMarshal = new CppMarshal(spec) + + val writeCxFile = writeCppFileGeneric(spec.cxOutFolder.get, spec.cxNamespace, spec.cxFileIdentStyle, spec.cxIncludePrefix, spec.cxExt, spec.cxHeaderExt) _ + def writeHxFile(name: String, origin: String, includes: Iterable[String], fwds: Iterable[String], f: IndentWriter => Unit, f2: IndentWriter => Unit = (w => {})) = + writeHppFileGeneric(spec.cxHeaderOutFolder.get, spec.cxNamespace, spec.cxFileIdentStyle, spec.cxHeaderExt)(name, origin, includes, fwds, f, f2) + + class CxRefs(name: String) { + var hx = mutable.TreeSet[String]() + var hxFwds = mutable.TreeSet[String]() + var cx = mutable.TreeSet[String]() + + def find(ty: TypeRef) { find(ty.resolved) } + def find(tm: MExpr) { + tm.args.foreach(find) + find(tm.base) + } + def find(m: Meta) = for(r <- cxMarshal.references(m, name)) r match { + case ImportRef(arg) => hx.add("#include " + arg) + case DeclRef(decl, Some(spec.cxNamespace)) => hxFwds.add(decl) + case DeclRef(_, _) => + } + + def findConvert(ty: TypeRef) { findConvert(ty.resolved) } + def findConvert(tm: MExpr) { + tm.args.foreach(find) + findConvert(tm.base) + } + def findConvert(m: Meta) = for(r <- cxMarshal.convertReferences(m, name)) r match { + case ImportRef(arg) => cx.add("#include " + arg) + } + } + + def writeCxFuncDecl(klass: String, method: Interface.Method, w: IndentWriter) { + val ret = cxMarshal.returnType(method.ret) + val params = method.params.map(p => cxMarshal.paramType(p.ty) + " " + idCx.local(p.ident)) + val constFlag = if (method.const) " const" else "" + w.wl(s"$ret $klass::${idCx.method(method.ident)}${params.mkString("(", ", ", ")")}") + } + + override def generateEnum(origin: String, ident: Ident, doc: Doc, e: Enum) { + val refs = new CxRefs(ident.name) + val self = cxMarshal.typename(ident, e) + + writeHxFile(ident, origin, refs.hx, refs.hxFwds, w => { + w.w(s"public enum class $self").bracedSemi { + for (o <- e.options) { + writeDoc(w, o.doc) + w.wl(idCx.enum(o.ident.name) + (if(o == e.options.last) "" else ",")) + } + } + }) + } + + def generateHxConstants(w: IndentWriter, consts: Seq[Const]) = { + for (c <- consts) { + w.wl + writeDoc(w, c.doc) + w.wl(s"static property ${cxMarshal.fieldType(c.ty)} ${idCx.const(c.ident)}") + w.braced { + w.wl(s"${cxMarshal.fieldType(c.ty)} get()") + w.braced { + w.wl(s"return ${idCx.const(c.ident)}_;") + } + } + } + } + + def generateHxPrivateConstants(w: IndentWriter, consts: Seq[Const]) = { + def writeCxConst(w: IndentWriter, ty: TypeRef, v: Any): Unit = v match { + case l: Long => w.w(l.toString) + case d: Double if cxMarshal.fieldType(ty) == "float" => w.w(d.toString + "f") + case d: Double => w.w(d.toString) + case b: Boolean => w.w(if (b) "true" else "false") + case s: String => w.w(s) + case e: EnumValue => w.w(cxMarshal.typename(ty) + "::" + idCx.enum(e.ty.name + "_" + e.name)) + case v: ConstRef => w.w(idCx.const(v)) + case z: Map[_, _] => { // Value is record + val recordMdef = ty.resolved.base.asInstanceOf[MDef] + val record = recordMdef.body.asInstanceOf[Record] + val vMap = z.asInstanceOf[Map[String, Any]] + w.wl(cxMarshal.typename(ty) + "(") + w.increase() + // Use exact sequence + val skipFirst = SkipFirst() + for (f <- record.fields) { + skipFirst {w.wl(",")} + writeCxConst(w, f.ty, vMap.apply(f.ident.name)) + w.w(" /* " + idCx.field(f.ident) + " */ ") + } + w.w(")") + w.decrease() + } + } + + for (c <- consts) { + w.wl + writeDoc(w, c.doc) + w.w(s"static const ${cxMarshal.fieldType(c.ty)} ${idCx.const(c.ident)}_ = ") + writeCxConst(w, c.ty, c.value) + w.wl(";") + } + } + +// def generateCxConstants(w: IndentWriter, consts: Seq[Const], selfName: String) = { +// def writeCxConst(w: IndentWriter, ty: TypeRef, v: Any): Unit = v match { +// case l: Long => w.w(l.toString) +// case d: Double if cxMarshal.fieldType(ty) == "float" => w.w(d.toString + "f") +// case d: Double => w.w(d.toString) +// case b: Boolean => w.w(if (b) "true" else "false") +// case s: String => w.w(s) +// case e: EnumValue => w.w(cxMarshal.typename(ty) + "::" + idCx.enum(e.ty.name + "_" + e.name)) +// case v: ConstRef => w.w(selfName + "::" + idCx.const(v)) +// case z: Map[_, _] => { // Value is record +// val recordMdef = ty.resolved.base.asInstanceOf[MDef] +// val record = recordMdef.body.asInstanceOf[Record] +// val vMap = z.asInstanceOf[Map[String, Any]] +// w.wl(cxMarshal.typename(ty) + "(") +// w.increase() +// // Use exact sequence +// val skipFirst = SkipFirst() +// for (f <- record.fields) { +// skipFirst {w.wl(",")} +// writeCxConst(w, f.ty, vMap.apply(f.ident.name)) +// w.w(" /* " + idCx.field(f.ident) + " */ ") +// } +// w.w(")") +// w.decrease() +// } +// } +// +// val skipFirst = SkipFirst() +// for (c <- consts) { +// skipFirst{ w.wl } +// w.w(s"${cxMarshal.fieldType(c.ty)} const $selfName::${idCx.const(c.ident)} = ") +// writeCxConst(w, c.ty, c.value) +// w.wl(";") +// } +// } + + override def generateRecord(origin: String, ident: Ident, doc: Doc, params: Seq[TypeParam], r: Record) { + val refs = new CxRefs(ident.name) + r.fields.foreach(f => refs.find(f.ty)) + r.consts.foreach(c => refs.find(c.ty)) + + val self = cxMarshal.typename(ident, r) + val (cxName, cxFinal) = if (r.ext.cx) (ident.name + "_base", "") else (ident.name, " sealed") + val actualSelf = cxMarshal.typename(cxName, r) + + // Requiring the extended class + if (r.ext.cx) { + refs.hx.add(s"publc ref struct $self;") + refs.cx.add("#include " + q("../" + spec.cxFileIdentStyle(ident) + "." + spec.cxHeaderExt)) + } + + // C++ Header + def writeCxPrototype(w: IndentWriter) { + writeDoc(w, doc) + writeCxTypeParams(w, params) + w.w("public ref struct " + actualSelf + cxFinal).bracedSemi { + generateHxConstants(w, r.consts) + // Field definitions. + for (f <- r.fields) { + writeDoc(w, f.doc) + w.wl("property " + cxMarshal.fieldType(f.ty) + " " + idCx.field(f.ident) + ";") + } + + // Constructor. + if (r.fields.nonEmpty) { + w.wl + writeAlignedCall(w, actualSelf + "(", r.fields, ")", f => cxMarshal.fieldType(f.ty) + " " + idCx.local("new_"+f.ident.name)) + w.wl + w.braced { + r.fields.map(f => w.wl(s"${idCx.local(f.ident)} = ${idCx.local("new_"+f.ident.name)};") ) + } + } + + if (r.derivingTypes.contains(DerivingType.Eq)) { + w.wl + w.wl(s"bool Equals($actualSelf^ rhs);") + } + if (r.derivingTypes.contains(DerivingType.Ord)) { + w.wl + w.wl(s"int32 CompareTo($actualSelf^ rhs);") + } + + w.wlOutdent("private:") + generateHxPrivateConstants(w, r.consts) + + } + } + + writeHxFile(cxName, origin, refs.hx, refs.hxFwds, writeCxPrototype) + + if (r.consts.nonEmpty || r.derivingTypes.nonEmpty) { + writeCxFile(cxName, origin, refs.cx, w => { +// generateCxConstants(w, r.consts, actualSelf) + + if (r.derivingTypes.contains(DerivingType.Eq)) { + w.wl + w.w(s"bool $actualSelf::Equals($actualSelf^ rhs)").braced { + if (!r.fields.isEmpty) { + writeAlignedCall(w, "return ", r.fields, " &&", "", f => s"this->${idCx.field(f.ident)} == rhs->${idCx.field(f.ident)}") + w.wl(";") + } else { + w.wl("return true;") + } + } + } + if (r.derivingTypes.contains(DerivingType.Ord)) { + w.wl + w.w(s"int32 $actualSelf::CompareTo($actualSelf^ rhs)").braced { + w.wl(s"if (rhs == nullptr) return 1;") + w.wl("int32 tempResult;") + for (f <- r.fields) { + f.ty.resolved.base match { + case MString => w.wl(s"tempResult = Platform::String::CompareOrdinal(this->${idCx.field(f.ident)}, rhs->${idCx.field(f.ident)});") + case t: MPrimitive => + w.wl(s"if (this->${idCx.field(f.ident)} < rhs->${idCx.field(f.ident)}) {").nested { + w.wl(s"tempResult = -1;") + } + w.wl(s"} else if (this->${idCx.field(f.ident)} > rhs->${idCx.field(f.ident)}) {").nested { + w.wl(s"tempResult = 1;") + } + w.wl(s"} else {").nested { + w.wl(s"tempResult = 0;") + } + w.wl("}") + case df: MDef => df.defType match { + case DRecord => w.wl(s"tempResult = this->${idCx.field(f.ident)}->CompareTo(rhs->${idCx.field(f.ident)});") + case DEnum => w.w(s"tempResult = this->${idCx.field(f.ident)}->CompareTo(rhs->${idCx.field(f.ident)});") + case _ => throw new AssertionError("Unreachable") + } + case _ => throw new AssertionError("Unreachable") + } + } + } + } + }) + } + } + + override def generateInterface(origin: String, ident: Ident, doc: Doc, typeParams: Seq[TypeParam], i: Interface) { + val refs = new CxRefs(ident.name) + refs.hx.add("#include ") + refs.hx.add("#include \"CppWrapperCache.h\"") + refs.hx.add("#include \""+spec.cppIncludePrefix + spec.cppFileIdentStyle(ident.name) + "." + spec.cppHeaderExt+"\"") + i.methods.map(m => { + m.params.map(p => refs.find(p.ty)) + m.ret.foreach(refs.find) + }) + i.consts.map(c => { + refs.find(c.ty) + }) + + refs.cx = refs.hx.clone() + i.methods.map(m => { + m.params.map(p => refs.findConvert(p.ty)) + m.ret.foreach(refs.findConvert) + }) + refs.cx.add("#include \"Marshal.h\"") + refs.cx.add("#include \""+ spec.cxcppIncludePrefix + cxcppMarshal.headerName(ident.name) + "." + spec.cxcppHeaderExt + "\"") + + val self = cxMarshal.typename(ident, i) + val cppSelf = cppMarshal.fqTypename(ident, i) + + writeHxFile(ident, origin, refs.hx, refs.hxFwds, w => { + writeDoc(w, doc) + writeCxTypeParams(w, typeParams) + if (i.ext.cx) w.wl(s"public interface class $self").bracedSemi { + // Constants + generateHxConstants(w, i.consts) + // Methods + for (m <- i.methods) { + w.wl + writeDoc(w, m.doc) + val ret = cxMarshal.returnType(m.ret) + val params = m.params.map(p => cxMarshal.paramType(p.ty) + " " + idCpp.local(p.ident)) + if (m.static) { + w.wl(s"static $ret ${idCx.method(m.ident)}${params.mkString("(", ", ", ")")};") + } else { + val constFlag = if (m.const) " const" else "" + w.wl(s"virtual $ret ${idCx.method(m.ident)}${params.mkString("(", ", ", ")")} = 0;") + } + } + } + else w.wl(s"public ref class $self sealed : public Platform::Object").bracedSemi { + w.wlOutdent("public:") + // Constants + generateHxConstants(w, i.consts) + // Methods + for (m <- i.methods) { + w.wl + writeDoc(w, m.doc) + val ret = cxMarshal.returnType(m.ret) + val params = m.params.map(p => cxMarshal.paramType(p.ty) + " " + idCpp.local(p.ident)) + if (m.static) { + w.wl(s"static $ret ${idCx.method(m.ident)}${params.mkString("(", ", ", ")")};") + } else { + val constFlag = if (m.const) " const" else "" + w.wl(s"$ret ${idCx.method(m.ident)}${params.mkString("(", ", ", ")")};") + } + } + //private members + w.wlOutdent("private:") + generateHxPrivateConstants(w, i.consts) + w.wlOutdent("internal:") + //construct from a cpp ref + w.wl(s"$self(const std::shared_ptr<$cppSelf>& cppRef);") + w.wl(s"::djinni::CppWrapperCache<$cppSelf>::Handle m_cppRef;") + } + }) + + // Cx only generated in need of Constants + writeCxFile(ident, origin, refs.cx, w => { + if (! i.ext.cx) { +// if (i.consts.nonEmpty) { +// generateCxConstants(w, i.consts, self) +// } + //constructor + w.wl(s"$self::$self(const std::shared_ptr<$cppSelf>& cppRef)") + w.braced { + w.wl("m_cppRef.assign(cppRef);") + } + //methods + for (m <- i.methods) { + w.wl + writeCxFuncDecl(self, m, w) + w.braced { +// w.w("try").bracedEnd(" DJINNI_TRANSLATE_EXCEPTIONS()") { + val ret = m.ret.fold("")(_ => "auto r = ") + val call = ret + (if (!m.static) "m_cppRef.get()->" else cppSelf + "::") + idCpp.method(m.ident) + "(" + writeAlignedCall(w, call, m.params, ")", p => cxMarshal.toCpp(p.ty, idCx.local(p.ident.name))) + w.wl(";") + m.ret.fold()(r => w.wl(s"return ${cxMarshal.fromCpp(r, "r")};")) + // } + } + } + } + }) + + } + + def writeCxTypeParams(w: IndentWriter, params: Seq[TypeParam]) { + if (params.isEmpty) return + w.wl("template " + params.map(p => "typename " + idCx.typeParam(p.ident)).mkString("<", ", ", ">")) + } + +} diff --git a/src/source/CxMarshal.scala b/src/source/CxMarshal.scala new file mode 100644 index 000000000..07d8be763 --- /dev/null +++ b/src/source/CxMarshal.scala @@ -0,0 +1,335 @@ +package djinni + +import djinni.ast._ +import djinni.generatorTools._ +import djinni.meta._ + +class CxMarshal(spec: Spec) extends Marshal(spec) { + + private val cppMarshal = new CppMarshal(spec) + +// override def typename(tm: MExpr): String = toCxType(tm, None)._1 + override def typename(tm: MExpr): String = { + val (name, needRef) = toCxType(tm, None) + val result = if(needRef) (s"${name}^") else (s"${name}") + result + } + + def typename(name: String, ty: TypeDef): String = ty match { + case e: Enum => idCx.enumType(name) + case i: Interface => if(i.ext.cx) s"I${idCx.ty(name)}" else idCx.ty(name) + case r: Record => idCx.ty(name) + } + + override def fqTypename(tm: MExpr): String = toCxType(tm, Some(spec.cxNamespace))._1 + def fqTypename(name: String, ty: TypeDef): String = ty match { + case e: Enum => withNs(Some(spec.cxNamespace), idCx.enumType(name)) + case i: Interface => if(i.ext.cx) withNs(Some(spec.cxNamespace), s"I${idCx.ty(name)}") else withNs(Some(spec.cxNamespace), idCx.ty(name)) + case r: Record => withNs(Some(spec.cxNamespace), idCx.ty(name)) + } + + override def paramType(tm: MExpr): String = toCxParamType(tm) + override def fqParamType(tm: MExpr): String = toCxParamType(tm, Some(spec.cxNamespace)) + + override def returnType(ret: Option[TypeRef]): String = ret.fold("void")((t: TypeRef) => toCxParamType(t.resolved)) + override def fqReturnType(ret: Option[TypeRef]): String = ret.fold("void")((t: TypeRef) => toCxParamType(t.resolved, Some(spec.cxNamespace))) + + override def fieldType(tm: MExpr): String = typename(tm) + override def fqFieldType(tm: MExpr): String = fqTypename(tm) + + + override def toCpp(tm: MExpr, expr: String): String = { + s"${ownClass(tm)}::toCpp($expr)" + } + override def fromCpp(tm: MExpr, expr: String): String = { + s"${ownClass(tm)}::fromCpp($expr)" + } + + + def ownClass(name: String) = s"${idCx.ty(name)}" + private def ownClass(tm: MExpr): String = ownName(tm) + ownTemplates(tm) + + private def ownName(tm: MExpr): String = tm.base match { + case d: MDef => d.defType match { + case DEnum => withNs(Some("djinni"), s"Enum<${cppMarshal.fqTypename(tm)}, ${fqTypename(tm)}>") + case _ => withNs(Some(spec.cxcppNamespace), s"${ownClass(d.name)}") + } + case o => withNs(Some("djinni"), o match { + case p: MPrimitive => p.idlName match { + case "i8" => "I8" + case "i16" => "I16" + case "i32" => "I32" + case "i64" => "I64" + case "f32" => "F32" + case "f64" => "F64" + case "bool" => "Bool" + } + case MOptional => "Optional" + case MBinary => "Binary" + case MDate => "Date" + case MString => "String" + case MList => "List" + case MSet => "Set" + case MMap => "Map" + case d: MDef => throw new AssertionError("unreachable") + case p: MParam => throw new AssertionError("not applicable") + }) + } + + private def ownTemplates(tm: MExpr): String = { + def f() = if(tm.args.isEmpty) "" else tm.args.map(ownClass).mkString("<", ", ", ">") + tm.base match { + case MOptional => + assert(tm.args.size == 1) + val argHelperClass = ownClass(tm.args.head) + s"<${spec.cppOptionalTemplate}, $argHelperClass>" //TODO THIS IS VERY WRONG! + case MList | MSet => + assert(tm.args.size == 1) + f + case MMap => + assert(tm.args.size == 2) + f + case _ => f + } + } + + def helperClass(name: String) = idCpp.ty(name) + private def helperClass(tm: MExpr): String = helperName(tm) + helperTemplates(tm) + + private def helperName(tm: MExpr): String = tm.base match { + case d: MDef => d.defType match { + case DEnum => withNs(Some("djinni"), s"Enum<${cppMarshal.fqTypename(tm)}, ${fqTypename(tm)}>") + case DInterface => + val ext = d.body.asInstanceOf[Interface].ext + if (ext.cpp && !ext.cx) + withNs(Some(spec.cxcppNamespace), helperClass(d.name)) + else + s"I${withNs(Some(spec.cxcppNamespace), helperClass(d.name))}" + case _ => withNs(Some(spec.cxcppNamespace), helperClass(d.name)) + } + case o => withNs(Some("djinni"), o match { + case p: MPrimitive => p.idlName match { + case "i8" => "I8" + case "i16" => "I16" + case "i32" => "I32" + case "i64" => "I64" + case "f32" => "F32" + case "f64" => "F64" + case "bool" => "Bool" + } + case MOptional => "Optional" + case MBinary => "Binary" + case MDate => "Date" + case MString => "String" + case MList => "List" + case MSet => "Set" + case MMap => "Map" + case d: MDef => throw new AssertionError("unreachable") + case p: MParam => throw new AssertionError("not applicable") + }) + } + + private def helperTemplates(tm: MExpr): String = { + def f() = if(tm.args.isEmpty) "" else tm.args.map(helperClass).mkString("<", ", ", ">") + tm.base match { + case MOptional => + assert(tm.args.size == 1) + val argHelperClass = helperClass(tm.args.head) + s"<${spec.cppOptionalTemplate}, $argHelperClass>" + case MList | MSet => + assert(tm.args.size == 1) + f + case MMap => + assert(tm.args.size == 2) + f + case _ => f + } + } + + def references(m: Meta, exclude: String): Seq[SymbolReference] = m match { + case p: MPrimitive => p.idlName match { + case "i8" | "i16" | "i32" | "i64" => List() + case _ => List() + } + case MString | MDate | MBinary | MOptional | MList | MSet | MMap => List() + case d: MDef => d.defType match { + case DEnum | DRecord => + if (d.name != exclude) { + List(ImportRef(q(spec.cxIncludePrefix + spec.cxFileIdentStyle(d.name) + "." + spec.cxHeaderExt))) + } else { + List() + } + case DInterface => + if (d.name != exclude) { + List(ImportRef(q(spec.cxIncludePrefix + spec.cxFileIdentStyle(d.name) + "." + spec.cxHeaderExt))) + } else { + List() + } + } + case p: MParam => List() + } + + def convertReferences(m: Meta, exclude: String): Seq[SymbolReference] = m match { + case p: MPrimitive => p.idlName match { + case "i8" | "i16" | "i32" | "i64" => List() + case _ => List() + } + case MString | MDate | MBinary | MOptional | MList | MSet | MMap => List() + case d: MDef => d.defType match { + case DEnum => List() //no headers to import for enums + case DRecord => //DEnum | DRecord => + if (d.name != exclude) { + List(ImportRef(q(spec.cxcppIncludePrefix + spec.cxFileIdentStyle(d.name) + "_convert." + spec.cxcppHeaderExt))) + } else { + List() + } + case DInterface => + if (d.name != exclude) { + List(ImportRef(q(spec.cxcppIncludePrefix + spec.cxFileIdentStyle(d.name) + "_convert." + spec.cxcppHeaderExt))) + } else { + List() + } + } + case p: MParam => List() + } + + def headerName(ident: String) = idCx.ty(ident) + "." + spec.cxHeaderExt + def include(ident: String) = q(spec.cxIncludePrefix + headerName(ident)) + + + def isReference(td: TypeDecl) = td.body match { + case i: Interface => true + case r: Record => true + case e: Enum => true + } + + def boxedTypename(td: TypeDecl) = td.body match { + case i: Interface => typename(td.ident, i) + case r: Record => typename(td.ident, r) + case e: Enum => "Platform::Object" + } + + +// // Return value: (Type_Name, Is_Class_Or_Not) +// def toCxType(ty: TypeRef, namespace: Option[String] = None): (String, Boolean) = toCxType(ty.resolved, namespace, false) +// def toCxType(ty: TypeRef, namespace: Option[String], needRef: Boolean): (String, Boolean) = toCxType(ty.resolved, namespace, needRef) +// def toCxType(tm: MExpr, namespace: Option[String]): (String, Boolean) = toCxType(tm, namespace, false) +// def toCxType(tm: MExpr, namespace: Option[String], needRef: Boolean): (String, Boolean) = { +// def f(tm: MExpr, needRef: Boolean): (String, Boolean) = { +// def base(tm: MExpr, needRef: Boolean): (String, Boolean) = { +// tm.base match { +// case MOptional => +// // We use "nil" for the empty optional. +// assert(tm.args.size == 1) +// val arg = tm.args.head +// arg.base match { +// case MOptional => throw new AssertionError("nested optional?") +// case m => f(arg, true) +// } +// case o => +// val base = o match { +// case p: MPrimitive => if (needRef) (p.cxBoxed, true) else (p.cxName, false) +// case MString => ("Platform::String", true) +// case MDate => ("Windows::Foundation::DateTime", true) +// case MBinary => ("Platform::Array", true) +// case MOptional => throw new AssertionError("optional should have been special cased") +// case MList => ("Windows::Foundation::Collections::IVector", true) +// case MSet => ("Windows::Foundation::Collections::IMap", true) //no set in C++/Cx FOr now this shit is broken until I can figure out how to make something map onto itself. +// case MMap => ("Windows::Foundation::Collections::IMap", true) +// case d: MDef => d.defType match { +// case DEnum => (idCx.ty(d.name), true) +// case DRecord => (idCx.ty(d.name), true) +// case DInterface => +// val ext = d.body.asInstanceOf[Interface].ext +// if (ext.cpp && !ext.cx) +// (idCx.ty(d.name), true) +// else +// (s"I${withNs(namespace, idCx.ty(d.name))}", true) +// } +// case e: MExtern => e.body match { +// case i: Interface => if (i.ext.cx) (s"I${e.cx.typename}", true) else (e.cx.typename, true) +// case _ => if (needRef) (e.cx.boxed, true) else (e.cx.typename, e.cx.reference) +// } +// case p: MParam => throw new AssertionError("Parameter should not happen at Cx top level") +// } +// base +// } +// } +// def expr(tm: MExpr, needRef: Boolean): (String, Boolean) = { +// val args = if (tm.args.isEmpty) "" else tm.args.map(expr).mkString("<", ", ", ">") +// base(tm, needRef) + args +// } +// expr(tm, needRef) +// } +// f(tm, needRef) +// } + + def toCxType(ty: TypeRef, namespace: Option[String] = None): (String, Boolean) = toCxType(ty.resolved, namespace, false) + def toCxType(ty: TypeRef, namespace: Option[String], needRef: Boolean): (String, Boolean) = toCxType(ty.resolved, namespace, needRef) + def toCxType(tm: MExpr, namespace: Option[String]): (String, Boolean) = toCxType(tm, namespace, false) + def toCxType(tm: MExpr, namespace: Option[String], needRef: Boolean): (String, Boolean) = { + def base(m: MExpr, namespace: Option[String], needRef: Boolean): (String, Boolean) = m.base match { + case p: MPrimitive => (p.cxName, false) + case MString => ("Platform::String", true) + case MDate => ("Windows::Foundation::DateTime", true) + case MBinary => ("Platform::Array", true) + case MOptional => // We use "nullptr" for the empty optional. + assert(tm.args.size == 1) + val arg = tm.args.head + arg.base match { + case MOptional => throw new AssertionError("nested optional?") + case p: MPrimitive => (p.cxBoxed, true) + case m => expr(arg, namespace, true) + } + case MList => ("Windows::Foundation::Collections::IVector", true) + case MSet => ("Windows::Foundation::Collections::IMap", true) + case MMap => ("Windows::Foundation::Collections::IMap", true) + case d: MDef => + d.defType match { + case DEnum => (withNs(namespace, idCx.enumType(d.name)), false) + case DRecord => (withNs(namespace, idCx.ty(d.name)), true) + case DInterface => + val ext = d.body.asInstanceOf[Interface].ext + if (ext.cpp && !ext.cx) + (idCx.ty(d.name), true) + else + (s"I${withNs(namespace, idCx.ty(d.name))}", true) + } + case e: MExtern => e.body match { + case i: Interface => if (i.ext.cx) (s"I${e.cx.typename}", true) else (e.cx.typename, true) + case _ => (e.cpp.typename, needRef) + } + case p: MParam => (idCx.typeParam(p.name), needRef) + } + def exprWithReference(tm: MExpr, namespace: Option[String], needRef:Boolean): String = { + val (arg, ref) = expr(tm, namespace, needRef) + if(ref) s"$arg^" else arg + } + def expr(tm: MExpr, namespace: Option[String], needRef: Boolean): (String, Boolean) = { + + val args = tm.base match { + case MOptional => + assert(tm.args.size == 1) + val arg = tm.args.head + arg.base match { + case MOptional => throw new AssertionError("nested optional?") +// case p: MPrimitive => "" + case m => "" //if (tm.args.isEmpty) "" else tm.args.map(arg => exprWithReference(arg, namespace, needRef)).mkString("<", ", ", ">") //(tm.args[0].typename, true) + } + case MSet => if (tm.args.size == 1) (tm.args :+ tm.args(0)).map(arg => exprWithReference(arg, namespace, needRef)).mkString("<", ", ", ">") else tm.args.map(arg => exprWithReference(arg, namespace, needRef)).mkString("<", ", ", ">") + case MMap => tm.args.map(arg => exprWithReference(arg, namespace, needRef)).mkString("<", ", ", ">") + case d => if (tm.args.isEmpty) "" else tm.args.map(arg => exprWithReference(arg, namespace, needRef)).mkString("<", ", ", ">") + } + val (ret, ref) = base(tm, namespace, needRef) + (ret+ args, ref) + } + expr(tm, namespace, needRef) + } + + // this can be used in c++ generation to know whether a const& should be applied to the parameter or not + private def toCxParamType(tm: MExpr, namespace: Option[String] = None): String = { + val (name, needRef) = toCxType(tm, namespace) + name + (if(needRef) "^" else "") + } + +} diff --git a/src/source/JNIGenerator.scala b/src/source/JNIGenerator.scala index c36234ec0..4cb0915c1 100644 --- a/src/source/JNIGenerator.scala +++ b/src/source/JNIGenerator.scala @@ -31,9 +31,9 @@ class JNIGenerator(spec: Spec) extends Generator(spec) { val jniBaseLibClassIdentStyle = IdentStyle.prefix("H", IdentStyle.camelUpper) val jniBaseLibFileIdentStyle = jniBaseLibClassIdentStyle - val writeJniCppFile = writeCppFileGeneric(spec.jniOutFolder.get, spec.jniNamespace, spec.jniFileIdentStyle, spec.jniIncludePrefix) _ + val writeJniCppFile = writeCppFileGeneric(spec.jniOutFolder.get, spec.jniNamespace, spec.jniFileIdentStyle, spec.jniIncludePrefix, spec.cppExt, spec.cppHeaderExt) _ def writeJniHppFile(name: String, origin: String, includes: Iterable[String], fwds: Iterable[String], f: IndentWriter => Unit, f2: IndentWriter => Unit = (w => {})) = - writeHppFileGeneric(spec.jniHeaderOutFolder.get, spec.jniNamespace, spec.jniFileIdentStyle)(name, origin, includes, fwds, f, f2) + writeHppFileGeneric(spec.jniHeaderOutFolder.get, spec.jniNamespace, spec.jniFileIdentStyle, spec.cppHeaderExt)(name, origin, includes, fwds, f, f2) class JNIRefs(name: String, cppPrefixOverride: Option[String]=None) { var jniHpp = mutable.TreeSet[String]() diff --git a/src/source/Main.scala b/src/source/Main.scala index 790988def..31e17a99b 100644 --- a/src/source/Main.scala +++ b/src/source/Main.scala @@ -79,7 +79,27 @@ object Main { var yamlOutFolder: Option[File] = None var yamlOutFile: Option[String] = None var yamlPrefix: String = "" - + var cxOutFolder: Option[File] = None + var cxcppOutFolder: Option[File] = None + var cxHeaderOutFolderOptional: Option[File] = None + var cxcppHeaderOutFolderOptional: Option[File] = None + var cxIncludePrefix: String = "" + var cxcppIncludePrefix: String = "" + var cxcppIncludeCppPrefix: String = "" + var cxcppIncludeCxPrefix: String = "" + var cxIdentStyle: CxIdentStyle = IdentStyle.cxDefault + var cxFileIdentStyle: IdentConverter = IdentStyle.camelUpper + var cxExt: String = "cpp" + var cxHeaderExt: String = "h" + var cxcppExt: String = "cpp" + var cxcppHeaderExt: String = "h" + var cxNamespace: String = "djinni" + var cxcppNamespace: String = "djinni_generated" + var cxBaseLibIncludePrefix: String = "" + var inFileListPath: Option[File] = None + var outFileListPath: Option[File] = None + var skipGeneration: Boolean = false + val argParser = new scopt.OptionParser[Unit]("djinni") { def identStyle(optionName: String, update: IdentConverter => Unit) = { @@ -194,6 +214,37 @@ object Main { .text("Optional file in which to write the list of output files produced.") opt[Boolean]("skip-generation").valueName("").foreach(x => skipGeneration = x) .text("Way of specifying if file generation should be skipped (default: false)") + opt[File]("cx-out").valueName("").foreach(x => cxOutFolder = Some(x)) + .text("HAHAHAHAHAHAHAHAHAHA") + opt[File]("cxcpp-out").valueName("").foreach(x => cxcppOutFolder = Some(x)) + .text("HAHAHAHAHAHAHAHAHAHA") + opt[File]("cx-header-out").valueName("").foreach(x => cxHeaderOutFolderOptional = Some(x)) + .text("HAHAHAHAHAHAHAHAHAHA") + opt[File]("cxcpp-header-out").valueName("").foreach(x => cxcppHeaderOutFolderOptional = Some(x)) + .text("HAHAHAHAHAHAHAHAHAHA") + opt[String]("cx-include-prefix").valueName("").foreach(cxIncludePrefix = _) + .text("HAHAHAHAHAHAHAHAHAHA") + opt[String]("cxcpp-include-prefix").valueName("").foreach(cxcppIncludePrefix = _) + .text("HAHAHAHAHAHAHAHAHAHA") + opt[String]("cxcpp-include-cpp-prefix").valueName("").foreach(cxcppIncludeCppPrefix = _) + .text("HAHAHAHAHAHAHAHAHAHA") + opt[String]("cxcpp-include-cx-prefix").valueName("").foreach(cxcppIncludeCxPrefix = _) + .text("HAHAHAHAHAHAHAHAHAHA") + opt[String]("cx-ext").valueName("").foreach(cxExt = _) + .text("HAHAHAHAHAHAHAHAHAHA") + opt[String]("cx-h-ext").valueName("").foreach(cxHeaderExt = _) + .text("HAHAHAHAHAHAHAHAHAHA") + opt[String]("cxcpp-ext").valueName("").foreach(cxcppExt = _) + .text("HAHAHAHAHAHAHAHAHAHA") + opt[String]("cxcpp-h-ext").valueName("").foreach(cxcppHeaderExt = _) + .text("HAHAHAHAHAHAHAHAHAHA") + opt[String]("cx-namespace").valueName("").foreach(cxNamespace = _) + .text("HAHAHAHAHAHAHAHAHAHA") + opt[String]("cxcpp-namespace").valueName("").foreach(cxcppNamespace = _) + .text("HAHAHAHAHAHAHAHAHAHA") + opt[String]("cx-base-lib-include-prefix").valueName("...").foreach(x => cxBaseLibIncludePrefix = x) + .text("The C++/Cx base library's include path, relative to the C++/Cx classes.") + note("\nIdentifier styles (ex: \"FooBar\", \"fooBar\", \"foo_bar\", \"FOO_BAR\", \"m_fooBar\")\n") identStyle("ident-java-enum", c => { javaIdentStyle = javaIdentStyle.copy(enum = c) }) @@ -215,6 +266,13 @@ object Main { identStyle("ident-objc-type-param", c => { objcIdentStyle = objcIdentStyle.copy(typeParam = c) }) identStyle("ident-objc-local", c => { objcIdentStyle = objcIdentStyle.copy(local = c) }) identStyle("ident-objc-file", c => { objcFileIdentStyleOptional = Some(c) }) + identStyle("ident-cx-enum", c => { cxIdentStyle = cxIdentStyle.copy(enum = c) }) + identStyle("ident-cx-field", c => { cxIdentStyle = cxIdentStyle.copy(field = c) }) + identStyle("ident-cx-method", c => { cxIdentStyle = cxIdentStyle.copy(method = c) }) + identStyle("ident-cx-type", c => { cxIdentStyle = cxIdentStyle.copy(ty = c) }) + identStyle("ident-cx-type-param", c => { cxIdentStyle = cxIdentStyle.copy(typeParam = c) }) + identStyle("ident-cx-local", c => { cxIdentStyle = cxIdentStyle.copy(local = c) }) + identStyle("ident-cx-file", c => { cxFileIdentStyle = c }) } @@ -229,6 +287,8 @@ object Main { val jniFileIdentStyle = jniFileIdentStyleOptional.getOrElse(cppFileIdentStyle) var objcFileIdentStyle = objcFileIdentStyleOptional.getOrElse(objcIdentStyle.ty) val objcppIncludeObjcPrefix = objcppIncludeObjcPrefixOptional.getOrElse(objcppIncludePrefix) + val cxHeaderOutFolder = if (cxHeaderOutFolderOptional.isDefined) cxHeaderOutFolderOptional else cxOutFolder + val cxcppHeaderOutFolder = if (cxcppHeaderOutFolderOptional.isDefined) cxcppHeaderOutFolderOptional else cxcppOutFolder // Add ObjC prefix to identstyle objcIdentStyle = objcIdentStyle.copy(ty = IdentStyle.prefix(objcTypePrefix,objcIdentStyle.ty)) @@ -238,6 +298,10 @@ object Main { cppIdentStyle = cppIdentStyle.copy(enumType = cppTypeEnumIdentStyle) } +// if (cxTypeEnumIdentStyle != null) { +// cxIdentStyle = cxIdentStyle.copy(enumType = cxTypeEnumIdentStyle) +// } + // Parse IDL file. System.out.println("Parsing...") val inFileListWriter = if (inFileListPath.isDefined) { @@ -330,7 +394,26 @@ object Main { skipGeneration, yamlOutFolder, yamlOutFile, - yamlPrefix) + yamlPrefix, + cxOutFolder, + cxcppOutFolder, + cxHeaderOutFolder, + cxcppHeaderOutFolder, + cxIncludePrefix, + cxcppIncludePrefix, + cxcppIncludeCppPrefix, + cxcppIncludeCxPrefix, + cxIdentStyle, + cxFileIdentStyle, + cxExt, + cxHeaderExt, + cxcppExt, + cxcppHeaderExt, + cxNamespace, + cxcppNamespace, + cxBaseLibIncludePrefix, + outFileListWriter, + skipGeneration) try { diff --git a/src/source/Marshal.scala b/src/source/Marshal.scala index f4fff656e..463684b4a 100644 --- a/src/source/Marshal.scala +++ b/src/source/Marshal.scala @@ -40,6 +40,7 @@ abstract class Marshal(spec: Spec) { protected val idCpp = spec.cppIdentStyle protected val idJava = spec.javaIdentStyle protected val idObjc = spec.objcIdentStyle + protected val idCx = spec.cxIdentStyle protected def withNs(namespace: Option[String], t: String) = namespace match { case None => t diff --git a/src/source/ObjcMarshal.scala b/src/source/ObjcMarshal.scala index 66edc49fa..8d8a568fe 100644 --- a/src/source/ObjcMarshal.scala +++ b/src/source/ObjcMarshal.scala @@ -21,7 +21,7 @@ class ObjcMarshal(spec: Spec) extends Marshal(spec) { val interfaceNullity = if (spec.cppNnType.nonEmpty) nonnull else nullable tm.base match { case MOptional => nullable - case MPrimitive(_,_,_,_,_,_,_,_) => None + case MPrimitive(_,_,_,_,_,_,_,_,_,_) => None case d: MDef => d.defType match { case DEnum => None case DInterface => interfaceNullity @@ -45,9 +45,9 @@ class ObjcMarshal(spec: Spec) extends Marshal(spec) { override def fqReturnType(ret: Option[TypeRef]): String = returnType(ret) override def fieldType(tm: MExpr): String = toObjcParamType(tm) + override def toCpp(tm: MExpr, expr: String): String = throw new AssertionError("direct objc to cpp conversion not possible") override def fqFieldType(tm: MExpr): String = toObjcParamType(tm) - override def toCpp(tm: MExpr, expr: String): String = throw new AssertionError("direct objc to cpp conversion not possible") override def fromCpp(tm: MExpr, expr: String): String = throw new AssertionError("direct cpp to objc conversion not possible") def references(m: Meta, exclude: String = ""): Seq[SymbolReference] = m match { diff --git a/src/source/YamlGenerator.scala b/src/source/YamlGenerator.scala index 6b6fe8b83..5d600ff0e 100644 --- a/src/source/YamlGenerator.scala +++ b/src/source/YamlGenerator.scala @@ -16,6 +16,8 @@ class YamlGenerator(spec: Spec) extends Generator(spec) { val objcppMarshal = new ObjcppMarshal(spec) val javaMarshal = new JavaMarshal(spec) val jniMarshal = new JNIMarshal(spec) + val cxMarshal = new CxMarshal(spec) + val cxcppMarshal = new CxCppMarshal(spec) case class QuotedString(str: String) // For anything that migt require escaping @@ -144,6 +146,19 @@ class YamlGenerator(spec: Spec) extends Generator(spec) { "typeSignature" -> QuotedString(jniMarshal.fqTypename(td.ident, td.body)) ) + private def cx(td: TypeDecl) = Map[String, Any]( + "typename" -> QuotedString(cxMarshal.fqTypename(td.ident, td.body)), + "header" -> QuotedString(cxMarshal.include(td.ident)), + "boxed" -> QuotedString(cxMarshal.boxedTypename(td)), + "reference" -> cxMarshal.isReference(td) + ) + + private def cxcpp(td: TypeDecl) = Map[String, Any]( + "typename" -> QuotedString(cxcppMarshal.fqTypename(td.ident, td.body)), + "header" -> QuotedString(cxcppMarshal.include(td.ident)), + "byValue" -> cxcppMarshal.byValue(td) + ) + // TODO: there has to be a way to do all this without the MExpr/Meta conversions? private def mexpr(td: TypeDecl) = MExpr(meta(td), List()) @@ -209,8 +224,17 @@ object YamlGenerator { nested(td, "jni")("translator").toString, nested(td, "jni")("header").toString, nested(td, "jni")("typename").toString, - nested(td, "jni")("typeSignature").toString) - ) + nested(td, "jni")("typeSignature").toString), + MExtern.Cx( + nested(td, "cx")("typename").toString, + nested(td, "cx")("header").toString, + nested(td, "cx")("boxed").toString, + nested(td, "cx")("reference").asInstanceOf[Boolean]), + MExtern.CxCpp( + nested(td, "cxcpp")("typename").toString, + nested(td, "cxcpp")("header").toString, + nested(td, "cxcpp")("byValue").asInstanceOf[Boolean]) + ); private def nested(td: ExternTypeDecl, key: String) = { td.properties.get(key).collect { case m: JMap[_, _] => m.collect { case (k: String, v: Any) => (k, v) } } getOrElse(Map[String, Any]()) diff --git a/src/source/ast.scala b/src/source/ast.scala index 6d2ca62c8..f238997e7 100644 --- a/src/source/ast.scala +++ b/src/source/ast.scala @@ -46,12 +46,14 @@ sealed abstract class TypeDecl { case class InternTypeDecl(override val ident: Ident, override val params: Seq[TypeParam], override val body: TypeDef, doc: Doc, override val origin: String) extends TypeDecl case class ExternTypeDecl(override val ident: Ident, override val params: Seq[TypeParam], override val body: TypeDef, properties: Map[String, Any], override val origin: String) extends TypeDecl -case class Ext(java: Boolean, cpp: Boolean, objc: Boolean) { +case class Ext(java: Boolean, cpp: Boolean, objc: Boolean, cx: Boolean) { def any(): Boolean = { java || cpp || objc } } +case class Ext(java: Boolean, cpp: Boolean, objc: Boolean, cx: Boolean) + case class TypeRef(expr: TypeExpr) { var resolved: MExpr = null } diff --git a/src/source/generator.scala b/src/source/generator.scala index b14564076..99864d2d9 100644 --- a/src/source/generator.scala +++ b/src/source/generator.scala @@ -78,7 +78,26 @@ package object generatorTools { skipGeneration: Boolean, yamlOutFolder: Option[File], yamlOutFile: Option[String], - yamlPrefix: String) + yamlPrefix: String, + cxOutFolder: Option[File], + cxcppOutFolder: Option[File], + cxHeaderOutFolder: Option[File], + cxcppHeaderOutFolder: Option[File], + cxIncludePrefix: String, + cxcppIncludePrefix: String, + cxcppIncludeCppPrefix: String, + cxcppIncludeCxPrefix: String, + cxIdentStyle: CxIdentStyle, + cxFileIdentStyle: IdentConverter, + cxExt: String, + cxHeaderExt: String, + cxcppExt: String, + cxcppHeaderExt: String, + cxNamespace: String, + cxcppNamespace: String, + cxBaseLibIncludePrefix: String, + outFileListWriter: Option[Writer], + skipGeneration: Boolean) def preComma(s: String) = { if (s.isEmpty) s else ", " + s @@ -100,6 +119,10 @@ package object generatorTools { method: IdentConverter, field: IdentConverter, local: IdentConverter, enum: IdentConverter, const: IdentConverter) + case class CxIdentStyle(ty: IdentConverter, enumType: IdentConverter, typeParam: IdentConverter, + method: IdentConverter, field: IdentConverter, local: IdentConverter, + enum: IdentConverter, const: IdentConverter) + object IdentStyle { val camelUpper = (s: String) => s.split('_').map(firstUpper).mkString val camelLower = (s: String) => { @@ -114,6 +137,7 @@ package object generatorTools { val javaDefault = JavaIdentStyle(camelUpper, camelUpper, camelLower, camelLower, camelLower, underCaps, underCaps) val cppDefault = CppIdentStyle(camelUpper, camelUpper, camelUpper, underLower, underLower, underLower, underCaps, underCaps) val objcDefault = ObjcIdentStyle(camelUpper, camelUpper, camelLower, camelLower, camelLower, camelUpper, camelUpper) + val cxDefault = CxIdentStyle(camelUpper, camelUpper, camelUpper, camelUpper, camelUpper, camelUpper, camelUpper, camelUpper) val styles = Map( "FooBar" -> camelUpper, @@ -219,6 +243,27 @@ package object generatorTools { } new YamlGenerator(spec).generate(idl) } + if (spec.cxOutFolder.isDefined) { + if (!spec.skipGeneration) { + createFolder("Cx", spec.cxOutFolder.get) + createFolder("Cx header", spec.cxHeaderOutFolder.get) + } + new CxGenerator(spec).generate(idl) + } + if (spec.cxcppOutFolder.isDefined) { + if (!spec.skipGeneration) { + createFolder("CxCpp", spec.cxcppOutFolder.get) + createFolder("CxCpp header", spec.cxcppHeaderOutFolder.get) + } + new CxCppGenerator(spec).generate(idl) + } + + if (spec.yamlOutFolder.isDefined) { + if (!spec.skipGeneration) { + createFolder("YAML", spec.yamlOutFolder.get) + new YamlGenerator(spec).generate(idl) + } + } None } catch { @@ -272,6 +317,7 @@ abstract class Generator(spec: Spec) val idCpp = spec.cppIdentStyle val idJava = spec.javaIdentStyle val idObjc = spec.objcIdentStyle + val idCx = spec.cxIdentStyle def wrapNamespace(w: IndentWriter, ns: String, f: IndentWriter => Unit) { ns match { @@ -293,8 +339,8 @@ abstract class Generator(spec: Spec) w.wl("} // end anonymous namespace") } - def writeHppFileGeneric(folder: File, namespace: String, fileIdentStyle: IdentConverter)(name: String, origin: String, includes: Iterable[String], fwds: Iterable[String], f: IndentWriter => Unit, f2: IndentWriter => Unit) { - createFile(folder, fileIdentStyle(name) + "." + spec.cppHeaderExt, (w: IndentWriter) => { + def writeHppFileGeneric(folder: File, namespace: String, fileIdentStyle: IdentConverter, headerExt: String)(name: String, origin: String, includes: Iterable[String], fwds: Iterable[String], f: IndentWriter => Unit, f2: IndentWriter => Unit) { + createFile(folder, fileIdentStyle(name) + "." + headerExt, (w: IndentWriter) => { w.wl("// AUTOGENERATED FILE - DO NOT MODIFY!") w.wl("// This file generated by Djinni from " + origin) w.wl @@ -317,12 +363,12 @@ abstract class Generator(spec: Spec) }) } - def writeCppFileGeneric(folder: File, namespace: String, fileIdentStyle: IdentConverter, includePrefix: String)(name: String, origin: String, includes: Iterable[String], f: IndentWriter => Unit) { - createFile(folder, fileIdentStyle(name) + "." + spec.cppExt, (w: IndentWriter) => { + def writeCppFileGeneric(folder: File, namespace: String, fileIdentStyle: IdentConverter, includePrefix: String, bodyExt: String, headerExt: String)(name: String, origin: String, includes: Iterable[String], f: IndentWriter => Unit) { + createFile(folder, fileIdentStyle(name) + "." + bodyExt, (w: IndentWriter) => { w.wl("// AUTOGENERATED FILE - DO NOT MODIFY!") w.wl("// This file generated by Djinni from " + origin) w.wl - val myHeader = q(includePrefix + fileIdentStyle(name) + "." + spec.cppHeaderExt) + val myHeader = q(includePrefix + fileIdentStyle(name) + "." + headerExt) w.wl(s"#include $myHeader // my header") val myHeaderInclude = s"#include $myHeader" for (include <- includes if include != myHeaderInclude) diff --git a/src/source/meta.scala b/src/source/meta.scala index 13ab7ba53..36ae71d89 100644 --- a/src/source/meta.scala +++ b/src/source/meta.scala @@ -30,7 +30,7 @@ abstract sealed class Meta case class MParam(name: String) extends Meta { val numParams = 0 } case class MDef(name: String, override val numParams: Int, defType: DefType, body: TypeDef) extends Meta -case class MExtern(name: String, override val numParams: Int, defType: DefType, body: TypeDef, cpp: MExtern.Cpp, objc: MExtern.Objc, objcpp: MExtern.Objcpp, java: MExtern.Java, jni: MExtern.Jni) extends Meta +case class MExtern(name: String, override val numParams: Int, defType: DefType, body: TypeDef, cpp: MExtern.Cpp, objc: MExtern.Objc, objcpp: MExtern.Objcpp, java: MExtern.Java, jni: MExtern.Jni, cx: MExtern.Cx, cxcpp: MExtern.CxCpp) extends Meta object MExtern { // These hold the information marshals need to interface with existing types correctly // All include paths are complete including quotation marks "a/b/c" or angle brackets . @@ -65,6 +65,17 @@ object MExtern { typename: String, // The JNI type to use (e.g. jobject, jstring) typeSignature: String // The mangled Java type signature (e.g. "Ljava/lang/String;") ) + case class Cx( + typename: String, + header: String, + boxed: String, + reference: Boolean + ) + case class CxCpp( + typename: String, + header: String, + byValue: Boolean + ) } abstract sealed class MOpaque extends Meta { val idlName: String } @@ -74,7 +85,7 @@ case object DEnum extends DefType case object DInterface extends DefType case object DRecord extends DefType -case class MPrimitive(_idlName: String, jName: String, jniName: String, cName: String, jBoxed: String, jSig: String, objcName: String, objcBoxed: String) extends MOpaque { val numParams = 0; val idlName = _idlName } +case class MPrimitive(_idlName: String, jName: String, jniName: String, cName: String, jBoxed: String, jSig: String, objcName: String, objcBoxed: String, cxName: String, cxBoxed: String) extends MOpaque { val numParams = 0; val idlName = _idlName } case object MString extends MOpaque { val numParams = 0; val idlName = "string" } case object MDate extends MOpaque { val numParams = 0; val idlName = "date" } case object MBinary extends MOpaque { val numParams = 0; val idlName = "binary" } @@ -84,13 +95,13 @@ case object MSet extends MOpaque { val numParams = 1; val idlName = "set" } case object MMap extends MOpaque { val numParams = 2; val idlName = "map" } val defaults: Map[String,MOpaque] = immutable.HashMap( - ("i8", MPrimitive("i8", "byte", "jbyte", "int8_t", "Byte", "B", "int8_t", "NSNumber")), - ("i16", MPrimitive("i16", "short", "jshort", "int16_t", "Short", "S", "int16_t", "NSNumber")), - ("i32", MPrimitive("i32", "int", "jint", "int32_t", "Integer", "I", "int32_t", "NSNumber")), - ("i64", MPrimitive("i64", "long", "jlong", "int64_t", "Long", "J", "int64_t", "NSNumber")), - ("f32", MPrimitive("f32", "float", "jfloat", "float", "Float", "F", "float", "NSNumber")), - ("f64", MPrimitive("f64", "double", "jdouble", "double", "Double", "D", "double", "NSNumber")), - ("bool", MPrimitive("bool", "boolean", "jboolean", "bool", "Boolean", "Z", "BOOL", "NSNumber")), + ("i8", MPrimitive("i8", "byte", "jbyte", "int8_t", "Byte", "B", "int8_t", "NSNumber", "int16", "Platform::Object")), + ("i16", MPrimitive("i16", "short", "jshort", "int16_t", "Short", "S", "int16_t", "NSNumber", "int16", "Platform::Object")), + ("i32", MPrimitive("i32", "int", "jint", "int32_t", "Integer", "I", "int32_t", "NSNumber", "int32", "Platform::Object")), + ("i64", MPrimitive("i64", "long", "jlong", "int64_t", "Long", "J", "int64_t", "NSNumber", "int64", "Platform::Object")), + ("f32", MPrimitive("f32", "float", "jfloat", "float", "Float", "F", "float", "NSNumber", "float32", "Platform::Object")), + ("f64", MPrimitive("f64", "double", "jdouble", "double", "Double", "D", "double", "NSNumber", "float64", "Platform::Object")), + ("bool", MPrimitive("bool", "boolean", "jboolean", "bool", "Boolean", "Z", "BOOL", "NSNumber", "bool", "Platform::Object")), ("string", MString), ("binary", MBinary), ("optional", MOptional), diff --git a/src/source/parser.scala b/src/source/parser.scala index a7f06841f..59c5a2a04 100644 --- a/src/source/parser.scala +++ b/src/source/parser.scala @@ -63,13 +63,14 @@ private object IdlParser extends RegexParsers { } def ext(default: Ext) = (rep1("+" ~> ident) >> checkExts) | success(default) - def extRecord = ext(Ext(false, false, false)) - def extInterface = ext(Ext(true, true, true)) + def extRecord = ext(Ext(false, false, false, false)) + def extInterface = ext(Ext(true, true, true, true)) def checkExts(parts: List[Ident]): Parser[Ext] = { var foundCpp = false var foundJava = false var foundObjc = false + var foundCx = false for (part <- parts) part.name match { @@ -85,9 +86,13 @@ private object IdlParser extends RegexParsers { if (foundObjc) return err("Found multiple \"o\" modifiers.") foundObjc = true } + case "x" => { + if (foundCx) return err("Found multiple \"x\" modifiers.") + foundCx = true + } case _ => return err("Invalid modifier \"" + part.name + "\"") } - success(Ext(foundJava, foundCpp, foundObjc)) + success(Ext(foundJava, foundCpp, foundObjc, foundCx)) } def typeDef: Parser[TypeDef] = record | enum | interface diff --git a/support-lib/cx/CppWrapperCache.h b/support-lib/cx/CppWrapperCache.h new file mode 100755 index 000000000..e16388242 --- /dev/null +++ b/support-lib/cx/CppWrapperCache.h @@ -0,0 +1,86 @@ +// +// Copyright 2014 Dropbox, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// This header can only be imported to C++/Cx source code! +#pragma once + +#include +#include +#include + +namespace djinni { + +template +class CppWrapperCache { +public: + static const std::shared_ptr & getInstance() { + static const std::shared_ptr instance(new CppWrapperCache); + // Return by const-ref. This is safe to call any time except during static destruction. + // Returning by reference lets us avoid touching the refcount unless needed. + return instance; + } + + template + Platform::Object^ get(const std::shared_ptr & cppRef, const AllocFunc & alloc) { + std::unique_lock lock(m_mutex); + T* ptr = cppRef.get(); + auto got = m_mapping.find(ptr); + Platform::Object^ ret; + if (got != m_mapping.end()) { + ret = got->second.Resolve(); + if (ret == nullptr) { + ret = alloc(cppRef); + m_mapping[ptr] = Platform::WeakReference(ret); + } + } else { + ret = alloc(cppRef); + m_mapping[ptr] = Platform::WeakReference(ret); + } + return ret; + } + + void remove(const std::shared_ptr & cppRef) { + std::unique_lock lock(m_mutex); + T* ptr = cppRef.get(); + if (m_mapping[ptr] == nullptr) { + m_mapping.erase(ptr); + } + } + + class Handle { + public: + Handle() = default; + ~Handle() { + if (_ptr) { + _cache->remove(_ptr); + } + } + void assign(const std::shared_ptr& ptr) { _ptr = ptr; } + const std::shared_ptr& get() const { return _ptr; } + + private: + const std::shared_ptr _cache = getInstance(); + std::shared_ptr _ptr; + }; + +private: + std::unordered_map m_mapping; + std::mutex m_mutex; + + CppWrapperCache() {} +}; + +} // namespace djinni diff --git a/support-lib/cx/CxWrapperCache.h b/support-lib/cx/CxWrapperCache.h new file mode 100755 index 000000000..d2a954456 --- /dev/null +++ b/support-lib/cx/CxWrapperCache.h @@ -0,0 +1,85 @@ +// +// Copyright 2015 Slack Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// This header can only be imported to C++/Cx source code! + +#include +#include +#include +#include + +namespace djinni { + +template +class CxWrapperCache { +public: + static const std::shared_ptr & getInstance() { + static const std::shared_ptr instance(new CxWrapperCache); + // Return by const-ref. This is safe to call any time except during static destruction. + // Returning by reference lets us avoid touching the refcount unless needed. + return instance; + } + + std::shared_ptr get(Platform::Object^ cxRef) { + std::unique_lock lock(m_mutex); + std::shared_ptr ret; + auto it = m_mapping.find(reinterpret_cast(cxRef)); + if (it != m_mapping.end()) { + ret = std::static_pointer_cast(it->second.lock()); + if (ret == nullptr) { + ret = new_wrapper(cxRef); + } + } else { + ret = new_wrapper(cxRef); + } + return ret; + } + + void remove(Platform::Object^ cxRef) { + std::unique_lock lock(m_mutex); + m_mapping.erase(reinterpret_cast(cxRef)); + } + + class Handle { + public: + Handle(Platform::Object^ cx) : _cx(cx) { }; + ~Handle() { + if (_cx) { + _cache->remove(_cx); + } + } + Platform::Object^ get() const { return _cx; } + + private: + const std::shared_ptr _cache = getInstance(); + Platform::Object^ _cx; + }; + + +private: + std::unordered_map> m_mapping; + std::mutex m_mutex; + + std::shared_ptr new_wrapper(Platform::Object^ cxRef) { + auto ret = std::shared_ptr(new T(cxRef)); + std::weak_ptr ptr(std::static_pointer_cast(ret)); + m_mapping[reinterpret_cast(cxRef)] = ptr; + return ret; + } + +}; + +} // namespace djinni diff --git a/support-lib/cx/Marshal.h b/support-lib/cx/Marshal.h new file mode 100755 index 000000000..d5d3beaec --- /dev/null +++ b/support-lib/cx/Marshal.h @@ -0,0 +1,301 @@ +// +// Marshal.h +// Djinni +// +// Created by D.E. Goodman-Wilson on 07.08.15. +// Copyright (c) 2015 Slack Technologies, Inc. All rights reserved. +// + +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace djinni { + + struct Bool { + using CppType = bool; + using CxType = bool; + + static CppType toCpp(CxType x) { return x ? true : false; } + static CxType fromCpp(CppType x) { return x ? true : false; } + + struct Boxed { + using CxType = Platform::Object^; + static CppType toCpp(CxType x) { assert(x); return Bool::toCpp((bool)x); } + static CxType fromCpp(CppType x) { CxType cx = Bool::fromCpp(x); return cx; } + }; + }; + + template //, class CXT> + struct Primitive { + using CppType = T; + using CxType = T; + + static CppType toCpp(CxType x) { return x; } + static CxType fromCpp(CppType x) { return x; } + + struct Boxed { + using CxType = Platform::Object^; + static CppType toCpp(CxType x) { assert(x); return Self::unbox(x); } + static CxType fromCpp(CppType x) { return Self::box(x); } + }; + }; + + //stupid C++/Cx doesn't have int8_t; we'll pass it up as a uint8_t instead, and pray. + class I8 : public Primitive { + friend Primitive; + static int16 unbox(Boxed::CxType x) { return (uint8)x; } + static Boxed::CxType box(CppType x) { Boxed::CxType cx = (uint8)x; return cx; } + }; + + class I16 : public Primitive { + friend Primitive; + static int16 unbox(Boxed::CxType x) { return (int16)x; } + static Boxed::CxType box(CppType x) { Boxed::CxType cx = x; return cx; } + }; + + class I32 : public Primitive { + friend Primitive; + static int32 unbox(Boxed::CxType x) { return (int32)x; } + static Boxed::CxType box(CppType x) { Boxed::CxType cx = x; return cx; } + }; + + class I64 : public Primitive { + friend Primitive; + static int64 unbox(Boxed::CxType x) { return (int64)x; } + static Boxed::CxType box(CppType x) { Boxed::CxType cx = x; return cx; } + }; + + class F32 : public Primitive { + friend Primitive; + static float unbox(Boxed::CxType x) { return (float)x; } + static Boxed::CxType box(CppType x) { Boxed::CxType cx = x; return cx; } + }; + + class F64 : public Primitive { + friend Primitive; + static double unbox(Boxed::CxType x) { return (double)x; } + static Boxed::CxType box(CppType x) { Boxed::CxType cx = x; return cx; } + }; + + + template + struct Enum { + using CppType = CppEnum; + using CxType = CxEnum; + + static CppType toCpp(CxType e) { return static_cast(e); } + static CxType fromCpp(CppType e) { return static_cast(e); } + + struct Boxed { + //yeah, I just don't know about these two lines. So...weird? How _do_ you box an enum? + static CppType toCpp(CxType x) { return Enum::toCpp(static_cast(static_cast(x))); } + static CxType fromCpp(CppType x) { return Enum::toCx(static_cast(static_cast(x))); } + }; + }; + + struct String { + using CppType = std::string; + using CxType = Platform::String^; + + using Boxed = String; + + static CppType toCpp(CxType string) { + //Empty strings are null. + if(string) + { + std::wstring wstring{ string->Data() }; + std::wstring_convert, wchar_t> converter; + return converter.to_bytes(wstring); + } + return {""}; + } + + static CxType fromCpp(const CppType& string) { + //TODO this doesn't seem to work. assert(string.size() <= std::numeric_limits::max()); + std::wstring_convert> converter; + return ref new Platform::String(converter.from_bytes(string).c_str()); + } + }; + +// struct Date { +// using CppType = std::chrono::system_clock::time_point; +// using CxType = Windows::Foundation::DateTime; +// +// using Boxed = Date; +// +// static CppType toCpp(CxType date) { +// // date is "A 64-bit signed integer that represents a point in time as the number of 100-nanosecond +// // intervals prior to or after midnight on January 1, 1601 (according to the Gregorian Calendar)." +// // So helpful +// using namespace std::chrono; +// static const auto POSIX_EPOCH = system_clock::from_time_t(0); +// +// // rather than calculate by hand the difference in time offsets between POSIX epoch and 1601, let's use a helper +// Windows::Globalization::Calendar^ calendar = ref new Windows::Globalization::Calendar(); +// calendar.year = 1970; +// calendar.month = 1; +// calendar.day = 1; +// calendar.hour = 0; +// calendar.minute = 0; +// calendar.second = 0; +// calendar.nanosecond = 0; +// uint64_t epochDate = (date.UniversalTime - calendar.GetDateTime().UniversalTime) / 10000; +// auto timeIntervalSince1970 = duration(epochDate); +// return POSIX_EPOCH + duration_cast(timeIntervalSince1970); +// } +// +// static CxType fromCpp(const CppType& date) { +// using namespace std::chrono; +// static const auto POSIX_EPOCH = system_clock::from_time_t(0); +// return [NSDate dateWithTimeIntervalSince1970:duration_cast>(date - POSIX_EPOCH).count()]; +// +// } +// }; + + // struct Binary { + // using CppType = std::vector; + // using CxType = NSData*; + // + // using Boxed = Binary; + // + // static CppType toCpp(CxType data) { + // assert(data); + // auto bytes = reinterpret_cast(data.bytes); + // return data.length > 0 ? CppType{bytes, bytes + data.length} : CppType{}; + // } + // + // static CxType fromCpp(const CppType& bytes) { + // assert(bytes.size() <= std::numeric_limits::max()); + // // Using the pointer from .data() on an empty vector is UB + // return bytes.empty() ? [NSData data] : [NSData dataWithBytes:bytes.data() + // length:static_cast(bytes.size())]; + // } + // }; + // + template class OptionalType, class T> + class Optional { + public: + using CppType = OptionalType; + using CxType = typename T::Boxed::CxType; + + using Boxed = Optional; + + static CppType toCpp(CxType cx) { + return cx ? CppType(T::Boxed::toCpp(cx)) : CppType(); + } + + static CxType fromCpp(const CppType& opt) { + return opt ? T::Boxed::fromCpp(*opt) : nullptr; + } + }; + + template + class List { + using ECppType = typename T::CppType; + using ECxType = typename T::CxType; + + public: + using CppType = std::vector; + using CxType = Windows::Foundation::Collections::IVector^; + + using Boxed = List; + + static CppType toCpp(CxType v) { + assert(v); + CppType nv; + for(ECxType val : v) + { + nv.push_back(T::Boxed::toCpp(val)); + } + return nv; + } + + static CxType fromCpp(const CppType& v) { + auto nv = ref new Platform::Collections::Vector; + for (ECppType val : v) + { + nv->Append(T::Boxed::fromCpp(val)); + } + return nv; + } + //We ought to specialize this for types C++/Cx knows how to convert for us. + }; + + template + class Set { + using ECppType = typename T::CppType; + using ECxType = typename T::CxType; + + public: + using CppType = std::unordered_set; + using CxType = Windows::Foundation::Collections::IMap^; //no sets. Seriously. So we'll just map objects to themselves + + using Boxed = Set; + + static CppType toCpp(CxType set) { + assert(set); + auto s = CppType(); + std::for_each(begin(set), end(set), [&s](Windows::Foundation::Collections::IKeyValuePair^ pair) + { + s.insert(T::toCpp(pair->Key)); + }); + + return s; + } + + static CxType fromCpp(const CppType& s) { + auto set = ref new Platform::Collections::Map; + for (const auto& val : s) { + set->Insert(T::fromCpp(val), T::fromCpp(val)); + } + return set; + } + }; + + template + class Map { + using CppKeyType = typename Key::CppType; + using CppValueType = typename Value::CppType; + using CxKeyType = typename Key::CxType; + using CxValueType = typename Value::CxType; + + public: + using CppType = std::unordered_map; + using CxType = Windows::Foundation::Collections::IMap^; + + using Boxed = Map; + + static CppType toCpp(CxType map) { + assert(map); + auto m = CppType(); + m.reserve(map.count); + + std::for_each(begin(map), end(map), [&m](Windows::Foundation::Collections::IKeyValuePair^ pair) + { + m.emplace(Key::toCpp(pair->Key), Value::toCpp(pair->Value)); + }); + + return m; + } + + static CxType fromCpp(const CppType& m) { + //assert(m.size() <= std::numeric_limits::max()); + auto map = ref new Platform::Collections::Map; + for(const auto& kvp : m) { + map->Insert(Value::fromCpp(kvp.first), Key::fromCpp(kvp.second)); + } + return map; + } + }; + +} // namespace djinni diff --git a/support-lib/support-lib.iml b/support-lib/support-lib.iml index 908ad4f52..798e7aa9f 100644 --- a/support-lib/support-lib.iml +++ b/support-lib/support-lib.iml @@ -8,4 +8,5 @@ - \ No newline at end of file + + diff --git a/support-lib/support_lib.gyp b/support-lib/support_lib.gyp index 7ac16a19b..8a150470c 100644 --- a/support-lib/support_lib.gyp +++ b/support-lib/support_lib.gyp @@ -43,5 +43,19 @@ ], }, }, + { + "target_name": "djinni_cx", + "type": "static_library", + "sources": [ + ], + "include_dirs": [ + "cx", + ], + "direct_dependent_settings": { + "include_dirs": [ + "cx", + ], + }, + }, ], }