From 4c50256ce868b20955011fe3b48a2bec0b5ef3bd Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Sun, 15 Oct 2023 15:39:33 +0200 Subject: [PATCH] Add Gcode abstract syntax tree (AST) representations New classes are added to define the structure of Gcode commands, comments and related entities in an AST. This includes common structure like comments, commands and optional values plus specific types for the G0 and G1 instructions. The definitions allow parsing Gcode to construct an intermediate representation that can be further processed or translated. In addition to the AST implementation, the main application (translator_main) is updated to include a preliminary use of the parser, demonstrating its functionality. The commit also includes updates to CMakeLists.txt and conanfile.py to bring in necessary dependencies. Contribute to CURA-10561 --- CMakeLists.txt | 2 + apps/translator_main.cpp | 15 ++++++- conanfile.py | 3 ++ include/dulcificum/gcode/ast/ast.h | 12 +++++ include/dulcificum/gcode/ast/rules.h | 28 ++++++++++++ include/dulcificum/gcode/ast/words/G0.h | 39 ++++++++++++++++ include/dulcificum/gcode/ast/words/G1.h | 44 +++++++++++++++++++ include/dulcificum/gcode/ast/words/command.h | 21 +++++++++ include/dulcificum/gcode/ast/words/comment.h | 15 +++++++ include/dulcificum/gcode/ast/words/entry.h | 17 +++++++ .../gcode/ast/words/optional_values.h | 29 ++++++++++++ include/dulcificum/gcode/ast/words/words.h | 15 +++++++ include/dulcificum/utils/char_range_literal.h | 24 ++++++++++ 13 files changed, 262 insertions(+), 2 deletions(-) create mode 100644 include/dulcificum/gcode/ast/ast.h create mode 100644 include/dulcificum/gcode/ast/rules.h create mode 100644 include/dulcificum/gcode/ast/words/G0.h create mode 100644 include/dulcificum/gcode/ast/words/G1.h create mode 100644 include/dulcificum/gcode/ast/words/command.h create mode 100644 include/dulcificum/gcode/ast/words/comment.h create mode 100644 include/dulcificum/gcode/ast/words/entry.h create mode 100644 include/dulcificum/gcode/ast/words/optional_values.h create mode 100644 include/dulcificum/gcode/ast/words/words.h create mode 100644 include/dulcificum/utils/char_range_literal.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 365d880..c54fa6a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,7 @@ message(STATUS "Configuring Dulcificum version: ${DULCIFICUM_VERSION}") find_package(nlohmann_json REQUIRED) find_package(spdlog REQUIRED) find_package(range-v3 REQUIRED) +find_package(Boost REQUIRED) # --- Setup the shared C++ mgjtp library --- set(DULCIFICUM_SRC @@ -35,6 +36,7 @@ target_link_libraries(dulcificum PUBLIC nlohmann_json::nlohmann_json PRIVATE + boost::boost range-v3::range-v3 spdlog::spdlog) diff --git a/apps/translator_main.cpp b/apps/translator_main.cpp index d6177e6..e8991ee 100644 --- a/apps/translator_main.cpp +++ b/apps/translator_main.cpp @@ -1,21 +1,26 @@ #include "cmdline.h" +#include #include #include +#include +#include +#include #include + int main(int argc, const char** argv) { constexpr bool show_help = true; const std::map args = docopt::docopt(fmt::format(apps::cmdline::USAGE, apps::cmdline::NAME), { argv + 1, argv + argc }, show_help, apps::cmdline::VERSION_ID); - if (args.contains("--quiet")) + if (args.at("--quiet").asBool()) { spdlog::set_level(spdlog::level::err); } - else if (args.contains("--verbose")) + else if (args.at("--verbose").asBool()) { spdlog::set_level(spdlog::level::debug); } @@ -24,4 +29,10 @@ int main(int argc, const char** argv) spdlog::set_level(spdlog::level::info); } spdlog::info("Tasting the menu"); + + auto input{ dulcificum::utils::readFile(args.at("INPUT").asString()).value() }; + std::vector parsedCommand; + bool r = boost::spirit::x3::phrase_parse(input.begin(), input.end(), *dulcificum::gcode::ast::all_rule, boost::spirit::x3::space, parsedCommand); + + return 0; } \ No newline at end of file diff --git a/conanfile.py b/conanfile.py index 0e1b7cf..d959771 100644 --- a/conanfile.py +++ b/conanfile.py @@ -70,6 +70,7 @@ def config_options(self): del self.options.fPIC def configure(self): + self.options["boost"].header_only = True if self.options.shared: self.options.rm_safe("fPIC") @@ -81,6 +82,8 @@ def requirements(self): self.requires("nlohmann_json/3.11.2", transitive_headers = True) self.requires("range-v3/0.12.0") self.requires("spdlog/1.10.0") + self.requires("boost/1.82.0") + self.requires("zlib/1.2.13") if self.options.with_apps: self.requires("docopt.cpp/0.6.3") if self.options.with_python_bindings: diff --git a/include/dulcificum/gcode/ast/ast.h b/include/dulcificum/gcode/ast/ast.h new file mode 100644 index 0000000..1c0efd6 --- /dev/null +++ b/include/dulcificum/gcode/ast/ast.h @@ -0,0 +1,12 @@ +#ifndef DULCIFICUM_INCLUDE_DULCIFICUM_GCODE_AST_AST_H +#define DULCIFICUM_INCLUDE_DULCIFICUM_GCODE_AST_AST_H + +#include "dulcificum/gcode/ast/words/words.h" + +namespace dulcificum::gcode::ast +{ +auto const all_rule = boost::spirit::x3::rule{} = g0_rule | g1_rule; // NOLINT + +} + +#endif // DULCIFICUM_INCLUDE_DULCIFICUM_GCODE_AST_AST_H diff --git a/include/dulcificum/gcode/ast/rules.h b/include/dulcificum/gcode/ast/rules.h new file mode 100644 index 0000000..44db634 --- /dev/null +++ b/include/dulcificum/gcode/ast/rules.h @@ -0,0 +1,28 @@ +#ifndef DULCIFICUM_INCLUDE_DULCIFICUM_GCODE_AST_RULES_H +#define DULCIFICUM_INCLUDE_DULCIFICUM_GCODE_AST_RULES_H + +#include + +namespace dulcificum::gcode::ast +{ + +// TODO: Figure out if the I should maybe use this pattern +//struct rules { +// const boost::spirit::x3::rule x_rule; +// +// rules() : x_rule{ "x" } +// { +// x_rule = 'X' >> boost::spirit::x3::double_; +// } +//}; +// NOLINTBEGIN +auto const x_rule = boost::spirit::x3::rule{ "x" } = 'X' >> boost::spirit::x3::double_; +auto const y_rule = boost::spirit::x3::rule{ "y" } = 'Y' >> boost::spirit::x3::double_; +auto const z_rule = boost::spirit::x3::rule{ "z" } = 'Z' >> boost::spirit::x3::double_; +auto const e_rule = boost::spirit::x3::rule{ "e" } = 'E' >> boost::spirit::x3::double_; +auto const f_rule = boost::spirit::x3::rule{ "f" } = 'F' >> boost::spirit::x3::double_; +// NOLINTEND + +} // namespace dulcificum::gcode::ast + +#endif // DULCIFICUM_INCLUDE_DULCIFICUM_GCODE_AST_RULES_H diff --git a/include/dulcificum/gcode/ast/words/G0.h b/include/dulcificum/gcode/ast/words/G0.h new file mode 100644 index 0000000..542aaf9 --- /dev/null +++ b/include/dulcificum/gcode/ast/words/G0.h @@ -0,0 +1,39 @@ +#ifndef DULCIFICUM_INCLUDE_DULCIFICUM_GCODE_WORDS_G0_H +#define DULCIFICUM_INCLUDE_DULCIFICUM_GCODE_WORDS_G0_H +#include "dulcificum/gcode/ast/rules.h" +#include "dulcificum/gcode/ast/words/command.h" +#include "dulcificum/gcode/ast/words/optional_values.h" +namespace dulcificum::gcode::ast +{ +struct G0 : public Command<"G0"> +{ + std::optional X{}; ///< X coordinate + std::optional Y{}; ///< Y coordinate + std::optional Z{}; ///< Z coordinate + std::optional F{}; ///< Feedrate +}; + + +// Construct the parser rules + +details::OptionalValues G0_VALUES; + +auto G0_ASSIGN_OPTIONAL_VALUES = [](auto& ctx) +{ + _val(ctx).X = G0_VALUES.val_map["x"]; + _val(ctx).Y = G0_VALUES.val_map["y"]; + _val(ctx).Z = G0_VALUES.val_map["z"]; + _val(ctx).F = G0_VALUES.val_map["f"]; +}; + +// clang-format off +auto const g0_rule = boost::spirit::x3::rule{ "g0" } = G0::word.data() + >> *(x_rule[G0_VALUES.assign("x", G0_VALUES.val_map)] + | y_rule[G0_VALUES.assign("y", G0_VALUES.val_map)] + | z_rule[G0_VALUES.assign("z", G0_VALUES.val_map)] + | f_rule[G0_VALUES.assign("f", G0_VALUES.val_map)]) + >> boost::spirit::x3::eps[G0_ASSIGN_OPTIONAL_VALUES]; +// clang-format on + +} // namespace dulcificum::gcode::ast +#endif // DULCIFICUM_INCLUDE_DULCIFICUM_GCODE_WORDS_G0_H \ No newline at end of file diff --git a/include/dulcificum/gcode/ast/words/G1.h b/include/dulcificum/gcode/ast/words/G1.h new file mode 100644 index 0000000..9b9dcf7 --- /dev/null +++ b/include/dulcificum/gcode/ast/words/G1.h @@ -0,0 +1,44 @@ +#ifndef DULCIFICUM_INCLUDE_DULCIFICUM_GCODE_WORDS_G1_H +#define DULCIFICUM_INCLUDE_DULCIFICUM_GCODE_WORDS_G1_H + +#include "dulcificum/gcode/ast/rules.h" +#include "dulcificum/gcode/ast/words/command.h" + +namespace dulcificum::gcode::ast +{ +struct G1 : public Command<"G1"> +{ + std::optional X{}; ///< X coordinate + std::optional Y{}; ///< Y coordinate + std::optional Z{}; ///< Z coordinate + std::optional E{}; ///< E position + std::optional F{}; ///< Feedrate +}; + + +// Construct the parser rules + +details::OptionalValues G1_VALUES; + +auto G1_ASSIGN_OPTIONAL_VALUES = [](auto& ctx) +{ + _val(ctx).X = G0_VALUES.val_map["x"]; + _val(ctx).Y = G0_VALUES.val_map["y"]; + _val(ctx).Z = G0_VALUES.val_map["z"]; + _val(ctx).E = G0_VALUES.val_map["e"]; + _val(ctx).F = G0_VALUES.val_map["f"]; +}; + +// clang-format off +auto const g1_rule = boost::spirit::x3::rule{ "g1" } = G1::word.data() + >> *(x_rule[G1_VALUES.assign("x", G1_VALUES.val_map)] + | y_rule[G1_VALUES.assign("y", G1_VALUES.val_map)] + | z_rule[G1_VALUES.assign("z", G1_VALUES.val_map)] + | e_rule[G1_VALUES.assign("e", G1_VALUES.val_map)] + | f_rule[G1_VALUES.assign("f", G1_VALUES.val_map)]) + >> boost::spirit::x3::eps[G1_ASSIGN_OPTIONAL_VALUES]; +// clang-format on + +} // namespace dulcificum::gcode::ast + +#endif // DULCIFICUM_INCLUDE_DULCIFICUM_GCODE_WORDS_G1_H diff --git a/include/dulcificum/gcode/ast/words/command.h b/include/dulcificum/gcode/ast/words/command.h new file mode 100644 index 0000000..6e3fc69 --- /dev/null +++ b/include/dulcificum/gcode/ast/words/command.h @@ -0,0 +1,21 @@ +#ifndef DULCIFICUM_INCLUDE_DULCIFICUM_GCODE_WORDS_COMMAND_H +#define DULCIFICUM_INCLUDE_DULCIFICUM_GCODE_WORDS_COMMAND_H + +#include "dulcificum/gcode/ast/words/comment.h" +#include "dulcificum/gcode/ast/words/entry.h" +#include "dulcificum/utils/char_range_literal.h" + +#include + +namespace dulcificum::gcode::ast +{ +template +struct Command : public Entry +{ + static constexpr std::string_view word{ Word.value }; ///< The specific word + std::optional comment; ///< Optional comment related to the command +}; + +} // namespace dulcificum::gcode::details + +#endif // DULCIFICUM_INCLUDE_DULCIFICUM_GCODE_WORDS_COMMAND_H diff --git a/include/dulcificum/gcode/ast/words/comment.h b/include/dulcificum/gcode/ast/words/comment.h new file mode 100644 index 0000000..21955dd --- /dev/null +++ b/include/dulcificum/gcode/ast/words/comment.h @@ -0,0 +1,15 @@ +#ifndef DULCIFICUM_INCLUDE_DULCIFICUM_GCODE_WORDS_COMMENT_H +#define DULCIFICUM_INCLUDE_DULCIFICUM_GCODE_WORDS_COMMENT_H + +#include "dulcificum/gcode/ast/words/entry.h" + +namespace dulcificum::gcode::ast +{ +struct Comment : public Entry +{ + static constexpr std::string_view word{ ";" }; + std::string msg; ///< Message contained in the comment +}; +} // namespace dulcificum::gcode::details + +#endif // DULCIFICUM_INCLUDE_DULCIFICUM_GCODE_WORDS_COMMENT_H diff --git a/include/dulcificum/gcode/ast/words/entry.h b/include/dulcificum/gcode/ast/words/entry.h new file mode 100644 index 0000000..65e7070 --- /dev/null +++ b/include/dulcificum/gcode/ast/words/entry.h @@ -0,0 +1,17 @@ +#ifndef DULCIFICUM_INCLUDE_DULCIFICUM_GCODE_WORDS_ENTRY_H +#define DULCIFICUM_INCLUDE_DULCIFICUM_GCODE_WORDS_ENTRY_H + +#include + +namespace dulcificum::gcode::ast +{ +struct Entry : public boost::spirit::x3::position_tagged +{ + std::size_t index{ 0 }; ///< Represents the index in the source + std::string raw_value; ///< Raw value as represented in the source +}; + +} // namespace dulcificum::gcode::details + + +#endif // DULCIFICUM_INCLUDE_DULCIFICUM_GCODE_WORDS_ENTRY_H diff --git a/include/dulcificum/gcode/ast/words/optional_values.h b/include/dulcificum/gcode/ast/words/optional_values.h new file mode 100644 index 0000000..e1b63ca --- /dev/null +++ b/include/dulcificum/gcode/ast/words/optional_values.h @@ -0,0 +1,29 @@ +#ifndef DULCIFICUM_INCLUDE_DULCIFICUM_GCODE_AST_WORDS_OPTIONAL_VALUES_H +#define DULCIFICUM_INCLUDE_DULCIFICUM_GCODE_AST_WORDS_OPTIONAL_VALUES_H + +#include +#include + +namespace dulcificum::gcode::ast::details +{ + +struct OptionalValues +{ + std::map val_map; + struct AssignCtxValToVar + { + template + auto operator()(const std::string& key, T& map) const + { + return [&, key](auto& ctx) + { + map[key] = _attr(ctx); + }; + } + } assign; +}; + +} // namespace dulcificum::gcode::ast::details + + +#endif // DULCIFICUM_INCLUDE_DULCIFICUM_GCODE_AST_WORDS_OPTIONAL_VALUES_H diff --git a/include/dulcificum/gcode/ast/words/words.h b/include/dulcificum/gcode/ast/words/words.h new file mode 100644 index 0000000..818622c --- /dev/null +++ b/include/dulcificum/gcode/ast/words/words.h @@ -0,0 +1,15 @@ +#ifndef DULCIFICUM_INCLUDE_DULCIFICUM_GCODE_WORDS_WORDS_H +#define DULCIFICUM_INCLUDE_DULCIFICUM_GCODE_WORDS_WORDS_H + +#include "dulcificum/gcode/ast/words/G0.h" +#include "dulcificum/gcode/ast/words/G1.h" + +#include + +namespace dulcificum::gcode::ast +{ +using command_t = std::variant; +} + + +#endif // DULCIFICUM_INCLUDE_DULCIFICUM_GCODE_WORDS_WORDS_H diff --git a/include/dulcificum/utils/char_range_literal.h b/include/dulcificum/utils/char_range_literal.h new file mode 100644 index 0000000..55bda48 --- /dev/null +++ b/include/dulcificum/utils/char_range_literal.h @@ -0,0 +1,24 @@ +#ifndef DULCIFICUM_INCLUDE_DULCIFICUM_UTILS_CHAR_RANGE_LITERAL_H +#define DULCIFICUM_INCLUDE_DULCIFICUM_UTILS_CHAR_RANGE_LITERAL_H + +#include +#include + +namespace dulcificum::utils +{ + +// NOLINTBEGIN +template +struct CharRangeLiteral +{ + constexpr CharRangeLiteral(const char (&str)[N]) noexcept + { + std::copy_n(str, N, value); + } + char value[N]; ///< The character array holding the literal +}; +// NOLINTEND + +} // namespace dulcificum::utils + +#endif // DULCIFICUM_INCLUDE_DULCIFICUM_UTILS_CHAR_RANGE_LITERAL_H