From fc725a2042893baad5bae8795153f039d816592c Mon Sep 17 00:00:00 2001 From: Alexey Fedoseev Date: Sat, 6 Jan 2024 01:46:36 +0300 Subject: [PATCH] yEd format decoder implementation --- LICENSE | 165 ++++ Makefile | 46 + README.md | 22 + cyberiadaml.c | 931 ++++++++++++++++++ cyberiadaml.h | 140 +++ .../CyberiadaFormat-Autoborder.graphml | 98 ++ graph-samples/CyberiadaFormat-Blinker.graphml | 79 ++ graph-samples/berloga-autoborder.graphml | 162 +++ graph-samples/berloga-stapler.graphml | 175 ++++ graph-samples/orbita-orient.graphml | 588 +++++++++++ graph-samples/ostranna-player.graphml | 899 +++++++++++++++++ test.c | 88 ++ 12 files changed, 3393 insertions(+) create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 cyberiadaml.c create mode 100644 cyberiadaml.h create mode 100644 graph-samples/CyberiadaFormat-Autoborder.graphml create mode 100644 graph-samples/CyberiadaFormat-Blinker.graphml create mode 100644 graph-samples/berloga-autoborder.graphml create mode 100644 graph-samples/berloga-stapler.graphml create mode 100644 graph-samples/orbita-orient.graphml create mode 100644 graph-samples/ostranna-player.graphml create mode 100644 test.c diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0a04128 --- /dev/null +++ b/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..42c8d21 --- /dev/null +++ b/Makefile @@ -0,0 +1,46 @@ +LIB_TARGET_STATIC := libcyberiadaml.a +LIB_TARGET_DYNAMIC := libcyberiadaml.so + +ifeq ($(DYNAMIC), 1) + LIB_TARGET := $(LIB_TARGET_DYNAMIC) +else + LIB_TARGET := $(LIB_TARGET_STATIC) +endif + +TEST_TARGET := cyberiada_test +LIB_SOURCES := cyberiadaml.c +TEST_SOURCES := test.c +LIB_OBJECTS := $(patsubst %.c, %.o, $(LIB_SOURCES)) +TEST_OBJECTS := $(patsubst %.c, %.o, $(TEST_SOURCES)) + +ifeq ($(DEBUG), 1) + CFLAGS := -fPIC -g3 -D__DEBUG__ +else + CFLAGS := -fPIC +endif + +INCLUDE := -I. -I/usr/include/libxml2 +LIBS := -L/usr/lib -lxml2 +TEST_LIBS := -L. -lcyberiadaml + +$(LIB_TARGET): $(LIB_OBJECTS) +ifeq ($(DYNAMIC), 1) + gcc -shared $(LIBS) $(LIB_OBJECTS) -o $@ +else + ar rcs $@ $(LIB_OBJECTS) +endif + +$(TEST_TARGET): $(TEST_OBJECTS) $(TARGET) + gcc $(TEST_OBJECTS) -Wl,--no-as-needed $(LIBS) $(TEST_LIBS) -o $@ + +%.o: %.c + gcc -c $< $(CFLAGS) $(INCLUDE) -o $@ + +clean: + rm -f *~ *.o $(TARGET) $(TEST_TARGET) $(LIB_TARGET_STATIC) $(LIB_TARGET_DYNAMIC) + +test: $(TEST_TARGET) + +all: $(LIB_TARGET) $(TEST_TARGET) + +.PHONY: all clean test diff --git a/README.md b/README.md new file mode 100644 index 0000000..b82aea4 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# The Cyberida State Machine Library + +The C library for processing CyberiadaML - the version of GraphML for storing state machine graphs +for Cyberiada Project, Berloga games and Orbita satellite + +The code is distributed under the Lesser GNU Public License (version 3), the documentation -- under +the GNU Free Documentation License (version 1.3). + +## Requirements + +* build-essential +* libxml2-dev + +## Installation + +Run `make` to build the library binaries. + +Run `make test` to build the test program. + +Use variables: +* `DEBUG=1` debug version of the library +* `DYNAMIC=1` build shared version of the library diff --git a/cyberiadaml.c b/cyberiadaml.c new file mode 100644 index 0000000..f8ecd8f --- /dev/null +++ b/cyberiadaml.c @@ -0,0 +1,931 @@ +/* ----------------------------------------------------------------------------- + * The Cyberiada GraphML library implemention + * + * The C library implementation + * + * Copyright (C) 2024 Alexey Fedoseev + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/ + * ----------------------------------------------------------------------------- */ + +#include +#include +#include +#include + +#include "cyberiadaml.h" + +#define GRAPHML_NAMESPACE_URI "http://graphml.graphdrawing.org/xmlns" +#define GRAPHML_GRAPHML_ELEMENT "graphml" +#define GRAPHML_BERLOGA_SCHEMENAME_ATTR "SchemeName" +#define GRAPHML_GRAPH_ELEMENT "graph" +#define GRAPHML_NODE_ELEMENT "node" +#define GRAPHML_EDGE_ELEMENT "edge" +#define GRAPHML_ID_ATTRIBUTE "id" +#define GRAPHML_SOURCE_ATTRIBUTE "source" +#define GRAPHML_TARGET_ATTRIBUTE "target" +#define GRAPHML_YED_GEOMETRYNODE "Geometry" +#define GRAPHML_YED_PATHNODE "Path" +#define GRAPHML_YED_POINTNODE "Point" +#define GRAPHML_YED_GEOM_X_ATTRIBUTE "x" +#define GRAPHML_YED_GEOM_Y_ATTRIBUTE "y" +#define GRAPHML_YED_GEOM_WIDTH_ATTRIBUTE "width" +#define GRAPHML_YED_GEOM_HEIGHT_ATTRIBUTE "height" +#define GRAPHML_YED_GEOM_SOURCE_X_ATTRIBUTE "sx" +#define GRAPHML_YED_GEOM_SOURCE_Y_ATTRIBUTE "sy" +#define GRAPHML_YED_GEOM_TARGET_X_ATTRIBUTE "tx" +#define GRAPHML_YED_GEOM_TARGET_Y_ATTRIBUTE "ty" +#define GRAPHML_YED_COMMENTNODE "UMLNoteNode" +#define GRAPHML_YED_GROUPNODE "GroupNode" +#define GRAPHML_YED_GENERICNODE "GenericNode" +#define GRAPHML_YED_LABELNODE "NodeLabel" +#define GRAPHML_YED_NODE_CONFIG_ATTRIBUTE "configuration" +#define GRAPHML_YED_NODE_CONFIG_START "com.yworks.bpmn.Event" +#define GRAPHML_YED_NODE_CONFIG_START2 "com.yworks.bpmn.Event.withShadow" +#define GRAPHML_YED_PROPNODE "Property" +#define GRAPHML_YED_PROP_VALUE_ATTRIBUTE "value" +#define GRAPHML_YED_PROP_VALUE_START "EVENT_CHARACTERISTIC_START" +#define GRAPHML_YED_EDGELABEL "EdgeLabel" + +#define CYBERIADA_HOLLOW_NODE "" + +#define CYBERIADA_ENTRY_ACTION "entry" +#define CYBERIADA_EXIT_ACTION "exit" + +#define MAX_STR_LEN 4096 + +#ifdef __DEBUG__ +#define DEBUG(...) fprintf(stderr, __VA_ARGS__) +#else +#define DEBUG(...) +#endif + +#define ERROR(...) fprintf(stderr, __VA_ARGS__) + +/* ----------------------------------------------------------------------------- + * Utility functions + * ----------------------------------------------------------------------------- */ + +int cyberiada_copy_string(char** target, unsigned int* size, const char* source) +{ + char* target_str; + unsigned int strsize; + if (!source) + return CYBERIADA_BAD_PARAMETER; + strsize = strlen(source); + if (strsize > MAX_STR_LEN - 1) { + strsize = MAX_STR_LEN - 1; + } + target_str = (char*)malloc(strsize + 1); + strncpy(target_str, source, strsize); + target_str[strsize] = 0; + *target = target_str; + *size = strsize; + return CYBERIADA_NO_ERROR; +} + +/* ----------------------------------------------------------------------------- + * Graph manipulation functions + * ----------------------------------------------------------------------------- */ + +CyberiadaNode* cyberiada_graph_find_node(CyberiadaNode* root, const char* id) +{ + CyberiadaNode* node; + CyberiadaNode* found; + if (strcmp(root->id, id) == 0) { + return root; + } + for (node = root->next; node; node = node->next) { + found = cyberiada_graph_find_node(node, id); + if (found) + return found; + } + if (root->children) { + return cyberiada_graph_find_node(root->children, id); + } + return NULL; +} + +CyberiadaNode* cyberiada_new_node(const char* id) +{ + CyberiadaNode* new_node = (CyberiadaNode*)malloc(sizeof(CyberiadaNode)); + cyberiada_copy_string(&(new_node->id), &(new_node->id_len), id); + new_node->title = NULL; + new_node->title_len = 0; + new_node->type = cybNodeSimple; + new_node->next = NULL; + new_node->parent = NULL; + new_node->children = NULL; + new_node->action = NULL; + new_node->action_len = 0; + new_node->geometry_rect.x = new_node->geometry_rect.y = + new_node->geometry_rect.width = new_node->geometry_rect.height = 0.0; + return new_node; +} + +CyberiadaEdge* cyberiada_new_edge(const char* id, CyberiadaNode* source, CyberiadaNode* target) +{ + CyberiadaEdge* new_edge = (CyberiadaEdge*)malloc(sizeof(CyberiadaEdge)); + cyberiada_copy_string(&(new_edge->id), &(new_edge->id_len), id); + new_edge->source = source; + new_edge->target = target; + new_edge->action = NULL; + new_edge->next = NULL; + new_edge->geometry_source_point.x = new_edge->geometry_source_point.y = + new_edge->geometry_target_point.x = new_edge->geometry_target_point.y = 0.0; + new_edge->geometry_polyline = NULL; + return new_edge; +} + +int cyberiada_graph_add_sibling_node(CyberiadaNode* sibling, CyberiadaNode* new_node) +{ + CyberiadaNode* node = sibling; + if (!new_node) { + return CYBERIADA_BAD_PARAMETER; + } + new_node->parent = sibling->parent; + while (node->next) node = node->next; + node->next = new_node; + return CYBERIADA_NO_ERROR; +} + +int cyberiada_graph_add_edge(CyberiadaSM* sm, const char* id, CyberiadaNode* source, CyberiadaNode* target) +{ + CyberiadaEdge* last_edge; + CyberiadaEdge* new_edge; + if (!sm) { + return CYBERIADA_BAD_PARAMETER; + } + new_edge = cyberiada_new_edge(id, source, target); + last_edge = sm->edges; + if (last_edge == NULL) { + sm->edges = new_edge; + } else { + while (last_edge->next) last_edge = last_edge->next; + last_edge->next = new_edge; + } + return CYBERIADA_NO_ERROR; +} + +CyberiadaEdge* cyberiada_graph_find_last_edge(CyberiadaSM* sm) +{ + CyberiadaEdge* edge; + if (!sm) { + return NULL; + } + edge = sm->edges; + while (edge && edge->next) edge = edge->next; + return edge; +} + +/* ----------------------------------------------------------------------------- + * The Cyberiada GraphML library fucntions declarations + * ----------------------------------------------------------------------------- */ + +CyberiadaSM* cyberiada_create_sm() +{ + CyberiadaSM* sm = (CyberiadaSM*)malloc(sizeof(CyberiadaSM)); + cyberiada_init_sm(sm); + return sm; +} + +int cyberiada_init_sm(CyberiadaSM* sm) +{ + if (sm) { + sm->name = NULL; + sm->name = 0; + sm->version = NULL; + sm->version_len = 0; + sm->nodes = NULL; + sm->start = NULL; + sm->edges = NULL; + } + return CYBERIADA_NO_ERROR; +} + +static int cyberiada_destroy_node(CyberiadaNode* node) +{ + CyberiadaNode* n; + if(node != NULL) { + do { + n = node; + node = node->next; + if(n->id) free(n->id); + if(n->title) free(n->title); + if(n->children) { + cyberiada_destroy_node(n->children); + } + if(n->action) free(n->action); + } while (node); + } +} + +int cyberiada_cleanup_sm(CyberiadaSM* sm) +{ + CyberiadaNode* n; + CyberiadaEdge *edge, *e; + CyberiadaPolyline *polyline, *pl; + + if (sm == NULL) { + return CYBERIADA_NO_ERROR; + } + + if (sm->name) free(sm->name); + if (sm->version) free(sm->version); + if (sm->nodes) { + cyberiada_destroy_node(sm->nodes); + } + if (sm->edges) { + edge = sm->edges; + do { + e = edge; + edge = edge->next; + if (e->id) free(e->id); + if (e->action) free(e->action); + if (e->geometry_polyline) { + polyline = e->geometry_polyline; + do { + pl = polyline; + polyline = polyline->next; + free(pl); + } while (polyline); + } + free(e); + } while (edge); + } + + return CYBERIADA_NO_ERROR; +} + +int cyberiada_destroy_sm(CyberiadaSM* sm) +{ + int res = cyberiada_cleanup_sm(sm); + if (res != CYBERIADA_NO_ERROR) { + return res; + } + free(sm); + return CYBERIADA_NO_ERROR; +} + +static int cyberiada_get_attr_value(char* buffer, unsigned int buffer_len, + xmlNode* node, const char* attrname) +{ + xmlAttr* attribute = node->properties; + while(attribute) { + if (strcmp(attribute->name, attrname) == 0) { + xmlChar* value = xmlNodeListGetString(node->doc, attribute->children, 1); + strncpy(buffer, (char*)value, buffer_len); + xmlFree(value); + return CYBERIADA_NO_ERROR; + } + attribute = attribute->next; + } + return CYBERIADA_NOT_FOUND; +} + +static int cyberiada_get_element_text(char* buffer, unsigned int buffer_len, + xmlNode* node) +{ + xmlChar* value = xmlNodeListGetString(node->doc, + node->xmlChildrenNode, + 1); + strncpy(buffer, (char*)value, buffer_len); + xmlFree(value); + return CYBERIADA_NO_ERROR; +} + +/* ----------------------------------------------------------------------------- + * The Cyberiada GraphML XML processor state machine + * ----------------------------------------------------------------------------- */ + +typedef enum { + gpsInit = 0, + gpsGraph, + gpsNode, + gpsNodeGeometry, + gpsNodeTitle, + gpsNodeAction, + gpsNodeStart, + gpsEdge, + gpsEdgePath, + gpsInvalid +} GraphProcessorState; + +const char* debug_state_names[] = { + "Init", + "Graph", + "Node", + "NodeGeometry", + "NodeTitle", + "NodeAction", + "NodeStart", + "Edge", + "EdgePath", + "Invalid" +}; + +static GraphProcessorState handle_new_graph(xmlNode* xml_node, + CyberiadaSM* sm, + CyberiadaNode** current) +{ + CyberiadaNode* node; + char buffer[MAX_STR_LEN]; + unsigned int buffer_len = sizeof(buffer) - 1; + /* process the top graph element only */ + if(cyberiada_get_attr_value(buffer, buffer_len, + xml_node, + GRAPHML_ID_ATTRIBUTE) != CYBERIADA_NO_ERROR) { + return gpsInvalid; + } + node = cyberiada_new_node(CYBERIADA_HOLLOW_NODE); + DEBUG("found graph %s \n", buffer); + if (sm->nodes == NULL) { + sm->nodes = node; + } else { + (*current)->children = node; + node->parent = *current; + } + *current = node; + return gpsGraph; +} + +static GraphProcessorState handle_new_node(xmlNode* xml_node, + CyberiadaSM* sm, + CyberiadaNode** current) +{ + CyberiadaNode* node; + char buffer[MAX_STR_LEN]; + unsigned int buffer_len = sizeof(buffer) - 1; + if (cyberiada_get_attr_value(buffer, buffer_len, + xml_node, + GRAPHML_ID_ATTRIBUTE) != CYBERIADA_NO_ERROR) { + return gpsInvalid; + } + DEBUG("found node %s\n", buffer); + if (*current == NULL) { + ERROR("current node invalid\n"); + return gpsInvalid; + } + if(strcmp((*current)->id, CYBERIADA_HOLLOW_NODE) == 0) { + if (*current == sm->nodes) { + node = cyberiada_new_node(buffer); + (*current)->children = node; + node->parent = *current; + *current = node; + } else { + free((*current)->id); + cyberiada_copy_string(&((*current)->id), &((*current)->id_len), buffer); + } + } else { + node = cyberiada_new_node(buffer); + cyberiada_graph_add_sibling_node(*current, node); + *current = node; + } + return gpsNode; +} + +static GraphProcessorState handle_group_node(xmlNode* xml_node, + CyberiadaSM* sm, + CyberiadaNode** current) +{ + (*current)->type = cybNodeComplex; + return gpsNodeGeometry; +} + +static GraphProcessorState handle_comment_node(xmlNode* xml_node, + CyberiadaSM* sm, + CyberiadaNode** current) +{ + (*current)->type = cybNodeComment; + cyberiada_copy_string(&((*current)->title), &((*current)->title_len), "COMMENT"); + return gpsNodeGeometry; +} + +static GraphProcessorState handle_generic_node(xmlNode* xml_node, + CyberiadaSM* sm, + CyberiadaNode** current) +{ + char buffer[MAX_STR_LEN]; + unsigned int buffer_len = sizeof(buffer) - 1; + if (cyberiada_get_attr_value(buffer, buffer_len, + xml_node, + GRAPHML_YED_NODE_CONFIG_ATTRIBUTE) == CYBERIADA_NO_ERROR & + (strcmp(buffer, GRAPHML_YED_NODE_CONFIG_START) == 0 || + strcmp(buffer, GRAPHML_YED_NODE_CONFIG_START2) == 0)) { + (*current)->type = cybNodeInitial; + if ((*current)->title != NULL) { + ERROR("Trying to set start node %s label twice\n", (*current)->id); + return gpsInvalid; + } + cyberiada_copy_string(&((*current)->title), &((*current)->title_len), ""); + } else { + (*current)->type = cybNodeSimple; + } + return gpsNodeGeometry; +} + +static int cyberiada_xml_read_coord(xmlNode* xml_node, + const char* attr_name, + double* result) +{ + char buffer[MAX_STR_LEN]; + unsigned int buffer_len = sizeof(buffer) - 1; + if (cyberiada_get_attr_value(buffer, buffer_len, + xml_node, + attr_name) != CYBERIADA_NO_ERROR) { + return CYBERIADA_BAD_PARAMETER; + } + *result = atof(buffer); + return CYBERIADA_NO_ERROR; +} + +static int cyberiada_xml_read_rect(xmlNode* xml_node, + CyberiadaRect* r) +{ + if (cyberiada_xml_read_coord(xml_node, + GRAPHML_YED_GEOM_X_ATTRIBUTE, + &(r->x)) != CYBERIADA_NO_ERROR || + cyberiada_xml_read_coord(xml_node, + GRAPHML_YED_GEOM_Y_ATTRIBUTE, + &(r->y)) != CYBERIADA_NO_ERROR || + cyberiada_xml_read_coord(xml_node, + GRAPHML_YED_GEOM_WIDTH_ATTRIBUTE, + &(r->width)) != CYBERIADA_NO_ERROR || + cyberiada_xml_read_coord(xml_node, + GRAPHML_YED_GEOM_HEIGHT_ATTRIBUTE, + &(r->height)) != CYBERIADA_NO_ERROR) { + return CYBERIADA_BAD_PARAMETER; + } + return CYBERIADA_NO_ERROR; +} + +static GraphProcessorState handle_node_geometry(xmlNode* xml_node, + CyberiadaSM* sm, + CyberiadaNode** current) +{ + CyberiadaNodeType type = (*current)->type; + if (cyberiada_xml_read_rect(xml_node, + &((*current)->geometry_rect)) != CYBERIADA_NO_ERROR) { + return gpsInvalid; + } + if (type == cybNodeInitial) { + (*current)->geometry_rect.x += (*current)->geometry_rect.width / 2.0; + (*current)->geometry_rect.y += (*current)->geometry_rect.height / 2.0; + (*current)->geometry_rect.width = 0.0; + (*current)->geometry_rect.height = 0.0; + return gpsNodeStart; + } else if (type == cybNodeComment) { + return gpsNodeAction; + } else { + return gpsNodeTitle; + } +} + +static GraphProcessorState handle_property(xmlNode* xml_node, + CyberiadaSM* sm, + CyberiadaNode** current) +{ + char buffer[MAX_STR_LEN]; + unsigned int buffer_len = sizeof(buffer) - 1; + if (cyberiada_get_attr_value(buffer, buffer_len, + xml_node, + GRAPHML_YED_PROP_VALUE_ATTRIBUTE) != CYBERIADA_NO_ERROR) { + return gpsInvalid; + } + if (strcmp(buffer, GRAPHML_YED_PROP_VALUE_START) == 0) { + return gpsGraph; + } + return gpsNodeStart; +} + +static GraphProcessorState handle_node_title(xmlNode* xml_node, + CyberiadaSM* sm, + CyberiadaNode** current) +{ + char buffer[MAX_STR_LEN]; + unsigned int buffer_len = sizeof(buffer) - 1; + if ((*current)->title != NULL) { + ERROR("Trying to set node %s label twice\n", (*current)->id); + return gpsInvalid; + } + cyberiada_get_element_text(buffer, buffer_len, xml_node); + DEBUG("Set node %s title %s\n", (*current)->id, buffer); + cyberiada_copy_string(&((*current)->title), &((*current)->title_len), buffer); + return gpsNodeAction; +} + +static GraphProcessorState handle_node_action(xmlNode* xml_node, + CyberiadaSM* sm, + CyberiadaNode** current) +{ + char buffer[MAX_STR_LEN]; + unsigned int buffer_len = sizeof(buffer) - 1; + if ((*current)->action != NULL) { + ERROR("Trying to set node %s action twice\n", (*current)->id); + return gpsInvalid; + } + cyberiada_get_element_text(buffer, buffer_len, xml_node); + DEBUG("Set node %s action %s\n", (*current)->id, buffer); + cyberiada_copy_string(&((*current)->action), &((*current)->action_len), buffer); + return gpsGraph; +} + +static GraphProcessorState handle_new_edge(xmlNode* xml_node, + CyberiadaSM* sm, + CyberiadaNode** node) +{ + CyberiadaNode* source = NULL; + CyberiadaNode* target = NULL; + char buffer[MAX_STR_LEN]; + unsigned int buffer_len = sizeof(buffer) - 1; + if(cyberiada_get_attr_value(buffer, buffer_len, + xml_node, + GRAPHML_SOURCE_ATTRIBUTE) != CYBERIADA_NO_ERROR) { + return gpsInvalid; + } + source = cyberiada_graph_find_node(sm->nodes, buffer); + if (source == NULL) { + return gpsInvalid; + } + if(cyberiada_get_attr_value(buffer, buffer_len, + xml_node, + GRAPHML_TARGET_ATTRIBUTE) != CYBERIADA_NO_ERROR) { + return gpsInvalid; + } + target = cyberiada_graph_find_node(sm->nodes, buffer); + if (target == NULL) { + return gpsInvalid; + } + if(cyberiada_get_attr_value(buffer, buffer_len, + xml_node, + GRAPHML_ID_ATTRIBUTE) != CYBERIADA_NO_ERROR) { + buffer[0] = 0; + } + DEBUG("found edge %s\n", buffer); + DEBUG("add edge %s %s -> %s\n", buffer, source->id, target->id); + cyberiada_graph_add_edge(sm, buffer, source, target); + return gpsEdgePath; +} + +static GraphProcessorState handle_edge_geometry(xmlNode* xml_node, + CyberiadaSM* sm, + CyberiadaNode** node) +{ + CyberiadaEdge *current = cyberiada_graph_find_last_edge(sm); + if (current == NULL) { + ERROR("no current edge\n"); + return gpsInvalid; + } + if (cyberiada_xml_read_coord(xml_node, + GRAPHML_YED_GEOM_SOURCE_X_ATTRIBUTE, + &(current->geometry_source_point.x)) != CYBERIADA_NO_ERROR || + cyberiada_xml_read_coord(xml_node, + GRAPHML_YED_GEOM_SOURCE_Y_ATTRIBUTE, + &(current->geometry_source_point.y)) != CYBERIADA_NO_ERROR || + cyberiada_xml_read_coord(xml_node, + GRAPHML_YED_GEOM_TARGET_X_ATTRIBUTE, + &(current->geometry_target_point.x)) != CYBERIADA_NO_ERROR || + cyberiada_xml_read_coord(xml_node, + GRAPHML_YED_GEOM_TARGET_Y_ATTRIBUTE, + &(current->geometry_target_point.y)) != CYBERIADA_NO_ERROR) { + return gpsInvalid; + } + return gpsEdgePath; +} + +static GraphProcessorState handle_edge_point(xmlNode* xml_node, + CyberiadaSM* sm, + CyberiadaNode** node) +{ + CyberiadaEdge *current = cyberiada_graph_find_last_edge(sm); + double x, y; + CyberiadaPolyline *pl, *last_pl; + if (current == NULL) { + ERROR("no current edge\n"); + return gpsInvalid; + } + if (cyberiada_xml_read_coord(xml_node, + GRAPHML_YED_GEOM_X_ATTRIBUTE, + &x) != CYBERIADA_NO_ERROR || + cyberiada_xml_read_coord(xml_node, + GRAPHML_YED_GEOM_Y_ATTRIBUTE, + &y) != CYBERIADA_NO_ERROR) { + return gpsInvalid; + } + pl = (CyberiadaPolyline*)malloc(sizeof(CyberiadaPolyline)); + pl->point.x = x; + pl->point.y = y; + pl->next = NULL; + if (current->geometry_polyline == NULL) { + current->geometry_polyline = pl; + } else { + last_pl = current->geometry_polyline; + while (last_pl->next) last_pl = last_pl->next; + last_pl->next = pl; + } + return gpsEdgePath; +} + +static GraphProcessorState handle_edge_label(xmlNode* xml_node, + CyberiadaSM* sm, + CyberiadaNode** node) +{ + char buffer[MAX_STR_LEN]; + unsigned int buffer_len = sizeof(buffer) - 1; + CyberiadaEdge *current; + current = cyberiada_graph_find_last_edge(sm); + if (current == NULL) { + ERROR("no current edge\n"); + return gpsInvalid; + } + if (current->action != NULL) { + ERROR("Trying to set edge %s:%s label twice\n", + current->source->id, current->target->id); + return gpsInvalid; + } + cyberiada_get_element_text(buffer, buffer_len, xml_node); + DEBUG("add edge %s:%s label %s\n", + current->source->id, current->target->id, buffer); + cyberiada_copy_string(&(current->action), &(current->action_len), buffer); + return gpsGraph; +} + +typedef GraphProcessorState (*Handler)(xmlNode* xml_root, + CyberiadaSM* sm, + CyberiadaNode** current); + +typedef struct { + GraphProcessorState state; + const char* symbol; + Handler handler; +} ProcessorTransition; + +static ProcessorTransition processor_state_table[] = { + {gpsInit, GRAPHML_GRAPH_ELEMENT, &handle_new_graph}, + {gpsGraph, GRAPHML_NODE_ELEMENT, &handle_new_node}, + {gpsGraph, GRAPHML_EDGE_ELEMENT, &handle_new_edge}, + {gpsGraph, GRAPHML_GRAPH_ELEMENT, &handle_new_graph}, + {gpsNode, GRAPHML_YED_COMMENTNODE, &handle_comment_node}, + {gpsNode, GRAPHML_YED_GROUPNODE, &handle_group_node}, + {gpsNode, GRAPHML_YED_GENERICNODE, &handle_generic_node}, + {gpsNodeGeometry, GRAPHML_YED_GEOMETRYNODE, &handle_node_geometry}, + {gpsNodeStart, GRAPHML_YED_PROPNODE, &handle_property}, + {gpsNodeStart, GRAPHML_NODE_ELEMENT, &handle_new_node}, + {gpsNodeTitle, GRAPHML_YED_LABELNODE, &handle_node_title}, + {gpsNodeAction, GRAPHML_YED_LABELNODE, &handle_node_action}, + {gpsEdge, GRAPHML_EDGE_ELEMENT, &handle_new_edge}, + {gpsEdgePath, GRAPHML_YED_PATHNODE, &handle_edge_geometry}, + {gpsEdgePath, GRAPHML_YED_POINTNODE, &handle_edge_point}, + {gpsEdgePath, GRAPHML_YED_EDGELABEL, &handle_edge_label}, + {gpsEdgePath, GRAPHML_EDGE_ELEMENT, &handle_new_edge}, +}; +const unsigned int processor_state_table_size = sizeof(processor_state_table) / sizeof(ProcessorTransition); + +static int dispatch_processor(xmlNode* xml_node, + CyberiadaSM* sm, + CyberiadaNode** current, + GraphProcessorState* gps) { + unsigned int i; + if (xml_node->type == XML_ELEMENT_NODE) { + for (i = 0; i < processor_state_table_size; i++) { + if (processor_state_table[i].state == *gps && + strcmp(xml_node->name, processor_state_table[i].symbol) == 0) { + *gps = (*(processor_state_table[i].handler))(xml_node, sm, current); + return CYBERIADA_NO_ERROR; + } + } + } + return CYBERIADA_NOT_FOUND; +} + +static int cyberiada_build_graph(xmlNode* xml_root, + CyberiadaSM* sm, + CyberiadaNode** current, + GraphProcessorState* gps) +{ + xmlNode *cur_xml_node = NULL; + for (cur_xml_node = xml_root; cur_xml_node; cur_xml_node = cur_xml_node->next) { + DEBUG("xml node %s sm root %s current %s gps %s\n", + cur_xml_node->name, + sm->nodes ? sm->nodes->id : "none", + *current ? (*current)->id : "none", + debug_state_names[*gps]); + dispatch_processor(cur_xml_node, sm, current, gps); + if (*gps == gpsInvalid) { + return CYBERIADA_FORMAT_ERROR; + } + if (cur_xml_node->children) { + int res = cyberiada_build_graph(cur_xml_node->children, sm, current, gps); + if (res != CYBERIADA_NO_ERROR) { + return res; + } + } + } + return CYBERIADA_NO_ERROR; +} + +static int cyberiada_decode_yed_xml(xmlNode* root, CyberiadaSM* sm) +{ + char buffer[MAX_STR_LEN]; + unsigned int buffer_len = sizeof(buffer) - 1; + GraphProcessorState gps = gpsInit; + CyberiadaNode* current = NULL; + int res; + + if ((res = cyberiada_build_graph(root, sm, ¤t, &gps)) != CYBERIADA_NO_ERROR) { + return res; + } + if (cyberiada_get_attr_value(buffer, buffer_len, + root, + GRAPHML_BERLOGA_SCHEMENAME_ATTR) != CYBERIADA_NO_ERROR) { + if (sm->nodes && sm->nodes->children) { + cyberiada_copy_string(&(sm->name), &(sm->name_len), sm->nodes->children->title); + } else { + cyberiada_copy_string(&(sm->name), &(sm->name_len), ""); + } + cyberiada_copy_string(&(sm->version), &(sm->version_len), "YED Ostranna"); + } else { + cyberiada_copy_string(&(sm->name), &(sm->name_len), buffer); + cyberiada_copy_string(&(sm->version), &(sm->version_len), "YED Berloga 1.4"); + } + return CYBERIADA_NO_ERROR; +} + +static int cyberiada_decode_cyberiada_xml(xmlNode* root, CyberiadaSM* sm) +{ + return CYBERIADA_FORMAT_ERROR; +} + +static int cyberiada_check_graphml_ns(xmlNode* root) +{ + xmlNs* ns; + if (!root || !(ns = root->nsDef)) { + ERROR("bad GraphML XML NS: null ns ptr\n"); + return CYBERIADA_XML_ERROR; + } + do { + if (strcmp(ns->href, GRAPHML_NAMESPACE_URI) == 0) { + return CYBERIADA_NO_ERROR; + } + ns = ns->next; + } while (ns); + ERROR("no GraphML XML NS href\n"); + return CYBERIADA_XML_ERROR; +} + +int cyberiada_read_sm(CyberiadaSM* sm, const char* filename, CyberiadaXMLFormat format) +{ + int res; + xmlDoc* doc = NULL; + xmlNode* root = NULL; + char buffer[MAX_STR_LEN]; + unsigned int buffer_len; + + cyberiada_init_sm(sm); + + /* parse the file and get the DOM */ + if ((doc = xmlReadFile(filename, NULL, 0)) == NULL) { + ERROR("error: could not parse file %s\n", filename); + return CYBERIADA_XML_ERROR; + } + + /* get the root element node */ + root = xmlDocGetRootElement(doc); + + if (strcmp(root->name, GRAPHML_GRAPHML_ELEMENT) != 0) { + ERROR("error: could not find GraphML root node %s\n", filename); + return CYBERIADA_XML_ERROR; + } + + /* check whether the xml is graphml */ + if (cyberiada_check_graphml_ns(root)) { + ERROR("error: no graphml namespace in %s\n", filename); + return CYBERIADA_XML_ERROR; + } + + if (format == cybxmlYED) { + res = cyberiada_decode_yed_xml(root, sm); + } else if (format == cybxmlCyberiada) { + res = cyberiada_decode_cyberiada_xml(root, sm); + } else { + ERROR("error: unsupported GraphML format %d of file %s\n", + format, filename); + return CYBERIADA_XML_ERROR; + } + + xmlFreeDoc(doc); + xmlCleanupParser(); + + return res; +} + +static int cyberiada_print_node(CyberiadaNode* node, CyberiadaNode* start, int level) +{ + CyberiadaNode* cur_node; + char levelspace[16]; + char buffer[MAX_STR_LEN]; + unsigned int buffer_len = sizeof(buffer) - 1; + int i; + + memset(levelspace, 0, sizeof(levelspace)); + for(i = 0; i < level; i++) { + if (i == 14) break; + levelspace[i] = ' '; + } + + printf("%sNode {id: %s, title: \"%s\", type: %d", + levelspace, node->id, node->title, (int)node->type); + if (node == start) { + printf(", S"); + } + printf("}\n"); + printf("%sGeometry: (%lf, %lf, %lf, %lf)\n", + levelspace, + node->geometry_rect.x, + node->geometry_rect.y, + node->geometry_rect.width, + node->geometry_rect.height); + + printf("%sActions:\n", levelspace); + if(node->action) { + printf("%s\"%s\"\n", levelspace, node->action); + } + + printf("%sChildren:\n", levelspace); + for (cur_node = node->children; cur_node; cur_node = cur_node->next) { + cyberiada_print_node(cur_node, start, level + 1); + } + + return CYBERIADA_NO_ERROR; +} + +static int cyberiada_print_edge(CyberiadaEdge* edge) +{ + char buffer[MAX_STR_LEN]; + unsigned int buffer_len = sizeof(buffer) - 1; + CyberiadaPolyline* polyline; + int i; + printf(" Edge %s [%s %s]->[%s %s]\n", + edge->id, + edge->source->id, + edge->source->type == cybNodeInitial ? "INIT" : edge->source->title, + edge->target->id, + edge->target->title == cybNodeInitial ? "INIT" : edge->target->title); + if (edge->geometry_polyline == NULL) { + printf(" Geometry: (%lf, %lf)->(%lf, %lf)\n", + edge->geometry_source_point.x, + edge->geometry_source_point.y, + edge->geometry_target_point.x, + edge->geometry_target_point.y); + } else { + int i; + printf(" Geometry: (\n"); + printf(" (%lf, %lf)\n", edge->geometry_source_point.x, edge->geometry_source_point.y); + for (polyline = edge->geometry_polyline; polyline; polyline = polyline->next) { + printf(" (%lf, %lf)\n", + polyline->point.x, + polyline->point.y); + } + printf(" (%lf, %lf)\n", edge->geometry_target_point.x, edge->geometry_target_point.y); + printf(" )\n"); + } + if (edge->action) { + printf(" Action:\n %s\n", edge->action); + } + return CYBERIADA_NO_ERROR; +} + +int cyberiada_print_sm(CyberiadaSM* sm) +{ + char buffer[MAX_STR_LEN]; + unsigned int buffer_len = sizeof(buffer) - 1; + CyberiadaNode* cur_node; + CyberiadaEdge* cur_edge; + + printf("\nState Machine {name: %s, version: %s}\n", sm->name, sm->version); + + printf("Nodes:\n"); + for (cur_node = sm->nodes; cur_node; cur_node = cur_node->next) { + cyberiada_print_node(cur_node, sm->start, 0); + } + printf("\n"); + + printf("Edges:\n"); + for (cur_edge = sm->edges; cur_edge; cur_edge = cur_edge->next) { + cyberiada_print_edge(cur_edge); + } + printf("\n"); + + return CYBERIADA_NO_ERROR; +} diff --git a/cyberiadaml.h b/cyberiadaml.h new file mode 100644 index 0000000..803b8b6 --- /dev/null +++ b/cyberiadaml.h @@ -0,0 +1,140 @@ +/* ----------------------------------------------------------------------------- + * The Cyberiada GraphML library implemention + * + * The C library header + * + * Copyright (C) 2024 Alexey Fedoseev + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/ + * ----------------------------------------------------------------------------- */ + +#ifndef __CYBERIADA_ML_H +#define __CYBERIADA_ML_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ----------------------------------------------------------------------------- + * The Cyberiada GraphML library types + * ----------------------------------------------------------------------------- */ + +/* SM node types: */ +typedef enum { + cybNodeInitial = 0, /* initial node */ + cybNodeSimple, /* simple node */ + cybNodeComplex, /* complex node */ + cybNodeComment /* comment node */ +} CyberiadaNodeType; + +/* SM node geometry */ + +typedef struct { + double x, y; +} CyberiadaPoint; + +typedef struct { + double x, y, width, height; +} CyberiadaRect; + +typedef struct _CyberiadaPolyline { + CyberiadaPoint point; + struct _CyberiadaPolyline* next; +} CyberiadaPolyline; + +/* SM node (state) */ +typedef struct _CyberiadaNode { + char* id; + unsigned int id_len; + char* title; + unsigned int title_len; + CyberiadaNodeType type; + char* action; + unsigned int action_len; + CyberiadaRect geometry_rect; + struct _CyberiadaNode* next; + struct _CyberiadaNode* parent; + struct _CyberiadaNode* children; +} CyberiadaNode; + +/* SM edge (transition) */ +typedef struct _CyberiadaEdge { + char* id; + unsigned int id_len; + CyberiadaNode* source; + CyberiadaNode* target; + char* action; + unsigned int action_len; + CyberiadaPoint geometry_source_point; + CyberiadaPoint geometry_target_point; + CyberiadaPolyline* geometry_polyline; + struct _CyberiadaEdge* next; +} CyberiadaEdge; + +/* SM graph (state machine) */ +typedef struct { + char* name; + unsigned int name_len; + char* version; + unsigned int version_len; + CyberiadaNode* nodes; + CyberiadaNode* start; + CyberiadaEdge* edges; +} CyberiadaSM; + +/* SM GraphML supported formats */ +typedef enum { + cybxmlYED = 0, + cybxmlCyberiada +} CyberiadaXMLFormat; + +/* ----------------------------------------------------------------------------- + * The Cyberiada GraphML error codes + * ----------------------------------------------------------------------------- */ + +#define CYBERIADA_NO_ERROR 0 +#define CYBERIADA_XML_ERROR 1 +#define CYBERIADA_FORMAT_ERROR 2 +#define CYBERIADA_NOT_FOUND 3 +#define CYBERIADA_BAD_PARAMETER 4 + +/* ----------------------------------------------------------------------------- + * The Cyberiada GraphML library functions + * ----------------------------------------------------------------------------- */ + + /* Allocate the SM structure in memory (for heap usage) */ + CyberiadaSM* cyberiada_create_sm(); + + /* Initialize the SM structure. Do not use the structure before the initialization! */ + int cyberiada_init_sm(CyberiadaSM* sm); + + /* Cleanup the content of the SM structure, free the conents memory */ + int cyberiada_cleanup_sm(CyberiadaSM* sm); + + /* Free the allocated SM structure (for heap usage) */ + int cyberiada_destroy_sm(CyberiadaSM* sm); + + /* Read an XML file and decode the SM structure */ + int cyberiada_read_sm(CyberiadaSM* sm, const char* filename, CyberiadaXMLFormat format); + + /* Print the SM structure to stdout */ + int cyberiada_print_sm(CyberiadaSM* sm); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/graph-samples/CyberiadaFormat-Autoborder.graphml b/graph-samples/CyberiadaFormat-Autoborder.graphml new file mode 100644 index 0000000..960a968 --- /dev/null +++ b/graph-samples/CyberiadaFormat-Autoborder.graphml @@ -0,0 +1,98 @@ + + + +Cyberiada-GraphML + + + + + + + + + + + + BearlogaDefend + name/ Автобортник +author/ Матросов В.М. +contact/ matrosov@mail.ru +description/ Пример описания схемы, +который может быть многострочным, потому что так удобнее +unit/ Autoborder + + + + + Бой + entry/ + +exit/ + + + + + Сближение + entry/ +МодульДвижения.ДвигатьсяКЦели() + +exit/ + + + + + Атака + entry/ +ОружиеЦелевое.АтаковатьЦель() + +exit/ + + + + + + + Скан + entry/ +Сенсор.ПоискВрагаПоДистанции(мин) + +exit/ +Сенсор.ОстановкаПоиска() + + + + + + + + + + + АнализаторЦели.ЦельУничтожена/ + + + + АнализаторЦели.ЦельПотеряна/ + + + + Сенсор.ЦельПолучена/ + + + + ОружиеЦелевое.ЦельВошлаВЗонуАтаки/ + + + + ОружиеЦелевое.ЦельВышлаИзЗоныАтаки/ + + + + + + \ No newline at end of file diff --git a/graph-samples/CyberiadaFormat-Blinker.graphml b/graph-samples/CyberiadaFormat-Blinker.graphml new file mode 100644 index 0000000..d6cbf91 --- /dev/null +++ b/graph-samples/CyberiadaFormat-Blinker.graphml @@ -0,0 +1,79 @@ + + + +Cyberiada-GraphML + + + + + + + + + + + ArduinoUno + name/ Arduino-Blinker +description/ Включение и выключение лампочки по таймеру + + + + + + + + + + LED1 + type/ LED +name/ Светодиод +description/ Встроенный в плату светодиод, чтобы им мигать +pin/ 12 + + + + + timer1 + type/ Timer +name/ Светодиод +description/ Программный таймер. + + + + + Включен + entry/ +LED1.on() +timer1.start(1000) + + + + + + Выключен + entry/ +LED1.off() +timer1.start(1000) + + + + + + + + + timer1.timeout/ + #F29727 + + + + + timer1.timeout/ + + #F24C3D + + + + diff --git a/graph-samples/berloga-autoborder.graphml b/graph-samples/berloga-autoborder.graphml new file mode 100644 index 0000000..4d635b5 --- /dev/null +++ b/graph-samples/berloga-autoborder.graphml @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Бой + entry/ +exit/ + + + + + + + + + + + + + + + + + + Сближение + entry/ +МодульДвижения.ДвигатьсяКЦели() +exit/ + + + + + + + + + + + Атака + entry/ +ОружиеЦелевое.АтаковатьЦель() +exit/ + + + + + + + + + + + + + Скан + entry/ +Сенсор.ПоискВрагаПоДистанции(мин) +exit/ +Сенсор.ОстановкаПоиска() + + + + + + + + + + + АнализаторЦели.ЦельПотеряна/ + + + + + + + + + + + АнализаторЦели.ЦельУничтожена/ + + + + + + + + + + + Сенсор.ЦельПолучена/ + + + + + + + + + + + ОружиеЦелевое.ЦельВошлаВЗонуАтаки/ + + + + + + + + + + + ОружиеЦелевое.ЦельВышлаИзЗоныАтаки/ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/graph-samples/berloga-stapler.graphml b/graph-samples/berloga-stapler.graphml new file mode 100644 index 0000000..5728f07 --- /dev/null +++ b/graph-samples/berloga-stapler.graphml @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Бой + entry/ +exit/ + + + + + + + + + + + + + + + + + + Сближение + entry/ +МодульДвижения.ДвигатьсяКЦели() +exit/ + + + + + + + + + + + Атака + entry/ +ОружиеЦелевое.АтаковатьЦель() +МодульДвижения.Стоп() +exit/ + + + + + + + + + + + + + Скан + entry/ +Сенсор.ПоискВрагаПоДистанции(мин) +exit/ +Сенсор.ОстановкаПоиска() + + + + + + + + + + + АнализаторЦели.ЦельПотеряна/ + + + + + + + + + + + АнализаторЦели.ЦельУничтожена/ + + + + + + + + + + + Сенсор.ЦельПолучена/ +[ОружиеЦелевое.ЦельВЗонеАтаки == 1] + + + + + + + + + + + Сенсор.ЦельПолучена/ + + + + + + + + + + + ОружиеЦелевое.ЦельВошлаВЗонуАтаки/ + + + + + + + + + + + ОружиеЦелевое.ЦельВышлаИзЗоныАтаки/ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/graph-samples/orbita-orient.graphml b/graph-samples/orbita-orient.graphml new file mode 100644 index 0000000..6fab4c2 --- /dev/null +++ b/graph-samples/orbita-orient.graphml @@ -0,0 +1,588 @@ + + + + + + + + + + + + + + + + + + + + + + + + + orientation + + +entry/ + + + + + + + + + + + get_shot + + + + + + + + + + + + + + + + + + + turn + + +entry/ +orientation.start_motor(AXIS_Z) + +exit/ +orientation.stop_motor(AXIS_Z) + + + + + + + + + + + get_shot + + + + + + + + + + + + + + + + + + slow_down + + +entry/ +reduce_speed() + + + + + + + + + + + + + + + start_turn + + +entry/ +calculate_turn() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + finish_turn + + +entry/ +complete_turn() + + + + + + + + + + + + + + + + + idle + + +entry/ + + + + + + + + + + + + + + + + + wait + + +entry/ + + + + + + + + + + + + get_shot + + + + + + + + + + + + + + + + + + + maintain + + +entry/ +orientation.start_motor(AXIS_Z) + +exit/ +orientation.stop_motor(AXIS_Z) + + + + + + + + + + + get_shot + + + + + + + + + + + + + + + + + + correct_cw + + +entry/ +orientation.set_motor_moment(AXIS_Z, -M) + + + + + + + + + + + + + + + correct_ccw + + +entry/ +orientation.set_motor_moment(AXIS_Z, M) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + static + + +entry/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Init scripts: + +test3sm_constants.py +test3sm_orient.py + + + + + + + + + + + + + + + + + + + + + + + STOP + + + + + + + + + + + + + TANGENT(target_a) / +update_turn_parameters(target_a, 0.0) + + + + + + + + + + + + + BACK(target_a) / +update_turn_parameters(target_a, 180.0) + + + + + + + + + + + + + + + + + + + + + [completed()] + + + + + + + + + + + [completed()] + + + + + + + + + + + + + TIME_TICK [orientation_completed()] / +DISPATCH(cpu, 'ORIENTED') +DISPATCH(navigation, 'ORIENTED') + + + + + + + + + + + [orientation.get_angular_velocity(AXIS_Z) < DW] + + + + + + + + + + + [orientation.get_angular_velocity(AXIS_Z) > DW] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [completed()] + + + + + + + + + + + [is_target_dw()] + + + + + + + + + + + [not is_target_dw()] + + + + + + + + + diff --git a/graph-samples/ostranna-player.graphml b/graph-samples/ostranna-player.graphml new file mode 100644 index 0000000..0f9d518 --- /dev/null +++ b/graph-samples/ostranna-player.graphml @@ -0,0 +1,899 @@ + + + + + + + + + + + + + + + + + + + + + + + + State fields (do not delete this caption): + +unsigned int CharHP; +QStateHandler StartState; +unsigned int TimerAgony; + + + + + + + + + + + Code for h-file: (do not delete this caption): +#define HEALTHY 1 +#define AGONY 2 +#define DEAD 0 +#define GHOUL_GOOD 3 +#define GHOUL_WOUNDED 4 +#define GHOUL_HEALING 5 +#define BLESSED 6 +#define FLASH_MS 200 +#define FLASH_SEC 1010 +#define FLASH_1M 60100 +#define TIMEOUT_AGONY_S 600 +#define TIMEOUT_DEATH_S 15 +#define TIMEOUT_RADX_S 900 +#define LONG_BEEP_MS 15000 +#define MEDIUM_BEEP_MS 3000 +#define SHORT_BEEP_MS 500 +#define RED 255 +#define RED_MEDIUM 127 +#define GREEN_MEDIUM 127 +#define BLUE_MEDIUM 127 + +#define DEFAULT_HP 27000 +#define GHOUL_HP (DEFAULT_HP/3) + + + + + + + + + + + Constructor fields (do not delete this caption): +unsigned int HP; +unsigned int State; +unsigned int TimerAgony; + + + + + + + + + + + + Constructor code: (do not delete this caption): +me->CharHP = HP; +me->TimerAgony = TimerAgony; +switch (State) { + case HEALTHY: { + me->StartState = + (QStateHandler)&OregonPlayer_healthy; + break; + } + case AGONY: { + me->StartState = + (QStateHandler)& OregonPlayer_agony; + break; + } + case DEAD: { + me->StartState = + (QStateHandler)& OregonPlayer_dead; + break; + + } + case GHOUL_GOOD: { + me->StartState = + (QStateHandler)& OregonPlayer_ghoul_good; + break; + } + case GHOUL_WOUNDED: { + me->StartState = + (QStateHandler)& OregonPlayer_wounded; + break; + } + case GHOUL_HEALING: { + me->StartState = + (QStateHandler)& OregonPlayer_ghoul_healing; + break; + } + case BLESSED: { + me->StartState = + (QStateHandler)& OregonPlayer_blessed; + break; + } + default: + me->StartState =(QStateHandler)& OregonPlayer_healthy; + } + + + + + + + + + + + + Comments: + +void ShowCurrentHealth (OregonPlayer* me) { + Flash(255 - me->CharHP*255/DEFAULT_HP, me>CharHP*255/DEFAULT_HP, 0, FLASH_MS); +} + +void ShowCurrentHealthGhoul (OregonPlayer* me) { + Flash(255 - me->CharHP*255/DEFAULT_HP, me>CharHP*255/DEFAULT_HP, 0, FLASH_MS); +} + +void UpdateHP(OregonPlayer* me, unsigned int NewHP) { + if (NewHP <= DEFAULT_HP) { + me->CharHP = NewHP;; + } else { + me->CharHP = DEFAULT_HP + SaveHP(me->CharHP); +} + +void Reset(OregonPlayer* me) { + UpdateHP(me, DEFAULT_HP); + me->TimerDeath = 0; + UpdateTimerAgony(0); + return Q_TRAN(healthy); + +} + +void UpdateTimerAgony(PlayerOregon* me, unsigned int Timer) { + me->TimerAgony = Timer; + SaveTimerAgony(Timer); +} + + + + + + + + + + + + active + + +entry/ + + + + + + + + + + + + Folder 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + alive + + + entry/ + +\ + + + + + + + + + + + + get_shot + + + + + + + + + + + + + + + + + + agony + + +entry/ + BeepForPeriod(LONG_BEEP_MS); + SaveState(AGONY); + UpdateHP(me, 1); + me->TimerAgony = 0; + +TIME_TICK_1S[else]/ + me->TimerAgony++; + Flash(RED, 0, 0, FLASH_MS); + +TIME_TICK_10S/ + BeepForPeriod(SHORT_BEEP_MS); + + + + + + + + + + + + + + + healthy + + +entry/ + SaveState(HEALTHY); + BeepForTime(MEDIUM_BEEP_MS); + +RAD_RCVD[else]/ + UpdateHP(me, me->CharHP-((oregonPlayerQEvt*)e)->value); + +TIME_TICK_1S/ + ShowCurrentHealth(me); + + HEAL/ + UpdateHP(me, me->CharHP+((oregonPlayerQEvt*)e)->value); + + + + + + + + + + + + + + + + immune + + +entry/ + BeepForTime(MEDIUM_BEEP_MS); + +TIME_TICK_1S/ + Flash(0, GREEN_MEDIUM, 0, FLASH_SEC); + + HEAL/ + UpdateHP(me, me->CharHP+((oregonPlayerQEvt*)e)->value); + + + + + + + + + + + Folder 4 + + + + + + + + + + + + + + + + + temp_immune + + +entry/ + + + + + + + + + + + + + + + + + + blessed + + +entry/ + SaveState(BLESSED); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ghoul + + +entry/ + +TIME_TICK_1S/ + ShowCurrentHealthGhoul(me); + + + + + + + + + + + Folder 4 + + + + + + + + + + + + + + + + + ghoul_good + + +entry/ + SaveState(GHOUL_GOOD); + BeepForPeriod(MEDIUM_BEEP_MS); + UpdateHP(me, GHOUL_HP); + + + + + + + + + + + + + + + + wounded + + +entry/ + SaveState(GHOUL_WOUNDED); + UpdateHP(me, 1); + BeepForPeriod(LONG_BEEP_MS); + +TIME_TICK_10S/ + BeepForPeriod(SHORT_BEEP_MS); + + + + + + + + + + + + + + + ghoul_healing + + +entry/ + SaveState(GHOUL_HEALING); + +RAD_RCVD[else]/ + UpdateHP(me, me->CharHP + ((oregonPlayerQEvt*)e)->value); + + + + + + + + + + + + + + + + + + dead + + +entry/ + SaveState(DEAD); + BeepForTime(LONG_BEEP_MS); + UpdateHP(me, 0); + Flash(RED, 0, 0, 1000); + +TIME_TICK_1M/ + BeepForPeriod(SHORT_BEEP_MS); + Flash(255, 0, 0, FLASH_1M); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Event fields (do not delete this caption): + +unsigned int value; + + + + + + + + + + + test + + +entry/ + BeepForPeriod(SHORT_BEEP_MS); + Flash(127, 0, 0, FLASH_MS); + +RAD_RCVD/ + BeepForPeriod(SHORT_BEEP_MS); + Flash(127, 0, 0, FLASH_MS); + + + + + + + + + + + + + + RAD_RCVD[((oregonPlayerQEvt*)e)->value >= me->CharHP] + + + + + + + + + + + + + + + TIME_TICK_1S[me->TimerAgony > TIMEOUT_AGONY_S] + + + + + + + + + + + return Q_TRAN(me->StartState); + + + + + + + + + + + + + AGONY + + + + + + + + + + + + + + + IMMUNE + + + + + + + + + + + + + PILL_GHOUL + + + + + + + + + + + + + + + + + PILL_REMOVED + + + + + + + + + + + + + + + + + RAD_RCVD/ + UpdateHP(me, me->CharHP + ((oregonPlayerQEvt*)e)->value); + + + + + + + + + + + + + NOT_IMMUNE + + + + + + + + + + + + + BLESSED + + + + + + + + + + + + + + + AGONY + + + + + + + + + + + + + + + + PILL_RESET/ + Reset(me); + + + + + + + + + + + RAD_RCVD [((((oregonPlayerQEvt*)e)->value+me->CharHP )>=GHOUL_HP)] + + + + + + + + + + + PILL_GHOUL + + + + + + + + + + + + + + PILL_RESET/ + Reset(me); + + + + + + + + + + + PILL_TEST + + + + + + + + + + + + + + HEAL/ + UpdateHP(me, me->CharHP + ((oregonPlayerQEvt*)e)->value); + + + + + + + + + + + TEST_TRIGGER + + + + + + + + + + + [me->CharHP>0] + + + + + + + + + + + [else] + + + + + + + + + diff --git a/test.c b/test.c new file mode 100644 index 0000000..b6e9324 --- /dev/null +++ b/test.c @@ -0,0 +1,88 @@ +/* ----------------------------------------------------------------------------- + * The Cyberiada GraphML library implemention + * + * The testing program + * + * Copyright (C) 2024 Alexey Fedoseev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/ + * ----------------------------------------------------------------------------- */ + +#include +#include + +#include "cyberiadaml.h" + +const char* formats[] = { + "yed", /* cybxmlYED */ + "cyberiada" /* cybxmlCyberiada */ +}; + +const char* format_names[] = { + "yEd editor format used by Ostranna projects and the Orbita Simulator", + "Cyberiada-GraphML format" +}; + +unsigned int format_count = sizeof(formats) / sizeof(char*); + +void print_usage(const char* name) +{ + unsigned int i; + fprintf(stderr, "%s -t \n\n", name); + fprintf(stderr, "Supported formats:\n"); + for (i = 0; i < format_count; i++) { + fprintf(stderr, " %-20s %s\n", formats[i], format_names[i]); + } + fprintf(stderr, "\n"); +} + +int main(int argc, char** argv) +{ + char *filename; + char *format_str = ""; + CyberiadaXMLFormat format; + unsigned int i; + int res; + CyberiadaSM sm; + + if (argc != 4 || + strcmp(argv[1], "-t") != 0) { + print_usage(argv[0]); + return 1; + } + for(i = 0; i < format_count; i++) { + if (strcmp(argv[2], formats[i]) == 0) { + format = (CyberiadaXMLFormat)i; + format_str = argv[2]; + break; + } + } + if(strlen(format_str) == 0) { + fprintf(stderr, "unsupported graphml format %s\n", argv[2]); + print_usage(argv[0]); + return 2; + } + filename = argv[3]; + if ((res = cyberiada_read_sm(&sm, filename, format)) != CYBERIADA_NO_ERROR) { + fprintf(stderr, "error while reading %s file: %d\n", + filename, res); + return 2; + } + + cyberiada_print_sm(&sm); + + cyberiada_cleanup_sm(&sm); + + return 0; +}