2022-09-07 13:39:31 -04:00
/*
* Copyright ( c ) 2022 , Tim Flynn < trflynn89 @ serenityos . org >
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# include "GeneratorUtil.h"
2022-09-09 09:51:03 -04:00
# include <AK/AnyOf.h>
2023-12-16 17:49:34 +03:30
# include <AK/ByteString.h>
2023-03-05 10:14:25 -05:00
# include <AK/QuickSort.h>
2022-09-07 13:39:31 -04:00
# include <AK/SourceGenerator.h>
# include <AK/StringUtils.h>
# include <AK/Types.h>
# include <LibCore/ArgsParser.h>
2022-10-26 16:04:38 -04:00
# include <LibCore/Directory.h>
2023-03-22 02:35:30 +11:00
# include <LibFileSystem/FileSystem.h>
2022-09-07 13:39:31 -04:00
# include <LibUnicode/Emoji.h>
struct Emoji {
2022-11-18 11:04:33 -05:00
size_t name { 0 } ;
2023-02-22 20:51:26 -05:00
Optional < size_t > image_path ;
2022-09-07 13:39:31 -04:00
Unicode : : EmojiGroup group ;
2023-12-16 17:49:34 +03:30
ByteString subgroup ;
2022-09-07 13:39:31 -04:00
u32 display_order { 0 } ;
Vector < u32 > code_points ;
2023-12-16 17:49:34 +03:30
ByteString encoded_code_points ;
ByteString status ;
2022-11-06 07:57:23 +01:00
size_t code_point_array_index { 0 } ;
2022-09-07 13:39:31 -04:00
} ;
struct EmojiData {
2022-11-18 11:04:33 -05:00
UniqueStringStorage unique_strings ;
2022-09-07 13:39:31 -04:00
Vector < Emoji > emojis ;
} ;
2023-03-01 08:43:22 -05:00
static void set_image_path_for_emoji ( StringView emoji_resource_path , EmojiData & emoji_data , Emoji & emoji )
2022-10-26 16:04:38 -04:00
{
StringBuilder builder ;
for ( auto code_point : emoji . code_points ) {
if ( code_point = = 0xfe0f )
continue ;
if ( ! builder . is_empty ( ) )
builder . append ( ' _ ' ) ;
builder . appendff ( " U+{:X} " , code_point ) ;
}
2023-12-16 17:49:34 +03:30
auto file = ByteString : : formatted ( " {}.png " , builder . to_byte_string ( ) ) ;
auto path = ByteString : : formatted ( " {}/{} " , emoji_resource_path , file ) ;
2023-03-22 02:35:30 +11:00
if ( ! FileSystem : : exists ( path ) )
2023-02-22 20:51:26 -05:00
return ;
2023-03-01 08:43:22 -05:00
emoji . image_path = emoji_data . unique_strings . ensure ( move ( file ) ) ;
2022-10-26 16:04:38 -04:00
}
2023-05-03 18:45:18 -04:00
static ErrorOr < void > parse_emoji_test_data ( Core : : InputBufferedFile & file , EmojiData & emoji_data )
2022-09-07 13:39:31 -04:00
{
static constexpr auto group_header = " # group: " sv ;
2022-10-26 16:04:38 -04:00
static constexpr auto subgroup_header = " # subgroup: " sv ;
2022-09-07 13:39:31 -04:00
Array < u8 , 1024 > buffer ;
Unicode : : EmojiGroup group ;
2023-12-16 17:49:34 +03:30
ByteString subgroup ;
2022-09-07 13:39:31 -04:00
u32 display_order { 0 } ;
while ( TRY ( file . can_read_line ( ) ) ) {
auto line = TRY ( file . read_line ( buffer ) ) ;
if ( line . is_empty ( ) )
continue ;
if ( line . starts_with ( ' # ' ) ) {
if ( line . starts_with ( group_header ) ) {
auto name = line . substring_view ( group_header . length ( ) ) ;
group = Unicode : : emoji_group_from_string ( name ) ;
2022-10-26 16:04:38 -04:00
} else if ( line . starts_with ( subgroup_header ) ) {
subgroup = line . substring_view ( subgroup_header . length ( ) ) ;
2022-09-07 13:39:31 -04:00
}
continue ;
}
auto status_index = line . find ( ' ; ' ) ;
VERIFY ( status_index . has_value ( ) ) ;
auto emoji_and_name_index = line . find ( ' # ' , * status_index ) ;
VERIFY ( emoji_and_name_index . has_value ( ) ) ;
Emoji emoji { } ;
emoji . group = group ;
2022-10-26 16:04:38 -04:00
emoji . subgroup = subgroup ;
2022-09-07 13:39:31 -04:00
emoji . display_order = display_order + + ;
auto code_points = line . substring_view ( 0 , * status_index ) . split_view ( ' ' ) ;
TRY ( emoji . code_points . try_ensure_capacity ( code_points . size ( ) ) ) ;
for ( auto code_point : code_points ) {
auto value = AK : : StringUtils : : convert_to_uint_from_hex < u32 > ( code_point ) ;
VERIFY ( value . has_value ( ) ) ;
emoji . code_points . unchecked_append ( * value ) ;
}
auto emoji_and_name = line . substring_view ( * emoji_and_name_index + 1 ) ;
auto emoji_and_name_spaces = emoji_and_name . find_all ( " " sv ) ;
VERIFY ( emoji_and_name_spaces . size ( ) > 2 ) ;
auto name = emoji_and_name . substring_view ( emoji_and_name_spaces [ 2 ] ) . trim_whitespace ( ) ;
emoji . name = emoji_data . unique_strings . ensure ( name . to_titlecase_string ( ) ) ;
2022-10-26 16:04:38 -04:00
emoji . encoded_code_points = emoji_and_name . substring_view ( 0 , emoji_and_name_spaces [ 1 ] ) . trim_whitespace ( ) ;
emoji . status = line . substring_view ( * status_index + 1 , * emoji_and_name_index - * status_index - 1 ) . trim_whitespace ( ) ;
2022-09-07 13:39:31 -04:00
TRY ( emoji_data . emojis . try_append ( move ( emoji ) ) ) ;
}
return { } ;
}
2023-05-03 18:45:18 -04:00
static ErrorOr < void > parse_emoji_serenity_data ( Core : : InputBufferedFile & file , EmojiData & emoji_data )
2022-09-09 09:51:03 -04:00
{
static constexpr auto code_point_header = " U+ " sv ;
Array < u8 , 1024 > buffer ;
auto display_order = static_cast < u32 > ( emoji_data . emojis . size ( ) ) + 1u ;
while ( TRY ( file . can_read_line ( ) ) ) {
auto line = TRY ( file . read_line ( buffer ) ) ;
if ( line . is_empty ( ) )
continue ;
auto index = line . find ( code_point_header ) ;
if ( ! index . has_value ( ) )
continue ;
line = line . substring_view ( * index ) ;
StringBuilder builder ;
Emoji emoji { } ;
emoji . group = Unicode : : EmojiGroup : : SerenityOS ;
emoji . display_order = display_order + + ;
2023-02-24 21:07:09 +00:00
TRY ( line . for_each_split_view ( ' ' , SplitBehavior : : Nothing , [ & ] ( auto segment ) - > ErrorOr < void > {
2022-09-09 09:51:03 -04:00
if ( segment . starts_with ( code_point_header ) ) {
segment = segment . substring_view ( code_point_header . length ( ) ) ;
auto code_point = AK : : StringUtils : : convert_to_uint_from_hex < u32 > ( segment ) ;
VERIFY ( code_point . has_value ( ) ) ;
2023-02-24 21:07:09 +00:00
TRY ( emoji . code_points . try_append ( * code_point ) ) ;
2022-09-09 09:51:03 -04:00
} else {
if ( ! builder . is_empty ( ) )
2023-02-24 21:07:09 +00:00
TRY ( builder . try_append ( ' ' ) ) ;
TRY ( builder . try_append ( segment ) ) ;
2022-09-09 09:51:03 -04:00
}
2023-02-24 21:07:09 +00:00
return { } ;
} ) ) ;
2022-09-09 09:51:03 -04:00
2023-12-16 17:49:34 +03:30
auto name = builder . to_byte_string ( ) ;
2022-09-09 09:51:03 -04:00
if ( ! any_of ( name , is_ascii_lower_alpha ) )
name = name . to_titlecase ( ) ;
emoji . name = emoji_data . unique_strings . ensure ( move ( name ) ) ;
TRY ( emoji_data . emojis . try_append ( move ( emoji ) ) ) ;
}
return { } ;
}
2023-03-03 12:03:22 -05:00
static ErrorOr < void > validate_emoji ( StringView emoji_resource_path , EmojiData & emoji_data )
{
2023-03-15 14:56:20 +00:00
TRY ( Core : : Directory : : for_each_entry ( emoji_resource_path , Core : : DirIterator : : SkipDots , [ & ] ( auto & entry , auto & ) - > ErrorOr < IterationDecision > {
auto lexical_path = LexicalPath ( entry . name ) ;
2023-03-03 12:03:22 -05:00
if ( lexical_path . extension ( ) ! = " png " )
2023-03-15 14:56:20 +00:00
return IterationDecision : : Continue ;
2023-03-03 12:03:22 -05:00
2023-03-15 14:54:00 +00:00
auto title = lexical_path . title ( ) ;
if ( ! title . starts_with ( " U+ " sv ) )
return IterationDecision : : Continue ;
2023-03-03 12:03:22 -05:00
Vector < u32 > code_points ;
2023-03-15 14:54:00 +00:00
TRY ( title . for_each_split_view ( ' _ ' , SplitBehavior : : Nothing , [ & ] ( auto segment ) - > ErrorOr < void > {
2023-03-03 12:03:22 -05:00
auto code_point = AK : : StringUtils : : convert_to_uint_from_hex < u32 > ( segment . substring_view ( 2 ) ) ;
VERIFY ( code_point . has_value ( ) ) ;
TRY ( code_points . try_append ( * code_point ) ) ;
return { } ;
} ) ) ;
auto it = emoji_data . emojis . find_if ( [ & ] ( auto const & emoji ) {
return emoji . code_points = = code_points ;
} ) ;
if ( it = = emoji_data . emojis . end ( ) ) {
2023-03-15 14:56:20 +00:00
warnln ( " \x1b [1;31mError! \x1b [0m Emoji data for \x1b [35m{} \x1b [0m not found. Please check emoji-test.txt and emoji-serenity.txt. " , entry . name ) ;
2023-03-03 12:03:22 -05:00
return Error : : from_errno ( ENOENT ) ;
}
2023-03-15 14:56:20 +00:00
return IterationDecision : : Continue ;
} ) ) ;
2023-03-03 12:03:22 -05:00
return { } ;
}
2023-05-03 18:45:18 -04:00
static ErrorOr < void > generate_emoji_data_header ( Core : : InputBufferedFile & file , EmojiData const & )
2022-09-07 13:39:31 -04:00
{
StringBuilder builder ;
SourceGenerator generator { builder } ;
2023-03-01 16:28:32 +01:00
TRY ( file . write_until_depleted ( generator . as_string_view ( ) . bytes ( ) ) ) ;
2022-09-07 13:39:31 -04:00
return { } ;
}
2023-05-03 18:45:18 -04:00
static ErrorOr < void > generate_emoji_data_implementation ( Core : : InputBufferedFile & file , EmojiData const & emoji_data )
2022-09-07 13:39:31 -04:00
{
StringBuilder builder ;
SourceGenerator generator { builder } ;
2022-11-18 11:04:33 -05:00
generator . set ( " string_index_type " sv , emoji_data . unique_strings . type_that_fits ( ) ) ;
2023-12-16 17:49:34 +03:30
generator . set ( " emojis_size " sv , ByteString : : number ( emoji_data . emojis . size ( ) ) ) ;
2022-09-07 13:39:31 -04:00
generator . append ( R " ~~~(
# include <AK/Array.h>
# include <AK/BinarySearch.h>
# include <AK/Span.h>
# include <AK/StringView.h>
# include <AK/Types.h>
# include <LibUnicode/Emoji.h>
# include <LibUnicode/EmojiData.h>
namespace Unicode {
) ~ ~ ~ " );
emoji_data . unique_strings . generate ( generator ) ;
2022-11-06 07:57:23 +01:00
size_t total_code_point_count { 0 } ;
for ( auto const & emoji : emoji_data . emojis ) {
total_code_point_count + = emoji . code_points . size ( ) ;
}
2023-12-16 17:49:34 +03:30
generator . set ( " total_code_point_count " , ByteString : : number ( total_code_point_count ) ) ;
2022-11-06 07:57:23 +01:00
generator . append ( R " ~~~(
static constexpr Array < u32 , @ total_code_point_count @ > s_emoji_code_points { { ) ~ ~ ~ " );
bool first = true ;
for ( auto const & emoji : emoji_data . emojis ) {
for ( auto code_point : emoji . code_points ) {
generator . append ( first ? " " sv : " , " sv ) ;
2023-12-16 17:49:34 +03:30
generator . append ( ByteString : : formatted ( " {:#x} " , code_point ) ) ;
2022-11-06 07:57:23 +01:00
first = false ;
}
}
generator . append ( " } }; " sv ) ;
2022-09-07 13:39:31 -04:00
generator . append ( R " ~~~(
struct EmojiData {
2023-02-22 20:51:26 -05:00
Emoji to_unicode_emoji ( ) const
2022-09-07 13:39:31 -04:00
{
Emoji emoji { } ;
emoji . name = decode_string ( name ) ;
2023-02-22 20:51:26 -05:00
if ( image_path ! = 0 )
emoji . image_path = decode_string ( image_path ) ;
2022-09-07 13:39:31 -04:00
emoji . group = static_cast < EmojiGroup > ( group ) ;
emoji . display_order = display_order ;
2022-11-06 07:57:23 +01:00
emoji . code_points = code_points ( ) ;
2022-09-07 13:39:31 -04:00
return emoji ;
}
2023-02-05 19:02:54 +00:00
constexpr ReadonlySpan < u32 > code_points ( ) const
2022-11-06 07:57:23 +01:00
{
2023-02-05 19:02:54 +00:00
return ReadonlySpan < u32 > ( s_emoji_code_points . data ( ) + code_point_start , code_point_count ) ;
2022-11-06 07:57:23 +01:00
}
2022-09-07 13:39:31 -04:00
@ string_index_type @ name { 0 } ;
2023-02-22 20:51:26 -05:00
@ string_index_type @ image_path { 0 } ;
2022-09-07 13:39:31 -04:00
u8 group { 0 } ;
u32 display_order { 0 } ;
2022-11-06 07:57:23 +01:00
size_t code_point_start { 0 } ;
size_t code_point_count { 0 } ;
2022-09-07 13:39:31 -04:00
} ;
) ~ ~ ~ " );
generator . append ( R " ~~~(
static constexpr Array < EmojiData , @ emojis_size @ > s_emojis { { ) ~ ~ ~ " );
for ( auto const & emoji : emoji_data . emojis ) {
2023-12-16 17:49:34 +03:30
generator . set ( " name " sv , ByteString : : number ( emoji . name ) ) ;
generator . set ( " image_path " sv , ByteString : : number ( emoji . image_path . value_or ( 0 ) ) ) ;
generator . set ( " group " sv , ByteString : : number ( to_underlying ( emoji . group ) ) ) ;
generator . set ( " display_order " sv , ByteString : : number ( emoji . display_order ) ) ;
generator . set ( " code_point_start " sv , ByteString : : number ( emoji . code_point_array_index ) ) ;
generator . set ( " code_point_count " sv , ByteString : : number ( emoji . code_points . size ( ) ) ) ;
2022-09-07 13:39:31 -04:00
generator . append ( R " ~~~(
2023-02-22 20:51:26 -05:00
{ @ name @ , @ image_path @ , @ group @ , @ display_order @ , @ code_point_start @ , @ code_point_count @ } , ) ~ ~ ~ " );
2022-09-07 13:39:31 -04:00
}
generator . append ( R " ~~~(
} } ;
2023-03-05 10:14:25 -05:00
struct EmojiCodePointComparator {
constexpr int operator ( ) ( ReadonlySpan < u32 > code_points , EmojiData const & emoji )
{
auto emoji_code_points = emoji . code_points ( ) ;
if ( code_points . size ( ) ! = emoji_code_points . size ( ) )
return static_cast < int > ( code_points . size ( ) ) - static_cast < int > ( emoji_code_points . size ( ) ) ;
for ( size_t i = 0 ; i < code_points . size ( ) ; + + i ) {
if ( code_points [ i ] ! = emoji_code_points [ i ] )
return static_cast < int > ( code_points [ i ] ) - static_cast < int > ( emoji_code_points [ i ] ) ;
}
return 0 ;
2022-09-07 13:39:31 -04:00
}
2023-03-05 10:14:25 -05:00
} ;
2022-09-07 13:39:31 -04:00
2023-03-05 10:14:25 -05:00
Optional < Emoji > find_emoji_for_code_points ( ReadonlySpan < u32 > code_points )
{
if ( auto const * emoji = binary_search ( s_emojis , code_points , nullptr , EmojiCodePointComparator { } ) )
return emoji - > to_unicode_emoji ( ) ;
2022-09-07 13:39:31 -04:00
return { } ;
}
}
) ~ ~ ~ " );
2023-03-01 16:28:32 +01:00
TRY ( file . write_until_depleted ( generator . as_string_view ( ) . bytes ( ) ) ) ;
2022-09-07 13:39:31 -04:00
return { } ;
}
2023-05-03 18:45:18 -04:00
static ErrorOr < void > generate_emoji_installation ( Core : : InputBufferedFile & file , EmojiData const & emoji_data )
2022-10-26 16:04:38 -04:00
{
StringBuilder builder ;
SourceGenerator generator { builder } ;
auto current_group = Unicode : : EmojiGroup : : Unknown ;
StringView current_subgroup ;
for ( auto const & emoji : emoji_data . emojis ) {
if ( ! emoji . image_path . has_value ( ) )
continue ;
if ( emoji . group = = Unicode : : EmojiGroup : : SerenityOS )
continue ; // SerenityOS emojis are in emoji-serenity.txt
if ( current_group ! = emoji . group ) {
if ( ! builder . is_empty ( ) )
generator . append ( " \n " sv ) ;
generator . set ( " group " sv , Unicode : : emoji_group_to_string ( emoji . group ) ) ;
generator . append ( " # group: @group@ \n " ) ;
current_group = emoji . group ;
}
if ( current_subgroup ! = emoji . subgroup ) {
generator . set ( " subgroup " sv , emoji . subgroup ) ;
generator . append ( " \n # subgroup: @subgroup@ \n " ) ;
current_subgroup = emoji . subgroup ;
}
generator . set ( " emoji " sv , emoji . encoded_code_points ) ;
generator . set ( " name " sv , emoji_data . unique_strings . get ( emoji . name ) ) ;
generator . set ( " status " sv , emoji . status ) ;
generator . append ( " @emoji@ " sv ) ;
generator . append ( " - " sv ) ;
2023-12-16 17:49:34 +03:30
generator . append ( ByteString : : join ( " " sv , emoji . code_points , " U+{:X} " sv ) ) ;
2022-10-26 16:04:38 -04:00
generator . append ( " @name@ (@status@) \n " sv ) ;
}
2023-03-01 16:28:32 +01:00
TRY ( file . write_until_depleted ( generator . as_string_view ( ) . bytes ( ) ) ) ;
2022-10-26 16:04:38 -04:00
return { } ;
}
2022-09-07 13:39:31 -04:00
ErrorOr < int > serenity_main ( Main : : Arguments arguments )
{
StringView generated_header_path ;
StringView generated_implementation_path ;
2022-10-26 16:04:38 -04:00
StringView generated_installation_path ;
2022-09-07 13:39:31 -04:00
StringView emoji_test_path ;
2022-09-09 09:51:03 -04:00
StringView emoji_serenity_path ;
2022-10-26 16:04:38 -04:00
StringView emoji_resource_path ;
2022-09-07 13:39:31 -04:00
Core : : ArgsParser args_parser ;
args_parser . add_option ( generated_header_path , " Path to the Unicode Data header file to generate " , " generated-header-path " , ' h ' , " generated-header-path " ) ;
args_parser . add_option ( generated_implementation_path , " Path to the Unicode Data implementation file to generate " , " generated-implementation-path " , ' c ' , " generated-implementation-path " ) ;
2022-10-26 16:04:38 -04:00
args_parser . add_option ( generated_installation_path , " Path to the emoji.txt file to generate " , " generated-installation-path " , ' i ' , " generated-installation-path " ) ;
2022-09-07 13:39:31 -04:00
args_parser . add_option ( emoji_test_path , " Path to emoji-test.txt file " , " emoji-test-path " , ' e ' , " emoji-test-path " ) ;
2022-09-09 09:51:03 -04:00
args_parser . add_option ( emoji_serenity_path , " Path to emoji-serenity.txt file " , " emoji-serenity-path " , ' s ' , " emoji-serenity-path " ) ;
2022-10-26 16:04:38 -04:00
args_parser . add_option ( emoji_resource_path , " Path to the /res/emoji directory " , " emoji-resource-path " , ' r ' , " emoji-resource-path " ) ;
2022-09-07 13:39:31 -04:00
args_parser . parse ( arguments ) ;
2023-03-22 02:35:30 +11:00
VERIFY ( ! emoji_resource_path . is_empty ( ) & & FileSystem : : exists ( emoji_resource_path ) ) ;
2022-09-07 13:39:31 -04:00
2023-02-22 20:51:26 -05:00
auto emoji_test_file = TRY ( open_file ( emoji_test_path , Core : : File : : OpenMode : : Read ) ) ;
2022-09-07 13:39:31 -04:00
EmojiData emoji_data { } ;
TRY ( parse_emoji_test_data ( * emoji_test_file , emoji_data ) ) ;
2022-10-29 13:58:19 -04:00
if ( ! emoji_serenity_path . is_empty ( ) ) {
2023-02-09 03:02:46 +01:00
auto emoji_serenity_file = TRY ( open_file ( emoji_serenity_path , Core : : File : : OpenMode : : Read ) ) ;
2022-10-29 13:58:19 -04:00
TRY ( parse_emoji_serenity_data ( * emoji_serenity_file , emoji_data ) ) ;
2023-03-03 12:03:22 -05:00
TRY ( validate_emoji ( emoji_resource_path , emoji_data ) ) ;
2022-10-29 13:58:19 -04:00
}
2023-03-05 10:14:25 -05:00
for ( auto & emoji : emoji_data . emojis )
2023-03-01 08:43:22 -05:00
set_image_path_for_emoji ( emoji_resource_path , emoji_data , emoji ) ;
2023-03-05 10:14:25 -05:00
if ( ! generated_installation_path . is_empty ( ) ) {
TRY ( Core : : Directory : : create ( LexicalPath { generated_installation_path } . parent ( ) , Core : : Directory : : CreateDirectories : : Yes ) ) ;
auto generated_installation_file = TRY ( open_file ( generated_installation_path , Core : : File : : OpenMode : : Write ) ) ;
TRY ( generate_emoji_installation ( * generated_installation_file , emoji_data ) ) ;
2022-11-06 07:57:23 +01:00
}
2022-10-29 13:58:19 -04:00
if ( ! generated_header_path . is_empty ( ) ) {
2023-02-09 03:02:46 +01:00
auto generated_header_file = TRY ( open_file ( generated_header_path , Core : : File : : OpenMode : : Write ) ) ;
2022-10-29 13:58:19 -04:00
TRY ( generate_emoji_data_header ( * generated_header_file , emoji_data ) ) ;
}
2023-03-05 10:14:25 -05:00
2022-10-29 13:58:19 -04:00
if ( ! generated_implementation_path . is_empty ( ) ) {
2023-03-05 10:14:25 -05:00
quick_sort ( emoji_data . emojis , [ ] ( auto const & lhs , auto const & rhs ) {
if ( lhs . code_points . size ( ) ! = rhs . code_points . size ( ) )
return lhs . code_points . size ( ) < rhs . code_points . size ( ) ;
for ( size_t i = 0 ; i < lhs . code_points . size ( ) ; + + i ) {
if ( lhs . code_points [ i ] < rhs . code_points [ i ] )
return true ;
if ( lhs . code_points [ i ] > rhs . code_points [ i ] )
return false ;
}
2022-09-07 13:39:31 -04:00
2023-03-05 10:14:25 -05:00
return false ;
} ) ;
2022-10-26 16:04:38 -04:00
2023-03-05 10:14:25 -05:00
size_t code_point_array_index { 0 } ;
for ( auto & emoji : emoji_data . emojis ) {
emoji . code_point_array_index = code_point_array_index ;
code_point_array_index + = emoji . code_points . size ( ) ;
}
auto generated_implementation_file = TRY ( open_file ( generated_implementation_path , Core : : File : : OpenMode : : Write ) ) ;
TRY ( generate_emoji_data_implementation ( * generated_implementation_file , emoji_data ) ) ;
2022-10-26 16:04:38 -04:00
}
2022-09-07 13:39:31 -04:00
return 0 ;
}