diff --git a/lib/unifex/code_generator/base_types/enum.ex b/lib/unifex/code_generator/base_types/enum.ex index ca1e8e8..c677c5d 100644 --- a/lib/unifex/code_generator/base_types/enum.ex +++ b/lib/unifex/code_generator/base_types/enum.ex @@ -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 @@ -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) { @@ -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}")}) { @@ -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 diff --git a/lib/unifex/code_generators/common.ex b/lib/unifex/code_generators/common.ex index 5d09397..b852924 100644 --- a/lib/unifex/code_generators/common.ex +++ b/lib/unifex/code_generators/common.ex @@ -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 diff --git a/lib/unifex/specs_dsl.ex b/lib/unifex/specs_dsl.ex index 9b165ab..e979ac4 100644 --- a/lib/unifex/specs_dsl.ex +++ b/lib/unifex/specs_dsl.ex @@ -235,6 +235,13 @@ 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 | ... + + The numeric value assigned to any enum constant can only be used from C/C++. + In Elixir, the atom must be used. + 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} @@ -265,6 +272,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 diff --git a/test/fixtures/nif_ref_generated/nif/example.c b/test/fixtures/nif_ref_generated/nif/example.c index 50b8a82..a0197c4 100644 --- a/test/fixtures/nif_ref_generated/nif/example.c +++ b/test/fixtures/nif_ref_generated/nif/example.c @@ -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"); } @@ -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); @@ -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}}; diff --git a/test/fixtures/nif_ref_generated/nif/example.cpp b/test/fixtures/nif_ref_generated/nif/example.cpp index 50b8a82..a0197c4 100644 --- a/test/fixtures/nif_ref_generated/nif/example.cpp +++ b/test/fixtures/nif_ref_generated/nif/example.cpp @@ -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"); } @@ -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); @@ -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}}; diff --git a/test/fixtures/nif_ref_generated/nif/example.h b/test/fixtures/nif_ref_generated/nif/example.h index 86a27f2..b6d209b 100644 --- a/test/fixtures/nif_ref_generated/nif/example.h +++ b/test/fixtures/nif_ref_generated/nif/example.h @@ -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; @@ -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); @@ -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); diff --git a/test_projects/nif/c_src/example/example.c b/test_projects/nif/c_src/example/example.c index 5f58c05..0b09e60 100644 --- a/test_projects/nif/c_src/example/example.c +++ b/test_projects/nif/c_src/example/example.c @@ -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); } diff --git a/test_projects/nif/c_src/example/example.spec.exs b/test_projects/nif/c_src/example/example.spec.exs index d8031a7..ef54cc0 100644 --- a/test_projects/nif/c_src/example/example.spec.exs +++ b/test_projects/nif/c_src/example/example.spec.exs @@ -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 diff --git a/test_projects/nif/test/example_test.exs b/test_projects/nif/test/example_test.exs index c1c2359..b7ba2b5 100644 --- a/test_projects/nif/test/example_test.exs +++ b/test_projects/nif/test/example_test.exs @@ -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