Skip to content

Commit

Permalink
Generate YAML files with type descriptions for other projects
Browse files Browse the repository at this point in the history
These YAML files generated by Djinni hold all the necessary information to integrate these types with other Djinni projects, thus making (precompiled) libraries with possible.

There are still limitations:
- Extern enums/records cannot be used as constants as their enumerant/field information is not yet preserved.
- Include paths are fixed at the point of YAML generation, including paths do Djinni's support-lib. This may require some form of placeholders, or being smart about project structure in general.

Generation is controlled with following options
- --yaml-out: The output folder for YAML files (Generator disabled if unspecified).
- --yaml-out-file: If specified all types are merged into a single YAML file instead of generating one file per type  (relative to --yaml-out).
- --yaml-prefix: The prefix to add to type names stored in YAML files (default: "").
  • Loading branch information
mknejp committed Jul 13, 2015
1 parent 730f745 commit e181adc
Show file tree
Hide file tree
Showing 10 changed files with 287 additions and 37 deletions.
42 changes: 25 additions & 17 deletions src/source/CppMarshal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class CppMarshal(spec: Spec) extends Marshal(spec) {
case d: MDef => d.defType match {
case DEnum | DRecord =>
if (d.name != exclude) {
List(ImportRef(q(spec.cppIncludePrefix + spec.cppFileIdentStyle(d.name) + "." + spec.cppHeaderExt)))
List(ImportRef(include(d.name)))
} else {
List()
}
Expand All @@ -67,6 +67,8 @@ class CppMarshal(spec: Spec) extends Marshal(spec) {
case p: MParam => List()
}

def include(ident: String): String = q(spec.cppIncludePrefix + spec.cppFileIdentStyle(ident) + "." + spec.cppHeaderExt)

private def toCppType(ty: TypeRef, namespace: Option[String] = None): String = toCppType(ty.resolved, namespace)
private def toCppType(tm: MExpr, namespace: Option[String]): String = {
def base(m: Meta): String = m match {
Expand Down Expand Up @@ -97,26 +99,32 @@ class CppMarshal(spec: Spec) extends Marshal(spec) {
expr(tm)
}

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.cpp.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
}

// this can be used in c++ generation to know whether a const& should be applied to the parameter or not
private def toCppParamType(tm: MExpr, namespace: Option[String] = None): String = {
val cppType = toCppType(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 e: MExtern => e.defType match {
case DInterface => refType
case DEnum => valueType
case DRecord => if(e.cpp.byValue) valueType else refType
}
case MOptional => toType(expr.args.head)
case _ => refType
}
toType(tm)
if(byValue(tm)) valueType else refType
}
}
8 changes: 5 additions & 3 deletions src/source/JNIMarshal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class JNIMarshal(spec: Spec) extends Marshal(spec) {

// For JNI typename() is always fully qualified and describes the mangled Java type to be used in field/method signatures
override def typename(tm: MExpr): String = javaTypeSignature(tm)
def typename(name: String, ty: TypeDef): String = throw new AssertionError("not applicable")
def typename(name: String, ty: TypeDef) = s"L${undecoratedTypename(name, ty)};"

override def fqTypename(tm: MExpr): String = typename(tm)
def fqTypename(name: String, ty: TypeDef): String = typename(name, ty)
Expand All @@ -35,11 +35,13 @@ class JNIMarshal(spec: Spec) extends Marshal(spec) {

def references(m: Meta, exclude: String = ""): Seq[SymbolReference] = m match {
case o: MOpaque => List(ImportRef(q(spec.jniBaseLibIncludePrefix + "Marshal.hpp")))
case d: MDef => List(ImportRef(q(spec.jniIncludePrefix + spec.jniFileIdentStyle(d.name) + "." + spec.cppHeaderExt)))
case d: MDef => List(ImportRef(include(d.name)))
case e: MExtern => List(ImportRef(e.jni.header))
case _ => List()
}

def include(ident: String) = q(spec.jniIncludePrefix + spec.jniFileIdentStyle(ident) + "." + spec.cppHeaderExt)

def toJniType(ty: TypeRef): String = toJniType(ty.resolved, false)
def toJniType(m: MExpr, needRef: Boolean): String = m.base match {
case p: MPrimitive => if (needRef) "jobject" else p.jniName
Expand Down Expand Up @@ -81,7 +83,7 @@ class JNIMarshal(spec: Spec) extends Marshal(spec) {
params.map(f => typename(f.ty)).mkString("(", "", ")") + ret.fold("V")(typename)
}

private def helperName(tm: MExpr): String = tm.base match {
def helperName(tm: MExpr): String = tm.base match {
case d: MDef => withNs(Some(spec.jniNamespace), helperClass(d.name))
case e: MExtern => e.jni.translator
case o => withNs(Some("djinni"), o match {
Expand Down
6 changes: 6 additions & 0 deletions src/source/JavaMarshal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ class JavaMarshal(spec: Spec) extends Marshal(spec) {
}
}

def isReference(td: TypeDecl) = td.body match {
case i: Interface => true
case r: Record => true
case e: Enum => true
}

private def toJavaType(tm: MExpr, packageName: Option[String]): String = {
def args(tm: MExpr) = if (tm.args.isEmpty) "" else tm.args.map(f(_, true)).mkString("<", ", ", ">")
def f(tm: MExpr, needRef: Boolean): String = {
Expand Down
16 changes: 15 additions & 1 deletion src/source/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ object Main {
var inFileListPath: Option[File] = None
var outFileListPath: Option[File] = None
var skipGeneration: Boolean = false
var yamlOutFolder: Option[File] = None
var yamlOutFile: Option[String] = None
var yamlPrefix: String = ""

val argParser = new scopt.OptionParser[Unit]("djinni") {

Expand Down Expand Up @@ -153,6 +156,14 @@ object Main {
.text("The namespace name to use for generated Objective-C++ classes.")
opt[String]("objc-base-lib-include-prefix").valueName("...").foreach(x => objcBaseLibIncludePrefix = x)
.text("The Objective-C++ base library's include path, relative to the Objective-C++ classes.")
note("")
opt[File]("yaml-out").valueName("<out-folder>").foreach(x => yamlOutFolder = Some(x))
.text("The output folder for YAML files (Generator disabled if unspecified).")
opt[String]("yaml-out-file").valueName("<out-file>").foreach(x => yamlOutFile = Some(x))
.text("If specified all types are merged into a single YAML file instead of generating one file per type (relative to --yaml-out).")
opt[String]("yaml-prefix").valueName("<pre>").foreach(yamlPrefix = _)
.text("The prefix to add to type names stored in YAML files (default: \"\").")
note("")
opt[File]("list-in-files").valueName("<list-in-files>").foreach(x => inFileListPath = Some(x))
.text("Optional file in which to write the list of input files parsed.")
opt[File]("list-out-files").valueName("<list-out-files>").foreach(x => outFileListPath = Some(x))
Expand Down Expand Up @@ -282,7 +293,10 @@ object Main {
objcppNamespace,
objcBaseLibIncludePrefix,
outFileListWriter,
skipGeneration)
skipGeneration,
yamlOutFolder,
yamlOutFile,
yamlPrefix)


try {
Expand Down
17 changes: 15 additions & 2 deletions src/source/ObjcMarshal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class ObjcMarshal(spec: Spec) extends Marshal(spec) {
List(ImportRef("<Foundation/Foundation.h>"))
case d: MDef => d.defType match {
case DEnum =>
List(ImportRef(q(spec.objcIncludePrefix + headerName(d.name))))
List(ImportRef(include(d.name)))
case DInterface =>
val ext = d.body.asInstanceOf[Interface].ext
if (ext.cpp && !ext.objc) {
Expand All @@ -72,7 +72,20 @@ class ObjcMarshal(spec: Spec) extends Marshal(spec) {
case p: MParam => List()
}

def headerName(ident: String): String = idObjc.ty(ident) + "." + spec.objcHeaderExt
def headerName(ident: String) = idObjc.ty(ident) + "." + spec.objcHeaderExt
def include(ident: String) = q(spec.objcIncludePrefix + headerName(ident))

def isPointer(td: TypeDecl) = td.body match {
case i: Interface => true
case r: Record => true
case e: Enum => false
}

def boxedTypename(td: TypeDecl) = td.body match {
case i: Interface => typename(td.ident, i)
case r: Record => typename(td.ident, r)
case e: Enum => "NSNumber"
}

// Return value: (Type_Name, Is_Class_Or_Not)
def toObjcType(ty: TypeRef): (String, Boolean) = toObjcType(ty.resolved, false)
Expand Down
12 changes: 10 additions & 2 deletions src/source/ObjcppMarshal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class ObjcppMarshal(spec: Spec) extends Marshal(spec) {
case DEnum =>
List(ImportRef(q(spec.objcBaseLibIncludePrefix + "DJIMarshal+Private.h")))
case DInterface =>
List(ImportRef(q(spec.objcppIncludePrefix + privateHeaderName(d.name))))
List(ImportRef(include(m)))
case DRecord =>
val r = d.body.asInstanceOf[Record]
val objcName = d.name + (if (r.ext.objc) "_base" else "")
Expand All @@ -47,12 +47,20 @@ class ObjcppMarshal(spec: Spec) extends Marshal(spec) {
case p: MParam => List()
}

def include(m: Meta) = m match {
case d: MDef => d.defType match {
case DEnum => q(spec.objcBaseLibIncludePrefix + "DJIMarshal+Private.h")
case _ => q(spec.objcppIncludePrefix + privateHeaderName(d.name))
}
case _ => throw new AssertionError("not applicable")
}

def helperClass(name: String) = idCpp.ty(name)
private def helperClass(tm: MExpr): String = helperName(tm) + helperTemplates(tm)

def privateHeaderName(ident: String): String = idObjc.ty(ident) + "+Private." + spec.objcHeaderExt

private def helperName(tm: MExpr): String = tm.base match {
def helperName(tm: MExpr): String = tm.base match {
case d: MDef => d.defType match {
case DEnum => withNs(Some("djinni"), s"Enum<${cppMarshal.fqTypename(tm)}, ${objcMarshal.fqTypename(tm)}>")
case _ => withNs(Some(spec.objcppNamespace), helperClass(d.name))
Expand Down
181 changes: 181 additions & 0 deletions src/source/YamlGenerator.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package djinni

import djinni.ast._
import djinni.ast.Record.DerivingType.DerivingType
import djinni.generatorTools._
import djinni.meta._
import djinni.writer.IndentWriter
import java.util.{Map => JMap}
import scala.collection.JavaConversions._
import scala.collection.mutable

class YamlGenerator(spec: Spec) extends Generator(spec) {

val cppMarshal = new CppMarshal(spec)
val objcMarshal = new ObjcMarshal(spec)
val objcppMarshal = new ObjcppMarshal(spec)
val javaMarshal = new JavaMarshal(spec)
val jniMarshal = new JNIMarshal(spec)

case class QuotedString(str: String) // For anything that migt require escaping

private def writeYamlFile(name: String, origin: String, f: IndentWriter => Unit): Unit = {
createFile(spec.yamlOutFolder.get, name, out => new IndentWriter(out, " "), w => {
w.wl("# AUTOGENERATED FILE - DO NOT MODIFY!")
w.wl("# This file generated by Djinni from " + origin)
f(w)
})
}

private def writeYamlFile(tds: Seq[InternTypeDecl]): Unit = {
val origins = tds.map(_.origin).distinct.sorted.mkString(", ")
writeYamlFile(spec.yamlOutFile.get, origins, w => {
// Writing with SnakeYAML creates loads of cluttering and unnecessary tags, so write manually.
// We're not doing anything complicated anyway and it's good to have human readable output.
for(td <- tds) {
w.wl("---")
write(w, td)
}
})
}

private def writeYamlFile(ident: String, origin: String, td: InternTypeDecl): Unit =
writeYamlFile(spec.yamlPrefix + ident + ".yaml", origin, w => {
write(w, td)
})

private def write(w: IndentWriter, td: TypeDecl) {
write(w, preamble(td))
w.wl("cpp:").nested { write(w, cpp(td)) }
w.wl("objc:").nested { write(w, objc(td)) }
w.wl("objcpp:").nested { write(w, objcpp(td)) }
w.wl("java:").nested { write(w, java(td)) }
w.wl("jni:").nested { write(w, jni(td)) }
}

private def write(w: IndentWriter, m: Map[String, Any]) {
for((k, v) <- m) {
w.w(k + ": ")
v match {
case s: String => write(w, s)
case s: QuotedString => write(w, s)
case m: Map[_, _] => w.wl.nested { write(w, m.asInstanceOf[Map[String, Any]]) }
case s: Seq[_] => write(w, s)
case b: Boolean => write(w, b)
case _ => throw new AssertionError("unexpected map value")
}
}
}

private def write(w: IndentWriter, s: Seq[Any]) {
// The only arrays we have are small enough to use flow notation
w.wl(s.mkString("[", ",", "]"))
}

private def write(w: IndentWriter, b: Boolean) {
w.wl(if(b) "true" else "false")
}

private def write(w: IndentWriter, s: String) {
if(s.isEmpty) w.wl(q("")) else w.wl(s)
}

private def write(w: IndentWriter, s: QuotedString) {
if(s.str.isEmpty) w.wl(q("")) else w.wl("'" + s.str.replaceAllLiterally("'", "''") + "'")
}

private def preamble(td: TypeDecl) = Map[String, Any](
"name" -> (spec.yamlPrefix + td.ident.name),
"typedef" -> QuotedString(typeDef(td)),
"params" -> td.params.collect { case p: TypeParam => p.ident.name },
"prefix" -> spec.yamlPrefix
)

private def typeDef(td: TypeDecl) = {
def ext(e: Ext): String = (if(e.cpp) " +c" else "") + (if(e.objc) " +o" else "") + (if(e.java) " +j" else "")
def deriving(r: Record) = {
if(r.derivingTypes.isEmpty) {
""
} else {
r.derivingTypes.collect {
case Record.DerivingType.Eq => "eq"
case Record.DerivingType.Ord => "ord"
}.mkString(" deriving(", ", ", ")")
}
}
td.body match {
case i: Interface => "interface" + ext(i.ext)
case r: Record => "record" + ext(r.ext) + deriving(r)
case e: Enum => "enum"
}
}

private def cpp(td: TypeDecl) = Map[String, Any](
"typename" -> QuotedString(cppMarshal.fqTypename(td.ident, td.body)),
"header" -> QuotedString(cppMarshal.include(td.ident)),
"byValue" -> cppMarshal.byValue(td)
)

private def objc(td: TypeDecl) = Map[String, Any](
"typename" -> QuotedString(objcMarshal.fqTypename(td.ident, td.body)),
"header" -> QuotedString(objcMarshal.include(td.ident)),
"boxed" -> QuotedString(objcMarshal.boxedTypename(td)),
"pointer" -> objcMarshal.isPointer(td),
"hash" -> QuotedString("%s.hash")
)

private def objcpp(td: TypeDecl) = Map[String, Any](
"translator" -> QuotedString(objcppMarshal.helperName(mexpr(td))),
"header" -> QuotedString(objcppMarshal.include(meta(td)))
)

private def java(td: TypeDecl) = Map[String, Any](
"typename" -> QuotedString(javaMarshal.fqTypename(td.ident, td.body)),
"boxed" -> QuotedString(javaMarshal.fqTypename(td.ident, td.body)),
"reference" -> javaMarshal.isReference(td),
"generic" -> true,
"hash" -> QuotedString("%s.hashCode()")
)

private def jni(td: TypeDecl) = Map[String, Any](
"translator" -> QuotedString(jniMarshal.helperName(mexpr(td))),
"header" -> QuotedString(jniMarshal.include(td.ident)),
"typename" -> jniMarshal.fqParamType(mexpr(td)),
"typeSignature" -> QuotedString(jniMarshal.fqTypename(td.ident, td.body))
)

// 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())

private def meta(td: TypeDecl) = {
val defType = td.body match {
case i: Interface => DInterface
case r: Record => DRecord
case e: Enum => DEnum
}
MDef(td.ident, 0, defType, td.body)
}

override def generate(idl: Seq[TypeDecl]) {
val internOnly = idl.collect { case itd: InternTypeDecl => itd }.sortWith(_.ident.name < _.ident.name)
if(spec.yamlOutFile.isDefined) {
writeYamlFile(internOnly)
} else {
for(td <- internOnly) {
writeYamlFile(td.ident, td.origin, td)
}
}
}

override def generateEnum(origin: String, ident: Ident, doc: Doc, e: Enum) {
// unused
}

override def generateInterface(origin: String, ident: Ident, doc: Doc, typeParams: Seq[TypeParam], i: Interface) {
// unused
}

override def generateRecord(origin: String, ident: Ident, doc: Doc, params: Seq[TypeParam], r: Record) {
// unused
}
}
Loading

0 comments on commit e181adc

Please sign in to comment.