Skip to content
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

Allow explicit enum values #114

Open
wants to merge 10 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
11 changes: 7 additions & 4 deletions lib/unifex/code_generator/base_types/enum.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ defmodule Unifex.CodeGenerator.BaseTypes.Enum do

@impl true
def generate_native_type(ctx) do
~g<#{ctx.type_spec.name |> Atom.to_string() |> Macro.camelize()}>
~g<#{ctx.type_spec.name |> enum_to_string() |> Macro.camelize()}>
end

defmodule NIF do
Expand Down Expand Up @@ -114,7 +114,7 @@ defmodule Unifex.CodeGenerator.BaseTypes.Enum do
enum_name = ctx.type_spec.name

ctx.type_spec.types
|> Enum.map(&Atom.to_string/1)
|> Enum.map(&enum_to_string/1)
|> Enum.map_join(" else ", fn type ->
~g"""
if (strcmp(enum_as_string, "#{type}") == 0) {
Expand All @@ -134,11 +134,11 @@ defmodule Unifex.CodeGenerator.BaseTypes.Enum do
CodeGenerator.code_t()
def do_generate_arg_serialize_if_statements(name, ctx, serializer) do
{last_type, types} = List.pop_at(ctx.type_spec.types, -1)
last_type = Atom.to_string(last_type)
last_type = enum_to_string(last_type)
enum_name = ctx.type_spec.name

types
|> Enum.map(&Atom.to_string/1)
|> Enum.map(&enum_to_string/1)
|> Enum.map(fn type ->
~g"""
if (#{name} == #{String.upcase("#{enum_name}_#{type}")}) {
Expand All @@ -149,4 +149,7 @@ defmodule Unifex.CodeGenerator.BaseTypes.Enum do
|> Enum.concat(["{ #{serializer.(last_type, ctx)} }"])
|> Enum.join(" else ")
end

defp enum_to_string(name) when is_atom(name), do: Atom.to_string(name)
defp enum_to_string({name, _val}) when is_atom(name), do: Atom.to_string(name)
end
5 changes: 4 additions & 1 deletion lib/unifex/code_generators/common.ex
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,10 @@ defmodule Unifex.CodeGenerators.Common do
@spec generate_enum_native_definition(Specs.enum_t(), map) :: CodeGenerator.code_t()
def generate_enum_native_definition({enum_name, enum_types}, _ctx) do
enum_types =
Enum.map_join(enum_types, ",\n", fn type -> String.upcase("#{enum_name}_#{type}") end)
Enum.map_join(enum_types, ",\n", fn
{type, val} -> String.upcase("#{enum_name}_#{type} = #{val}")
type -> String.upcase("#{enum_name}_#{type}")
end)

enum_name =
enum_name
Expand Down
9 changes: 9 additions & 0 deletions lib/unifex/specs_dsl.ex
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,10 @@ defmodule Unifex.Specs.DSL do

type my_enum :: :option_one | :option_two | :option_three | ...

Enum constants can be given an explicit value with `enum_value`

type my_explicit_enum :: enum_value(:option_one, 1) | :option_two | :option_three | ...
Comment on lines +238 to +240
Copy link
Member

Choose a reason for hiding this comment

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

Please mention, that second argument passed to enum_value/2 can be used only in the code in C/C++ while Elixir API still supports only atoms


Struct or enums specified in such way can be used in like any other supported type, E.g.

spec my_function(in_enum :: my_enum) :: {:ok :: label, out_struct :: my_struct}
Expand Down Expand Up @@ -265,6 +269,11 @@ defmodule Unifex.Specs.DSL do
[type_name]
end

defp parse_enum_types({:enum_value, _meta, [type_name, value]})
when is_atom(type_name) and is_integer(value) do
[{type_name, value}]
end

defp parse_enum_types({:|, _meta, [first_arg, second_arg]}) do
parse_enum_types(first_arg) ++ parse_enum_types(second_arg)
end
Expand Down
75 changes: 75 additions & 0 deletions test/fixtures/nif_ref_generated/nif/example.c
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,35 @@ UNIFEX_TERM test_my_enum_result_ok(UnifexEnv *env, MyEnum out_enum) {
});
}

