Skip to content

Generate constructor helper #160

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 16, 2018
Merged
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
24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,35 @@ object vector {
def y: native.CFloat = !p._2
def y_=(value: native.CFloat): Unit = !p._2 = value
}
def struct_point()(implicit z: native.Zone): native.Ptr[struct_point] = native.alloc[struct_point]

implicit class struct_vector_ops(val p: native.Ptr[struct_vector]) extends AnyVal {
def a: native.Ptr[struct_point] = p._1
def a_=(value: native.Ptr[struct_point]): Unit = !p._1 = !value
def b: native.Ptr[struct_point] = p._2
def b_=(value: native.Ptr[struct_point]): Unit = !p._2 = !value
}
def struct_vector()(implicit z: native.Zone): native.Ptr[struct_vector] = native.alloc[struct_vector]
}

object struct_point {
import implicits._
def apply()(implicit z: native.Zone): native.Ptr[struct_point] = native.alloc[struct_point]
def apply(x: native.CFloat, y: native.CFloat)(implicit z: native.Zone): native.Ptr[struct_point] = {
val ptr = native.alloc[struct_point]
ptr.x = x
ptr.y = y
ptr
}
}

object struct_vector {
import implicits._
def apply()(implicit z: native.Zone): native.Ptr[struct_vector] = native.alloc[struct_vector]
def apply(a: native.Ptr[struct_point], b: native.Ptr[struct_point])(implicit z: native.Zone): native.Ptr[struct_vector] = {
val ptr = native.alloc[struct_vector]
ptr.a = a
ptr.b = b
ptr
}
}
}
```
Expand Down
7 changes: 7 additions & 0 deletions bindgen/ir/IR.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,13 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &s, const IR &ir) {
s << "\n object implicits {\n" << ir.getHelperMethods() << " }\n";
}

for (const auto &str : ir.structs) {
visitedTypes.clear();
if (ir.shouldOutput(str, visitedTypes) && str->hasHelperMethods()) {
s << "\n" << str->getConstructorHelper(ir.locationManager);
}
}

s << "}\n\n";

return s;
Expand Down
99 changes: 66 additions & 33 deletions bindgen/ir/Struct.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,6 @@ Struct::generateHelperClass(const LocationManager &locationManager) const {
}
s << " }\n";

/* makes struct instantiation easier */
s << " def "
<< type + "()(implicit z: native.Zone): native.Ptr[" + type + "]"
<< " = native.alloc[" + type + "]\n";

return s.str();
}

Expand Down Expand Up @@ -132,7 +127,8 @@ std::string Struct::generateSetterForStructRepresentation(
unsigned fieldIndex, const LocationManager &locationManager) const {
std::shared_ptr<Field> field = fields[fieldIndex];
std::string setter = handleReservedWords(field->getName(), "_=");
std::string parameterType = field->getType()->str(locationManager);
std::string parameterType =
wrapArrayOrRecordInPointer(field->getType())->str(locationManager);
std::string value = "value";
std::vector<std::shared_ptr<const Struct>> structTypesThatShouldBeReplaced =
shouldFieldBreakCycle(field);
Expand All @@ -141,9 +137,7 @@ std::string Struct::generateSetterForStructRepresentation(
std::shared_ptr<const Type> typeReplacement = getTypeReplacement(
field->getType(), structTypesThatShouldBeReplaced);
value = value + ".cast[" + typeReplacement->str(locationManager) + "]";
} else if (isAliasForType<ArrayType>(field->getType().get()) ||
isAliasForType<Struct>(field->getType().get())) {
parameterType = "native.Ptr[" + parameterType + "]";
} else if (isArrayOrRecord(field->getType())) {
value = "!" + value;
}
std::stringstream s;
Expand All @@ -156,17 +150,16 @@ std::string Struct::generateGetterForStructRepresentation(
unsigned fieldIndex, const LocationManager &locationManager) const {
std::shared_ptr<Field> field = fields[fieldIndex];
std::string getter = handleReservedWords(field->getName());
std::string returnType = field->getType()->str(locationManager);
std::string returnType =
wrapArrayOrRecordInPointer(field->getType())->str(locationManager);
std::string methodBody = "p._" + std::to_string(fieldIndex + 1);
if (isAliasForType<ArrayType>(field->getType().get()) ||
isAliasForType<Struct>(field->getType().get())) {
returnType = "native.Ptr[" + returnType + "]";
} else if (!shouldFieldBreakCycle(field).empty()) {
/* field type is changed to avoid cyclic types in generated code */
methodBody = "(!" + methodBody + ").cast[" +
field->getType()->str(locationManager) + "]";
} else {
if (!isArrayOrRecord(field->getType())) {
methodBody = "!" + methodBody;
if (!shouldFieldBreakCycle(field).empty()) {
/* field type is changed to avoid cyclic types in generated code */
methodBody = "(" + methodBody + ").cast[" +
field->getType()->str(locationManager) + "]";
}
}
std::stringstream s;
s << " def " << getter << ": " << returnType << " = " << methodBody
Expand All @@ -182,7 +175,8 @@ std::string Struct::generateSetterForArrayRepresentation(
unsigned int fieldIndex, const LocationManager &locationManager) const {
std::shared_ptr<Field> field = fields[fieldIndex];
std::string setter = handleReservedWords(field->getName(), "_=");
std::string parameterType = field->getType()->str(locationManager);
std::string parameterType =
wrapArrayOrRecordInPointer(field->getType())->str(locationManager);
std::string value = "value";
std::string castedField = "p._1";

Expand All @@ -201,9 +195,7 @@ std::string Struct::generateSetterForArrayRepresentation(
std::shared_ptr<const Type> typeReplacement = getTypeReplacement(
field->getType(), structTypesThatShouldBeReplaced);
value = value + ".cast[" + typeReplacement->str(locationManager) + "]";
} else if (isAliasForType<ArrayType>(field->getType().get()) ||
isAliasForType<Struct>(field->getType().get())) {
parameterType = pointerToFieldType.str(locationManager);
} else if (isArrayOrRecord(field->getType())) {
value = "!" + value;
}
std::stringstream s;
Expand All @@ -217,7 +209,8 @@ std::string Struct::generateGetterForArrayRepresentation(
unsigned fieldIndex, const LocationManager &locationManager) const {
std::shared_ptr<Field> field = fields[fieldIndex];
std::string getter = handleReservedWords(field->getName());
std::string returnType;
std::string returnType =
wrapArrayOrRecordInPointer(field->getType())->str(locationManager);
std::string methodBody;

PointerType pointerToFieldType = PointerType(field->getType());
Expand All @@ -230,17 +223,13 @@ std::string Struct::generateGetterForArrayRepresentation(
methodBody =
methodBody + ".cast[" + pointerToFieldType.str(locationManager) + "]";

if (isAliasForType<ArrayType>(field->getType().get()) ||
isAliasForType<Struct>(field->getType().get())) {
returnType = pointerToFieldType.str(locationManager);
} else if (!shouldFieldBreakCycle(field).empty()) {
/* field type is changed to avoid cyclic types in generated code */
methodBody = "(!" + methodBody + ").cast[" +
field->getType()->str(locationManager) + "]";
returnType = field->getType()->str(locationManager);
} else {
if (!isArrayOrRecord(field->getType())) {
methodBody = "!" + methodBody;
returnType = field->getType()->str(locationManager);
if (!shouldFieldBreakCycle(field).empty()) {
/* field type is changed to avoid cyclic types in generated code */
methodBody = "(" + methodBody + ").cast[" +
field->getType()->str(locationManager) + "]";
}
}
std::stringstream s;
s << " def " << getter << ": " << returnType << " = " << methodBody
Expand Down Expand Up @@ -352,3 +341,47 @@ bool Struct::hasBiggestName(const CycleNode &node,
}
return false;
}

std::string
Struct::getConstructorHelper(const LocationManager &locationManager) const {
std::stringstream s;
std::string type = replaceChar(getTypeName(), " ", "_");
s << " object " << type << " {\n"
<< " import implicits._\n";

/* constructor with no parameters */
s << " def apply()(implicit z: native.Zone): native.Ptr[" + type + "]"
<< " = native.alloc[" + type + "]\n";

/* constructor that initializes all fields */
s << " def apply(";
std::string sep = "";
for (const auto &field : fields) {
s << sep << handleReservedWords(field->getName()) << ": "
<< wrapArrayOrRecordInPointer(field->getType())->str(locationManager);
sep = ", ";
}
s << ")(implicit z: native.Zone): native.Ptr[" << type << "] = {\n"
<< " val ptr = native.alloc[" << type << "]\n";
for (const auto &field : fields) {
std::string name = handleReservedWords(field->getName());
s << " ptr." << name << " = " << name << "\n";
}
s << " ptr\n"
<< " }\n"
<< " }\n";
return s.str();
}

bool Struct::isArrayOrRecord(std::shared_ptr<const Type> type) const {
return isAliasForType<ArrayType>(type.get()) ||
isAliasForType<Struct>(type.get());
}

std::shared_ptr<const Type>
Struct::wrapArrayOrRecordInPointer(std::shared_ptr<const Type> type) const {
if (isArrayOrRecord(type)) {
return std::make_shared<PointerType>(type);
}
return type;
}
11 changes: 11 additions & 0 deletions bindgen/ir/Struct.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ class Struct : public Record {
const std::shared_ptr<const Struct> &startStruct, CycleNode &cycleNode,
std::vector<std::shared_ptr<const Type>> &visitedTypes) const override;

std::string
getConstructorHelper(const LocationManager &locationManager) const;

private:
/** type size is needed if number of fields is bigger than 22 */
uint64_t typeSize;
Expand Down Expand Up @@ -121,6 +124,14 @@ class Struct : public Record {
*/
bool hasBiggestName(const CycleNode &node,
std::vector<std::string> namesInCycle) const;

bool isArrayOrRecord(std::shared_ptr<const Type> type) const;

/**
* @return pointer to given type if given type is record or array
*/
std::shared_ptr<const Type>
wrapArrayOrRecordInPointer(std::shared_ptr<const Type> type) const;
};

#endif // SCALA_NATIVE_BINDGEN_STRUCT_H
4 changes: 4 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ lazy val docs = project("docs")
.link("vector")
.packageName("org.example")
},
nativeBindgenPath := {
Some(
(ThisBuild / baseDirectory).value / "bindgen/target/scala-native-bindgen")
},
Test / nativeBindgen / target := (Test / scalaSource).value / "org/example",
nativeLinkStubs := true,
compileTask("vector", Test / resourceDirectory),
Expand Down
51 changes: 28 additions & 23 deletions docs/src/paradox/using-generated-bindings.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +4,52 @@ Consider following header file:

@@snip [vector.h] (../test/resources/vector.h)

Bindgen will generate type aliases for the structs, binding for function `add`
and helper functions that make usage of structs easier.
Bindgen will output
* type aliases for the structs
* binding for function `cosine`
* helper functions that make usage of structs easier

@@snip [vector.h] (../test/scala/org/example/vector.scala)

Let's write code that creates two vectors, adds them and prints resulting
vector.
Let's write code that creates two line segments and prints angel between them.

First we need to create points for vectors. We will use `native.Zone` to
First we need to create points. We will use `native.Zone` to
allocate struct (more information on memory management can be found
here: [Scala Native memory management]).

Helper object `mylibHelpers` contains function for struct allocation.
To import it use `import mylibHelpers._`
Generated bindings contain helper functions that make struct allocation easier.
To import them use `import org.example.vector._`

Let's create points for first vector:
Let's create two points and the first line segment:

@@snip [step-1] (../test/scala/org/scalanative/bindgen/docs/VectorSpec.scala) { #step-1 }

Now we want to set fields of created points. Scala Native provides access
to fields by using `_N` methods where `N` is index of a field
(see [Scala Native memory layout types]).
There is no need to create points manually, just call `struct_lineSegment`
constructor and set point coordinates using fields setters.

Bindgen generates implicit helper classes that wrap calls to `_N` in functions
with meaningful names. We already imported helper class, so we can use the
functions:
Scala Native allows us to access a field by using `_N` method where `N` is index of a field
(see [Scala Native memory layout types]) but it is not convenient because we have to
match indices with fields names.

Bindgen provides implicit helper classes that wrap calls to `_N` in functions
with meaningful names. To import these classes add `import org.example.vector.implicits._`
to your code:

@@snip [step-2] (../test/scala/org/scalanative/bindgen/docs/VectorSpec.scala) { #step-2 }

Lets create first vector. Note that `struct_vector` contains
fields of type `struct_point` but setters accept variables of type
`native.Ptr[struct_point]`. It helps to avoid Scala Native limitation that
does not allow passing structs and arrays by value
(see @github[scala-native/scala-native#555](scala-native/scala-native#555)).
@@@ note

Note that `struct_lineSegment` contains fields of value type `struct_point`
but setters accept variables of type `native.Ptr[struct_point]`.
It helps to avoid Scala Native limitation that does not allow passing structs
and arrays by value (see @github[scala-native/scala-native#555](scala-native/scala-native#555)).

@@@

@@snip [step-3] (../test/scala/org/scalanative/bindgen/docs/VectorSpec.scala) { #step-3 }
Now we can calculate angel between line segments:

Repeat these steps to create second vector. Once both vectors are created we can
call `add` function and print the result:
@@snip [step-3] (../test/scala/org/scalanative/bindgen/docs/VectorSpec.scala) { #step-2 }

@@snip [step-4] (../test/scala/org/scalanative/bindgen/docs/VectorSpec.scala) { #step-4 }

[Scala Native memory management]: http://www.scala-native.org/en/latest/user/interop.html#memory-management
[Scala Native memory layout types]: http://www.scala-native.org/en/latest/user/interop.html#memory-layout-types
23 changes: 10 additions & 13 deletions docs/src/test/resources/vector.c
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
#include "vector.h"
#include <math.h>
#include <stdlib.h>

struct vector *add(struct vector *v1, struct vector *v2) {
struct vector *out = malloc(sizeof(struct vector));

if (!out)
return NULL;

out->a.x = v1->a.x + v2->a.x;
out->a.y = v1->a.y + v2->a.y;
out->b.x = v1->b.x + v2->b.x;
out->b.y = v1->b.y + v2->b.y;

return out;
}
float cosine(struct lineSegment *v1, struct lineSegment *v2) {
float v1x = fabsf(v1->b.x - v1->a.x);
float v1y = fabsf(v1->b.y - v1->a.y);
float v1Length = sqrtf(v1x * v1x + v1y * v1y);
float v2x = fabsf(v2->b.x - v2->a.x);
float v2y = fabsf(v2->b.y - v2->a.y);
float v2Length = sqrtf(v2x * v2x + v2y * v2y);
return (v1x * v2x + v1y * v2y) / (v1Length * v2Length);
}
4 changes: 2 additions & 2 deletions docs/src/test/resources/vector.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ struct point {
float y;
};

struct vector {
struct lineSegment {
struct point a;
struct point b;
};

struct vector *add(struct vector *v1, struct vector *v2);
float cosine(struct lineSegment *v1, struct lineSegment *v2);
Loading