commit aa6509b8f94f8016e2d72c5390fc2152657b4c27 Author: Alexey Fedoseev Date: Thu Apr 11 00:03:38 2024 +0300 initial commit 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..a4384b7 --- /dev/null +++ b/Makefile @@ -0,0 +1,48 @@ +LIB_TARGET_STATIC := libcyberiadamlpp.a +LIB_TARGET_DYNAMIC := libcyberiadamlpp.so + +ifeq ($(DYNAMIC), 1) + LIB_TARGET := $(LIB_TARGET_DYNAMIC) +else + LIB_TARGET := $(LIB_TARGET_STATIC) +endif + +TEST_TARGET := cyberiadapp_test +LIB_SOURCES := cyberiadamlpp.cpp +TEST_SOURCES := test.cpp +LIB_OBJECTS := $(patsubst %.cpp, %.o, $(LIB_SOURCES)) +TEST_OBJECTS := $(patsubst %.cpp, %.o, $(TEST_SOURCES)) + +ifeq ($(DEBUG), 1) + CFLAGS := -Wall -Wshadow -Wconversion -fPIC -g3 -D__DEBUG__ + LFLAGS := +else + CFLAGS := -fPIC + LFLAGS := +endif + +INCLUDE := -I. -I/usr/include/libxml2 -I./cyberiadaml +LIBS := -L/usr/lib -lxml2 -L./cyberiadaml -lcyberiadaml +TEST_LIBS := -L. -lcyberiadamlpp + +$(LIB_TARGET): $(LIB_OBJECTS) +ifeq ($(DYNAMIC), 1) + g++ -shared $(LIBS) $(LIB_OBJECTS) -o $@ +else + ar rcs $@ $(LIB_OBJECTS) +endif + +$(TEST_TARGET): $(TEST_OBJECTS) $(LIB_TARGET) $(LIB_ORJECTS) + g++ $(TEST_OBJECTS) -Wl,-\( $(LIBS) $(TEST_LIBS) -Wl,-\) -o $@ + +%.o: %.cpp + g++ -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..3d792be --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# The Cyberida State Machine C++ Library + +The C++ library for processing CyberiadaML - the version of GraphML for storing state machine graphs +used by the Cyberiada Project, the Berloga Project games and the Orbita Simulator. + +This is C++ wrapper interface to the libcyberiadaml C library. + +The code is distributed under the Lesser GNU Public License (version 3), the documentation -- under +the GNU Free Documentation License (version 1.3). + +## Requirements + +* libcyberidaml +* libstdc++ + +## 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/cyberiadamlpp.cpp b/cyberiadamlpp.cpp new file mode 100644 index 0000000..0c770b6 --- /dev/null +++ b/cyberiadamlpp.cpp @@ -0,0 +1,1344 @@ +/* ----------------------------------------------------------------------------- + * 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 "cyberiadamlpp.h" + +#define CYB_CHECK_RESULT(r) this->check_cyberiada_error((r), std::string(__FILE__) + ":" + std::to_string(__LINE__)) + +#define CYB_ASSERT(q) if (!(q)) { \ + throw AssertException(std::string(__FILE__) + ":" + std::to_string(__LINE__)); \ + } + +namespace Cyberiada { + + static const String STANDARD_VERSION = "1.0"; + static const String DEFAULT_GRAPHML_FORMAT = "Cyberiada-GraphML-1.0"; + const String VERTEX_ID_PREFIX = "n"; + const String SM_ID_PREFIX = "G"; + const String TRANTISION_ID_SEP = "-"; + const String TRANTISION_ID_NUM_SEP = "#"; + static const std::string tab = "\t"; +}; + +using namespace Cyberiada; + +// ----------------------------------------------------------------------------- +// Element +// ----------------------------------------------------------------------------- + +Element::Element(Element* _parent, ElementType _type, const ID& _id): + type(_type), id(_id), name_is_set(false), parent(_parent) +{ +} + +Element::Element(Element* _parent, ElementType _type, const ID& _id, const Name& _name): + type(_type), id(_id), parent(_parent) +{ + set_name(_name); +} + +void Element::set_name(const Name& n) +{ + name = n; + name_is_set = true; +} + +bool Element::has_qualified_name() const +{ + return is_root() || name_is_set || parent->has_qualified_name(); +} + +QualifiedName Element::qualified_name() const +{ + if (is_root()) { + return name; + } else { + if (parent->is_root()) { + return name; + } else { + return parent->qualified_name() + QUALIFIED_NAME_SEPARATOR + name; + } + } +} + +Element* Element::find_root() +{ + if (!parent) { + return this; + } else { + return parent->find_root(); + } +} + +std::ostream& Element::dump(std::ostream& os) const +{ + String type_str; + switch (type) { + case elementRoot: type_str = "doc"; break; + case elementSM: type_str = "stm"; break; + case elementSimpleState: type_str = "sst"; break; + case elementCompositeState: type_str = "cst"; break; + case elementComment: type_str = "ico"; break; + case elementFormalComment: type_str = "fco"; break; + case elementInitial: type_str = "ini"; break; + case elementFinal: type_str = "fin"; break; + case elementChoice: type_str = "cho"; break; + case elementTransition: type_str = "tra"; break; + default: + CYB_ASSERT(false); + } + os << "Element type: " << type_str << ", id: '" << id << "'"; + if (name_is_set) { + os << ", name: '" << name << "'"; + } + return os; +} + +std::ostream& Cyberiada::operator<<(std::ostream& os, const Element& e) +{ + e.dump(os); + return os; +} + +// ----------------------------------------------------------------------------- +// Geometry +// ----------------------------------------------------------------------------- + +Point::Point(CyberiadaPoint* p) +{ + if (p) { + valid = true; + x = p->x; + y = p->y; + } else { + valid = false; + } +} + +Rect::Rect(CyberiadaRect* r) +{ + if (r) { + valid = true; + x = r->x; + y = r->y; + width = r->width; + height = r->height; + } else { + valid = false; + } +} + +// ----------------------------------------------------------------------------- +// Action +// ----------------------------------------------------------------------------- + +Action::Action(ActionType _type, const Guard& _guard, const Behavior& _behavior): + type(_type), guard(_guard), behavior(_behavior) +{ +} + +Action::Action(const Event& _trigger, const Guard& _guard, const Behavior& _behavior): + type(actionTransition), trigger(_trigger), guard(_guard), behavior(_behavior) +{ +} + +std::ostream& Action::dump(std::ostream& os) const +{ + if (!trigger.empty()) { + os << "trigger: '" << trigger << "'"; + } + if (!guard.empty()) { + if (!trigger.empty()) { + os << ", "; + } + os << "guard: '" << guard << "'"; + } + if (!behavior.empty()) { + if (!trigger.empty() || !guard.empty()) { + os << ", "; + } + os << "behavior: '" << behavior << "'"; + } + return os; +} + +// ----------------------------------------------------------------------------- +// Geometry objects +// ----------------------------------------------------------------------------- + +std::ostream& Cyberiada::operator<<(std::ostream& os, const Point& p) +{ + if (!p.valid) { + os << "()"; + } else { + os << "(" << p.x << "; " << p.y << ")"; + } + return os; +} + +std::ostream& Cyberiada::operator<<(std::ostream& os, const Rect& r) +{ + if (!r.valid) { + os << "()"; + } else { + os << "(" << r.x << "; " << r.y << "; " << r.width << "; " << r.height << ")"; + } + return os; +} + +std::ostream& Cyberiada::operator<<(std::ostream& os, const Polyline& pl) +{ + os << "[ "; + for (Polyline::const_iterator i = pl.begin(); i != pl.end(); i++) { + os << *i; + if (std::next(i) != pl.end()) { + os << ", "; + } + } + os << " ]"; + return os; +} + +// ----------------------------------------------------------------------------- +// Comment +// ----------------------------------------------------------------------------- + +CommentSubject::CommentSubject(Element* _element, + const Point& source, const Point& target, const Polyline& pl): + type(commentSubjectElement), element(_element), has_frag(false), source_point(source), target_point(target), polyline(pl) +{ +} + +CommentSubject::CommentSubject(Element* _element, CommentSubjectType _type, const String& _fragment, + const Point& source, const Point& target, const Polyline& pl): + type(_type), element(_element), has_frag(true), fragment(_fragment), source_point(source), target_point(target), polyline(pl) +{ +} + +CommentSubject& CommentSubject::operator=(const CommentSubject& cs) +{ + element = cs.element; + has_frag = cs.has_frag; + fragment = cs.fragment; + source_point = cs.source_point; + target_point = cs.target_point; + polyline = cs.polyline; + return *this; +} + +std::ostream& Cyberiada::operator<<(std::ostream& os, const CommentSubject& cs) +{ + cs.dump(os); + return os; +} + +std::ostream& CommentSubject::dump(std::ostream& os) const +{ + os << "{"; + if (element) { + String type_str; + if (type == commentSubjectElement) { + type_str = "element"; + } else if (type == commentSubjectName) { + type_str = "name"; + } else { + CYB_ASSERT(type == commentSubjectData); + type_str = "data"; + } + os << "to: '" << element->get_id(); + os << "', type: " << type_str; + if (has_frag) { + os << ", fragment: '" << fragment << "'"; + } + if (source_point.valid) { + os << ", source point: " << source_point; + } + if (target_point.valid) { + os << ", target point: " << target_point; + } + if (!polyline.empty()) { + os << ", polyline: " << polyline; + } + } + os << "}"; + return os; +} + +Comment::Comment(Element* _parent, const ID& _id, const String& _body, bool _human_readable, + const String& _markup, const Rect& rect, const Color& _color): + Element(_parent, elementComment, _id), body(_body), markup(_markup), + human_readable(_human_readable), geometry_rect(rect), color(_color) +{ + update_comment_type(); +} + +Comment::Comment(Element* _parent, const ID& _id, const String& _body, bool _human_readable, + const Name& _name, const String& _markup, const Rect& rect, const Color& _color): + Element(_parent, elementComment, _id, _name), body(_body), markup(_markup), + human_readable(_human_readable), geometry_rect(rect), color(_color) +{ + update_comment_type(); +} + +void Comment::add_subject(const CommentSubject& s) +{ + subjects.push_back(s); +} + +void Comment::remove_subject(CommentSubjectType _type, const String& fragment) +{ + for (std::list::iterator i = subjects.begin(); i != subjects.end(); i++) { + if (i->get_type() == _type && + i->has_fragment() + && i->get_fragment() == fragment) { + + subjects.erase(i); + break; + } + } +} + +void Comment::update_comment_type() +{ + if (human_readable) { + this->set_type(elementComment); + } else { + this->set_type(elementFormalComment); + } +} + +std::ostream& Comment::dump(std::ostream& os) const +{ + os << "Comment { " << (human_readable ? "informal" : "formal") << ", "; + Element::dump(os); + os << ", body: '" << body << "'"; + if (has_geometry()) { + os << ", geometry: " << geometry_rect; + } + if (has_subjects()) { + os << "subjects: {"; + for (std::list::const_iterator i = subjects.begin(); i != subjects.end(); i++) { + os << *i; + if(std::next(i) != subjects.end()) { + os << ", "; + } + } + os << "}"; + } + os << "}"; + return os; +} + +// ----------------------------------------------------------------------------- +// Vertex +// ----------------------------------------------------------------------------- + +Vertex::Vertex(Element* _parent, ElementType _type, const ID& _id, const Point& pos): + Element(_parent, _type, _id), geometry_point(pos) +{ +} + +Vertex::Vertex(Element* _parent, ElementType _type, const ID& _id, const Name& _name, const Point& pos): + Element(_parent, _type, _id, _name), geometry_point(pos) +{ +} + +std::ostream& Vertex::dump(std::ostream& os) const +{ + if (has_geometry()) { + os << ", geometry: " << geometry_point; + } + return os; +} + +// ----------------------------------------------------------------------------- +// Collection of Elements +// ----------------------------------------------------------------------------- + +ElementCollection::ElementCollection(Element* _parent, ElementType _type, const ID& _id, const Name& _name, + const Rect& rect, const Color& _color): + Element(_parent, _type, _id, _name), geometry_rect(rect), color(_color) +{ +} + +ElementCollection::~ElementCollection() +{ + clear(); +} + +bool ElementCollection::has_qualified_name(const ID& element_id) const +{ + const Element* e = find_element_by_id(element_id); + if (!e) { + return false; + } else { + return e->has_qualified_name(); + } +} + +QualifiedName ElementCollection::qualified_name(const ID& element_id) const +{ + const Element* e = find_element_by_id(element_id); + if (!e) { + throw NotFoundException(); + } else { + if (is_root()) { + return e->qualified_name(); + } else { + return get_name() + QUALIFIED_NAME_SEPARATOR + e->qualified_name(); + } + } +} + +const Element* ElementCollection::find_element_by_id(const ID& _id) const +{ + for (ElementList::const_iterator i = children.begin(); i != children.end(); i++) { + const Element* e = *i; + if (e->get_id() == _id) { + return e; + } else if (e->has_children()) { + const ElementCollection* c = static_cast(e); + e = c->find_element_by_id(_id); + if (e) { + return e; + } + } + } + return NULL; +} + +Element* ElementCollection::find_element_by_id(const ID& _id) +{ + for (ElementList::iterator i = children.begin(); i != children.end(); i++) { + Element* e = *i; + if (e->get_id() == _id) { + return e; + } else if (e->has_children()) { + ElementCollection* c = static_cast(e); + e = c->find_element_by_id(_id); + if (e) { + return e; + } + } + } + return NULL; +} + +ConstElementList ElementCollection::find_elements_by_type(ElementType _type) const +{ + ConstElementList result; + for (ElementList::const_iterator i = children.begin(); i != children.end(); i++) { + const Element* e = *i; + if (e->get_type() == _type) { + result.push_back(e); + } else if (e->has_children()) { + const ElementCollection* c = static_cast(e); + ConstElementList r = c->find_elements_by_type(_type); + result.insert(result.end(), r.begin(), r.end()); + } + } + return result; +} + +ConstElementList ElementCollection::find_elements_by_types(const ElementTypes& types) const +{ + ConstElementList result; + for (ElementList::const_iterator i = children.begin(); i != children.end(); i++) { + const Element* e = *i; + if (std::find(types.begin(), types.end(), e->get_type()) != types.end()) { + result.push_back(e); + } else if (e->has_children()) { + const ElementCollection* c = static_cast(e); + ConstElementList r = c->find_elements_by_types(types); + result.insert(result.end(), r.begin(), r.end()); + } + } + return result; +} + +ElementList ElementCollection::find_elements_by_type(ElementType _type) +{ + ElementList result; + for (ElementList::iterator i = children.begin(); i != children.end(); i++) { + Element* e = *i; + if (e->get_type() == _type) { + result.push_back(e); + } else if (e->has_children()) { + ElementCollection* c = static_cast(e); + ElementList r = c->find_elements_by_type(_type); + result.insert(result.end(), r.begin(), r.end()); + } + } + return result; +} + +ElementList ElementCollection::find_elements_by_types(const ElementTypes& types) +{ + ElementList result; + for (ElementList::iterator i = children.begin(); i != children.end(); i++) { + Element* e = *i; + if (std::find(types.begin(), types.end(), e->get_type()) != types.end()) { + result.push_back(e); + } else if (e->has_children()) { + ElementCollection* c = static_cast(e); + ElementList r = c->find_elements_by_types(types); + result.insert(result.end(), r.begin(), r.end()); + } + } + return result; +} + +size_t ElementCollection::elements_count() const +{ + size_t count = 0; + for (ElementList::const_iterator i = children.begin(); i != children.end(); i++) { + count += (*i)->elements_count(); + } + return count; +} + +void ElementCollection::add_element(Element* e) +{ + CYB_ASSERT(e); + CYB_ASSERT(e->get_parent() == this); + children.push_back(e); +} + +void ElementCollection::remove_element(const ID& _id) +{ + for (ElementList::iterator i = children.begin(); i != children.end(); i++) { + if ((*i)->get_id() == _id) { + children.remove(*i); + break; + } + } +} + +void ElementCollection::clear() +{ + for (ElementList::iterator i = children.begin(); i != children.end(); i++) { + Element* e = *i; + delete e; + } + children.clear(); +} + +std::list ElementCollection::get_vertexes() const +{ + ElementTypes types = { elementSimpleState, + elementCompositeState, + elementInitial, + elementFinal, + elementChoice }; + std::list result; + ConstElementList vertexes = find_elements_by_types(types); + for (ConstElementList::const_iterator i = vertexes.begin(); i != vertexes.end(); i++) { + result.push_back(static_cast(*i)); + } + return result; +} + +std::list ElementCollection::get_vertexes() +{ + ElementTypes types = { elementComment, + elementFormalComment, + elementSimpleState, + elementCompositeState, + elementInitial, + elementFinal, + elementChoice }; + std::list result; + ElementList vertexes = find_elements_by_types(types); + for (ElementList::const_iterator i = vertexes.begin(); i != vertexes.end(); i++) { + result.push_back(static_cast(*i)); + } + return result; +} + +std::ostream& ElementCollection::dump(std::ostream& os) const +{ + if (has_geometry()) { + os << ", geometry: " << geometry_rect; + if (has_color()) { + os << ", color: " << color; + } + } + if (has_children()) { + os << ", elements: {"; + for (ElementList::const_iterator i = children.begin(); i != children.end(); i++) { + const Element* e = *i; + CYB_ASSERT(e); + os << *e; + if (std::next(i) != children.end()) { + os << ", "; + } + } + os << "}"; + } + return os; +} + +// ----------------------------------------------------------------------------- +// Pseudostate +// ----------------------------------------------------------------------------- + +Pseudostate::Pseudostate(Element* _parent, ElementType _type, const ID& _id, const Point& p): + Vertex(_parent, _type, _id, p) +{ +} + +Pseudostate::Pseudostate(Element* _parent, ElementType _type, const ID& _id, const Name& _name, const Point& p): + Vertex(_parent, _type, _id, _name, p) +{ +} + +// ----------------------------------------------------------------------------- +// Initial pseudostate +// ----------------------------------------------------------------------------- + +InitialPseudostate::InitialPseudostate(Element* _parent, const ID& _id, const Point& p): + Pseudostate(_parent, elementInitial, _id, p) +{ +} + +InitialPseudostate::InitialPseudostate(Element* _parent, const ID& _id, const Name& _name, const Point& p): + Pseudostate(_parent, elementInitial, _id, _name, p) +{ +} + +std::ostream& InitialPseudostate::dump(std::ostream& os) const +{ + os << "Initial {"; + Element::dump(os); + Vertex::dump(os); + os << "}"; + return os; +} + +// ----------------------------------------------------------------------------- +// Choice pseudostate +// ----------------------------------------------------------------------------- + +ChoicePseudostate::ChoicePseudostate(Element* _parent, const ID& _id, const Rect& r, const Color& _color): + Pseudostate(_parent, elementChoice, _id), geometry_rect(r), color(_color) +{ +} + +ChoicePseudostate::ChoicePseudostate(Element* _parent, const ID& _id, const Name& _name, const Rect& r, const Color& _color): + Pseudostate(_parent, elementChoice, _id, _name), geometry_rect(r), color(_color) +{ +} + +std::ostream& ChoicePseudostate::dump(std::ostream& os) const +{ + os << "Choice {"; + Element::dump(os); + if (has_geometry()) { + os << ", geometry: " << geometry_rect; + if (has_color()) { + os << ", color: " << color; + } + } + os << "}"; + return os; +} + +// ----------------------------------------------------------------------------- +// Final state +// ----------------------------------------------------------------------------- + +FinalState::FinalState(Element* _parent, const ID& _id, const Point& p): + Vertex(_parent, elementFinal, _id, p) +{ +} + +FinalState::FinalState(Element* _parent, const ID& _id, const Name& _name, const Point& p): + Vertex(_parent, elementFinal, _id, _name, p) +{ +} + +std::ostream& FinalState::dump(std::ostream& os) const +{ + os << "Final {"; + Element::dump(os); + Vertex::dump(os); + os << "}"; + return os; +} + +// ----------------------------------------------------------------------------- +// State +// ----------------------------------------------------------------------------- + +std::ostream& Cyberiada::operator<<(std::ostream& os, const Action& a) +{ + a.dump(os); + return os; +} + +State::State(Element* _parent, const ID& _id, const Name& _name, const Rect& r, const Color& c): + ElementCollection(_parent, elementSimpleState, _id, _name, r, c) +{ +} + +void State::add_element(Element* e) +{ + ElementCollection::add_element(e); + update_state_type(); +} + +void State::remove_element(const ID& _id) +{ + ElementCollection::remove_element(_id); + update_state_type(); +} + +void State::add_action(const Action& a) +{ + actions.push_back(a); +} + +std::list State::get_substates() const +{ + ElementTypes types = { elementSimpleState, + elementCompositeState }; + std::list result; + ConstElementList states = find_elements_by_types(types); + for (ConstElementList::const_iterator i = states.begin(); i != states.end(); i++) { + result.push_back(static_cast(*i)); + } + return result; +} + +std::list State::get_substates() +{ + ElementTypes types = { elementSimpleState, + elementCompositeState }; + std::list result; + ElementList states = find_elements_by_types(types); + for (ElementList::const_iterator i = states.begin(); i != states.end(); i++) { + result.push_back(static_cast(*i)); + } + return result; +} + +void State::update_state_type() +{ + if (has_children()) { + this->set_type(elementCompositeState); + } else { + this->set_type(elementSimpleState); + } +} + +std::ostream& State::dump(std::ostream& os) const +{ + if (is_simple_state()) { + os << "State {"; + } else { + os << "Composite state {"; + } + Element::dump(os); + if (has_actions()) { + os << ", actions: {"; + for (std::list::const_iterator i = actions.begin(); i != actions.end(); i++) { + os << *i; + if (std::next(i) != actions.end()) { + os << ", "; + } + } + os << "}"; + } + ElementCollection::dump(os); + os << "}"; + return os; +} + +// ----------------------------------------------------------------------------- +// Transition +// ----------------------------------------------------------------------------- + +Transition::Transition(Element* _parent, const ID& _id, Element* _source, Element* _target, const Action& _action, + const Polyline& pl, const Point& sp, const Point& tp, const Point& label, + const Color& c): + Element(_parent, elementTransition, _id), source(_source), target(_target), action(_action), + source_point(sp), target_point(tp), label_point(label), polyline(pl), color(c) +{ +} + +std::ostream& Transition::dump(std::ostream& os) const +{ + os << "Transition {"; + Element::dump(os); + if (has_action()) { + os << ", action: {"; + os << action; + os << "}"; + } + if (has_geometry()) { + if (source_point.valid) { + os << ", sp: " << source_point; + } + if (target_point.valid) { + os << ", tp: " << target_point; + } + if (label_point.valid) { + os << ", label: " << label_point; + } + if (has_polyline()) { + os << ", polyline: " << polyline; + } + if (has_color()) { + os << ", color: " << color; + } + } + os << "}"; + return os; +} + +// ----------------------------------------------------------------------------- +// State Machine +// ----------------------------------------------------------------------------- +StateMachine::StateMachine(Element* _parent, const ID& _id, const Name& _name, const Rect& r): + ElementCollection(_parent, elementSM, _id, _name, r) +{ +} + +std::list StateMachine::get_comments() const +{ + ElementTypes types = { elementComment, + elementFormalComment }; + std::list result; + ConstElementList comments = find_elements_by_types(types); + for (ConstElementList::const_iterator i = comments.begin(); i != comments.end(); i++) { + result.push_back(static_cast(*i)); + } + return result; +} + +std::list StateMachine::get_comments() +{ + ElementTypes types = { elementComment, + elementFormalComment }; + std::list result; + ElementList comments = find_elements_by_types(types); + for (ElementList::iterator i = comments.begin(); i != comments.end(); i++) { + result.push_back(static_cast(*i)); + } + return result; +} + +std::list StateMachine::get_transitions() const +{ + std::list result; + ConstElementList transitions = find_elements_by_type(elementTransition); + for (ConstElementList::const_iterator i = transitions.begin(); i != transitions.end(); i++) { + result.push_back(static_cast(*i)); + } + return result; +} + +std::list StateMachine::get_transitions() +{ + std::list result; + ElementList transitions = find_elements_by_type(elementTransition); + for (ElementList::iterator i = transitions.begin(); i != transitions.end(); i++) { + result.push_back(static_cast(*i)); + } + return result; +} + +std::ostream& StateMachine::dump(std::ostream& os) const +{ + os << "State Machine {"; + Element::dump(os); + ElementCollection::dump(os); + os << "}"; + return os; +} + +// ----------------------------------------------------------------------------- +// Cyberiada-GraphML Document +// ----------------------------------------------------------------------------- +Document::Document(): + ElementCollection(NULL, elementRoot, "", ""), + format(DEFAULT_GRAPHML_FORMAT) +{ + reset(); +} + +void Document::reset() +{ + metainfo = DocumentMetainformation(); + metainfo.standard_version = STANDARD_VERSION; + metainfo.transition_order_flag = false; + metainfo.event_propagation_flag = false; + format = DEFAULT_GRAPHML_FORMAT; + clear(); +} + +StateMachine* Document::new_state_machine(const String& sm_name, const Rect& r) +{ + StateMachine* sm = new StateMachine(this, generate_sm_id(), sm_name, r); + add_element(sm); + return sm; +} + +StateMachine* Document::new_state_machine(const ID& _id, const String& sm_name, const Rect& r) +{ + StateMachine* sm = new StateMachine(this, _id, sm_name, r); + add_element(sm); + return sm; +} + +void Document::check_cyberiada_error(int res, const String& msg) +{ + switch (res) { + case CYBERIADA_XML_ERROR: throw XMLException(msg); + case CYBERIADA_FORMAT_ERROR: throw CybMLException(msg); + case CYBERIADA_ACTION_FORMAT_ERROR: throw ActionException(msg); + case CYBERIADA_METADATA_FORMAT_ERROR: throw MetainformationException(msg); + case CYBERIADA_NOT_FOUND: throw NotFoundException(msg); + case CYBERIADA_BAD_PARAMETER: throw ParametersException(msg); + case CYBERIADA_ASSERT: throw AssertException(msg); + default: + break; + } +} + +void Document::import_nodes_recursively(ElementCollection* collection, CyberiadaNode* nodes) +{ + CYB_ASSERT(collection); + for (CyberiadaNode* n = nodes; n; n = n->next) { + Element* element = NULL; + State* state = NULL; + + CYB_ASSERT(n->id); + + Point point; + Rect rect; + Color _color; + String comment_body; + String comment_markup; + + if (n->geometry_rect) { + rect = Rect(n->geometry_rect); + } + if (n->geometry_point) { + point = Point(n->geometry_point); + } + if (n->color) { + _color = Color(n->color); + } + + switch (n->type) { + case cybNodeSimpleState: + case cybNodeCompositeState: + if (!n->title) { + throw CybMLException("State element w/o title"); + } + state = new State(collection, n->id, n->title, rect, _color); + CYB_ASSERT(state); + + for (CyberiadaAction* a = n->actions; a; a = a->next) { + if (a->type == cybActionTransition) { + state->add_action(Action(a->trigger, a->guard, a->behavior)); + } else { + ActionType at; + if (a->type == cybActionEntry) { + at = actionEntry; + } else if (a->type == cybActionExit) { + at = actionExit; + } else { + throw CybMLException("Unsupported action type " + std::to_string(a->type)); + } + state->add_action(Action(at, a->guard, a->behavior)); + } + } + + element = state; + + break; + + case cybNodeComment: + case cybNodeFormalComment: + if (!n->comment_data) { + throw CybMLException("No comment data in Comment element"); + } + + if (n->comment_data->body) { + comment_body = n->comment_data->body; + } + if (n->comment_data->markup) { + comment_markup = n->comment_data->markup; + } + + if (n->title) { + element = new Comment(collection, n->id, comment_body, n->type == cybNodeComment, + n->title, comment_markup, rect, _color); + } else { + element = new Comment(collection, n->id, comment_body, n->type == cybNodeComment, + comment_markup, rect, _color); + } + break; + + case cybNodeChoice: + if (n->title) { + element = new ChoicePseudostate(collection, n->id, n->title, rect, _color); + } else { + element = new ChoicePseudostate(collection, n->id, rect, _color); + } + break; + + case cybNodeInitial: + if (n->title) { + element = new InitialPseudostate(collection, n->id, n->title, point); + } else { + element = new InitialPseudostate(collection, n->id, point); + } + + break; + + case cybNodeFinal: + if (n->title) { + element = new FinalState(collection, n->id, n->title, point); + } else { + element = new FinalState(collection, n->id, point); + } + + break; + + default: + throw CybMLException("Unsupported node type " + std::to_string(n->type)); + } + + collection->add_element(element); + + if (n->children) { + if (collection->get_type() != elementSM && + collection->get_type() != elementSimpleState && + collection->get_type() != elementCompositeState) { + + throw CybMLException("Children nodes inside element with type " + + std::to_string(collection->get_type())); + } + + import_nodes_recursively(static_cast(element), n->children); + } + } +} + +void Document::import_edges(ElementCollection* collection, CyberiadaEdge* edges) +{ + CYB_ASSERT(collection); + for (CyberiadaEdge* e = edges; e; e = e->next) { + CYB_ASSERT(e->id); + + Element* element = NULL; + Point source_point, target_point, label_point; + Polyline polyline; + Color _color; + Action action; + + if (e->geometry_source_point) { + source_point = Point(e->geometry_source_point); + } + if (e->geometry_target_point) { + target_point = Point(e->geometry_target_point); + } + if (e->geometry_label_point) { + label_point = Point(e->geometry_label_point); + } + if (e->geometry_polyline) { + for (CyberiadaPolyline* pl = e->geometry_polyline; pl; pl = pl->next) { + polyline.push_back(Point(pl->point.x, pl->point.y)); + } + } + if (e->color) { + _color = Color(e->color); + } + + Element* source_element = find_element_by_id(e->source_id); + CYB_ASSERT(source_element); + Element* target_element = find_element_by_id(e->target_id); + CYB_ASSERT(target_element); + Comment* comment = NULL; + + switch (e->type) { + case cybEdgeTransition: + if (e->action) { + action = Action(e->action->trigger, e->action->guard, e->action->behavior); + } + + element = new Transition(collection, e->id, source_element, target_element, + action, polyline, source_point, target_point, label_point, + _color); + break; + + case cybEdgeComment: + CYB_ASSERT(source_element->get_type() == elementComment || + source_element->get_type() == elementFormalComment); + CYB_ASSERT(e->comment_subject); + + comment = static_cast(source_element); + + if (e->comment_subject->type == cybCommentSubjectNode) { + comment->add_subject(CommentSubject(target_element, source_point, target_point, polyline)); + } else { + CommentSubjectType cst; + if (e->comment_subject->type == cybCommentSubjectNameFragment) { + cst = commentSubjectName; + } else if (e->comment_subject->type == cybCommentSubjectDataFragment) { + cst = commentSubjectData; + } else { + throw CybMLException("Unsupported comment subject type " + std::to_string(e->comment_subject->type)); + } + CYB_ASSERT(e->comment_subject->fragment); + comment->add_subject(CommentSubject(target_element, cst, e->comment_subject->fragment, + source_point, target_point, polyline)); + } + break; + + default: + throw CybMLException("Unsupported edge type " + std::to_string(e->type)); + } + + collection->add_element(element); + } +} + +void Document::load(const String& path) +{ + reset(); + CyberiadaDocument doc; + int res = cyberiada_init_sm_document(&doc); + CYB_ASSERT(res == CYBERIADA_NO_ERROR); + + res = cyberiada_read_sm_document(&doc, path.c_str(), cybxmlUnknown); + if (res != CYBERIADA_NO_ERROR) { + cyberiada_cleanup_sm_document(&doc); + CYB_CHECK_RESULT(res); + } + + try { + + CYB_ASSERT(doc.format); + format = doc.format; + + CYB_ASSERT(doc.meta_info); + CYB_ASSERT(doc.meta_info->standard_version); + metainfo.standard_version = doc.meta_info->standard_version; + if (doc.meta_info->platform_name) { + metainfo.platform_name = doc.meta_info->platform_name; + } + if (doc.meta_info->platform_version) { + metainfo.platform_version = doc.meta_info->platform_version; + } + if (doc.meta_info->platform_language) { + metainfo.platform_language = doc.meta_info->platform_language; + } + if (doc.meta_info->target_system) { + metainfo.target_system = doc.meta_info->target_system; + } + if (doc.meta_info->name) { + metainfo.name = doc.meta_info->name; + } + if (doc.meta_info->author) { + metainfo.author = doc.meta_info->author; + } + if (doc.meta_info->contact) { + metainfo.contact = doc.meta_info->contact; + } + if (doc.meta_info->description) { + metainfo.description = doc.meta_info->description; + } + if (doc.meta_info->version) { + metainfo.version = doc.meta_info->version; + } + if (doc.meta_info->date) { + metainfo.date = doc.meta_info->date; + } + metainfo.transition_order_flag = bool(doc.meta_info->transition_order_flag); + metainfo.event_propagation_flag = bool(doc.meta_info->event_propagation_flag); + + for (CyberiadaSM* sm = doc.state_machines; sm; sm = sm->next) { + CyberiadaNode* root = sm->nodes; + CYB_ASSERT(root); + CYB_ASSERT(root->type == cybNodeSM); + CYB_ASSERT(!root->next); + CYB_ASSERT(root->id); + StateMachine* new_sm; + if (root->title) { + new_sm = new_state_machine(root->id, root->title, root->geometry_rect); + } else { + new_sm = new_state_machine(root->id, "", root->geometry_rect); + } + CYB_ASSERT(new_sm); + if (root->children) { + import_nodes_recursively(new_sm, root->children); + } + if (sm->edges) { + import_edges(new_sm, sm->edges); + } + } + } catch (const CybMLException& e) { + cyberiada_cleanup_sm_document(&doc); + throw CybMLException(e.str()); + } catch (const Exception& e) { + cyberiada_cleanup_sm_document(&doc); + throw AssertException("Internal load error: " + e.str()); + } + + cyberiada_cleanup_sm_document(&doc); +} + +void Document::save(const String& path) const +{ + // TODO +} + +std::list Document::get_state_machines() const +{ + std::list result; + for (ElementList::const_iterator i = children.begin(); i != children.end(); i++) { + CYB_ASSERT((*i)->get_type() == elementSM); + result.push_back(static_cast(*i)); + } + return result; +} + +std::list Document::get_state_machines() +{ + std::list result; + for (ElementList::const_iterator i = children.begin(); i != children.end(); i++) { + CYB_ASSERT((*i)->get_type() == elementSM); + result.push_back(static_cast(*i)); + } + return result; +} + +std::ostream& Document::dump(std::ostream& os) const +{ + os << "Document [" << format << "] {"; + Element::dump(os); + os << ", meta: {"; + std::list params; + if (!metainfo.standard_version.empty()) { + params.push_back("standard version: '" + metainfo.standard_version + "'"); + } + if (!metainfo.platform_name.empty()) { + params.push_back("platform name: '" + metainfo.platform_name + "'"); + } + if (!metainfo.platform_version.empty()) { + params.push_back("platform version: '" + metainfo.platform_version + "'"); + } + if (!metainfo.platform_language.empty()) { + params.push_back("platform language: '" + metainfo.platform_language + "'"); + } + if (!metainfo.target_system.empty()) { + params.push_back("target system: '" + metainfo.target_system + "'"); + } + if (!metainfo.name.empty()) { + params.push_back("name: '" + metainfo.name + "'"); + } + if (!metainfo.author.empty()) { + params.push_back("author: '" + metainfo.author + "'"); + } + if (!metainfo.contact.empty()) { + params.push_back("contact: '" + metainfo.contact + "'"); + } + if (!metainfo.description.empty()) { + params.push_back("description: '" + metainfo.description + "'"); + } + if (!metainfo.version.empty()) { + params.push_back("version: '" + metainfo.version + "'"); + } + if (!metainfo.date.empty()) { + params.push_back("date: '" + metainfo.date + "'"); + } + params.push_back(String("transition order: ") + (metainfo.transition_order_flag ? "transition first": "exit first")); + params.push_back(String("event propagation: ") + (metainfo.event_propagation_flag ? "block events": "propagate events")); + for (std::list::const_iterator i = params.begin(); i != params.end(); i++) { + os << *i; + if (std::next(i) != params.end()) { + os << ", "; + } + } + os << "}"; + ElementCollection::dump(os); + return os; +} + +ID Document::generate_sm_id() const +{ + std::list sm = get_state_machines(); + size_t id_num = sm.size(); + ID result; + do { + std::ostringstream s; + s << SM_ID_PREFIX << id_num; + result = ID(s.str()); + id_num++; + } while(find_element_by_id(result)); + return result; +} + +ID Document::generate_vertex_id(const Element* p) const +{ + size_t id_num = 0; + ID result; + String base_name; + if (p != NULL && p->get_type() != elementRoot && p->get_type() != elementSM) { + base_name = p->get_id(); + } + do { + std::ostringstream s; + if (base_name.empty()) { + s << VERTEX_ID_PREFIX << id_num; + } else { + s << base_name << QUALIFIED_NAME_SEPARATOR << id_num; + } + result = ID(s.str()); + id_num++; + } while(find_element_by_id(result)); + return result; +} + +ID Document::generate_transition_id(const String& source_id, const String& target_id) const +{ + std::ostringstream s; + size_t id_num = 0; + String base_name; + s << source_id << TRANTISION_ID_SEP << target_id; + base_name = s.str(); + + String result = ID(base_name); + while (find_element_by_id(result)) { + std::ostringstream s2; + s2 << base_name << TRANTISION_ID_NUM_SEP << id_num; + result = ID(s2.str()); + id_num++; + } + return result; +} diff --git a/cyberiadamlpp.h b/cyberiadamlpp.h new file mode 100644 index 0000000..7988655 --- /dev/null +++ b/cyberiadamlpp.h @@ -0,0 +1,596 @@ +/* ----------------------------------------------------------------------------- + * 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_CPP_H +#define __CYBERIADA_ML_CPP_H + +#include +#include +#include +#include +#include + +// ----------------------------------------------------------------------------- +// The Cyberiada GraphML classes +// ----------------------------------------------------------------------------- + +namespace Cyberiada { + +// ----------------------------------------------------------------------------- +// Base types +// ----------------------------------------------------------------------------- + // Cyberiada diagram element types: + enum ElementType { + elementRoot, // document (root namespace) + elementSM, // state machine + elementSimpleState, // simple state + elementCompositeState, // composite state + elementComment, // informal (human-readable) comment node + elementFormalComment, // formal (machine-readable) comment node + elementInitial, // initial pseudostate + elementFinal, // final state + elementChoice, // choice pseudostate + elementTransition // transition + }; + + typedef std::string String; + typedef String ID; + typedef String Name; + typedef String QualifiedName; + typedef String Color; + + const String QUALIFIED_NAME_SEPARATOR = "::"; + +// ----------------------------------------------------------------------------- +// Base Element +// ----------------------------------------------------------------------------- + class Element { + public: + Element(Element* parent, ElementType type, const ID& id); + Element(Element* parent, ElementType type, const ID& id, const Name& name); + virtual ~Element() {} + + ElementType get_type() const { return type; } + + const ID& get_id() const { return id; } + + bool has_name() const { return name_is_set; } + const Name& get_name() const { return name; } + void set_name(const Name& name); + bool has_qualified_name() const; + QualifiedName qualified_name() const; + + bool is_root() const { return !parent; } + Element* get_parent() { return parent; } + virtual bool has_children() const { return false; } + virtual size_t elements_count() const { return 1; } + + virtual bool has_geometry() const = 0; + + friend std::ostream& operator<<(std::ostream& os, const Element& e); + + protected: + Element* find_root(); + void set_type(ElementType t) { type = t; }; + virtual std::ostream& dump(std::ostream& os) const; + + private: + ElementType type; + ID id; + Name name; + bool name_is_set; + Element* parent; + }; + + std::ostream& operator<<(std::ostream& os, const Element& e); + +// ----------------------------------------------------------------------------- +// Geometry +// ----------------------------------------------------------------------------- + struct Point { + Point(): valid(false) {} + Point(float _x, float _y): + valid(true), x(_x), y(_y) {} + Point(CyberiadaPoint* p); + + bool valid; + float x, y; + }; + + struct Rect { + Rect(): valid(false) {} + Rect(float _x, float _y, float _width, float _height): + valid(true), x(_x), y(_y), width(_width), height(_height) {} + Rect(CyberiadaRect* r); + + bool valid; + float x, y; + float width, height; + }; + + typedef std::list Polyline; + + std::ostream& operator<<(std::ostream& os, const Point& p); + std::ostream& operator<<(std::ostream& os, const Rect& r); + std::ostream& operator<<(std::ostream& os, const Polyline& pl); + +// ----------------------------------------------------------------------------- +// Comment +// ----------------------------------------------------------------------------- + enum CommentSubjectType { + commentSubjectElement = 0, + commentSubjectName, + commentSubjectData + }; + + class CommentSubject { + public: + CommentSubject(Element* element, + const Point& source = Point(), const Point& target = Point(), const Polyline& pl = Polyline()); + CommentSubject(Element* element, CommentSubjectType type, const String& fragment, + const Point& source = Point(), const Point& target = Point(), const Polyline& pl = Polyline()); + + CommentSubject& operator=(const CommentSubject& cs); + + CommentSubjectType get_type() const { return type; } + const Element* get_element() const { return element; } + Element* get_element() { return element; } + + bool has_fragment() const { return has_frag; } + const String& get_fragment() const { return fragment; } + + bool has_geometry() const { return source_point.valid || target_point.valid || !polyline.empty(); } + const Point& get_geometry_source_point() const { return source_point; } + const Point& get_geometry_target_point() const { return target_point; } + const Polyline& get_geometry_polyline() const { return polyline; } + + protected: + std::ostream& dump(std::ostream& os) const; + friend std::ostream& operator<<(std::ostream& os, const CommentSubject& cs); + + private: + CommentSubjectType type; + Element* element; + bool has_frag; + String fragment; + Point source_point; + Point target_point; + Polyline polyline; + }; + + std::ostream& operator<<(std::ostream& os, const CommentSubject& cs); + + class Comment: public Element { + public: + Comment(Element* parent, const ID& id, const String& body, bool human_readable = true, + const String& markup = String(), const Rect& rect = Rect(), const Color& color = Color()); + Comment(Element* parent, const ID& id, const String& body, bool human_readable, const Name& name, + const String& markup = String(), const Rect& rect = Rect(), const Color& color = Color()); + + bool is_human_readable() const { return human_readable; } + bool is_machine_readable() const { return !human_readable; } + + bool has_subjects() const { return !subjects.empty(); } + const std::list& get_subjects() const { return subjects; } + void add_subject(const CommentSubject& s); + void remove_subject(CommentSubjectType type, const String& fragment); + + virtual bool has_geometry() const { return geometry_rect.valid; } + const Rect& get_geometry_rect() const { return geometry_rect; } + + virtual bool has_children() const { return false; } + + bool has_color() const { return !color.empty(); } + const Color& get_color() const { return color; } + + bool has_markup() const { return !markup.empty(); } + const String& get_markup() const { return markup; } + + protected: + virtual std::ostream& dump(std::ostream& os) const; + + private: + void update_comment_type(); + + String body; + String markup; + bool human_readable; + Rect geometry_rect; + std::list subjects; + Color color; + }; + +// ----------------------------------------------------------------------------- +// Vertex +// ----------------------------------------------------------------------------- + class Vertex: public Element { + public: + Vertex(Element* parent, ElementType type, const ID& id, const Point& pos = Point()); + Vertex(Element* parent, ElementType type, const ID& id, const Name& name, const Point& pos = Point()); + + virtual bool has_geometry() const { return geometry_point.valid; } + const Point& get_geometry_point() const { return geometry_point; } + + virtual bool has_children() const { return false; } + + protected: + virtual std::ostream& dump(std::ostream& os) const; + + private: + Point geometry_point; + }; + +// ----------------------------------------------------------------------------- +// Collection of Elements +// (the combination of Namespace and Region from the PRIMS standard) +// ----------------------------------------------------------------------------- + typedef std::list ConstElementList; + typedef std::list ElementList; + typedef std::vector ElementTypes; + + class ElementCollection: public Element { + public: + ElementCollection(Element* parent, ElementType type, const ID& id, + const Name& name, const Rect& rect = Rect(), const Color& color = Color()); + virtual ~ElementCollection(); + + bool has_qualified_name(const ID& element_id) const; + QualifiedName qualified_name(const ID& element_id) const; + + virtual bool has_children() const { return !children.empty(); } + virtual size_t children_count() const { return children.size(); } + virtual size_t elements_count() const; + const Element* find_element_by_id(const ID& id) const; + Element* find_element_by_id(const ID& id); + ConstElementList find_elements_by_type(ElementType type) const; + ConstElementList find_elements_by_types(const ElementTypes& types) const; + ElementList find_elements_by_type(ElementType type); + ElementList find_elements_by_types(const ElementTypes& types); + + virtual void add_element(Element* e); + virtual void remove_element(const ID& id); + void clear(); + + std::list get_vertexes() const; + std::list get_vertexes(); + + virtual bool has_geometry() const { return geometry_rect.valid; } + const Rect& get_geometry_rect() const { return geometry_rect; } + + bool has_color() const { return !color.empty(); } + const Color& get_color() const { return color; } + + protected: + virtual std::ostream& dump(std::ostream& os) const; + + ElementList children; + + private: + Rect geometry_rect; + Color color; + }; + +// ----------------------------------------------------------------------------- +// Pseudostate +// ----------------------------------------------------------------------------- + class Pseudostate: public Vertex { + public: + Pseudostate(Element* parent, ElementType type, const ID& id, const Point& p = Point()); + Pseudostate(Element* parent, ElementType type, const ID& id, const Name& name, const Point& p = Point()); + }; + +// ----------------------------------------------------------------------------- +// Initial pseudostate +// ----------------------------------------------------------------------------- + class InitialPseudostate: public Pseudostate { + public: + InitialPseudostate(Element* parent, const ID& id, const Point& p = Point()); + InitialPseudostate(Element* parent, const ID& id, const Name& name, const Point& p = Point()); + + protected: + virtual std::ostream& dump(std::ostream& os) const; + }; + +// ----------------------------------------------------------------------------- +// Choice pseudostate +// ----------------------------------------------------------------------------- + class ChoicePseudostate: public Pseudostate { + public: + ChoicePseudostate(Element* parent, const ID& id, + const Rect& r = Rect(), const Color& color = Color()); + ChoicePseudostate(Element* parent, const ID& id, const Name& name, + const Rect& r = Rect(), const Color& color = Color()); + + virtual bool has_geometry() const { return geometry_rect.valid; } + const Rect& get_geometry_rect() const { return geometry_rect; } + + bool has_color() const { return !color.empty(); } + const Color& get_color() const { return color; } + + protected: + virtual std::ostream& dump(std::ostream& os) const; + + Rect geometry_rect; + Color color; + }; + +// ----------------------------------------------------------------------------- +// Final state +// ----------------------------------------------------------------------------- + class FinalState: public Vertex { + public: + FinalState(Element* parent, const ID& id, const Point& point = Point()); + FinalState(Element* parent, const ID& id, const Name& name, const Point& point = Point()); + + protected: + + virtual std::ostream& dump(std::ostream& os) const; + }; + +// ----------------------------------------------------------------------------- +// Action +// ----------------------------------------------------------------------------- + + // Cyberiada action types: + typedef enum { + actionTransition, + actionEntry, + actionExit + } ActionType; + + typedef String Event; + typedef String Guard; + typedef String Behavior; + + class Action { + public: + Action(ActionType type, const Guard& guard = Guard(), const Behavior& behavior = Behavior()); + Action(const Event& trigger = Event(), const Guard& guard = Guard(), const Behavior& behavior = Behavior()); + + ActionType get_type() const { return type; } + bool has_trigger() const { return !trigger.empty(); } + const Event& get_trigger() { return trigger; } + bool has_guard() const { return !guard.empty(); } + const Guard& get_guard() { return guard; } + bool has_behavior() const { return !behavior.empty(); } + const Behavior& get_behavior() { return behavior; } + + protected: + std::ostream& dump(std::ostream& os) const; + friend std::ostream& operator<<(std::ostream& os, const Action& a); + + private: + ActionType type; + Event trigger; + Guard guard; + Behavior behavior; + }; + + std::ostream& operator<<(std::ostream& os, const Action& a); + +// ----------------------------------------------------------------------------- +// State +// ----------------------------------------------------------------------------- + class State: public ElementCollection { + public: + State(Element* parent, const ID& id, const Name& name, + const Rect& r = Rect(), const Color& color = Color()); + + virtual void add_element(Element* e); + virtual void remove_element(const ID& id); + + bool is_simple_state() const { return get_type() == elementSimpleState; } + bool is_composite_state() const { return get_type() == elementCompositeState; } + + std::list get_substates() const; + std::list get_substates(); + + bool has_actions() const { return !actions.empty(); } + const std::list& get_actions() const { return actions; } + std::list& get_actions() { return actions; } + void add_action(const Action& a); + + protected: + virtual std::ostream& dump(std::ostream& os) const; + void update_state_type(); + + std::list actions; + }; + +// ----------------------------------------------------------------------------- +// Transition +// ----------------------------------------------------------------------------- + class Transition: public Element { + public: + Transition(Element* parent, const ID& id, Element* source, Element* target, const Action& action, + const Polyline& pl = Polyline(), const Point& sp = Point(), const Point& tp = Point(), + const Point& label = Point(), const Color& color = Color()); + + const Element* source_element() const { return source; } + const Element* target_element() const { return target; } + + bool has_action() const { return (action.has_trigger() || + action.has_guard() || + action.has_behavior()); } + const Action& get_action() const { return action; } + Action& get_action() { return action; } + + bool has_geometry() const { return (source_point.valid || + target_point.valid || + label_point.valid || + has_polyline()); } + bool has_polyline() const { return !polyline.empty(); } + const Polyline& get_geometry_polyline() const { return polyline; } + const Point& get_source_point() const { return source_point; } + const Point& get_target_point() const { return target_point; } + const Point& get_label_point() const { return label_point; } + + bool has_color() const { return !color.empty(); } + + protected: + virtual std::ostream& dump(std::ostream& os) const; + + private: + Element* source; + Element* target; + Action action; + Point source_point; + Point target_point; + Point label_point; + Polyline polyline; + Color color; + }; + +// ----------------------------------------------------------------------------- +// State Machine +// ----------------------------------------------------------------------------- + class StateMachine: public ElementCollection { + public: + StateMachine(Element* parent, const ID& id, const Name& name = "", const Rect& r = Rect()); + + std::list get_comments() const; + std::list get_comments(); + std::list get_transitions() const; + std::list get_transitions(); + + protected: + virtual std::ostream& dump(std::ostream& os) const; + }; + +// ----------------------------------------------------------------------------- +// Cyberiada-GraphML document +// ----------------------------------------------------------------------------- + + struct DocumentMetainformation { + String standard_version; // PRIMS standard version + String platform_name; // target platform name + String platform_version; // target platform version + String platform_language; // target platform language + String target_system; // target system controlled by the SM + String name; // document name + String author; // document author + String contact; // document author's contact + String description; // document description + String version; // document version + String date; // document date + bool transition_order_flag; + bool event_propagation_flag; + }; + + class Document: public ElementCollection { + public: + Document(); + + void reset(); + StateMachine* new_state_machine(const String& sm_nam, const Rect& r = Rect()); + StateMachine* new_state_machine(const ID& id, const String& sm_name, const Rect& r = Rect()); + void load(const String& path); + void save(const String& path) const; + + const DocumentMetainformation& meta() const { return metainfo; } + DocumentMetainformation& meta() { return metainfo; } + + std::list get_state_machines() const; + std::list get_state_machines(); + + protected: + virtual std::ostream& dump(std::ostream& os) const; + + private: + void check_cyberiada_error(int res, const String& msg = ""); + ID generate_sm_id() const; + ID generate_vertex_id(const Element* element) const; + ID generate_transition_id(const String& source_id, const String& target_id) const; + void import_nodes_recursively(ElementCollection* collection, CyberiadaNode* nodes); + void import_edges(ElementCollection* collection, CyberiadaEdge* edges); + + String format; + DocumentMetainformation metainfo; + }; + +// ----------------------------------------------------------------------------- +// Exceptions +// ----------------------------------------------------------------------------- + class Exception { + public: + Exception(const String& msg = "", const String& e = "Generic Error"): + error_type(e), message(msg) {} + + String str() const { return error_type + ": " + message; } + + private: + String error_type; + String message; + }; +// ----------------------------------------------------------------------------- + class FormatException: public Exception { + public: + FormatException(const String& msg = "", const String& e = "Format Exception"): + Exception(e, msg) {} + }; +// ----------------------------------------------------------------------------- + class XMLException: public FormatException { + public: + XMLException(const String& msg = "", const String& e = "XML Exception"): + FormatException(msg, e) {} + }; +// ----------------------------------------------------------------------------- + class CybMLException: public FormatException { + public: + CybMLException(const String& msg = "", const String& e = "CyberiadaML Exception"): + FormatException(msg, e) {} + }; +// ----------------------------------------------------------------------------- + class ActionException: public CybMLException { + public: + ActionException(const String& msg = "", const String& e = "Action Exception"): + CybMLException(msg, e) {} + }; +// ----------------------------------------------------------------------------- + class MetainformationException: public FormatException { + public: + MetainformationException(const String& msg = "", const String& e = "Metainfo Exception"): + FormatException(msg, e) {} + }; +// ----------------------------------------------------------------------------- + class ParametersException: public Exception { + public: + ParametersException(const String& msg = "", const String& e = "Parameters Exception"): + Exception(msg, e) {} + }; +// ----------------------------------------------------------------------------- + class NotFoundException: public ParametersException { + public: + NotFoundException(const String& msg = "", const String& e = "Not Found Exception"): + ParametersException(msg, e) {} + }; +// ----------------------------------------------------------------------------- + class AssertException: public Exception { + public: + AssertException(const String& msg, const String& e = "Assert Exception"): + Exception(msg, e) {} + }; +// ----------------------------------------------------------------------------- + +}; + +#endif diff --git a/test.cpp b/test.cpp new file mode 100644 index 0000000..5f6053b --- /dev/null +++ b/test.cpp @@ -0,0 +1,52 @@ +/* ----------------------------------------------------------------------------- + * The Cyberiada GraphML C++ 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 "cyberiadamlpp.h" + +using namespace Cyberiada; +using namespace std; + +void usage(const char* program) +{ + cerr << program << " " << endl; + cerr << "\tPrint the graphml SM structure of the file " << endl; + exit(1); +} + +int main(int argc, char** argv) +{ + Document d; + + if (argc != 2) { + usage(argv[0]); + } + + try { + d.load(argv[1]); + cout << d << endl; + } catch (const Cyberiada::Exception& e) { + cerr << "Error while opening graphml file: " << e.str() << endl; + return 2; + } + return 0; +}