From c5ae70e58546815fc40e00e6c57fbb85789b24f5 Mon Sep 17 00:00:00 2001 From: Kevin Day Date: Tue, 8 Dec 2020 22:39:15 -0600 Subject: [PATCH] Progress: controller program. --- level_3/controller/c/private-common.h | 7 + level_3/controller/c/private-controller.c | 81 +++++---- level_3/controller/c/private-rule.c | 270 +++++++++++++++++++++++++++++- level_3/controller/c/private-rule.h | 31 ++++ 4 files changed, 354 insertions(+), 35 deletions(-) diff --git a/level_3/controller/c/private-common.h b/level_3/controller/c/private-common.h index 7e6e906..342e233 100644 --- a/level_3/controller/c/private-common.h +++ b/level_3/controller/c/private-common.h @@ -223,6 +223,7 @@ extern "C" { typedef struct { f_status_t status; + f_number_signed_t process; // @todo: for background/threaded support (ideally should hold the process id, but remove if this ends up not being the strategy) (this can also be used by the parent/main process to check to see if the child no longer a child of this process). f_time_spec_t timestamp; @@ -246,6 +247,7 @@ extern "C" { { \ F_known_not, \ 0, \ + 0, \ f_time_spec_t_initialize, \ f_string_dynamic_t_initialize, \ f_string_dynamic_t_initialize, \ @@ -441,6 +443,7 @@ extern "C" { typedef struct { bool interruptable; + bool lock; // @todo: this is intend for mutex write locking of this setting for thread safety, remove this if another approach is used. uint8_t ready; f_string_dynamic_t path_pid; @@ -453,6 +456,7 @@ extern "C" { #define controller_setting_t_initialize \ { \ F_false, \ + F_false, \ 0, \ f_string_dynamic_t_initialize, \ f_string_dynamic_t_initialize, \ @@ -477,6 +481,7 @@ extern "C" { f_string_range_t range_action; f_array_lengths_t ats; + f_array_lengths_t stack; f_fss_comments_t comments; f_fss_delimits_t delimits; @@ -503,6 +508,7 @@ extern "C" { f_time_spec_t_initialize, \ f_string_range_t_initialize, \ f_array_lengths_t_initialize, \ + f_array_lengths_t_initialize, \ f_fss_comments_t_initialize, \ f_fss_delimits_t_initialize, \ f_fss_content_t_initialize, \ @@ -520,6 +526,7 @@ extern "C" { #define controller_macro_cache_t_delete_simple(cache) \ f_macro_array_lengths_t_delete_simple(cache.ats) \ + f_macro_array_lengths_t_delete_simple(cache.stack) \ f_macro_fss_comments_t_delete_simple(cache.comments) \ f_macro_fss_delimits_t_delete_simple(cache.delimits) \ f_macro_fss_content_t_delete_simple(cache.content_action) \ diff --git a/level_3/controller/c/private-controller.c b/level_3/controller/c/private-controller.c index 7b9c526..057ebf9 100644 --- a/level_3/controller/c/private-controller.c +++ b/level_3/controller/c/private-controller.c @@ -525,6 +525,7 @@ extern "C" { cache->line_item = 0; cache->name_action.used = 0; cache->name_item.used = 0; + cache->stack.used = 0; if (setting->ready == controller_setting_ready_yes) { status = controller_perform_ready(data, setting, cache); @@ -645,20 +646,6 @@ extern "C" { return status; } else { - - // rule execution will re-use the existing cache, so save the current cache. - const f_array_length_t cache_line_action = cache->line_action; - const f_array_length_t cache_line_item = cache->line_item; - - const f_string_length_t cache_name_action_used = cache->name_action.used; - const f_string_length_t cache_name_item_used = cache->name_item.used; - - char cache_name_item[cache_name_action_used]; - char cache_name_action[cache_name_item_used]; - - memcpy(cache_name_item, cache->name_item.string, cache->name_item.used); - memcpy(cache_name_action, cache->name_action.string, cache->name_action.used); - const f_string_length_t rule_id_length = actions->array[cache->ats.array[at_j]].parameters.array[0].used + actions->array[cache->ats.array[at_j]].parameters.array[1].used + 1; char rule_id_name[rule_id_length + 1]; const f_string_static_t rule_id = f_macro_string_static_t_initialize(rule_id_name, rule_id_length); @@ -684,40 +671,60 @@ extern "C" { } } - if (F_status_is_error_not(status) && actions->array[cache->ats.array[at_j]].type == controller_entry_action_type_rule) { - if (similate) { - // @todo print message about how this would execute the rule. - } - else { - //status = controller_rule_process(); + if (F_status_is_error_not(status)) { + + // rule execution will re-use the existing cache, so save the current cache. + const f_array_length_t cache_line_action = cache->line_action; + const f_array_length_t cache_line_item = cache->line_item; + + const f_string_length_t cache_name_action_used = cache->name_action.used; + const f_string_length_t cache_name_item_used = cache->name_item.used; + const f_string_length_t cache_name_file_used = cache->name_file.used; + + char cache_name_action[cache_name_action_used]; + char cache_name_item[cache_name_item_used]; + char cache_name_file[cache_name_file_used]; + + memcpy(cache_name_action, cache->name_action.string, cache->name_action.used); + memcpy(cache_name_item, cache->name_item.string, cache->name_item.used); + memcpy(cache_name_file, cache->name_file.string, cache->name_file.used); + + if (actions->array[cache->ats.array[at_j]].type == controller_entry_action_type_rule) { + // @todo: this will also need to support the asynchronous/wait behavior. + status = controller_rule_process(data, at, simulate, setting, cache); } - } - // restore cache. - memcpy(cache->name_action.string, cache_name_action, cache_name_action_used); - memcpy(cache->name_item.string, cache_name_item, cache_name_item_used); + // restore cache. + memcpy(cache->name_action.string, cache_name_action, cache_name_action_used); + memcpy(cache->name_item.string, cache_name_item, cache_name_item_used); + memcpy(cache->name_file.string, cache_name_file, cache_name_file_used); - cache->name_action.string[cache_name_action_used] = 0; - cache->name_item.string[cache_name_item_used] = 0; + cache->name_action.string[cache_name_action_used] = 0; + cache->name_item.string[cache_name_item_used] = 0; + cache->name_file.string[cache_name_file_used] = 0; - cache->name_action.used = cache_name_action_used; - cache->name_item.used = cache_name_item_used; + cache->name_action.used = cache_name_action_used; + cache->name_item.used = cache_name_item_used; + cache->name_file.used = cache_name_file_used; - cache->line_action = cache_line_action; - cache->line_item = cache_line_item; + cache->line_action = cache_line_action; + cache->line_item = cache_line_item; + } if (F_status_is_error(status)) { controller_entry_error_print(data.error, *cache); - if (!simulate) break; + if (!simulate || F_status_set_fine(status) == F_memory_not || F_status_set_fine(status) == F_memory_allocation || F_status_set_fine(status) == F_memory_reallocation) { + break; + } } } } else if (actions->array[cache->ats.array[at_j]].type == controller_entry_action_type_timeout) { - // @todo + // @todo set the appropriate timeout value (set the entry actions timeouts which are later used as the initial defaults as the rule timeouts). } else if (actions->array[cache->ats.array[at_j]].type == controller_entry_action_type_failsafe) { - // @todo + // @todo set the failsafe rule to this rule id (find the rule and then assign by the rule id and then assign by the array index). } } // for @@ -725,6 +732,12 @@ extern "C" { cache->line_action = 0; cache->name_action.used = 0; + if (F_status_is_error(status)) { + if (!simulate || F_status_set_fine(status) == F_memory_not || F_status_set_fine(status) == F_memory_allocation || F_status_set_fine(status) == F_memory_reallocation) { + break; + } + } + // end of actions found, so drop to previous loop in stack. if (cache->ats.array[at_j] == actions->used) { @@ -746,7 +759,7 @@ extern "C" { fll_error_print(data.error, F_status_set_fine(status), "controller_string_dynamic_append_terminated", F_true); controller_entry_error_print(data.error, *cache); - return status; + break; } } } // for diff --git a/level_3/controller/c/private-rule.c b/level_3/controller/c/private-rule.c index 9b595d3..41a94ae 100644 --- a/level_3/controller/c/private-rule.c +++ b/level_3/controller/c/private-rule.c @@ -274,7 +274,7 @@ extern "C" { for (; i < setting.rules.used; ++i) { - if (fl_string_dynamic_compare(setting.rules.array[i], rule_id) == F_equal_to) { + if (fl_string_dynamic_compare(setting.rules.array[i].id, rule_id) == F_equal_to) { return i; } } // for @@ -644,6 +644,274 @@ extern "C" { } #endif // _di_controller_rule_read_ +#ifndef _di_controller_rule_process_ + f_return_status controller_rule_process(const controller_data_t data, const f_array_length_t index, const bool simulate, controller_setting_t *setting, controller_cache_t *cache) { + + if (index >= setting->rules.used) { + fll_error_print(data.error, F_parameter, "controller_rule_process", F_true); + controller_rule_error_print(data.error, *cache, F_true); + + return F_status_set_error(F_parameter); + } + + f_status_t status = fl_type_array_lengths_increase_by(controller_default_allocation_step, &cache->stack); + + if (F_status_is_error(status)) { + fll_error_print(data.error, F_status_set_fine(status), "fl_type_array_lengths_increase_by", F_true); + controller_rule_error_print(data.error, *cache, F_true); + + return status; + } + + f_array_length_t i = 0; + + for (; i < cache->stack.used; ++i) { + + if (cache->stack.array[i] == index) { + if (data.error.verbosity != f_console_verbosity_quiet) { + fprintf(data.error.to.stream, "%c", f_string_eol[0]); + fprintf(data.error.to.stream, "%s%sThe rule '", data.error.context.before->string, data.error.prefix ? data.error.prefix : ""); + fprintf(data.error.to.stream, "%s%s%s%s", data.error.context.after->string, data.error.notable.before->string, setting->rules.array[i].name.string, data.error.notable.after->string); + fprintf(data.error.to.stream, "%s' is already on the execution stack, this recursion is prohibited.%s%c", data.error.context.before->string, data.error.context.after->string, f_string_eol[0]); + + controller_rule_error_print(data.error, *cache, F_true); + } + + // never continue on recursion errors even in simulate mode. + return F_status_set_error(F_recurse); + } + } + + cache->stack.array[cache->stack.used++] = index; + + cache->name_action.used = 0; + cache->name_item.used = 0; + cache->name_file.used = 0; + + status = fl_string_append(controller_string_rules, controller_string_rules_length, &cache->name_file); + + if (F_status_is_error_not(status)) { + status = fl_string_append(f_path_separator, f_path_separator_length, &cache->name_file); + } + + if (F_status_is_error(status)) { + fll_error_print(data.error, F_status_set_fine(status), "fl_string_append", F_true); + controller_rule_error_print(data.error, *cache, F_true); + + return status; + } + + status = fl_string_dynamic_append(setting->rules.array[index].id, &cache->name_file); + + if (F_status_is_error(status)) { + fll_error_print(data.error, F_status_set_fine(status), "fl_string_dynamic_append", F_true); + controller_rule_error_print(data.error, *cache, F_true); + + return status; + } + + status = fl_string_append(f_path_extension_separator, f_path_extension_separator_length, &cache->name_file); + + if (F_status_is_error_not(status)) { + status = fl_string_append(controller_string_rule, controller_string_rule_length, &cache->name_file); + } + + if (F_status_is_error(status)) { + fll_error_print(data.error, F_status_set_fine(status), "fl_string_append", F_true); + controller_rule_error_print(data.error, *cache, F_true); + + return status; + } + + status = fl_string_dynamic_terminate_after(&cache->name_file); + + if (F_status_is_error(status)) { + fll_error_print(data.error, F_status_set_fine(status), "fl_string_dynamic_terminate_after", F_true); + controller_rule_error_print(data.error, *cache, F_true); + + return status; + } + + controller_rule_t *rule = &setting->rules.array[index]; + + { + f_array_length_t j = 0; + f_array_length_t k = 0; + f_array_length_t at = 0; + + f_string_dynamics_t *dynamics[] = { + &rule->need, + &rule->want, + &rule->wish, + }; + + for (i = 0; i < 3; ++i) { + + for (j = 0; j < dynamics[i]->used; ++j) { + at = controller_rule_find_loaded(data, *setting, dynamics[i]->array[j]); + + if (at == setting->rules.used) { + if (i == 0) { + + if (data.error.verbosity != f_console_verbosity_quiet) { + fprintf(data.error.to.stream, "%c", f_string_eol[0]); + fprintf(data.error.to.stream, "%s%sThe needed rule '", data.error.context.before->string, data.error.prefix ? data.error.prefix : ""); + fprintf(data.error.to.stream, "%s%s%s%s", data.error.context.after->string, data.error.notable.before->string, dynamics[i]->array[j].string, data.error.notable.after->string); + fprintf(data.error.to.stream, "%s' was not found.%s%c", data.error.context.before->string, data.error.context.after->string, f_string_eol[0]); + + controller_rule_error_print(data.error, *cache, F_true); + } + + status = F_status_set_error(F_found_not); + + if (!simulate) break; + } + else { + if (data.warning.verbosity == f_console_verbosity_debug) { + fprintf(data.warning.to.stream, "%c", f_string_eol[0]); + fprintf(data.warning.to.stream, "%s%sThe %s rule '", data.warning.context.before->string, data.warning.prefix ? data.warning.prefix : "", i == 1 ? "wanted" : "wished for"); + fprintf(data.warning.to.stream, "%s%s%s%s", data.warning.context.after->string, data.error.notable.before->string, dynamics[i]->array[j].string, data.warning.notable.after->string); + fprintf(data.warning.to.stream, "%s' was not found.%s%c", data.warning.context.before->string, data.error.context.after->string, f_string_eol[0]); + + controller_rule_error_print(data.warning, *cache, F_true); + } + } + } + + if (F_status_is_error_not(status) && at < setting->rules.used) { + + // @todo: this will also need to support the asynchronous/wait behavior. + //if (setting->rules.array[at].status == F_busy) { ... if wait then block ... } + + // when the status is unknown, then the rule has not been executed, so attempt to execute it. + if (setting->rules.array[at].status == F_known_not) { + + status = fl_type_array_lengths_increase_by(controller_default_allocation_step, &cache->stack); + + if (F_status_is_error(status)) { + fll_error_print(data.error, F_status_set_fine(status), "fl_type_array_lengths_increase_by", F_true); + + // always exit on memory errors, even in simulate mode. + break; + } + + // rule execution will re-use the existing cache, so save the current cache. + const f_array_length_t cache_line_action = cache->line_action; + const f_array_length_t cache_line_item = cache->line_item; + + const f_string_length_t cache_name_action_used = cache->name_action.used; + const f_string_length_t cache_name_item_used = cache->name_item.used; + const f_string_length_t cache_name_file_used = cache->name_file.used; + + char cache_name_action[cache_name_action_used]; + char cache_name_item[cache_name_item_used]; + char cache_name_file[cache_name_file_used]; + + memcpy(cache_name_action, cache->name_action.string, cache->name_action.used); + memcpy(cache_name_item, cache->name_item.string, cache->name_item.used); + memcpy(cache_name_file, cache->name_file.string, cache->name_file.used); + + // @todo: this should pass or use the asynchronous state. + status = controller_rule_process(data, at, simulate, setting, cache); + + // restore cache. + memcpy(cache->name_action.string, cache_name_action, cache_name_action_used); + memcpy(cache->name_item.string, cache_name_item, cache_name_item_used); + memcpy(cache->name_file.string, cache_name_file, cache_name_file_used); + + cache->name_action.string[cache_name_action_used] = 0; + cache->name_item.string[cache_name_item_used] = 0; + + cache->name_action.used = cache_name_action_used; + cache->name_item.used = cache_name_item_used; + cache->name_file.used = cache_name_file_used; + + cache->line_action = cache_line_action; + cache->line_item = cache_line_item; + + if (F_status_is_error(status)) { + if (i == 0 || i == 1 || F_status_set_fine(status) == F_memory_not || F_status_set_fine(status) == F_memory_allocation || F_status_set_fine(status) == F_memory_reallocation) { + + if (data.error.verbosity != f_console_verbosity_quiet) { + fprintf(data.error.to.stream, "%c", f_string_eol[0]); + fprintf(data.error.to.stream, "%s%sThe %s rule '", data.error.context.before->string, data.error.prefix ? data.error.prefix : "", i ? "wanted" : "needed"); + fprintf(data.error.to.stream, "%s%s%s%s", data.error.context.after->string, data.error.notable.before->string, dynamics[i]->array[j].string, data.error.notable.after->string); + fprintf(data.error.to.stream, "%s' failed during execution.%s%c", data.error.context.before->string, data.error.context.after->string, f_string_eol[0]); + + controller_rule_error_print(data.error, *cache, F_true); + } + + if (!simulate || F_status_set_fine(status) == F_memory_not || F_status_set_fine(status) == F_memory_allocation || F_status_set_fine(status) == F_memory_reallocation) { + break; + } + } + else { + if (data.warning.verbosity == f_console_verbosity_debug) { + fprintf(data.warning.to.stream, "%c", f_string_eol[0]); + fprintf(data.warning.to.stream, "%s%sThe wished for rule '", data.warning.context.before->string, data.warning.prefix ? data.warning.prefix : ""); + fprintf(data.warning.to.stream, "%s%s%s%s", data.warning.context.after->string, data.error.notable.before->string, dynamics[i]->array[j].string, data.warning.notable.after->string); + fprintf(data.warning.to.stream, "%s' failed during execution.%s%c", data.warning.context.before->string, data.error.context.after->string, f_string_eol[0]); + + controller_rule_error_print(data.warning, *cache, F_true); + } + } + } + } + else if (F_status_is_error(setting->rules.array[at].status)) { + + if (i == 0 || i == 1) { + + if (data.error.verbosity != f_console_verbosity_quiet) { + fprintf(data.error.to.stream, "%c", f_string_eol[0]); + fprintf(data.error.to.stream, "%s%sThe %s rule '", data.error.context.before->string, data.error.prefix ? data.error.prefix : "", i ? "wanted" : "needed"); + fprintf(data.error.to.stream, "%s%s%s%s", data.error.context.after->string, data.error.notable.before->string, dynamics[i]->array[j].string, data.error.notable.after->string); + fprintf(data.error.to.stream, "%s' is in a failed state.%s%c", data.error.context.before->string, data.error.context.after->string, f_string_eol[0]); + + controller_rule_error_print(data.error, *cache, F_true); + } + + status = F_status_set_error(F_found_not); + + if (!simulate) break; + } + else { + if (data.warning.verbosity == f_console_verbosity_debug) { + fprintf(data.warning.to.stream, "%c", f_string_eol[0]); + fprintf(data.warning.to.stream, "%s%sThe wished for rule '", data.warning.context.before->string, data.warning.prefix ? data.warning.prefix : ""); + fprintf(data.warning.to.stream, "%s%s%s%s", data.warning.context.after->string, data.error.notable.before->string, dynamics[i]->array[j].string, data.warning.notable.after->string); + fprintf(data.warning.to.stream, "%s' is in a failed state.%s%c", data.warning.context.before->string, data.error.context.after->string, f_string_eol[0]); + + controller_rule_error_print(data.warning, *cache, F_true); + } + } + } + } + } + + if (F_status_is_error(status)) break; + } // for + } + + if (F_status_is_error_not(status)) { + if (simulate) { + //status = controller_rule_simulate(); + } + else { + //status = controller_rule_execute(); + } + } + + // remove this rule off the stack. + cache->stack.used--; + + if (F_status_is_error(status)) { + return status; + } + + return F_none; + } +#endif // _di_controller_rule_process_ + #ifndef _di_controller_rule_setting_read_ f_return_status controller_rule_setting_read(const controller_data_t data, controller_cache_t *cache, controller_rule_t *rule) { f_status_t status = F_none; diff --git a/level_3/controller/c/private-rule.h b/level_3/controller/c/private-rule.h index 71b2ede..ef1abc6 100644 --- a/level_3/controller/c/private-rule.h +++ b/level_3/controller/c/private-rule.h @@ -225,6 +225,37 @@ extern "C" { #endif // _di_controller_rule_read_ /** + * Process and execute the given rule by the rule id. + * + * Any dependent rules are loaded and executed as per "need", "want", and "wish" rule settings. + * All dependent rules must be already loaded, this function will not load any rules. + * + * This function is recursively called for each "need", "want", and "wish", and has a max recursion length of the max size of the f_array_lengths_t array. + * + * @todo add asynchronous boolean? (will also need a wait boolean, so this should probably be a uint8_t with using bitwise states). + * + * @param data + * The program data. + * @param index + * Position in the rules array representing the rule to execute + * @param simulate + * If TRUE, then the rule execution is simulated (printing a message that the rule would be executed but does not execut the rule). + * If FALSE, the rule is not simulated and is executed as normal. + * @param setting + * The controller settings data. + * @param cache + * A structure for containing and caching relevant data. + * This utilizes cache.stack for recursive executions, no function called by this may therefore safely use cache.stack for any other purpose. + * This utilizes line_action, line_item, name_action, and name_item from cache, but they are backed up before starting and then restored after finishing. + * + * @return + * F_none on success. + */ +#ifndef _di_controller_rule_process_ + extern f_return_status controller_rule_process(const controller_data_t data, const f_array_length_t index, const bool simulate, controller_setting_t *setting, controller_cache_t *cache) f_gcc_attribute_visibility_internal; +#endif // _di_controller_rule_process_ + +/** * Read the content within the buffer, extracting all valid settings. * * This will perform additional FSS read functions as appropriate. -- 1.8.3.1