/* * fy-composer.c - Composer support * * Copyright (c) 2021 Pantelis Antoniou * * SPDX-License-Identifier: MIT */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include "fy-parse.h" #include "fy-doc.h" #include "fy-utils.h" struct fy_composer * fy_composer_create(struct fy_composer_cfg *cfg) { struct fy_composer *fyc; struct fy_path *fypp; /* verify configuration and mandatory ops */ if (!cfg || !cfg->ops || !cfg->ops->process_event) return NULL; fyc = malloc(sizeof(*fyc)); if (!fyc) return NULL; memset(fyc, 0, sizeof(*fyc)); fyc->cfg = *cfg; fy_path_list_init(&fyc->paths); fypp = fy_path_create(); if (!fypp) goto err_no_path; fy_path_list_add_tail(&fyc->paths, fypp); return fyc; err_no_path: free(fyc); return NULL; } void fy_composer_destroy(struct fy_composer *fyc) { struct fy_path *fypp; if (!fyc) return; fy_diag_unref(fyc->cfg.diag); while ((fypp = fy_path_list_pop(&fyc->paths)) != NULL) fy_path_destroy(fypp); free(fyc); } static enum fy_composer_return fy_composer_process_event_private(struct fy_composer *fyc, struct fy_event *fye, struct fy_path *fypp) { const struct fy_composer_ops *ops; struct fy_eventp *fyep; struct fy_path_component *fypc, *fypc_last; struct fy_path *fyppt; struct fy_document *fyd; bool is_collection, is_map, is_start, is_end; int rc = 0; enum fy_composer_return ret; bool stop_req = false; assert(fyc); assert(fye); assert(fypp); fyep = container_of(fye, struct fy_eventp, e); ops = fyc->cfg.ops; assert(ops); rc = 0; switch (fye->type) { case FYET_MAPPING_START: is_collection = true; is_start = true; is_end = false; is_map = true; break; case FYET_MAPPING_END: is_collection = true; is_start = false; is_end = true; is_map = true; break; case FYET_SEQUENCE_START: is_collection = true; is_start = true; is_end = false; is_map = false; break; case FYET_SEQUENCE_END: is_collection = true; is_start = false; is_end = true; is_map = false; break; case FYET_SCALAR: is_collection = false; is_start = true; is_end = true; is_map = false; break; case FYET_ALIAS: is_collection = false; is_start = true; is_end = true; is_map = false; break; case FYET_STREAM_START: case FYET_STREAM_END: case FYET_DOCUMENT_START: case FYET_DOCUMENT_END: return ops->process_event(fyc, fypp, fye); default: return FYCR_OK_CONTINUE; } fypc_last = fy_path_component_list_tail(&fypp->components); if (fy_path_component_is_mapping(fypc_last) && fypc_last->map.accumulating_complex_key) { /* get the next one */ fyppt = fy_path_next(&fyc->paths, fypp); assert(fyppt); assert(fyppt != fypp); assert(fyppt->parent == fypp); /* and pass along */ ret = fy_composer_process_event_private(fyc, fye, fyppt); if (!fy_composer_return_is_ok(ret)) { /* XXX TODO handle skip */ return ret; } if (!stop_req) stop_req = ret == FYCR_OK_STOP; rc = fy_document_builder_process_event(fypp->fydb, fyep ? &fyep->e : NULL); if (rc == 0) return FYCR_OK_CONTINUE; fyc_error_check(fyc, rc > 0, err_out, "fy_document_builder_process_event() failed\n"); /* get the document */ fyd = fy_document_builder_take_document(fypp->fydb); fyc_error_check(fyc, fyd, err_out, "fy_document_builder_take_document() failed\n"); fy_document_builder_destroy(fypp->fydb); fypp->fydb = NULL; fypc_last->map.is_complex_key = true; fypc_last->map.accumulating_complex_key = false; fypc_last->map.complex_key = fyd; fypc_last->map.has_key = true; fypc_last->map.await_key = false; fypc_last->map.complex_key_complete = true; fypc_last->map.root = false; fyppt = fy_path_list_pop_tail(&fyc->paths); assert(fyppt); fy_path_destroy(fyppt); fyc_error_check(fyc, rc >= 0, err_out, "fy_path_component_build_text() failed\n"); return !stop_req ? FYCR_OK_CONTINUE : FYCR_OK_STOP; } /* start of something on a mapping */ if (is_start && fy_path_component_is_mapping(fypc_last) && fypc_last->map.await_key && is_collection) { /* the configuration must support a document builder for complex keys */ FYC_TOKEN_ERROR_CHECK(fyc, fy_event_get_token(fye), FYEM_DOC, ops->create_document_builder, err_out, "composer configuration does not support complex keys"); /* call out for creating the document builder */ fypp->fydb = ops->create_document_builder(fyc); fyc_error_check(fyc, fypp->fydb, err_out, "ops->create_document_builder() failed\n"); /* and pass the current event; must return 0 since we know it's a collection start */ rc = fy_document_builder_process_event(fypp->fydb, fyep ? &fyep->e : NULL); fyc_error_check(fyc, !rc, err_out, "fy_document_builder_process_event() failed\n"); fypc_last->map.is_complex_key = true; fypc_last->map.accumulating_complex_key = true; fypc_last->map.complex_key = NULL; fypc_last->map.complex_key_complete = false; /* create new path */ fyppt = fy_path_create(); fyc_error_check(fyc, fyppt, err_out, "fy_path_create() failed\n"); /* append it to the end */ fyppt->parent = fypp; fy_path_list_add_tail(&fyc->paths, fyppt); /* and pass along */ ret = fy_composer_process_event_private(fyc, fye, fyppt); if (!fy_composer_return_is_ok(ret)) { /* XXX TODO handle skip */ return ret; } if (!stop_req) stop_req = ret == FYCR_OK_STOP; return !stop_req ? FYCR_OK_CONTINUE : FYCR_OK_STOP; } if (is_start && fy_path_component_is_sequence(fypc_last)) { /* start in a sequence */ if (fypc_last->seq.idx < 0) fypc_last->seq.idx = 0; else fypc_last->seq.idx++; } if (is_collection && is_start) { /* collection start */ if (is_map) { fypc = fy_path_component_create_mapping(fypp); fyc_error_check(fyc, fypc, err_out, "fy_path_component_create_mapping() failed\n"); } else { fypc = fy_path_component_create_sequence(fypp); fyc_error_check(fyc, fypc, err_out, "fy_path_component_create_sequence() failed\n"); } /* append to the tail */ fy_path_component_list_add_tail(&fypp->components, fypc); } else if (is_collection && is_end) { /* collection end */ assert(fypc_last); fy_path_component_clear_state(fypc_last); } else if (!is_collection && fy_path_component_is_mapping(fypc_last) && fypc_last->map.await_key) { fypc_last->map.is_complex_key = false; fypc_last->map.scalar.tag = fy_token_ref(fy_event_get_tag_token(fye)); fypc_last->map.scalar.key = fy_token_ref(fy_event_get_token(fye)); fypc_last->map.has_key = true; fypc_last->map.root = false; } /* process the event */ ret = ops->process_event(fyc, fypp, fye); if (!fy_composer_return_is_ok(ret)) { /* XXX TODO handle skip */ return ret; } if (!stop_req) stop_req = ret == FYCR_OK_STOP; if (is_collection && is_end) { /* for the end of a collection, pop the last component */ fypc = fy_path_component_list_pop_tail(&fypp->components); assert(fypc); assert(fypc == fypc_last); fy_path_component_recycle(fypp, fypc); /* and get the new last */ fypc_last = fy_path_component_list_tail(&fypp->components); } /* at the end of something */ if (is_end && fy_path_component_is_mapping(fypc_last)) { if (!fypc_last->map.await_key) { fy_path_component_clear_state(fypc_last); fypc_last->map.await_key = true; } else fypc_last->map.await_key = false; } return !stop_req ? FYCR_OK_CONTINUE : FYCR_OK_STOP; err_out: return FYCR_ERROR; } void fy_composer_halt_one(struct fy_composer *fyc, struct fy_path *fypp, enum fy_composer_return rc) { const struct fy_composer_ops *ops; struct fy_eventp ev_none; struct fy_path_component *fypc; /* for normal stop, we don't teardown */ if (rc == FYCR_OK_STOP) return; ops = fyc->cfg.ops; assert(ops); memset(&ev_none, 0, sizeof(ev_none)); ev_none.e.type = FYET_NONE; /* pump FYET_NONEs to clean the stack */ while ((fypc = fy_path_component_list_tail(&fypp->components)) != NULL) { ops->process_event(fyc, fypp, &ev_none.e); fy_path_component_list_del(&fypp->components, fypc); fy_path_component_free(fypc); } /* and the final one to clean the root */ ops->process_event(fyc, fypp, &ev_none.e); } void fy_composer_halt(struct fy_composer *fyc, enum fy_composer_return rc) { struct fy_path *fypp; /* for normal stop, we don't teardown */ if (rc == FYCR_OK_STOP) return; for (fypp = fy_path_list_head(&fyc->paths); fypp; fypp = fy_path_next(&fyc->paths, fypp)) fy_composer_halt_one(fyc, fypp, rc); } enum fy_composer_return fy_composer_process_event(struct fy_composer *fyc, struct fy_event *fye) { struct fy_path *fypp; int rc; if (!fyc || !fye) return FYCR_ERROR; /* start at the head */ fypp = fy_path_list_head(&fyc->paths); /* no top? something's very out of order */ if (!fypp) return FYCR_ERROR; rc = fy_composer_process_event_private(fyc, fye, fypp); if (rc == FYCR_ERROR || rc == FYCR_OK_STOP) fy_composer_halt(fyc, rc); return rc; } struct fy_composer_cfg *fy_composer_get_cfg(struct fy_composer *fyc) { if (!fyc) return NULL; return &fyc->cfg; } void *fy_composer_get_cfg_userdata(struct fy_composer *fyc) { if (!fyc) return NULL; return fyc->cfg.userdata; } struct fy_diag *fy_composer_get_diag(struct fy_composer *fyc) { if (!fyc) return NULL; return fyc->cfg.diag; } struct fy_path *fy_composer_get_root_path(struct fy_composer *fyc) { if (!fyc) return NULL; return fy_path_list_head(&fyc->paths); } struct fy_path *fy_composer_get_next_path(struct fy_composer *fyc, struct fy_path *fypp) { if (!fyc || !fypp) return NULL; return fy_path_next(&fyc->paths, fypp); } struct fy_path *fy_composer_get_path(struct fy_composer *fyc) { struct fy_path *fypp, *fyppt; struct fy_path_component *fypc_last; if (!fyc) return NULL; fypp = fy_path_list_head(&fyc->paths); if (!fypp) return NULL; /* traverse until we find the last non complex key acculating */ for (;;) { fypc_last = fy_path_component_list_tail(&fypp->components); if (!fy_path_component_is_mapping(fypc_last) || !fypc_last->map.accumulating_complex_key) break; fyppt = fy_path_next(&fyc->paths, fypp); if (!fyppt) break; fypp = fyppt; } return fypp; }