Skip to content
This repository has been archived by the owner on Mar 26, 2020. It is now read-only.

Adding the ability to declare properties in interfaces. #287

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/source/CppGenerator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,9 @@ class CppGenerator(spec: Spec) extends Generator(spec) {
i.consts.map(c => {
refs.find(c.ty, true)
})
i.properties.map(p => {
refs.find(p.ty, true)
})

val self = marshal.typename(ident, i)
val methodNamesInScope = i.methods.map(m => idCpp.method(m.ident))
Expand All @@ -297,6 +300,16 @@ class CppGenerator(spec: Spec) extends Generator(spec) {
w.wl(s"virtual $ret ${idCpp.method(m.ident)}${params.mkString("(", ", ", ")")}$constFlag = 0;")
}
}
// Properties
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice if this shared more code with the method-generating codepaths, since that will help to keep the code simple and make it likely to all be updated in sync with future changes. You should be able to create method definitions from the properties, then use the normal method-generation code.

One option to consider (though it might be too aggressive) would be to implement property generation in most languages as a transformation on the AST, by generating a new AST with methods in place of properties, then running the normal generator on them. That could be a suitable approach for all generators which don't have special support for properties (i.e. ObjC with its @Property declaration).

for (p <- i.properties) {
w.wl
writeDoc(w, p.doc)
val ret = marshal.fieldType(p.ty)
w.wl(s"virtual $ret get_${idCpp.method(p.ident)}() = 0;")
if(!p.readOnly) {
w.wl(s"virtual void set_${idCpp.method(p.ident)}($ret new_${idCpp.method(p.ident)}) = 0;")
}
}
}
})

Expand Down
24 changes: 24 additions & 0 deletions src/source/JNIGenerator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ class JNIGenerator(spec: Spec) extends Generator(spec) {
i.consts.foreach(c => {
refs.find(c.ty)
})
i.properties.foreach(p => {
refs.find(p.ty)
})

val jniSelf = jniMarshal.helperClass(ident)
val cppSelf = cppMarshal.fqTypename(ident, i) + cppTypeArgs(typeParams)
Expand Down Expand Up @@ -366,6 +369,27 @@ class JNIGenerator(spec: Spec) extends Generator(spec) {
m.ret.fold()(r => w.wl(s"return ::djinni::release(${jniMarshal.fromCpp(r, "r")});"))
})
}
for(p <- i.properties) {
nativeHook(s"native_get${idJava.method(p.ident).capitalize}", false, Nil, Option(p.ty), {
w.wl(s"const auto& ref = ::djinni::objectFromHandleAddress<$cppSelf>(nativeRef);")
val methodName = "get_" + idCpp.method(p.ident)
val ret = "auto r = "
val call = s"ref->$methodName"
w.wl(s"${ret}${call}();")
w.wl(s"return ::djinni::release(${jniMarshal.fromCpp(p.ty, "r")});")
})
if(!p.readOnly) {
val setterParam = Iterable(Field(p.ident, p.ty, Doc(Nil)));
nativeHook(s"native_set${idJava.method(p.ident).capitalize}", false, setterParam, Option(null), {
w.wl(s"const auto& ref = ::djinni::objectFromHandleAddress<$cppSelf>(nativeRef);")
val methodName = "set_" + idCpp.method(p.ident)
val call = s"ref->$methodName"
val params = jniMarshal.toCpp(p.ty, "j_" + idJava.local(p.ident))
w.wl(s"${call}(${params});")
w.wl(";")
})
}
}
}
}

