From 5d83250c54d12511a9cb14f023260f83aed6ce5d Mon Sep 17 00:00:00 2001 From: Mike Rivnak Date: Tue, 10 Dec 2024 21:29:30 -0500 Subject: [PATCH] c: react --- c/react/HELP.md | 73 ++++++++++ c/react/README.md | 32 +++++ c/react/react.c | 354 ++++++++++++++++++++++++++++++++++++++++++++++ c/react/react.h | 29 ++++ 4 files changed, 488 insertions(+) create mode 100644 c/react/HELP.md create mode 100644 c/react/README.md create mode 100644 c/react/react.c create mode 100644 c/react/react.h diff --git a/c/react/HELP.md b/c/react/HELP.md new file mode 100644 index 0000000..560c9df --- /dev/null +++ b/c/react/HELP.md @@ -0,0 +1,73 @@ +# Help + +## Running the tests + +Get the first test compiling, linking and passing by following the [three rules of test-driven development][3-tdd-rules]. + +The included makefile can be used to create and run the tests using the `test` task. + +```console +$ make test +``` + +Create just the functions you need to satisfy any compiler errors and get the test to fail. +Then write just enough code to get the test to pass. +Once you've done that, move onto the next test. + +As you progress through the tests, take the time to refactor your implementation for readability and expressiveness and then go on to the next test. + +Try to use standard C99 facilities in preference to writing your own low-level algorithms or facilities by hand. + +## Checking for memory leaks + +The makefile comes also with a build that checks some common mistakes regarding memory leaks and out of bound access to arrays. +To run these checks, use the following at the command line: + +```console +$ make memcheck +``` + +[3-tdd-rules]: https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html + +## Submitting your solution + +You can submit your solution using the `exercism submit react.c react.h` command. +This command will upload your solution to the Exercism website and print the solution page's URL. + +It's possible to submit an incomplete solution which allows you to: + +- See how others have completed the exercise +- Request help from a mentor + +## Need to get help? + +If you'd like help solving the exercise, check the following pages: + +- The [C track's documentation](https://exercism.org/docs/tracks/c) +- The [C track's programming category on the forum](https://forum.exercism.org/c/programming/c) +- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5) +- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) + +Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. + +Make sure you have read the [C track-specific documentation][c-track] on the Exercism site. +This covers the basic information on setting up the development environment expected by the exercises. + +## Submitting Incomplete Solutions + +If you are struggling with a particular exercise, it is possible to submit an incomplete solution so you can see how others have completed the exercise. + +## Resources + +To get help if having trouble, you can use the following resources: + +- [StackOverflow][] can be used to search for your problem and see if it has been answered already. You can also ask and answer questions. +- [CPPReference][] can be used to look up information on C concepts, operators, types, standard library functions and more. +- [TutorialsPoint][] has similar content as CPPReference in its C programming section. +- [The C Programming][K&R] book by K&R is the original source of the language and is still useful today. + +[c-track]: https://exercism.org/docs/tracks/c +[stackoverflow]: http://stackoverflow.com/questions/tagged/c +[cppreference]: https://en.cppreference.com/w/c +[tutorialspoint]: https://www.tutorialspoint.com/cprogramming/ +[K&R]: https://www.amazon.com/Programming-Language-2nd-Brian-Kernighan/dp/0131103628/ \ No newline at end of file diff --git a/c/react/README.md b/c/react/README.md new file mode 100644 index 0000000..7efc49c --- /dev/null +++ b/c/react/README.md @@ -0,0 +1,32 @@ +# React + +Welcome to React on Exercism's C Track. +If you need help running the tests or submitting your code, check out `HELP.md`. + +## Instructions + +Implement a basic reactive system. + +Reactive programming is a programming paradigm that focuses on how values are computed in terms of each other to allow a change to one value to automatically propagate to other values, like in a spreadsheet. + +Implement a basic reactive system with cells with settable values ("input" cells) and cells with values computed in terms of other cells ("compute" cells). +Implement updates so that when an input value is changed, values propagate to reach a new stable system state. + +In addition, compute cells should allow for registering change notification callbacks. +Call a cell’s callbacks when the cell’s value in a new stable state has changed from the previous stable state. + +## Source + +### Created by + +- @petertseng + +### Contributed to by + +- @bcc32 +- @Gamecock +- @h-3-0 +- @patricksjackson +- @QLaille +- @ryanplusplus +- @wolf99 \ No newline at end of file diff --git a/c/react/react.c b/c/react/react.c new file mode 100644 index 0000000..73065ca --- /dev/null +++ b/c/react/react.c @@ -0,0 +1,354 @@ +#include "react.h" + +#include +#include + +enum compute_type +{ + COMPUTE_TYPE_SINGLE, + COMPUTE_TYPE_DOUBLE, +}; + +struct compute_registry +{ + enum compute_type type; + struct cell *input_cell_a; + struct cell *input_cell_b; + compute1 single_fn; + compute2 double_fn; + struct cell *output_cell; + struct compute_registry *next; +}; + +struct callback_registry +{ + struct cell *cell; + callback_id id; + callback function; + void *param; + bool ready; + bool previously_called; + int last_value; + struct callback_registry *next; +}; + +struct reactor +{ + struct cell *cells; + struct compute_registry *compute_reg; + struct callback_registry *callback_reg; +}; + +struct cell +{ + struct reactor *reactor; + int value; + struct cell *next; +}; + +void add_cell(struct reactor *reactor, struct cell *cell); +void propagate_compute(struct reactor *reactor, struct cell *cell); +void mark_callbacks(struct reactor *reactor, struct cell *cell); +void trigger_callbacks(struct reactor *reactor); + +struct reactor *create_reactor(void) +{ + struct reactor *reactor = (struct reactor *)malloc(sizeof(struct reactor)); + + reactor->cells = NULL; + reactor->compute_reg = NULL; + reactor->callback_reg = NULL; + + return reactor; +} + +// destroy_reactor should free all cells created under that reactor. +void destroy_reactor(struct reactor *reactor) +{ + struct callback_registry *callback_reg = reactor->callback_reg; + while (callback_reg != NULL) + { + struct callback_registry *next = callback_reg->next; + free(callback_reg); + + callback_reg = next; + } + + struct compute_registry *compute_reg = reactor->compute_reg; + while (compute_reg != NULL) + { + struct compute_registry *next = compute_reg->next; + free(compute_reg); + + compute_reg = next; + } + + struct cell *cell = reactor->cells; + while (cell != NULL) + { + struct cell *next = cell->next; + free(cell); + + cell = next; + } + + free(reactor); +} + +struct cell *create_input_cell(struct reactor *reactor, int initial_value) +{ + struct cell *cell = (struct cell *)malloc(sizeof(struct cell)); + + // Create input cell + cell->reactor = reactor; + cell->next = NULL; + cell->value = initial_value; + add_cell(reactor, cell); + + return cell; +} + +struct cell *create_compute1_cell(struct reactor *reactor, struct cell *cell, compute1 compute) +{ + struct cell *compute_cell = (struct cell *)malloc(sizeof(struct cell)); + + // Create compute cell + compute_cell->reactor = reactor; + compute_cell->value = compute(cell->value); + compute_cell->next = NULL; + add_cell(reactor, compute_cell); + + // Create new compute registry + struct compute_registry *new_reg = (struct compute_registry *)malloc(sizeof(struct compute_registry)); + new_reg->type = COMPUTE_TYPE_SINGLE; + new_reg->single_fn = compute; + new_reg->input_cell_a = cell; + new_reg->output_cell = compute_cell; + new_reg->next = NULL; + + // Append compute registry to reactor + if (reactor->compute_reg == NULL) + { + reactor->compute_reg = new_reg; + } + else + { + struct compute_registry *reg = reactor->compute_reg; + while (reg->next != NULL) + { + reg = reg->next; + } + reg->next = new_reg; + } + + return compute_cell; +} + +struct cell *create_compute2_cell(struct reactor *reactor, struct cell *cell_a, struct cell *cell_b, compute2 compute) +{ + struct cell *compute_cell = (struct cell *)malloc(sizeof(struct cell)); + + // Create compute cell + compute_cell->reactor = reactor; + compute_cell->value = compute(cell_a->value, cell_b->value); + compute_cell->next = NULL; + add_cell(reactor, compute_cell); + + // Create new compute registry + struct compute_registry *new_reg = (struct compute_registry *)malloc(sizeof(struct compute_registry)); + new_reg->type = COMPUTE_TYPE_DOUBLE; + new_reg->double_fn = compute; + new_reg->input_cell_a = cell_a; + new_reg->input_cell_b = cell_b; + new_reg->output_cell = compute_cell; + new_reg->next = NULL; + + // Append compute registry to reactor + if (reactor->compute_reg == NULL) + { + reactor->compute_reg = new_reg; + } + else + { + struct compute_registry *reg = reactor->compute_reg; + while (reg->next != NULL) + { + reg = reg->next; + } + reg->next = new_reg; + } + + return compute_cell; +} + +int get_cell_value(struct cell *cell) +{ + return cell->value; +} + +void set_cell_value(struct cell *cell, int new_value) +{ + if (cell->value == new_value) + { + return; + } + + cell->value = new_value; + + propagate_compute(cell->reactor, cell); + trigger_callbacks(cell->reactor); +} + +callback_id add_callback(struct cell *cell, void *obj, callback function) +{ + struct callback_registry *new_reg = (struct callback_registry *)malloc(sizeof(struct callback_registry)); + new_reg->cell = cell; + new_reg->param = obj; + new_reg->function = function; + new_reg->ready = false; + new_reg->last_value = cell->value; + new_reg->next = NULL; + + struct reactor *reactor = cell->reactor; + + // Append callback registry to reactor + if (reactor->callback_reg == NULL) + { + reactor->callback_reg = new_reg; + new_reg->id = 0; + } + else + { + struct callback_registry *reg = reactor->callback_reg; + while (reg->next != NULL) + { + reg = reg->next; + } + reg->next = new_reg; + new_reg->id = reg->id + 1; + } + + return new_reg->id; +} + +void remove_callback(struct cell *cell, callback_id function_id) +{ + struct reactor *reactor = cell->reactor; + + struct callback_registry *reg = reactor->callback_reg; + if (reg == NULL) + { + // no callbacks registered + return; + } + + // Callback is the first in the list + if (reg->id == function_id) + { + reactor->callback_reg = reg->next; + + free(reg); + return; + } + while (reg->next != NULL) + { + if (reg->next->id == function_id) + { + struct callback_registry *current = reg->next; + reg->next = current->next; + + free(current); + } + reg = reg->next; + } +} + +void add_cell(struct reactor *reactor, struct cell *cell) +{ + // Append callback registry to reactor + if (reactor->cells == NULL) + { + reactor->cells = cell; + } + else + { + struct cell *cells = reactor->cells; + while (cells->next != NULL) + { + cells = cells->next; + } + cells->next = cell; + } +} + +void propagate_compute(struct reactor *reactor, struct cell *cell) +{ + int value = cell->value; + mark_callbacks(reactor, cell); + + struct compute_registry *reg = reactor->compute_reg; + while (reg != NULL) + { + int old_value = reg->output_cell->value; + switch (reg->type) + { + case COMPUTE_TYPE_SINGLE: + if (reg->input_cell_a == cell) + { + reg->output_cell->value = reg->single_fn(value); + if (old_value != reg->output_cell->value) + { + propagate_compute(reactor, reg->output_cell); + } + } + break; + case COMPUTE_TYPE_DOUBLE: + if (reg->input_cell_a == cell) + { + reg->output_cell->value = reg->double_fn(value, reg->input_cell_b->value); + if (old_value != reg->output_cell->value) + { + propagate_compute(reactor, reg->output_cell); + } + } + else if (reg->input_cell_b == cell) + { + reg->output_cell->value = reg->double_fn(reg->input_cell_a->value, value); + if (old_value != reg->output_cell->value) + { + propagate_compute(reactor, reg->output_cell); + } + } + break; + } + reg = reg->next; + } +} + +void mark_callbacks(struct reactor *reactor, struct cell *cell) +{ + struct callback_registry *reg = reactor->callback_reg; + while (reg != NULL) + { + if (reg->cell == cell) + { + reg->ready = true; + } + reg = reg->next; + } +} + +void trigger_callbacks(struct reactor *reactor) +{ + struct callback_registry *reg = reactor->callback_reg; + while (reg != NULL) + { + if (reg->ready && reg->last_value != reg->cell->value) + { + reg->last_value = reg->cell->value; + reg->function(reg->param, reg->cell->value); + reg->ready = false; + } + reg = reg->next; + } +} diff --git a/c/react/react.h b/c/react/react.h new file mode 100644 index 0000000..9a0ff89 --- /dev/null +++ b/c/react/react.h @@ -0,0 +1,29 @@ +#ifndef REACT_H +#define REACT_H + +struct reactor; +struct cell; + +typedef int (*compute1)(int); +typedef int (*compute2)(int, int); + +struct reactor *create_reactor(void); +// destroy_reactor should free all cells created under that reactor. +void destroy_reactor(struct reactor *); + +struct cell *create_input_cell(struct reactor *, int initial_value); +struct cell *create_compute1_cell(struct reactor *, struct cell *, compute1); +struct cell *create_compute2_cell(struct reactor *, struct cell *, + struct cell *, compute2); + +int get_cell_value(struct cell *); +void set_cell_value(struct cell *, int new_value); + +typedef void (*callback)(void *, int); +typedef int callback_id; + +// The callback should be called with the same void * given in add_callback. +callback_id add_callback(struct cell *, void *, callback); +void remove_callback(struct cell *, callback_id); + +#endif