infix
A JIT-Powered FFI Library for C
Loading...
Searching...
No Matches
type_registry.c
Go to the documentation of this file.
1
41#include <ctype.h>
42#include <string.h>
43
46
53#define INITIAL_REGISTRY_BUCKETS 61
54
69
70// Hash Table Implementation
71
79static uint64_t _registry_hash_string(const char * str) {
80 uint64_t hash = 5381;
81 int c;
82 while ((c = *str++))
83 hash = ((hash << 5) + hash) + c; // hash * 33 + c
84 return hash;
85}
86
95 if (!registry || !name)
96 return nullptr;
97 size_t index = _registry_hash_string(name) % registry->num_buckets;
98 // Traverse the linked list (chain) at the computed bucket index.
99 for (_infix_registry_entry_t * current = registry->buckets[index]; current; current = current->next)
100 if (strcmp(current->name, name) == 0)
101 return current;
102 return nullptr;
103}
104
119 size_t index = _registry_hash_string(name) % registry->num_buckets;
120 _infix_registry_entry_t * new_entry =
122 if (!new_entry) {
124 return nullptr;
125 }
126 size_t name_len = strlen(name) + 1;
127 char * name_copy = infix_arena_alloc(registry->arena, name_len, 1);
128 if (!name_copy) {
130 return nullptr;
131 }
132 infix_memcpy((void *)name_copy, name, name_len);
133 new_entry->name = name_copy;
134 new_entry->type = nullptr;
135 new_entry->is_forward_declaration = false;
136 // Prepend to the linked list in the bucket.
137 new_entry->next = registry->buckets[index];
138 registry->buckets[index] = new_entry;
140 return new_entry;
141}
142
143// Public API: Registry Lifecycle
144
182
220
231 if (!registry)
232 return;
233 // Only destroy the arena if it was created internally by `infix_registry_create`.
236 // Always free the registry struct itself.
238}
239
240// Internal Type Graph Resolution
241
261 infix_type ** type_ptr,
263 resolve_memo_node_t ** memo_head) {
264 if (!type_ptr || !*type_ptr || !(*type_ptr)->is_arena_allocated)
265 return INFIX_SUCCESS;
266
267 infix_type * type = *type_ptr;
268
269 // Cycle detection: If we've seen this node before, we're in a cycle.
270 // Return success to break the loop.
271 for (resolve_memo_node_t * node = *memo_head; node != nullptr; node = node->next)
272 if (node->src == type)
273 return INFIX_SUCCESS;
274
275 // Allocate the memoization node from the stable temporary arena.
276 resolve_memo_node_t * memo_node =
277 infix_arena_alloc(temp_arena, sizeof(resolve_memo_node_t), _Alignof(resolve_memo_node_t));
278 if (!memo_node) {
281 }
282 memo_node->src = type;
283 memo_node->next = *memo_head;
284 *memo_head = memo_node;
285
287 if (!registry) {
290 }
291 const char * name = type->meta.named_reference.name;
293 if (!entry || !entry->type) {
296 }
297 *type_ptr = entry->type;
298 return INFIX_SUCCESS;
299 }
300
302 switch (type->category) {
305 temp_arena, &type->meta.pointer_info.pointee_type, registry, memo_head);
306 break;
307 case INFIX_TYPE_ARRAY:
308 status =
310 break;
312 case INFIX_TYPE_UNION:
313 for (size_t i = 0; i < type->meta.aggregate_info.num_members; ++i) {
315 temp_arena, &type->meta.aggregate_info.members[i].type, registry, memo_head);
316 if (status != INFIX_SUCCESS)
317 break;
318 }
319 break;
322 temp_arena, &type->meta.func_ptr_info.return_type, registry, memo_head);
323 if (status != INFIX_SUCCESS)
324 break;
325 for (size_t i = 0; i < type->meta.func_ptr_info.num_args; ++i) {
327 temp_arena, &type->meta.func_ptr_info.args[i].type, registry, memo_head);
328 if (status != INFIX_SUCCESS)
329 break;
330 }
331 break;
332 case INFIX_TYPE_ENUM:
334 temp_arena, &type->meta.enum_info.underlying_type, registry, memo_head);
335 break;
337 status =
339 break;
342 temp_arena, &type->meta.vector_info.element_type, registry, memo_head);
343 break;
344 default:
345 break;
346 }
347
348 return status;
349}
350
359 // Create a temporary arena solely for the visited list's lifetime.
360 infix_arena_t * temp_arena = infix_arena_create(1024);
361 if (!temp_arena) {
364 }
365
366 resolve_memo_node_t * memo_head = nullptr;
367 infix_status status = _resolve_type_graph_inplace_recursive(temp_arena, type_ptr, registry, &memo_head);
368
369 infix_arena_destroy(temp_arena);
370 return status;
371}
372
373// Public API: Type Registration
374
383typedef struct {
384 const char * p;
385 const char * start;
387
394 while (1) {
395 while (isspace((unsigned char)*state->p))
396 state->p++;
397 if (*state->p == '#') // Skip comments
398 while (*state->p != '\n' && *state->p != '\0')
399 state->p++;
400 else
401 break;
402 }
403}
404
413static char * _registry_parser_parse_name(_registry_parser_state_t * state, char * buffer, size_t buf_size) {
415 const char * name_start = state->p;
416 while (isalnum((unsigned char)*state->p) || *state->p == '_' || *state->p == ':') {
417 if (*state->p == ':' && state->p[1] != ':')
418 break; // Handle single colon as non-identifier char.
419 if (*state->p == ':')
420 state->p++; // Skip the first ':' of '::'
421 state->p++;
422 }
423 size_t len = state->p - name_start;
424 if (len == 0 || len >= buf_size)
425 return nullptr;
426 infix_memcpy(buffer, name_start, len);
427 buffer[len] = '\0';
428 return buffer;
429}
430
462 if (!registry || !definitions) {
465 }
466
467 _registry_parser_state_t state = {.p = definitions, .start = definitions};
469
470 // A temporary structure to hold information about each definition found in Pass 1.
471 struct def_info {
473 const char * def_body_start;
474 size_t def_body_len;
475 };
476
477 size_t defs_capacity = 64; // Start with an initial capacity.
478 struct def_info * defs_found = infix_malloc(sizeof(struct def_info) * defs_capacity);
479 if (!defs_found) {
482 }
483 size_t num_defs_found = 0;
484 infix_status final_status = INFIX_SUCCESS;
485
486 // Pass 1: Scan & Index all names and their definition bodies.
487 while (*state.p != '\0') {
489 if (*state.p == '\0')
490 break;
491
492 if (*state.p != '@') {
494 final_status = INFIX_ERROR_INVALID_ARGUMENT;
495 goto cleanup;
496 }
497 state.p++;
498
499 char name_buffer[256];
500 if (!_registry_parser_parse_name(&state, name_buffer, sizeof(name_buffer))) {
502 final_status = INFIX_ERROR_INVALID_ARGUMENT;
503 goto cleanup;
504 }
505 _infix_registry_entry_t * entry = _registry_lookup(registry, name_buffer);
507
508 if (*state.p == '=') { // This is a full definition.
509 state.p++;
511 // It's an error to redefine a type that wasn't a forward declaration.
512 if (entry && !entry->is_forward_declaration) {
514 final_status = INFIX_ERROR_INVALID_ARGUMENT;
515 goto cleanup;
516 }
517 if (!entry) { // If it doesn't exist, create it.
518 entry = _registry_insert(registry, name_buffer);
519 if (!entry) {
520 final_status = INFIX_ERROR_ALLOCATION_FAILED;
521 goto cleanup;
522 }
523 }
524 if (num_defs_found >= defs_capacity) {
525 size_t new_capacity = defs_capacity * 2;
526 struct def_info * new_defs = infix_realloc(defs_found, sizeof(struct def_info) * new_capacity);
527 if (!new_defs) {
529 final_status = INFIX_ERROR_ALLOCATION_FAILED;
530 goto cleanup;
531 }
532 defs_found = new_defs;
533 defs_capacity = new_capacity;
534 }
535
536 // Find the end of the type definition body (the next top-level ';').
537 defs_found[num_defs_found].entry = entry;
538 defs_found[num_defs_found].def_body_start = state.p;
539 int nest_level = 0;
540 const char * body_end = state.p;
541 while (*body_end != '\0' &&
542 !(*body_end == ';' &&
543 nest_level == 0)) { // Explicitly check for and skip over the '->' token as a single unit.
544 if (*body_end == '-' && body_end[1] == '>') {
545 body_end += 2; // Advance the pointer past the entire token.
546 continue; // Continue to the next character in the loop.
547 }
548 if (*body_end == '{' || *body_end == '<' || *body_end == '(' || *body_end == '[')
549 nest_level++;
550 if (*body_end == '}' || *body_end == '>' || *body_end == ')' || *body_end == ']')
551 nest_level--;
552 body_end++;
553 }
554 defs_found[num_defs_found].def_body_len = body_end - state.p;
555 state.p = body_end;
556 num_defs_found++;
557 }
558 else if (*state.p == ';') { // This is a forward declaration.
559 if (entry) { // Cannot forward-declare an already-defined type.
561 final_status = INFIX_ERROR_INVALID_ARGUMENT;
562 goto cleanup;
563 }
564 entry = _registry_insert(registry, name_buffer);
565 if (!entry) {
566 final_status = INFIX_ERROR_ALLOCATION_FAILED;
567 goto cleanup;
568 }
569 entry->is_forward_declaration = true;
570 }
571 else {
573 final_status = INFIX_ERROR_INVALID_ARGUMENT;
574 goto cleanup;
575 }
576
577 if (*state.p == ';')
578 state.p++;
579 }
580
581 // Pass 2: Parse the bodies of all found definitions into the registry.
582 for (size_t i = 0; i < num_defs_found; ++i) {
583 _infix_registry_entry_t * entry = defs_found[i].entry;
584 // Make a temporary, null-terminated copy of the definition body substring.
585 char * body_copy = infix_malloc(defs_found[i].def_body_len + 1);
586 if (!body_copy) {
588 final_status = INFIX_ERROR_ALLOCATION_FAILED;
589 goto cleanup;
590 }
591 infix_memcpy(body_copy, defs_found[i].def_body_start, defs_found[i].def_body_len);
592 body_copy[defs_found[i].def_body_len] = '\0';
593
594 // "Parse" step: parse into a temporary arena.
595 infix_type * raw_type = nullptr;
596 infix_arena_t * parser_arena = nullptr;
597 infix_status status = _infix_parse_type_internal(&raw_type, &parser_arena, body_copy);
598 infix_free(body_copy);
599 if (status != INFIX_SUCCESS) {
600 // Adjust the error position to be relative to the full definition string.
602 _infix_set_error(err.category, err.code, (defs_found[i].def_body_start - definitions) + err.position);
603 final_status = INFIX_ERROR_INVALID_ARGUMENT;
604 infix_arena_destroy(parser_arena);
605 goto cleanup;
606 }
607
608 const infix_type * type_to_alias = raw_type;
609
610 // If the RHS is another named type (e.g., @MyAlias = @ExistingType),
611 // we need to resolve it first to get the actual type we're aliasing.
612 if (raw_type->category == INFIX_TYPE_NAMED_REFERENCE) {
614 if (existing_entry && existing_entry->type)
615 type_to_alias = existing_entry->type;
616 else {
619 (size_t)(defs_found[i].def_body_start - definitions));
620 final_status = INFIX_ERROR_INVALID_ARGUMENT;
621 infix_arena_destroy(parser_arena);
622 goto cleanup;
623 }
624 }
625
626 if (!type_to_alias->is_arena_allocated) {
627 // This is a static type (e.g., primitive). We MUST create a mutable
628 // copy in the registry's arena before we can attach a name to it.
629 // This prevents corrupting the global static singletons.
630 infix_type * prim_copy = infix_arena_alloc(registry->arena, sizeof(infix_type), _Alignof(infix_type));
631 if (!prim_copy) {
632 final_status = INFIX_ERROR_ALLOCATION_FAILED;
633 infix_arena_destroy(parser_arena);
634 goto cleanup;
635 }
636 *prim_copy = *type_to_alias;
637 prim_copy->is_arena_allocated = true;
638 prim_copy->arena = registry->arena;
639 entry->type = prim_copy;
640 }
641 else
642 entry->type = _copy_type_graph_to_arena(registry->arena, type_to_alias);
643
644 infix_arena_destroy(parser_arena); // The temporary raw_type is no longer needed.
645
646 if (!entry->type) {
648 final_status = INFIX_ERROR_ALLOCATION_FAILED;
649 goto cleanup;
650 }
651
652 // Attach the new alias name to the copied type. This is the key to preserving semantic names.
653 entry->type->name = entry->name;
654 entry->is_forward_declaration = false;
655 }
656
657 // Pass 3: Resolve and layout all the newly defined types.
658 for (size_t i = 0; i < num_defs_found; ++i) {
659 _infix_registry_entry_t * entry = defs_found[i].entry;
660 if (entry->type) {
661 // "Resolve" and "Layout" steps.
663 final_status = INFIX_ERROR_INVALID_ARGUMENT;
664 goto cleanup;
665 }
667 }
668 }
669
670cleanup:
671 infix_free(defs_found);
672 return final_status;
673}
674
675// Registry Introspection API Implementation
696 // Return an iterator positioned before the first element.
697 // The first call to next() will advance it to the first valid element.
698 return (infix_registry_iterator_t){registry, 0, nullptr};
699}
700
709 if (!iter || !iter->registry)
710 return false;
711
712 const _infix_registry_entry_t * entry = iter->current_entry;
713
714 // If we have a current entry, start from the next one in the chain.
715 if (iter->current_entry)
716 entry = entry->next;
717
718 // Otherwise, if we are starting, begin with the head of the current bucket.
719 else if (iter->current_bucket < iter->registry->num_buckets)
720 entry = iter->registry->buckets[iter->current_bucket];
721
722 while (true) {
723 // Traverse the current chain looking for a valid entry.
724 while (entry) {
725 if (entry->type && !entry->is_forward_declaration) {
726 // Found one. Update the iterator and return successfully.
727 iter->current_entry = entry;
728 return true;
729 }
730 entry = entry->next;
731 }
732
733 // If we're here, the current chain is exhausted. Move to the next bucket.
734 iter->current_bucket++;
735
736 // If there are no more buckets, we're done.
737 if (iter->current_bucket >= iter->registry->num_buckets) {
738 iter->current_entry = nullptr;
739 return false;
740 }
741
742 // Start the search from the head of the new bucket.
743 entry = iter->registry->buckets[iter->current_bucket];
744 }
745}
746
756 if (!iter || !iter->current_entry)
757 return nullptr;
758 return iter->current_entry->name;
759}
760
770 if (!iter || !iter->current_entry)
771 return nullptr;
772 return iter->current_entry->type;
773}
774
786 if (!registry || !name)
787 return false;
789 // It's defined if an entry exists, it has a type, and it's not a lingering forward declaration.
790 return entry != nullptr && entry->type != nullptr && !entry->is_forward_declaration;
791}
792
803 if (!registry || !name)
804 return nullptr;
806 if (entry && entry->type && !entry->is_forward_declaration)
807 return entry->type;
808 return nullptr;
809}
infix_arena_t * arena
Definition 005_layouts.c:68
const char * definitions
Definition 008_registry_introspection.c:39
infix_registry_t * registry
Definition 008_registry_introspection.c:35
infix_status status
Definition 103_unions.c:66
#define c23_nodiscard
A compatibility macro for the C23 [[nodiscard]] attribute.
Definition compat_c23.h:113
#define INFIX_TLS
Definition error.c:58
infix_error_details_t infix_get_last_error(void)
Retrieves detailed information about the last error that occurred on the current thread.
Definition error.c:270
@ INFIX_CODE_UNRESOLVED_NAMED_TYPE
Definition infix.h:1346
@ INFIX_CODE_UNEXPECTED_TOKEN
Definition infix.h:1335
@ INFIX_CODE_UNKNOWN
Definition infix.h:1327
@ INFIX_CODE_OUT_OF_MEMORY
Definition infix.h:1330
@ INFIX_CATEGORY_ALLOCATION
Definition infix.h:1316
@ INFIX_CATEGORY_GENERAL
Definition infix.h:1315
@ INFIX_CATEGORY_PARSER
Definition infix.h:1317
struct infix_type_t::@0::@1 pointer_info
Metadata for INFIX_TYPE_POINTER.
size_t position
Definition infix.h:1363
union infix_type_t::@0 meta
A union containing metadata specific to the type's category.
struct infix_type_t::@0::@7 vector_info
Metadata for INFIX_TYPE_VECTOR.
infix_error_category_t category
Definition infix.h:1361
infix_type * type
Definition infix.h:289
struct infix_type_t::@0::@4 func_ptr_info
Metadata for INFIX_TYPE_REVERSE_TRAMPOLINE.
infix_arena_t * arena
Definition infix.h:217
infix_struct_member * members
Definition infix.h:231
struct infix_type_t::@0::@6 complex_info
Metadata for INFIX_TYPE_COMPLEX.
const char * name
Definition infix.h:212
infix_function_argument * args
Definition infix.h:244
infix_status
Enumerates the possible status codes returned by infix API functions.
Definition infix.h:389
infix_type_category category
Definition infix.h:213
struct infix_type_t::@0::@2 aggregate_info
Metadata for INFIX_TYPE_STRUCT and INFIX_TYPE_UNION.
struct infix_type_t::@0::@3 array_info
Metadata for INFIX_TYPE_ARRAY.
struct infix_type_t * pointee_type
Definition infix.h:226
infix_type * type
Definition infix.h:279
struct infix_type_t * element_type
Definition infix.h:237
struct infix_type_t * return_type
Definition infix.h:243
struct infix_type_t::@0::@5 enum_info
Metadata for INFIX_TYPE_ENUM.
struct infix_type_t * base_type
Definition infix.h:256
infix_error_code_t code
Definition infix.h:1362
size_t num_members
Definition infix.h:232
struct infix_type_t * underlying_type
Definition infix.h:251
struct infix_type_t::@0::@8 named_reference
Metadata for INFIX_TYPE_NAMED_REFERENCE.
size_t num_args
Definition infix.h:245
bool is_arena_allocated
Definition infix.h:216
@ INFIX_ERROR_ALLOCATION_FAILED
Definition infix.h:391
@ INFIX_SUCCESS
Definition infix.h:390
@ INFIX_ERROR_INVALID_ARGUMENT
Definition infix.h:392
#define infix_free
A macro that can be defined to override the default free function.
Definition infix.h:330
void infix_arena_destroy(infix_arena_t *)
Destroys an arena and frees all memory allocated from it.
Definition arena.c:90
#define infix_memcpy
A macro that can be defined to override the default memcpy function.
Definition infix.h:335
c23_nodiscard void * infix_arena_calloc(infix_arena_t *, size_t, size_t, size_t)
Allocates and zero-initializes a block of memory from an arena.
Definition arena.c:198
#define infix_realloc
A macro that can be defined to override the default realloc function.
Definition infix.h:325
c23_nodiscard void * infix_arena_alloc(infix_arena_t *, size_t, size_t)
Allocates a block of memory from an arena.
Definition arena.c:126
#define infix_malloc
A macro that can be defined to override the default malloc function.
Definition infix.h:315
c23_nodiscard infix_arena_t * infix_arena_create(size_t)
Creates a new memory arena.
Definition arena.c:55
void infix_registry_destroy(infix_registry_t *registry)
Destroys a type registry and frees all associated memory.
Definition type_registry.c:230
c23_nodiscard infix_status infix_register_types(infix_registry_t *registry, const char *definitions)
Parses a string of type definitions and adds them to a registry.
Definition type_registry.c:460
c23_nodiscard infix_registry_t * infix_registry_create(void)
Creates a new, empty named type registry.
Definition type_registry.c:156
c23_nodiscard const infix_type * infix_registry_lookup_type(const infix_registry_t *registry, const char *name)
Retrieves the canonical infix_type object for a given name from the registry.
Definition type_registry.c:802
c23_nodiscard infix_registry_t * infix_registry_create_in_arena(infix_arena_t *arena)
Creates a new, empty named type registry that allocates from a user-provided arena.
Definition type_registry.c:196
c23_nodiscard const infix_type * infix_registry_iterator_get_type(const infix_registry_iterator_t *iter)
Gets the infix_type object of the type at the iterator's current position.
Definition type_registry.c:769
c23_nodiscard infix_registry_iterator_t infix_registry_iterator_begin(const infix_registry_t *registry)
Initializes an iterator for traversing the types in a registry.
Definition type_registry.c:695
c23_nodiscard bool infix_registry_iterator_next(infix_registry_iterator_t *iter)
Advances the iterator to the next defined type in the registry.
Definition type_registry.c:708
c23_nodiscard const char * infix_registry_iterator_get_name(const infix_registry_iterator_t *iter)
Gets the name of the type at the iterator's current position.
Definition type_registry.c:755
c23_nodiscard bool infix_registry_is_defined(const infix_registry_t *registry, const char *name)
Checks if a type with the given name is fully defined in the registry.
Definition type_registry.c:785
@ INFIX_TYPE_UNION
Definition infix.h:166
@ INFIX_TYPE_COMPLEX
Definition infix.h:170
@ INFIX_TYPE_ARRAY
Definition infix.h:167
@ INFIX_TYPE_VECTOR
Definition infix.h:171
@ INFIX_TYPE_POINTER
Definition infix.h:164
@ INFIX_TYPE_NAMED_REFERENCE
Definition infix.h:172
@ INFIX_TYPE_REVERSE_TRAMPOLINE
Definition infix.h:168
@ INFIX_TYPE_ENUM
Definition infix.h:169
@ INFIX_TYPE_STRUCT
Definition infix.h:165
Internal data structures, function prototypes, and constants.
void _infix_set_error(infix_error_category_t category, infix_error_code_t code, size_t position)
Sets the thread-local error state with detailed information.
Definition error.c:155
void _infix_type_recalculate_layout(infix_type *type)
Recalculates the layout of a fully resolved type graph.
Definition types.c:780
c23_nodiscard infix_status _infix_parse_type_internal(infix_type **, infix_arena_t **, const char *)
The internal core of the signature parser.
Definition signature.c:979
infix_type * _copy_type_graph_to_arena(infix_arena_t *, const infix_type *)
Performs a deep copy of a type graph into a destination arena.
Definition types.c:971
void _infix_clear_error(void)
Clears the thread-local error state.
Definition error.c:257
A single entry in the registry's hash table.
Definition infix_internals.h:161
bool is_forward_declaration
Definition infix_internals.h:164
const char * name
Definition infix_internals.h:162
infix_type * type
Definition infix_internals.h:163
struct _infix_registry_entry_t * next
Definition infix_internals.h:165
Definition type_registry.c:383
const char * start
Definition type_registry.c:385
const char * p
Definition type_registry.c:384
Internal definition of a memory arena.
Definition infix_internals.h:146
Provides detailed, thread-local information about the last error that occurred.
Definition infix.h:1360
Internal definition of a registry iterator.
Definition infix_internals.h:190
const _infix_registry_entry_t * current_entry
Definition infix_internals.h:193
const infix_registry_t * registry
Definition infix_internals.h:191
size_t current_bucket
Definition infix_internals.h:192
Internal definition of a named type registry.
Definition infix_internals.h:175
size_t num_items
Definition infix_internals.h:179
bool is_external_arena
Definition infix_internals.h:177
infix_arena_t * arena
Definition infix_internals.h:176
_infix_registry_entry_t ** buckets
Definition infix_internals.h:180
size_t num_buckets
Definition infix_internals.h:178
A semi-opaque structure that describes a C type.
Definition infix.h:211
Definition type_registry.c:65
struct resolve_memo_node_t * next
Definition type_registry.c:67
infix_type * src
Definition type_registry.c:66
static uint64_t _registry_hash_string(const char *str)
Definition type_registry.c:79
#define INITIAL_REGISTRY_BUCKETS
Definition type_registry.c:53
c23_nodiscard infix_status _infix_resolve_type_graph_inplace(infix_type **type_ptr, infix_registry_t *registry)
Resolves all named type references in a type graph in-place.
Definition type_registry.c:358
static _infix_registry_entry_t * _registry_lookup(infix_registry_t *registry, const char *name)
Definition type_registry.c:94
static _infix_registry_entry_t * _registry_insert(infix_registry_t *registry, const char *name)
Definition type_registry.c:118
static char * _registry_parser_parse_name(_registry_parser_state_t *state, char *buffer, size_t buf_size)
Definition type_registry.c:413
static void _registry_parser_skip_whitespace(_registry_parser_state_t *state)
Definition type_registry.c:393
static infix_status _resolve_type_graph_inplace_recursive(infix_arena_t *temp_arena, infix_type **type_ptr, infix_registry_t *registry, resolve_memo_node_t **memo_head)
Definition type_registry.c:260
INFIX_TLS const char * g_infix_last_signature_context
A thread-local pointer to the full signature string being parsed.
Definition error.c:89