Expand Down
37 changes: 37 additions & 0 deletions src/source/JavaGenerator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ class JavaGenerator(spec: Spec) extends Generator(spec) {
i.consts.map(c => {
refs.find(c.ty)
})
i.properties.map(p => {
refs.find(p.ty)
})

if (i.ext.cpp) {
refs.java.add("java.util.concurrent.atomic.AtomicBoolean")
}
Expand Down Expand Up @@ -167,6 +171,17 @@ class JavaGenerator(spec: Spec) extends Generator(spec) {
marshal.nullityAnnotation(m.ret).foreach(w.wl)
w.wl("public static native "+ ret + " " + idJava.method(m.ident) + params.mkString("(", ", ", ")") + ";")
}
for (p <- i.properties) {
skipFirst { w.wl }
writeDoc(w, p.doc)
val ret = marshal.fqTypename(p.ty)
val meth = idJava.method(p.ident).capitalize;
w.wl("public abstract " + ret + " get" + meth + "();")
if(!p.readOnly) {
w.wl("public abstract void set" + meth + "(" + ret + " new" + meth + ");")
}
}

if (i.ext.cpp) {
w.wl
javaAnnotationHeader.foreach(w.wl)
Expand Down Expand Up @@ -202,6 +217,28 @@ class JavaGenerator(spec: Spec) extends Generator(spec) {
}
w.wl(s"private native $ret native_$meth(long _nativeRef${preComma(params)});")
}

for (p <- i.properties) {
val ret = marshal.fieldType(p.ty)
var meth = idJava.method(p.ident).capitalize
w.wl
w.wl(s"@Override")
w.wl(s"public $ret get$meth()$throwException").braced {
w.wl("assert !this.destroyed.get() : \"trying to use a destroyed object\";")
w.wl(s"return native_get$meth(this.nativeRef);")
}
w.wl(s"private native ${ret} native_get${meth}(long _nativeRef);")

if(!p.readOnly) {
w.wl
w.wl(s"@Override")
w.wl(s"public void set$meth(${ret} new${meth})$throwException").braced {
w.wl("assert !this.destroyed.get() : \"trying to use a destroyed object\";")
w.wl(s"native_set$meth(this.nativeRef, new${meth});")
}
w.wl(s"private native void native_set${meth}(long _nativeRef, ${ret} new${meth});")
}
}
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/source/ObjcGenerator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,13 @@ class ObjcGenerator(spec: Spec) extends BaseObjcGenerator(spec) {
writeObjcFuncDecl(m, w)
w.wl(";")
}
for(p <- i.properties) {
w.wl
writeDoc(w, p.doc)
val nullability = marshal.nullability(p.ty.resolved).fold("")(", " + _)
val readonly = if(p.readOnly) ", readonly" else ""
w.wl(s"@property (nonatomic${nullability}${readonly}) ${marshal.fqFieldType(p.ty)} ${idObjc.field(p.ident)};")
}
for (c <- i.consts if !marshal.canBeConstVariable(c)) {
w.wl
writeDoc(w, c.doc)
Expand Down
20 changes: 20 additions & 0 deletions src/source/ObjcppGenerator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ class ObjcppGenerator(spec: Spec) extends BaseObjcGenerator(spec) {
i.consts.map(c => {
refs.find(c.ty)
})
i.properties.map(p => {
refs.find(p.ty)
})

val self = objcMarshal.typename(ident, i)
val cppSelf = cppMarshal.fqTypename(ident, i)
Expand Down Expand Up @@ -197,6 +200,23 @@ class ObjcppGenerator(spec: Spec) extends BaseObjcGenerator(spec) {
}
}

for (p <- i.properties) {
w.wl
w.wl(s"- (${marshal.fqFieldType(p.ty)})${idObjc.method(p.ident)}")
w.braced {
val call = "_cppRefHandle.get()->get_" + idCpp.method(p.ident) + "();"
w.wl(s"auto objcpp_result_ = ${call}")
w.wl(s"return ${objcppMarshal.fromCpp(p.ty, "objcpp_result_")};")
}
if(!p.readOnly) {
w.wl(s"- (void)set${idObjc.method(p.ident).capitalize}:(${marshal.fqFieldType(p.ty)})${idCpp.method(p.ident)}")
w.braced {
val call = s"_cppRefHandle.get()->set_${idCpp.method(p.ident)}(${objcppMarshal.toCpp(p.ty, idCpp.method(p.ident))})";
w.wl(s"${call};")
}
}
}

if (i.consts.nonEmpty) {
w.wl
generateObjcConstants(w, i.consts, self, ObjcConstantType.ConstMethod)
Expand Down
3 changes: 2 additions & 1 deletion src/source/ast.scala
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,10 @@ object Record {
}
}

case class Interface(ext: Ext, methods: Seq[Interface.Method], consts: Seq[Const]) extends TypeDef
case class Interface(ext: Ext, methods: Seq[Interface.Method], consts: Seq[Const], properties: Seq[Interface.Property]) extends TypeDef
object Interface {
case class Method(ident: Ident, params: Seq[Field], ret: Option[TypeRef], doc: Doc, static: Boolean, const: Boolean)
case class Property(ident: Ident, ty: TypeRef, doc: Doc, readOnly: Boolean)
}

case class Field(ident: Ident, ty: TypeRef, doc: Doc)
21 changes: 16 additions & 5 deletions src/source/parser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@

package djinni

import java.io.{File, InputStreamReader, FileInputStream, Writer}
import java.io.{File, FileInputStream, InputStreamReader, Writer}

import djinni.ast.Interface.Method
import djinni.ast.Interface.{Method, Property}
import djinni.ast.Record.DerivingType.DerivingType
import djinni.syntax._
import djinni.ast._
import java.util.{Map => JMap}

import org.yaml.snakeyaml.Yaml

import scala.collection.JavaConversions._
import scala.collection.mutable
import scala.util.parsing.combinator.RegexParsers
Expand Down Expand Up @@ -119,18 +121,19 @@ private object IdlParser extends RegexParsers {
}

def interfaceHeader = "interface" ~> extInterface
def interface: Parser[Interface] = interfaceHeader ~ bracesList(method | const) ^^ {
def interface: Parser[Interface] = interfaceHeader ~ bracesList(method | const | property) ^^ {
case ext~items => {
val methods = items collect {case m: Method => m}
val consts = items collect {case c: Const => c}
Interface(ext, methods, consts)
val properties = items collect {case p: Property => p}
Interface(ext, methods, consts, properties)
}
}

def externTypeDecl: Parser[TypeDef] = externEnum | externInterface | externRecord
def externEnum: Parser[Enum] = enumHeader ^^ { case _ => Enum(List()) }
def externRecord: Parser[Record] = recordHeader ~ opt(deriving) ^^ { case ext~deriving => Record(ext, List(), List(), deriving.getOrElse(Set[DerivingType]())) }
def externInterface: Parser[Interface] = interfaceHeader ^^ { case ext => Interface(ext, List(), List()) }
def externInterface: Parser[Interface] = interfaceHeader ^^ { case ext => Interface(ext, List(), List(), List()) }

def staticLabel: Parser[Boolean] = ("static ".r | "".r) ^^ {
case "static " => true
Expand All @@ -140,6 +143,10 @@ private object IdlParser extends RegexParsers {
case "const " => true
case "" => false
}
def readOnlyLabel: Parser[Boolean] = ("readonly ".r | "".r) ^^ {
case "readonly " => true
case "" => false
}
def method: Parser[Interface.Method] = doc ~ staticLabel ~ constLabel ~ ident ~ parens(repsepend(field, ",")) ~ opt(ret) ^^ {
case doc~staticLabel~constLabel~ ident~params~ret => Interface.Method(ident, params, ret, doc, staticLabel, constLabel)
}
Expand All @@ -164,6 +171,10 @@ private object IdlParser extends RegexParsers {
case doc~_~ident~_~typeRef~_~value => Const(ident, typeRef, value, doc)
}

def property: Parser[Interface.Property] = doc ~ readOnlyLabel ~ ident ~ ret ^^ {
case doc~readOnlyLabel~ident~ret => Interface.Property(ident, ret, doc, readOnlyLabel)
}

def typeRef: Parser[TypeRef] = typeExpr ^^ TypeRef
def typeExpr: Parser[TypeExpr] = ident ~ typeList(typeExpr) ^^ {
case ident~typeArgs => TypeExpr(ident, typeArgs)
Expand Down
5 changes: 5 additions & 0 deletions src/source/resolver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,11 @@ private def resolveInterface(scope: Scope, i: Interface) {
case _ =>
}
}

for (p <- i.properties) {
dupeChecker.check(p.ident)
resolveRef(scope, p.ty)
}
// Name checking for constants. Type check only possible after resolving record field types.
for (c <- i.consts) {
dupeChecker.check(c.ident)
Expand Down
3 changes: 2 additions & 1 deletion test-suite/djinni/common.djinni
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@import "properties.djinni"
@import "set.djinni"
@import "derivings.djinni"
@import "nested_collection.djinni"
Expand All @@ -14,4 +15,4 @@
@import "single_language_interfaces.djinni"
@import "extended_record.djinni"
@import "varnames.djinni"
@import "relative_paths.djinni"
@import "relative_paths.djinni"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: newline at end of file

9 changes: 9 additions & 0 deletions test-suite/djinni/properties.djinni
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
properties_test_helper = interface +c {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you include a +o +j test case as well?


item: i32;
test_string: string;
test_list: list<i32>;
readonly read_only_bool: bool;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test case should include some methods as well as properties, to test that they can be freely mixed. I'd suggest interspersing them in the file too, rather than grouping them together.

static create_new(): properties_test_helper;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: newline at end of file

31 changes: 31 additions & 0 deletions test-suite/generated-src/cpp/properties_test_helper.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// AUTOGENERATED FILE - DO NOT MODIFY!
// This file generated by Djinni from properties.djinni

#pragma once

#include <cstdint>
#include <memory>
#include <string>
#include <vector>

namespace testsuite {

class PropertiesTestHelper {
public:
virtual ~PropertiesTestHelper() {}

static std::shared_ptr<PropertiesTestHelper> create_new();

virtual int32_t get_item() = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getters should be const methods in C++, unless you think there's an issue with that I'm not considering.

virtual void set_item(int32_t new_item) = 0;

virtual std::string get_test_string() = 0;
virtual void set_test_string(std::string new_test_string) = 0;

virtual std::vector<int32_t> get_test_list() = 0;
virtual void set_test_list(std::vector<int32_t> new_test_list) = 0;

virtual bool get_read_only_bool() = 0;
};

} // namespace testsuite
1 change: 1 addition & 0 deletions test-suite/generated-src/inFileList.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
djinni/all.djinni
djinni/common.djinni
djinni/properties.djinni
djinni/set.djinni
djinni/derivings.djinni
djinni/nested_collection.djinni
Expand Down
Loading