UNIFEX_TERM test_my_explicit_enum_result_ok(UnifexEnv *env,
MyExplicitEnum out_enum) {
return ({
const ERL_NIF_TERM terms[] = {enif_make_atom(env, "ok"), ({
ERL_NIF_TERM res;
if (out_enum == MY_EXPLICIT_ENUM_A) {
const char *enum_as_string = "a";
res = enif_make_atom(env, enum_as_string);

} else if (out_enum == MY_EXPLICIT_ENUM_B) {
const char *enum_as_string = "b";
res = enif_make_atom(env, enum_as_string);

} else if (out_enum == MY_EXPLICIT_ENUM_C) {
const char *enum_as_string = "c";
res = enif_make_atom(env, enum_as_string);

} else {
const char *enum_as_string = "d";
res = enif_make_atom(env, enum_as_string);
}
res;
})

};
enif_make_tuple_from_array(env, terms, 2);
});
}

UNIFEX_TERM test_nil_bugged_result_nil(UnifexEnv *env) {
return enif_make_atom(env, "nil");
}
Expand Down Expand Up @@ -1253,6 +1282,51 @@ static ERL_NIF_TERM export_test_my_enum(ErlNifEnv *env, int argc,
return result;
}

static ERL_NIF_TERM export_test_my_explicit_enum(ErlNifEnv *env, int argc,
const ERL_NIF_TERM argv[]) {
UNIFEX_MAYBE_UNUSED(argc);
UNIFEX_MAYBE_UNUSED(argv);
ERL_NIF_TERM result;
UnifexEnv *unifex_env = env;
MyExplicitEnum in_enum;

if (!({
int res = 0;
char *enum_as_string = NULL;

if (unifex_alloc_and_get_atom(env, argv[0], &enum_as_string)) {
if (strcmp(enum_as_string, "a") == 0) {
in_enum = MY_EXPLICIT_ENUM_A;
res = 1;
} else if (strcmp(enum_as_string, "b") == 0) {
in_enum = MY_EXPLICIT_ENUM_B;
res = 1;
} else if (strcmp(enum_as_string, "c") == 0) {
in_enum = MY_EXPLICIT_ENUM_C;
res = 1;
} else if (strcmp(enum_as_string, "d") == 0) {
in_enum = MY_EXPLICIT_ENUM_D;
res = 1;
}

if (enum_as_string != NULL) {
unifex_free((void *)enum_as_string);
}
}

res;
})) {
result = unifex_raise_args_error(env, "in_enum", ":my_explicit_enum");
goto exit_export_test_my_explicit_enum;
}

result = test_my_explicit_enum(unifex_env, in_enum);
goto exit_export_test_my_explicit_enum;
exit_export_test_my_explicit_enum:

return result;
}

static ERL_NIF_TERM export_test_nil_bugged(ErlNifEnv *env, int argc,
const ERL_NIF_TERM argv[]) {
UNIFEX_MAYBE_UNUSED(argc);
Expand Down Expand Up @@ -1305,6 +1379,7 @@ static ErlNifFunc nif_funcs[] = {
{"unifex_test_nested_struct", 1, export_test_nested_struct, 0},
{"unifex_test_list_of_structs", 1, export_test_list_of_structs, 0},
{"unifex_test_my_enum", 1, export_test_my_enum, 0},
{"unifex_test_my_explicit_enum", 1, export_test_my_explicit_enum, 0},
{"unifex_test_nil_bugged", 0, export_test_nil_bugged, 0},
{"unifex_test_nil_tuple_bugged", 1, export_test_nil_tuple_bugged, 0}};

Expand Down
75 changes: 75 additions & 0 deletions test/fixtures/nif_ref_generated/nif/example.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,35 @@ UNIFEX_TERM test_my_enum_result_ok(UnifexEnv *env, MyEnum out_enum) {
});
}

