Skip to content

Commit 429af10

Browse files
committed
Add Windows C header and script to generate stubs and function pointers.
1 parent 33b6d6f commit 429af10

File tree

4 files changed

+763
-0
lines changed

4 files changed

+763
-0
lines changed
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import os
5+
import re
6+
import sys
7+
8+
HEADER_GUARD_PREFIX = "FIREBASE_ANALYTICS_"
9+
INCLUDE_PATH = "src/windows"
10+
INCLUDE_PREFIX = "analytics/" + INCLUDE_PATH
11+
12+
def generate_function_pointers(header_file_path, output_h_path, output_c_path):
13+
"""
14+
Parses a C header file to generate a header with extern function pointer
15+
declarations and a source file with stub functions, initialized pointers,
16+
and a dynamic loading function for Windows.
17+
18+
Args:
19+
header_file_path (str): The path to the input C header file.
20+
output_h_path (str): The path for the generated C header output file.
21+
output_c_path (str): The path for the generated C source output file.
22+
"""
23+
print(f"Reading header file: {header_file_path}")
24+
try:
25+
with open(header_file_path, 'r', encoding='utf-8') as f:
26+
header_content = f.read()
27+
except FileNotFoundError:
28+
print(f"Error: Header file not found at '{header_file_path}'")
29+
return
30+
31+
function_pattern = re.compile(
32+
r"ANALYTICS_API\s+([\w\s\*]+?)\s+(\w+)\s*\((.*?)\);",
33+
re.DOTALL
34+
)
35+
36+
matches = function_pattern.finditer(header_content)
37+
38+
extern_declarations = []
39+
stub_functions = []
40+
pointer_initializations = []
41+
function_details_for_loader = []
42+
43+
for match in matches:
44+
return_type = match.group(1).strip()
45+
function_name = match.group(2).strip()
46+
params_str = match.group(3).strip()
47+
48+
# Clean up newlines and extra spaces for declarations
49+
cleaned_params_for_decl = re.sub(r'\s+', ' ', params_str) if params_str else ""
50+
51+
# --- Prepare for Stub and Pointer Initialization ---
52+
stub_name = f"Stub_{function_name}"
53+
54+
# Generate the return statement for the stub
55+
if "void" in return_type:
56+
return_statement = " // No return value."
57+
elif "*" in return_type:
58+
return_statement = " return NULL;"
59+
else:
60+
return_statement = " return 0;"
61+
62+
stub_function = (
63+
f"// Stub for {function_name}\n"
64+
f"static {return_type} {stub_name}({params_str}) {{\n"
65+
f"{return_statement}\n"
66+
f"}}"
67+
)
68+
stub_functions.append(stub_function)
69+
70+
# Create the extern declaration for the header file
71+
declaration = f"extern {return_type} (*ptr_{function_name})({cleaned_params_for_decl});"
72+
extern_declarations.append(declaration)
73+
74+
# Create the initialized pointer definition for the source file
75+
pointer_init = f"{return_type} (*ptr_{function_name})({cleaned_params_for_decl}) = &{stub_name};"
76+
pointer_initializations.append(pointer_init)
77+
78+
# Collect details for the dynamic loader function
79+
function_details_for_loader.append((function_name, return_type, cleaned_params_for_decl))
80+
81+
print(f"Found {len(pointer_initializations)} functions. Generating output files...")
82+
83+
# --- Write the Header File (.h) ---
84+
header_guard = HEADER_GUARD_PREFIX + f"{os.path.basename(output_h_path).upper().replace('.', '_')}_"
85+
with open(output_h_path, 'w', encoding='utf-8') as f:
86+
f.write(f"// Generated from {os.path.basename(header_file_path)}\n\n")
87+
f.write(f"#ifndef {header_guard}\n")
88+
f.write(f"#define {header_guard}\n\n")
89+
f.write(f'#include "{INCLUDE_PREFIX}{os.path.basename(header_file_path)}"\n\n')
90+
f.write("#ifdef __cplusplus\n")
91+
f.write('extern "C" {\n')
92+
f.write("#endif\n\n")
93+
f.write("// --- Function Pointer Declarations ---\n")
94+
f.write("\n".join(extern_declarations))
95+
f.write("\n\n// --- Dynamic Loader Declaration for Windows ---\n")
96+
f.write("#if defined(_WIN32)\n")
97+
f.write('#include <windows.h> // For HMODULE\n')
98+
f.write("void LoadAnalyticsFunctions(HMODULE dll_handle);\n")
99+
f.write("#endif // defined(_WIN32)\n")
100+
f.write("\n#ifdef __cplusplus\n")
101+
f.write("}\n")
102+
f.write("#endif\n\n")
103+
f.write(f"#endif // {header_guard}\n")
104+
105+
print(f"Successfully generated header file: {output_h_path}")
106+
107+
# --- Write the Source File (.c) ---
108+
with open(output_c_path, 'w', encoding='utf-8') as f:
109+
f.write(f"// Generated from {os.path.basename(header_file_path)}\n\n")
110+
f.write(f'#include "{INCLUDE_PREFIX}{os.path.basename(output_h_path)}"\n')
111+
f.write('#include <stddef.h>\n\n')
112+
f.write("// --- Stub Function Definitions ---\n")
113+
f.write("\n\n".join(stub_functions))
114+
f.write("\n\n\n// --- Function Pointer Initializations ---\n")
115+
f.write("\n".join(pointer_initializations))
116+
f.write("\n\n// --- Dynamic Loader Function for Windows ---\n")
117+
loader_lines = [
118+
'#if defined(_WIN32)',
119+
'void LoadAnalyticsFunctions(HMODULE dll_handle) {',
120+
' if (!dll_handle) {',
121+
' return;',
122+
' }\n'
123+
]
124+
for name, ret_type, params in function_details_for_loader:
125+
pointer_type_cast = f"({ret_type} (*)({params}))"
126+
proc_check = [
127+
f' FARPROC proc_{name} = GetProcAddress(dll_handle, "{name}");',
128+
f' if (proc_{name}) {{',
129+
f' ptr_{name} = {pointer_type_cast}proc_{name};',
130+
f' }}'
131+
]
132+
loader_lines.extend(proc_check)
133+
loader_lines.append('\n}')
134+
loader_lines.append('#endif // defined(_WIN32)\n')
135+
f.write('\n'.join(loader_lines))
136+
137+
print(f"Successfully generated C source file: {output_c_path}")
138+
139+
140+
if __name__ == '__main__':
141+
parser = argparse.ArgumentParser(
142+
description="Generate C stubs and function pointers from a header file."
143+
)
144+
parser.add_argument(
145+
"--windows_header",
146+
default = os.path.join(os.path.dirname(sys.argv[0]), INCLUDE_PATH, "analytics_windows.h"),
147+
#required=True,
148+
help="Path to the input C header file."
149+
)
150+
parser.add_argument(
151+
"--output_header",
152+
default = os.path.join(os.path.dirname(sys.argv[0]), INCLUDE_PATH, "analytics_dynamic.h"),
153+
#required=True,
154+
help="Path for the generated output header file."
155+
)
156+
parser.add_argument(
157+
"--output_source",
158+
default = os.path.join(os.path.dirname(sys.argv[0]), INCLUDE_PATH, "analytics_dynamic.c"),
159+
#required=True,
160+
help="Path for the generated output source file."
161+
)
162+
163+
args = parser.parse_args()
164+
165+
generate_function_pointers(
166+
args.windows_header,
167+
args.output_header,
168+
args.output_source
169+
)
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
// Generated from analytics_windows.h
2+
3+
#include "analytics/src/windowsanalytics_dynamic.h"
4+
#include <stddef.h>
5+
6+
// --- Stub Function Definitions ---
7+
// Stub for GoogleAnalytics_Item_Create
8+
static GoogleAnalytics_Item* Stub_GoogleAnalytics_Item_Create() {
9+
return NULL;
10+
}
11+
12+
// Stub for GoogleAnalytics_Item_InsertInt
13+
static void Stub_GoogleAnalytics_Item_InsertInt(GoogleAnalytics_Item* item,
14+
const char* key,
15+
int64_t value) {
16+
// No return value.
17+
}
18+
19+
// Stub for GoogleAnalytics_Item_InsertDouble
20+
static void Stub_GoogleAnalytics_Item_InsertDouble(GoogleAnalytics_Item* item,
21+
const char* key,
22+
double value) {
23+
// No return value.
24+
}
25+
26+
// Stub for GoogleAnalytics_Item_InsertString
27+
static void Stub_GoogleAnalytics_Item_InsertString(GoogleAnalytics_Item* item,
28+
const char* key,
29+
const char* value) {
30+
// No return value.
31+
}
32+
33+
// Stub for GoogleAnalytics_Item_Destroy
34+
static void Stub_GoogleAnalytics_Item_Destroy(GoogleAnalytics_Item* item) {
35+
// No return value.
36+
}
37+
38+
// Stub for GoogleAnalytics_ItemVector_Create
39+
static GoogleAnalytics_ItemVector* Stub_GoogleAnalytics_ItemVector_Create() {
40+
return NULL;
41+
}
42+
43+
// Stub for GoogleAnalytics_ItemVector_InsertItem
44+
static void Stub_GoogleAnalytics_ItemVector_InsertItem(GoogleAnalytics_ItemVector* item_vector, GoogleAnalytics_Item* item) {
45+
// No return value.
46+
}
47+
48+
// Stub for GoogleAnalytics_ItemVector_Destroy
49+
static void Stub_GoogleAnalytics_ItemVector_Destroy(GoogleAnalytics_ItemVector* item_vector) {
50+
// No return value.
51+
}
52+
53+
// Stub for GoogleAnalytics_EventParameters_Create
54+
static GoogleAnalytics_EventParameters* Stub_GoogleAnalytics_EventParameters_Create() {
55+
return NULL;
56+
}
57+
58+
// Stub for GoogleAnalytics_EventParameters_InsertInt
59+
static void Stub_GoogleAnalytics_EventParameters_InsertInt(GoogleAnalytics_EventParameters* event_parameter_map, const char* key,
60+
int64_t value) {
61+
// No return value.
62+
}
63+
64+
// Stub for GoogleAnalytics_EventParameters_InsertDouble
65+
static void Stub_GoogleAnalytics_EventParameters_InsertDouble(GoogleAnalytics_EventParameters* event_parameter_map, const char* key,
66+
double value) {
67+
// No return value.
68+
}
69+
70+
// Stub for GoogleAnalytics_EventParameters_InsertString
71+
static void Stub_GoogleAnalytics_EventParameters_InsertString(GoogleAnalytics_EventParameters* event_parameter_map, const char* key,
72+
const char* value) {
73+
// No return value.
74+
}
75+
76+
// Stub for GoogleAnalytics_EventParameters_InsertItemVector
77+
static void Stub_GoogleAnalytics_EventParameters_InsertItemVector(GoogleAnalytics_EventParameters* event_parameter_map, const char* key,
78+
GoogleAnalytics_ItemVector* value) {
79+
// No return value.
80+
}
81+
82+
// Stub for GoogleAnalytics_EventParameters_Destroy
83+
static void Stub_GoogleAnalytics_EventParameters_Destroy(GoogleAnalytics_EventParameters* event_parameter_map) {
84+
// No return value.
85+
}
86+
87+
// Stub for GoogleAnalytics_LogEvent
88+
static void Stub_GoogleAnalytics_LogEvent(const char* name, GoogleAnalytics_EventParameters* parameters) {
89+
// No return value.
90+
}
91+
92+
// Stub for GoogleAnalytics_SetUserProperty
93+
static void Stub_GoogleAnalytics_SetUserProperty(const char* name,
94+
const char* value) {
95+
// No return value.
96+
}
97+
98+
// Stub for GoogleAnalytics_SetUserId
99+
static void Stub_GoogleAnalytics_SetUserId(const char* user_id) {
100+
// No return value.
101+
}
102+
103+
// Stub for GoogleAnalytics_ResetAnalyticsData
104+
static void Stub_GoogleAnalytics_ResetAnalyticsData() {
105+
// No return value.
106+
}
107+
108+
// Stub for GoogleAnalytics_SetAnalyticsCollectionEnabled
109+
static void Stub_GoogleAnalytics_SetAnalyticsCollectionEnabled(bool enabled) {
110+
// No return value.
111+
}
112+
113+
114+
// --- Function Pointer Initializations ---
115+
GoogleAnalytics_Item* (*ptr_GoogleAnalytics_Item_Create)() = &Stub_GoogleAnalytics_Item_Create;
116+
void (*ptr_GoogleAnalytics_Item_InsertInt)(GoogleAnalytics_Item* item, const char* key, int64_t value) = &Stub_GoogleAnalytics_Item_InsertInt;
117+
void (*ptr_GoogleAnalytics_Item_InsertDouble)(GoogleAnalytics_Item* item, const char* key, double value) = &Stub_GoogleAnalytics_Item_InsertDouble;
118+
void (*ptr_GoogleAnalytics_Item_InsertString)(GoogleAnalytics_Item* item, const char* key, const char* value) = &Stub_GoogleAnalytics_Item_InsertString;
119+
void (*ptr_GoogleAnalytics_Item_Destroy)(GoogleAnalytics_Item* item) = &Stub_GoogleAnalytics_Item_Destroy;
120+
GoogleAnalytics_ItemVector* (*ptr_GoogleAnalytics_ItemVector_Create)() = &Stub_GoogleAnalytics_ItemVector_Create;
121+
void (*ptr_GoogleAnalytics_ItemVector_InsertItem)(GoogleAnalytics_ItemVector* item_vector, GoogleAnalytics_Item* item) = &Stub_GoogleAnalytics_ItemVector_InsertItem;
122+
void (*ptr_GoogleAnalytics_ItemVector_Destroy)(GoogleAnalytics_ItemVector* item_vector) = &Stub_GoogleAnalytics_ItemVector_Destroy;
123+
GoogleAnalytics_EventParameters* (*ptr_GoogleAnalytics_EventParameters_Create)() = &Stub_GoogleAnalytics_EventParameters_Create;
124+
void (*ptr_GoogleAnalytics_EventParameters_InsertInt)(GoogleAnalytics_EventParameters* event_parameter_map, const char* key, int64_t value) = &Stub_GoogleAnalytics_EventParameters_InsertInt;
125+
void (*ptr_GoogleAnalytics_EventParameters_InsertDouble)(GoogleAnalytics_EventParameters* event_parameter_map, const char* key, double value) = &Stub_GoogleAnalytics_EventParameters_InsertDouble;
126+
void (*ptr_GoogleAnalytics_EventParameters_InsertString)(GoogleAnalytics_EventParameters* event_parameter_map, const char* key, const char* value) = &Stub_GoogleAnalytics_EventParameters_InsertString;
127+
void (*ptr_GoogleAnalytics_EventParameters_InsertItemVector)(GoogleAnalytics_EventParameters* event_parameter_map, const char* key, GoogleAnalytics_ItemVector* value) = &Stub_GoogleAnalytics_EventParameters_InsertItemVector;
128+
void (*ptr_GoogleAnalytics_EventParameters_Destroy)(GoogleAnalytics_EventParameters* event_parameter_map) = &Stub_GoogleAnalytics_EventParameters_Destroy;
129+
void (*ptr_GoogleAnalytics_LogEvent)(const char* name, GoogleAnalytics_EventParameters* parameters) = &Stub_GoogleAnalytics_LogEvent;
130+
void (*ptr_GoogleAnalytics_SetUserProperty)(const char* name, const char* value) = &Stub_GoogleAnalytics_SetUserProperty;
131+
void (*ptr_GoogleAnalytics_SetUserId)(const char* user_id) = &Stub_GoogleAnalytics_SetUserId;
132+
void (*ptr_GoogleAnalytics_ResetAnalyticsData)() = &Stub_GoogleAnalytics_ResetAnalyticsData;
133+
void (*ptr_GoogleAnalytics_SetAnalyticsCollectionEnabled)(bool enabled) = &Stub_GoogleAnalytics_SetAnalyticsCollectionEnabled;
134+
135+
// --- Dynamic Loader Function for Windows ---
136+
#if defined(_WIN32)
137+
void LoadAnalyticsFunctions(HMODULE dll_handle) {
138+
if (!dll_handle) {
139+
return;
140+
}
141+
142+
FARPROC proc_GoogleAnalytics_Item_Create = GetProcAddress(dll_handle, "GoogleAnalytics_Item_Create");
143+
if (proc_GoogleAnalytics_Item_Create) {
144+
ptr_GoogleAnalytics_Item_Create = (GoogleAnalytics_Item* (*)())proc_GoogleAnalytics_Item_Create;
145+
}
146+
FARPROC proc_GoogleAnalytics_Item_InsertInt = GetProcAddress(dll_handle, "GoogleAnalytics_Item_InsertInt");
147+
if (proc_GoogleAnalytics_Item_InsertInt) {
148+
ptr_GoogleAnalytics_Item_InsertInt = (void (*)(GoogleAnalytics_Item* item, const char* key, int64_t value))proc_GoogleAnalytics_Item_InsertInt;
149+
}
150+
FARPROC proc_GoogleAnalytics_Item_InsertDouble = GetProcAddress(dll_handle, "GoogleAnalytics_Item_InsertDouble");
151+
if (proc_GoogleAnalytics_Item_InsertDouble) {
152+
ptr_GoogleAnalytics_Item_InsertDouble = (void (*)(GoogleAnalytics_Item* item, const char* key, double value))proc_GoogleAnalytics_Item_InsertDouble;
153+
}
154+
FARPROC proc_GoogleAnalytics_Item_InsertString = GetProcAddress(dll_handle, "GoogleAnalytics_Item_InsertString");
155+
if (proc_GoogleAnalytics_Item_InsertString) {
156+
ptr_GoogleAnalytics_Item_InsertString = (void (*)(GoogleAnalytics_Item* item, const char* key, const char* value))proc_GoogleAnalytics_Item_InsertString;
157+
}
158+
FARPROC proc_GoogleAnalytics_Item_Destroy = GetProcAddress(dll_handle, "GoogleAnalytics_Item_Destroy");
159+
if (proc_GoogleAnalytics_Item_Destroy) {
160+
ptr_GoogleAnalytics_Item_Destroy = (void (*)(GoogleAnalytics_Item* item))proc_GoogleAnalytics_Item_Destroy;
161+
}
162+
FARPROC proc_GoogleAnalytics_ItemVector_Create = GetProcAddress(dll_handle, "GoogleAnalytics_ItemVector_Create");
163+
if (proc_GoogleAnalytics_ItemVector_Create) {
164+
ptr_GoogleAnalytics_ItemVector_Create = (GoogleAnalytics_ItemVector* (*)())proc_GoogleAnalytics_ItemVector_Create;
165+
}
166+
FARPROC proc_GoogleAnalytics_ItemVector_InsertItem = GetProcAddress(dll_handle, "GoogleAnalytics_ItemVector_InsertItem");
167+
if (proc_GoogleAnalytics_ItemVector_InsertItem) {
168+
ptr_GoogleAnalytics_ItemVector_InsertItem = (void (*)(GoogleAnalytics_ItemVector* item_vector, GoogleAnalytics_Item* item))proc_GoogleAnalytics_ItemVector_InsertItem;
169+
}
170+
FARPROC proc_GoogleAnalytics_ItemVector_Destroy = GetProcAddress(dll_handle, "GoogleAnalytics_ItemVector_Destroy");
171+
if (proc_GoogleAnalytics_ItemVector_Destroy) {
172+
ptr_GoogleAnalytics_ItemVector_Destroy = (void (*)(GoogleAnalytics_ItemVector* item_vector))proc_GoogleAnalytics_ItemVector_Destroy;
173+
}
174+
FARPROC proc_GoogleAnalytics_EventParameters_Create = GetProcAddress(dll_handle, "GoogleAnalytics_EventParameters_Create");
175+
if (proc_GoogleAnalytics_EventParameters_Create) {
176+
ptr_GoogleAnalytics_EventParameters_Create = (GoogleAnalytics_EventParameters* (*)())proc_GoogleAnalytics_EventParameters_Create;
177+
}
178+
FARPROC proc_GoogleAnalytics_EventParameters_InsertInt = GetProcAddress(dll_handle, "GoogleAnalytics_EventParameters_InsertInt");
179+
if (proc_GoogleAnalytics_EventParameters_InsertInt) {
180+
ptr_GoogleAnalytics_EventParameters_InsertInt = (void (*)(GoogleAnalytics_EventParameters* event_parameter_map, const char* key, int64_t value))proc_GoogleAnalytics_EventParameters_InsertInt;
181+
}
182+
FARPROC proc_GoogleAnalytics_EventParameters_InsertDouble = GetProcAddress(dll_handle, "GoogleAnalytics_EventParameters_InsertDouble");
183+
if (proc_GoogleAnalytics_EventParameters_InsertDouble) {
184+
ptr_GoogleAnalytics_EventParameters_InsertDouble = (void (*)(GoogleAnalytics_EventParameters* event_parameter_map, const char* key, double value))proc_GoogleAnalytics_EventParameters_InsertDouble;
185+
}
186+
FARPROC proc_GoogleAnalytics_EventParameters_InsertString = GetProcAddress(dll_handle, "GoogleAnalytics_EventParameters_InsertString");
187+
if (proc_GoogleAnalytics_EventParameters_InsertString) {
188+
ptr_GoogleAnalytics_EventParameters_InsertString = (void (*)(GoogleAnalytics_EventParameters* event_parameter_map, const char* key, const char* value))proc_GoogleAnalytics_EventParameters_InsertString;
189+
}
190+
FARPROC proc_GoogleAnalytics_EventParameters_InsertItemVector = GetProcAddress(dll_handle, "GoogleAnalytics_EventParameters_InsertItemVector");
191+
if (proc_GoogleAnalytics_EventParameters_InsertItemVector) {
192+
ptr_GoogleAnalytics_EventParameters_InsertItemVector = (void (*)(GoogleAnalytics_EventParameters* event_parameter_map, const char* key, GoogleAnalytics_ItemVector* value))proc_GoogleAnalytics_EventParameters_InsertItemVector;
193+
}
194+
FARPROC proc_GoogleAnalytics_EventParameters_Destroy = GetProcAddress(dll_handle, "GoogleAnalytics_EventParameters_Destroy");
195+
if (proc_GoogleAnalytics_EventParameters_Destroy) {
196+
ptr_GoogleAnalytics_EventParameters_Destroy = (void (*)(GoogleAnalytics_EventParameters* event_parameter_map))proc_GoogleAnalytics_EventParameters_Destroy;
197+
}
198+
FARPROC proc_GoogleAnalytics_LogEvent = GetProcAddress(dll_handle, "GoogleAnalytics_LogEvent");
199+
if (proc_GoogleAnalytics_LogEvent) {
200+
ptr_GoogleAnalytics_LogEvent = (void (*)(const char* name, GoogleAnalytics_EventParameters* parameters))proc_GoogleAnalytics_LogEvent;
201+
}
202+
FARPROC proc_GoogleAnalytics_SetUserProperty = GetProcAddress(dll_handle, "GoogleAnalytics_SetUserProperty");
203+
if (proc_GoogleAnalytics_SetUserProperty) {
204+
ptr_GoogleAnalytics_SetUserProperty = (void (*)(const char* name, const char* value))proc_GoogleAnalytics_SetUserProperty;
205+
}
206+
FARPROC proc_GoogleAnalytics_SetUserId = GetProcAddress(dll_handle, "GoogleAnalytics_SetUserId");
207+
if (proc_GoogleAnalytics_SetUserId) {
208+
ptr_GoogleAnalytics_SetUserId = (void (*)(const char* user_id))proc_GoogleAnalytics_SetUserId;
209+
}
210+
FARPROC proc_GoogleAnalytics_ResetAnalyticsData = GetProcAddress(dll_handle, "GoogleAnalytics_ResetAnalyticsData");
211+
if (proc_GoogleAnalytics_ResetAnalyticsData) {
212+
ptr_GoogleAnalytics_ResetAnalyticsData = (void (*)())proc_GoogleAnalytics_ResetAnalyticsData;
213+
}
214+
FARPROC proc_GoogleAnalytics_SetAnalyticsCollectionEnabled = GetProcAddress(dll_handle, "GoogleAnalytics_SetAnalyticsCollectionEnabled");
215+
if (proc_GoogleAnalytics_SetAnalyticsCollectionEnabled) {
216+
ptr_GoogleAnalytics_SetAnalyticsCollectionEnabled = (void (*)(bool enabled))proc_GoogleAnalytics_SetAnalyticsCollectionEnabled;
217+
}
218+
219+
}
220+
#endif // defined(_WIN32)

0 commit comments

Comments
 (0)