-module(libero@codegen_decoders). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/libero/codegen_decoders.gleam"). -export([generate_decoders_gleam/1, emit_class_statics/1, generate_decoders_ffi/4, emit_typed_decoders/1]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC( " Typed decoder generators.\n" "\n" " Emits JS decoder source from discovered types.\n" " Returns strings; writing to disk is the consumer's responsibility.\n" ). -file("src/libero/codegen_decoders.gleam", 19). ?DOC(" Generate the Gleam wrapper module source for the typed decoder FFI.\n"). -spec generate_decoders_gleam(binary()) -> binary(). generate_decoders_gleam(Ffi_module_path) -> <<<<"//// Code generated by libero. DO NOT EDIT. //// //// Gleam wrapper for the typed decoder FFI. Importing this module //// triggers constructor and decoder registration on the JavaScript target. @external(javascript, \"./"/utf8, Ffi_module_path/binary>>/binary, "\", \"ensure_decoders\") @external(erlang, \"libero_etf_ffi\", \"ensure_decoders\") pub fn ensure_decoders() -> Bool "/utf8>>. -file("src/libero/codegen_decoders.gleam", 493). -spec indent_js(binary()) -> binary(). indent_js(Source) -> _pipe = Source, _pipe@1 = gleam@string:trim(_pipe), _pipe@2 = gleam@string:split(_pipe@1, <<"\n"/utf8>>), _pipe@3 = gleam@list:map( _pipe@2, fun(Line) -> <<" "/utf8, Line/binary>> end ), _pipe@4 = gleam@string:join(_pipe@3, <<"\n"/utf8>>), (fun(Source@1) -> <> end)(_pipe@4). -file("src/libero/codegen_decoders.gleam", 479). -spec emit_ensure_decoders(binary()) -> binary(). emit_ensure_decoders(Setup) -> case gleam@string:trim(Setup) of <<""/utf8>> -> <<"export function ensure_decoders() { return true; }"/utf8>>; _ -> <<<<<<<<<<<<"let _decodersReady = false;\n\n"/utf8, "export function ensure_decoders() {\n"/utf8>>/binary, " if (_decodersReady) return true;\n"/utf8>>/binary, (indent_js(Setup))/binary>>/binary, " _decodersReady = true;\n"/utf8>>/binary, " return true;\n"/utf8>>/binary, "}"/utf8>> end. -file("src/libero/codegen_decoders.gleam", 217). -spec variant_hash(libero@walker:discovered_variant()) -> binary(). variant_hash(V) -> {_, Hash} = libero@wire_identity:wire_identity( erlang:element(2, V), erlang:element(3, V), erlang:element(7, V) ), Hash. -file("src/libero/codegen_decoders.gleam", 267). -spec decoder_fn_name(binary(), binary()) -> binary(). decoder_fn_name(Module_path, Type_name) -> <<<<<<"decode_"/utf8, (libero@codegen:module_to_underscored(Module_path))/binary>>/binary, "_"/utf8>>/binary, (libero@walker:to_snake_case(Type_name))/binary>>. -file("src/libero/codegen_decoders.gleam", 82). -spec emit_typed_decoder_registrations(list(libero@walker:discovered_type())) -> binary(). emit_typed_decoder_registrations(Discovered) -> case Discovered of [] -> <<""/utf8>>; _ -> Calls = begin _pipe = gleam@list:flat_map( Discovered, fun(T) -> Fn_name = decoder_fn_name( erlang:element(2, T), erlang:element(3, T) ), gleam@list:map( erlang:element(5, T), fun(V) -> <<<<<<<<<<<<"registerAtomDecoder(\""/utf8, (variant_hash(V))/binary>>/binary, "\", \""/utf8>>/binary, Fn_name/binary>>/binary, "\", "/utf8>>/binary, Fn_name/binary>>/binary, ");"/utf8>> end ) end ), gleam@string:join(_pipe, <<"\n"/utf8>>) end, <> end. -file("src/libero/codegen_decoders.gleam", 231). -spec emit_float_type_hint(libero@field_type:field_type()) -> binary(). emit_float_type_hint(Ft) -> case Ft of float_field -> <<"\"float\""/utf8>>; {list_of, Element} -> <<<<"{ kind: \"list\", element: "/utf8, (emit_float_type_hint(Element))/binary>>/binary, " }"/utf8>>; {option_of, Inner} -> <<<<"{ kind: \"option\", inner: "/utf8, (emit_float_type_hint(Inner))/binary>>/binary, " }"/utf8>>; {result_of, Ok, Err} -> <<<<<<<<"{ kind: \"result\", ok: "/utf8, (emit_float_type_hint(Ok))/binary>>/binary, ", err: "/utf8>>/binary, (emit_float_type_hint(Err))/binary>>/binary, " }"/utf8>>; {dict_of, Key, Value} -> <<<<<<<<"{ kind: \"dict\", key: "/utf8, (emit_float_type_hint(Key))/binary>>/binary, ", value: "/utf8>>/binary, (emit_float_type_hint(Value))/binary>>/binary, " }"/utf8>>; {tuple_of, Elements} -> Hints = begin _pipe = Elements, _pipe@1 = gleam@list:map(_pipe, fun emit_float_type_hint/1), gleam@string:join(_pipe@1, <<", "/utf8>>) end, <<<<"{ kind: \"tuple\", elements: ["/utf8, Hints/binary>>/binary, "] }"/utf8>>; int_field -> <<"null"/utf8>>; string_field -> <<"null"/utf8>>; bool_field -> <<"null"/utf8>>; bit_array_field -> <<"null"/utf8>>; nil_field -> <<"null"/utf8>>; {type_var, _} -> <<"null"/utf8>>; {user_type, _, _, _} -> <<"null"/utf8>> end. -file("src/libero/codegen_decoders.gleam", 192). ?DOC( " Emit per-variant class-static assignments for every discovered user\n" " type. `__wireAtom` carries the 10-char hash; `__fieldTypes` carries\n" " the positional field-type hints the encoder needs (Float marked,\n" " containers wrapped, primitives null). These statics replace the\n" " runtime `_bareToQualifiedAtom` and `fieldTypeRegistry` lookup tables.\n" "\n" " Even 0-arity variants get a `__fieldTypes = []` for symmetry; the\n" " encoder branches on key count so the empty array is never indexed.\n" ). -spec emit_class_statics(list(libero@walker:discovered_type())) -> binary(). emit_class_statics(Discovered) -> Lines = gleam@list:flat_map( Discovered, fun(T) -> gleam@list:flat_map( erlang:element(5, T), fun(V) -> Class_ref = <<<<<<"_m_"/utf8, (libero@codegen:module_to_underscored( erlang:element(2, V) ))/binary>>/binary, "."/utf8>>/binary, (erlang:element(3, V))/binary>>, Hints = begin _pipe = erlang:element(7, V), _pipe@1 = gleam@list:map( _pipe, fun emit_float_type_hint/1 ), gleam@string:join(_pipe@1, <<", "/utf8>>) end, [<<<<<>/binary, (variant_hash(V))/binary>>/binary, "\";"/utf8>>, <<<<<>/binary, Hints/binary>>/binary, "];"/utf8>>] end ) end ), case Lines of [] -> <<""/utf8>>; _ -> <<(gleam@string:join(Lines, <<"\n"/utf8>>))/binary, "\n"/utf8>> end. -file("src/libero/codegen_decoders.gleam", 296). -spec emit_variant_constructor_call( libero@walker:discovered_variant(), binary() ) -> binary(). emit_variant_constructor_call(Variant, Args) -> <<<<<<<<<<<<"new _m_"/utf8, (libero@codegen:module_to_underscored( erlang:element(2, Variant) ))/binary>>/binary, "."/utf8>>/binary, (erlang:element(3, Variant))/binary>>/binary, "("/utf8>>/binary, Args/binary>>/binary, ")"/utf8>>. -file("src/libero/codegen_decoders.gleam", 227). -spec variant_source_label(libero@walker:discovered_variant()) -> binary(). variant_source_label(V) -> <<<<(erlang:element(3, V))/binary, " @ "/utf8>>/binary, (erlang:element(2, V))/binary>>. -file("src/libero/codegen_decoders.gleam", 466). -spec emit_lambda_decoders(list(libero@field_type:field_type()), integer()) -> binary(). emit_lambda_decoders(Inners, Start_depth) -> _pipe = Inners, _pipe@1 = gleam@list:index_map( _pipe, fun(Ft, I) -> P_depth = Start_depth + I, Param = <<"t"/utf8, (erlang:integer_to_binary(P_depth))/binary>>, <<<<<<"("/utf8, Param/binary>>/binary, ") => "/utf8>>/binary, (field_decoder_call_depth(Ft, Param, P_depth + 1))/binary>> end ), gleam@string:join(_pipe@1, <<", "/utf8>>). -file("src/libero/codegen_decoders.gleam", 456). -spec emit_higher_order_decoder( binary(), list(libero@field_type:field_type()), binary(), integer() ) -> binary(). emit_higher_order_decoder(Fn_name, Inners, Term_expr, Start_depth) -> Decoders = emit_lambda_decoders(Inners, Start_depth), <<<<<<<<<>/binary, Decoders/binary>>/binary, ", "/utf8>>/binary, Term_expr/binary>>/binary, ")"/utf8>>. -file("src/libero/codegen_decoders.gleam", 399). -spec field_decoder_call_depth( libero@field_type:field_type(), binary(), integer() ) -> binary(). field_decoder_call_depth(Ft, Term_expr, Depth) -> Param = <<"t"/utf8, (erlang:integer_to_binary(Depth))/binary>>, Next = Depth + 1, case Ft of int_field -> <<<<"decode_int("/utf8, Term_expr/binary>>/binary, ")"/utf8>>; float_field -> <<<<"decode_float("/utf8, Term_expr/binary>>/binary, ")"/utf8>>; string_field -> <<<<"decode_string("/utf8, Term_expr/binary>>/binary, ")"/utf8>>; bool_field -> <<<<"decode_bool("/utf8, Term_expr/binary>>/binary, ")"/utf8>>; bit_array_field -> <<<<"decode_bit_array("/utf8, Term_expr/binary>>/binary, ")"/utf8>>; nil_field -> <<<<"decode_nil("/utf8, Term_expr/binary>>/binary, ")"/utf8>>; {list_of, Inner} -> <<<<<<<<<<<<"decode_list_of(("/utf8, Param/binary>>/binary, ") => "/utf8>>/binary, (field_decoder_call_depth(Inner, Param, Next))/binary>>/binary, ", "/utf8>>/binary, Term_expr/binary>>/binary, ")"/utf8>>; {option_of, Inner@1} -> <<<<<<<<<<<<"decode_option_of(("/utf8, Param/binary>>/binary, ") => "/utf8>>/binary, (field_decoder_call_depth(Inner@1, Param, Next))/binary>>/binary, ", "/utf8>>/binary, Term_expr/binary>>/binary, ")"/utf8>>; {result_of, Ok, Err} -> emit_higher_order_decoder( <<"decode_result_of"/utf8>>, [Ok, Err], Term_expr, Next ); {dict_of, K, V} -> emit_higher_order_decoder( <<"decode_dict_of"/utf8>>, [K, V], Term_expr, Next ); {tuple_of, Elems} -> Decoders = emit_lambda_decoders(Elems, Next), <<<<<<<<"decode_tuple_of(["/utf8, Decoders/binary>>/binary, "], "/utf8>>/binary, Term_expr/binary>>/binary, ")"/utf8>>; {type_var, Name} -> <<<<"(() => { throw new DecodeError(\"TypeVar<"/utf8, Name/binary>>/binary, "> not supported at runtime\"); })()"/utf8>>; {user_type, Module_path, Type_name, _} -> <<<<<<(decoder_fn_name(Module_path, Type_name))/binary, "("/utf8>>/binary, Term_expr/binary>>/binary, ")"/utf8>> end. -file("src/libero/codegen_decoders.gleam", 394). -spec field_decoder_call(libero@field_type:field_type(), binary()) -> binary(). field_decoder_call(Ft, Term_expr) -> field_decoder_call_depth(Ft, Term_expr, 0). -file("src/libero/codegen_decoders.gleam", 346). -spec emit_tagged_union_decoder(list(libero@walker:discovered_variant())) -> binary(). emit_tagged_union_decoder(Variants) -> Arms = gleam@list:map( Variants, fun(V) -> Expected_len = erlang:integer_to_binary( erlang:length(erlang:element(7, V)) + 1 ), Args = case erlang:element(7, V) of [] -> <<""/utf8>>; Fields -> _pipe = Fields, _pipe@1 = gleam@list:index_map( _pipe, fun(Ft, I) -> field_decoder_call( Ft, <<<<"term["/utf8, (erlang:integer_to_binary(I + 1))/binary>>/binary, "]"/utf8>> ) end ), gleam@string:join(_pipe@1, <<", "/utf8>>) end, Length_guard = case erlang:element(7, V) of [] -> <<<<<<<<<<<<"if (Array.isArray(term) && term.length !== "/utf8, Expected_len/binary>>/binary, ") throw new DecodeError(\"expected "/utf8>>/binary, (variant_hash(V))/binary>>/binary, " arity "/utf8>>/binary, Expected_len/binary>>/binary, ", got \" + term.length);\n"/utf8>>; _ -> <<<<<<<<<<<<"if (!Array.isArray(term) || term.length !== "/utf8, Expected_len/binary>>/binary, ") throw new DecodeError(\"expected "/utf8>>/binary, (variant_hash(V))/binary>>/binary, " arity "/utf8>>/binary, Expected_len/binary>>/binary, ", got \" + (Array.isArray(term) ? term.length : typeof term));\n"/utf8>> end, <<<<<<<<<<<<<<<<" case \""/utf8, (variant_hash(V))/binary>>/binary, "\": // "/utf8>>/binary, (variant_source_label(V))/binary>>/binary, "\n "/utf8>>/binary, Length_guard/binary>>/binary, " return "/utf8>>/binary, (emit_variant_constructor_call(V, Args))/binary>>/binary, ";"/utf8>> end ), <<<<<<<<" const tag = Array.isArray(term) ? term[0] : term;\n"/utf8, " switch (tag) {\n"/utf8>>/binary, (gleam@string:join(Arms, <<"\n"/utf8>>))/binary>>/binary, "\n default:\n throw new DecodeError(\"unknown variant atom: \" + String(tag));\n"/utf8>>/binary, " }"/utf8>>. -file("src/libero/codegen_decoders.gleam", 309). -spec emit_enum_decoder(list(libero@walker:discovered_variant())) -> binary(). emit_enum_decoder(Variants) -> Cases = gleam@list:map( Variants, fun(V) -> <<<<<<<<<<" if (term === \""/utf8, (variant_hash(V))/binary>>/binary, "\") return "/utf8>>/binary, (emit_variant_constructor_call(V, <<""/utf8>>))/binary>>/binary, "; // "/utf8>>/binary, (variant_source_label(V))/binary>> end ), <<(gleam@string:join(Cases, <<"\n"/utf8>>))/binary, "\n throw new DecodeError(\"unknown variant atom: \" + String(term));"/utf8>>. -file("src/libero/codegen_decoders.gleam", 274). -spec all_variants_zero_arity(list(libero@walker:discovered_variant())) -> boolean(). all_variants_zero_arity(Variants) -> gleam@list:all( Variants, fun(V) -> gleam@list:is_empty(erlang:element(7, V)) end ). -file("src/libero/codegen_decoders.gleam", 323). -spec emit_record_decoder(libero@walker:discovered_variant()) -> binary(). emit_record_decoder(Variant) -> Field_lines = gleam@list:index_map( erlang:element(7, Variant), fun(Ft, I) -> <<" "/utf8, (field_decoder_call( Ft, <<<<"term["/utf8, (erlang:integer_to_binary(I + 1))/binary>>/binary, "]"/utf8>> ))/binary>> end ), Args = <<<<"\n"/utf8, (gleam@string:join(Field_lines, <<",\n"/utf8>>))/binary>>/binary, "\n "/utf8>>, Hash = variant_hash(Variant), Label = variant_source_label(Variant), Expected_len = erlang:integer_to_binary( erlang:length(erlang:element(7, Variant)) + 1 ), <<<<<<<<<<<<<<<<<<<<<<" if (!Array.isArray(term) || term.length !== "/utf8, Expected_len/binary>>/binary, " || term[0] !== \""/utf8>>/binary, Hash/binary>>/binary, "\") throw new DecodeError(\"expected "/utf8>>/binary, Hash/binary>>/binary, " ("/utf8>>/binary, Label/binary>>/binary, "), got \" + (Array.isArray(term) ? String(term[0]) : typeof term));\n"/utf8>>/binary, " return "/utf8>>/binary, (emit_variant_constructor_call(Variant, Args))/binary>>/binary, ";"/utf8>>. -file("src/libero/codegen_decoders.gleam", 278). -spec emit_type_decoder(libero@walker:discovered_type()) -> binary(). emit_type_decoder(T) -> Fn_name = decoder_fn_name(erlang:element(2, T), erlang:element(3, T)), Body = case erlang:element(5, T) of [] -> <<" throw new DecodeError(\"empty type\");"/utf8>>; [Single] -> case erlang:element(7, Single) of [] -> emit_enum_decoder(erlang:element(5, T)); _ -> emit_record_decoder(Single) end; Variants -> case all_variants_zero_arity(Variants) of true -> emit_enum_decoder(Variants); false -> emit_tagged_union_decoder(Variants) end end, <<<<<<<<"export function "/utf8, Fn_name/binary>>/binary, "(term) {\n"/utf8>>/binary, Body/binary>>/binary, "\n}"/utf8>>. -file("src/libero/codegen_decoders.gleam", 76). -spec emit_type_decoder_functions(list(libero@walker:discovered_type())) -> binary(). emit_type_decoder_functions(Discovered) -> _pipe = Discovered, _pipe@1 = gleam@list:map(_pipe, fun(T) -> emit_type_decoder(T) end), gleam@string:join(_pipe@1, <<"\n\n"/utf8>>). -file("src/libero/codegen_decoders.gleam", 169). -spec module_owner(binary(), binary(), list(binary())) -> binary(). module_owner(Module_path, Package, Dependency_packages) -> case gleam@string:split(Module_path, <<"/"/utf8>>) of [First | _] -> case gleam@list:contains(Dependency_packages, First) of true -> First; false -> Package end; _ -> Package end. -file("src/libero/codegen_decoders.gleam", 107). -spec emit_decoder_imports( list(libero@walker:discovered_type()), binary(), binary(), list(binary()) ) -> binary(). emit_decoder_imports(Discovered, Relpath_prefix, Package, Dependency_packages) -> Module_paths = begin _pipe = gleam@list:fold( Discovered, {[], gleam@set:new()}, fun(Acc, T) -> {Paths_acc, Seen} = Acc, case gleam@set:contains(Seen, erlang:element(2, T)) of true -> Acc; false -> {lists:append(Paths_acc, [erlang:element(2, T)]), gleam@set:insert(Seen, erlang:element(2, T))} end end ), (fun(Pair) -> erlang:element(1, Pair) end)(_pipe) end, Prelude_import = <<<<<<<<<<<<<<"import { decode_int, decode_float, decode_string, decode_bool, "/utf8, "decode_bit_array, decode_nil, decode_list_of, decode_option_of, "/utf8>>/binary, "decode_result_of, decode_dict_of, decode_tuple_of, DecodeError, "/utf8>>/binary, "setResultCtors, setOptionCtors, setListCtors, "/utf8>>/binary, "setDictFromList } from \""/utf8>>/binary, Relpath_prefix/binary>>/binary, "libero/libero/decoders_prelude.mjs"/utf8>>/binary, "\";"/utf8>>, Stdlib_imports = <<<<<<<<<<<<<<<<"import { Ok, Error as ResultError, Empty, NonEmpty } from \""/utf8, Relpath_prefix/binary>>/binary, "gleam_stdlib/gleam.mjs\";\n"/utf8>>/binary, "import { Some, None } from \""/utf8>>/binary, Relpath_prefix/binary>>/binary, "gleam_stdlib/gleam/option.mjs\";\n"/utf8>>/binary, "import { from_list as dictFromList } from \""/utf8>>/binary, Relpath_prefix/binary>>/binary, "gleam_stdlib/gleam/dict.mjs\";"/utf8>>, Decoder_ffi_import = case gleam@list:is_empty(Discovered) of true -> <<""/utf8>>; false -> <<<<"\nimport { registerAtomDecoder } from \""/utf8, Relpath_prefix/binary>>/binary, "libero/libero/etf/wire_ffi.mjs\";"/utf8>> end, Module_imports = gleam@list:map( Module_paths, fun(Mp) -> <<<<<<<<<<"import * as _m_"/utf8, (libero@codegen:module_to_underscored(Mp))/binary>>/binary, " from \""/utf8>>/binary, Relpath_prefix/binary>>/binary, (libero@codegen:module_to_mjs_path( Mp, module_owner(Mp, Package, Dependency_packages) ))/binary>>/binary, "\";"/utf8>> end ), gleam@string:join( [Prelude_import, <> | Module_imports], <<"\n"/utf8>> ). -file("src/libero/codegen_decoders.gleam", 34). ?DOC(" Generate the JS FFI decoder source from discovered types.\n"). -spec generate_decoders_ffi( list(libero@walker:discovered_type()), binary(), binary(), list(binary()) ) -> binary(). generate_decoders_ffi(Discovered, Relpath_prefix, Package, Dependency_packages) -> Imports = emit_decoder_imports( Discovered, Relpath_prefix, Package, Dependency_packages ), Body = emit_type_decoder_functions(Discovered), Class_statics = emit_class_statics(Discovered), Ctor_setters = <<<<<<"setResultCtors(Ok, ResultError);\n"/utf8, "setOptionCtors(Some, None);\n"/utf8>>/binary, "setListCtors(Empty, NonEmpty);\n"/utf8>>/binary, "setDictFromList(dictFromList);\n"/utf8>>, Registrations = emit_typed_decoder_registrations(Discovered), Entry = emit_ensure_decoders( <<<>/binary, Registrations/binary>> ), <<<<<<<<<<"// Code generated by libero. DO NOT EDIT. // // Per-type decoder functions derived from the DiscoveredType graph. "/utf8, Imports/binary>>/binary, "\n\n"/utf8>>/binary, Body/binary>>/binary, "\n\n"/utf8>>/binary, Entry/binary>>. -file("src/libero/codegen_decoders.gleam", 68). ?DOC( " Emit a JS string with one decoder function per discovered type,\n" " registration calls that populate etf/wire_ffi.mjs's typed decoder registry,\n" " and an `ensure_decoders` entry point.\n" ). -spec emit_typed_decoders(list(libero@walker:discovered_type())) -> binary(). emit_typed_decoders(Discovered) -> Registrations = emit_typed_decoder_registrations(Discovered), Entry = emit_ensure_decoders(Registrations), Type_decoders = emit_type_decoder_functions(Discovered), Parts = gleam@list:filter( [Type_decoders, Entry], fun(S) -> S /= <<""/utf8>> end ), gleam@string:join(Parts, <<"\n\n"/utf8>>).