UNIFEX_TERM test_my_explicit_enum_result_ok(UnifexEnv *env,
MyExplicitEnum out_enum) {
return ({
const ERL_NIF_TERM terms[] = {enif_make_atom(env, "ok"), ({
ERL_NIF_TERM res;
if (out_enum == MY_EXPLICIT_ENUM_A) {
const char *enum_as_string = "a";
res = enif_make_atom(env, enum_as_string);

} else if (out_enum == MY_EXPLICIT_ENUM_B) {
const char *enum_as_string = "b";
res = enif_make_atom(env, enum_as_string);

} else if (out_enum == MY_EXPLICIT_ENUM_C) {
const char *enum_as_string = "c";
res = enif_make_atom(env, enum_as_string);

} else {
const char *enum_as_string = "d";
res = enif_make_atom(env, enum_as_string);
}
res;
})

};
enif_make_tuple_from_array(env, terms, 2);
});
}

UNIFEX_TERM test_nil_bugged_result_nil(UnifexEnv *env) {
return enif_make_atom(env, "nil");
}
Expand Down Expand Up @@ -1253,6 +1282,51 @@ static ERL_NIF_TERM export_test_my_enum(ErlNifEnv *env, int argc,
return result;
}

static ERL_NIF_TERM export_test_my_explicit_enum(ErlNifEnv *env, int argc,
const ERL_NIF_TERM argv[]) {
UNIFEX_MAYBE_UNUSED(argc);
UNIFEX_MAYBE_UNUSED(argv);
ERL_NIF_TERM result;
UnifexEnv *unifex_env = env;
MyExplicitEnum in_enum;

if (!({
int res = 0;
char *enum_as_string = NULL;

if (unifex_alloc_and_get_atom(env, argv[0], &enum_as_string)) {
if (strcmp(enum_as_string, "a") == 0) {
in_enum = MY_EXPLICIT_ENUM_A;
res = 1;
} else if (strcmp(enum_as_string, "b") == 0) {
in_enum = MY_EXPLICIT_ENUM_B;
res = 1;
} else if (strcmp(enum_as_string, "c") == 0) {
in_enum = MY_EXPLICIT_ENUM_C;
res = 1;
} else if (strcmp(enum_as_string, "d") == 0) {
in_enum = MY_EXPLICIT_ENUM_D;
res = 1;
}

if (enum_as_string != NULL) {
unifex_free((void *)enum_as_string);
}
}

res;
})) {
result = unifex_raise_args_error(env, "in_enum", ":my_explicit_enum");
goto exit_export_test_my_explicit_enum;
}

result = test_my_explicit_enum(unifex_env, in_enum);
goto exit_export_test_my_explicit_enum;
exit_export_test_my_explicit_enum:

return result;
}

static ERL_NIF_TERM export_test_nil_bugged(ErlNifEnv *env, int argc,
const ERL_NIF_TERM argv[]) {
UNIFEX_MAYBE_UNUSED(argc);
Expand Down Expand Up @@ -1305,6 +1379,7 @@ static ErlNifFunc nif_funcs[] = {
{"unifex_test_nested_struct", 1, export_test_nested_struct, 0},
{"unifex_test_list_of_structs", 1, export_test_list_of_structs, 0},
{"unifex_test_my_enum", 1, export_test_my_enum, 0},
{"unifex_test_my_explicit_enum", 1, export_test_my_explicit_enum, 0},
{"unifex_test_nil_bugged", 0, export_test_nil_bugged, 0},
{"unifex_test_nil_tuple_bugged", 1, export_test_nil_tuple_bugged, 0}};

Expand Down
20 changes: 20 additions & 0 deletions test/fixtures/nif_ref_generated/nif/example.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,23 @@ enum MyEnum_t {
typedef enum MyEnum_t MyEnum;
#endif

#ifdef __cplusplus
enum MyExplicitEnum {
MY_EXPLICIT_ENUM_A = 1,
MY_EXPLICIT_ENUM_B,
MY_EXPLICIT_ENUM_C = 4,
MY_EXPLICIT_ENUM_D = 8
};
#else
enum MyExplicitEnum_t {
MY_EXPLICIT_ENUM_A = 1,
MY_EXPLICIT_ENUM_B,
MY_EXPLICIT_ENUM_C = 4,
MY_EXPLICIT_ENUM_D = 8
};
typedef enum MyExplicitEnum_t MyExplicitEnum;
#endif

#ifdef __cplusplus
struct my_struct {
int id;
Expand Down Expand Up @@ -148,6 +165,7 @@ UNIFEX_TERM test_nested_struct(UnifexEnv *env, nested_struct in_struct);
UNIFEX_TERM test_list_of_structs(UnifexEnv *env, simple_struct *struct_list,
unsigned int struct_list_length);
UNIFEX_TERM test_my_enum(UnifexEnv *env, MyEnum in_enum);
UNIFEX_TERM test_my_explicit_enum(UnifexEnv *env, MyExplicitEnum in_enum);
UNIFEX_TERM test_nil_bugged(UnifexEnv *env);
UNIFEX_TERM test_nil_tuple_bugged(UnifexEnv *env, int in_int);

Expand Down Expand Up @@ -188,6 +206,8 @@ UNIFEX_TERM test_list_of_structs_result_ok(UnifexEnv *env,
simple_struct const *out_struct_list,
unsigned int out_struct_list_length);
UNIFEX_TERM test_my_enum_result_ok(UnifexEnv *env, MyEnum out_enum);
UNIFEX_TERM test_my_explicit_enum_result_ok(UnifexEnv *env,
MyExplicitEnum out_enum);
UNIFEX_TERM test_nil_bugged_result_nil(UnifexEnv *env);
UNIFEX_TERM test_nil_tuple_bugged_result_nil(UnifexEnv *env, int out_int);

Expand Down
4 changes: 4 additions & 0 deletions test_projects/nif/c_src/example/example.c
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ UNIFEX_TERM test_my_enum(UnifexEnv *env, MyEnum in_enum) {
return test_my_enum_result_ok(env, in_enum);
}

UNIFEX_TERM test_my_explicit_enum(UnifexEnv *env, MyExplicitEnum in_enum) {
return test_my_explicit_enum_result_ok(env, in_enum);
}

UNIFEX_TERM test_nested_struct(UnifexEnv *env, nested_struct in_struct) {
return test_nested_struct_result_ok(env, in_struct);
}
Expand Down
5 changes: 5 additions & 0 deletions test_projects/nif/c_src/example/example.spec.exs
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,16 @@ spec test_list_of_structs(struct_list :: [simple_struct]) :: {:ok :: label, out_

type my_enum :: :option_one | :option_two | :option_three | :option_four | :option_five

type my_explicit_enum :: enum_value(:a, 1) | :b | enum_value(:c, 4) | enum_value(:d, 8)

@doc """
test_my_enum docs
"""
spec test_my_enum(in_enum :: my_enum) :: {:ok :: label, out_enum :: my_enum}


spec test_my_explicit_enum(in_enum :: my_explicit_enum) :: {:ok :: label, out_enum :: my_explicit_enum}

# tests for bugged version of functions returning nil.
# these tests should be removed in unifex v2.0.0. For more information check:
# https://github.com/membraneframework/membrane_core/issues/758
Expand Down
13 changes: 12 additions & 1 deletion test_projects/nif/test/example_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,23 @@ defmodule ExampleTest do
end
end

test "explicit enum" do
assert {:ok, :a} = Example.test_my_explicit_enum(:a)
assert {:ok, :b} = Example.test_my_explicit_enum(:b)
assert {:ok, :c} = Example.test_my_explicit_enum(:c)
assert {:ok, :d} = Example.test_my_explicit_enum(:d)

assert_raise ErlangError, ~r/unifex_parse_arg.*in_enum.*my_explicit_enum/i, fn ->
Example.test_my_explicit_enum(:option_not_mentioned)
end
end

test "nested struct list" do
my_struct = %My.Struct{id: 1, name: "Jan Kowlaski", data: [1, 2, 3, 4, 5, 6, 7, 8, 9]}
nested_struct_list = %Nested.StructList{id: 1, struct_list: [my_struct]}
assert {:ok, ^nested_struct_list} = Example.test_nested_struct_list(nested_struct_list)
end

# tests for bugged version of functions returning nil.
# these tests should be removed in unifex v2.0.0. For more information check:
# https://github.com/membraneframework/membrane_core/issues/758
Expand Down