diff --git a/Grbl_Esp32/src/Configuration/Configurable.h b/Grbl_Esp32/src/Configuration/Configurable.h new file mode 100644 index 00000000..79580bf4 --- /dev/null +++ b/Grbl_Esp32/src/Configuration/Configurable.h @@ -0,0 +1,18 @@ +#pragma once + +#include "Generator.h" +#include "Parser.h" + +namespace Configuration +{ + class Configurable + { + Configurable(const Configurable&) = delete; + Configurable& operator=(const Configurable&) = delete; + + public: + Configurable() = default; + + virtual void generate(Generator& generator) const = 0; + }; +} \ No newline at end of file diff --git a/Grbl_Esp32/src/Configuration/Generator.cpp b/Grbl_Esp32/src/Configuration/Generator.cpp new file mode 100644 index 00000000..cc15bcbb --- /dev/null +++ b/Grbl_Esp32/src/Configuration/Generator.cpp @@ -0,0 +1,72 @@ +#include "Generator.h" + +#include "Configurable.h" + +#include + +namespace Configuration +{ + void Generator::enter(const char* name) + { + indent(); + addStr(name); + addStr(":\n"); + indent_++; + } + + void Generator::add(const char* key, const std::string& value) + { + add(key, value.c_str()); + } + + void Generator::add(const char* key, const char* value) + { + indent(); + addStr(key); + addStr(": "); + addStr(value); + addStr("\n"); + } + + void Generator::add(const char* key, bool value) + { + if (value) { add(key, "true"); } + else { add(key, "false"); } + } + + void Generator::add(const char* key, int value) + { + char tmp[11]; + snprintf(tmp, 11, "%d", value); + add(key, tmp); + } + + void Generator::add(const char* key, double value) + { + char tmp[20]; + snprintf(tmp, 20, "%f", value); + add(key, tmp); + } + + void Generator::add(const char* key, Pin value) + { + if (!value.undefined()) + { + add(key, value.str()); + } + } + + void Generator::add(Configuration::Configurable* configurable) + { + if (configurable != nullptr) + { + configurable->generate(*this); + } + } + + void Generator::leave() + { + addStr("\n"); + indent_--; + } +} \ No newline at end of file diff --git a/Grbl_Esp32/src/Configuration/Generator.h b/Grbl_Esp32/src/Configuration/Generator.h new file mode 100644 index 00000000..c3b38b6c --- /dev/null +++ b/Grbl_Esp32/src/Configuration/Generator.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include + +#include "../Pin.h" + +namespace Configuration +{ + class Configurable; + + class Generator + { + Generator(const Generator&) = delete; + Generator& operator=(const Generator&) = delete; + + std::vector config_; + int indent_; + + inline void addStr(const char* text) { + for (auto it = text; *it; ++it) + { + config_.push_back(*it); + } + } + + inline void indent() { + for (int i = 0; i < indent_ * 2; ++i) + { + config_.push_back(' '); + } + } + + public: + Generator() = default; + + void enter(const char* name); + void add(const char* key, const std::string& value); + void add(const char* key, const char* value); + void add(const char* key, bool value); + void add(const char* key, int value); + void add(const char* key, double value); + void add(const char* key, Pin value); + void add(Configuration::Configurable* configurable); + void leave(); + + inline std::string str() const { + return std::string(config_.begin(), config_.end()); + } + }; +} \ No newline at end of file diff --git a/Grbl_Esp32/src/Configuration/GenericFactory.h b/Grbl_Esp32/src/Configuration/GenericFactory.h new file mode 100644 index 00000000..ebe6e5f2 --- /dev/null +++ b/Grbl_Esp32/src/Configuration/GenericFactory.h @@ -0,0 +1,75 @@ +#pragma once + +#include +#include + +namespace Configuration { + template + class GenericFactory + { + static GenericFactory& instance() { + static GenericFactory instance_; + return instance_; + } + + GenericFactory() = default; + + GenericFactory(const GenericFactory&) = delete; + GenericFactory& operator=(const GenericFactory&) = delete; + + class BuilderBase { + const char* name_; + + public: + BuilderBase(const char* name) : name_(name) {} + + BuilderBase(const BuilderBase& o) = delete; + BuilderBase& operator=(const BuilderBase& o) = delete; + + virtual BaseType* create(Configuration::Parser& parser) const = 0; + + inline bool matches(const char* name) { + return !strcmp(name, name_); + } + + virtual ~BuilderBase() = default; + }; + + std::vector builders_; + + inline static void registerBuilder(BuilderBase* builder) + { + instance().builders_.push_back(builder); + } + + public: + template + class InstanceBuilder : public BuilderBase + { + public: + InstanceBuilder(const char* name) : BuilderBase(name) { + instance().registerBuilder(this); + } + + BaseType* create(Configuration::Parser& parser) const override + { + return new DerivedType(parser); + } + }; + + static const BuilderBase* find(const char* name) { + for (auto it : instance().builders_) + { + if (it->matches(name)) + { + return it; + } + } + return nullptr; + } + + inline static const BuilderBase* find(const std::string& name) { + return find(name.c_str()); + } + }; +} \ No newline at end of file diff --git a/Grbl_Esp32/src/Configuration/Parser.cpp b/Grbl_Esp32/src/Configuration/Parser.cpp index aa32a6c0..c3e397ef 100644 --- a/Grbl_Esp32/src/Configuration/Parser.cpp +++ b/Grbl_Esp32/src/Configuration/Parser.cpp @@ -11,7 +11,7 @@ namespace Configuration { } } - void Parser::ParseError(const char* description) const { + void Parser::parseError(const char* description) const { // Attempt to use the correct position in the parser: if (current_.keyEnd_) { throw ParseException(start_, current_.keyEnd_, description); @@ -25,7 +25,7 @@ namespace Configuration { /// MoveNext: moves to the next entry in the current section. By default we're in the /// root section. /// - bool Parser::MoveNext() { + bool Parser::moveNext() { // While the indent of the token is > current indent, we have to skip it. This is a // sub-section, that we're apparently not interested in. while (token_.indent_ > current_.indent_) { @@ -48,7 +48,7 @@ namespace Configuration { return current_.kind_ != TokenKind::Eof; } - void Parser::Enter() { + void Parser::enter() { indentStack_.push(current_.indent_); // If we can enter, token_.indent_ > current_.indent_: @@ -59,9 +59,10 @@ namespace Configuration { current_ = TokenData(); current_.indent_ = INT_MAX; } + indent_ = current_.indent_; } - void Parser::Leave() { + void Parser::leave() { // While the indent of the tokenizer is >= current, we can ignore the contents: while (token_.indent_ >= current_.indent_ && token_.kind_ != TokenKind::Eof) { Tokenize(); @@ -70,6 +71,7 @@ namespace Configuration { // At this point, we just know the indent is smaller. We don't know if we're in // the *right* section tho. auto last = indentStack_.top(); + indent_ = last; indentStack_.pop(); if (last == token_.indent_) { @@ -82,31 +84,40 @@ namespace Configuration { } } - std::string Parser::StringValue() const { + std::string Parser::stringValue() const { if (current_.kind_ != TokenKind::String) { - ParseError("Expected a string value (e.g. 'foo')"); + parseError("Expected a string value (e.g. 'foo')"); } return std::string(current_.sValueStart_, current_.sValueEnd_); } - bool Parser::BoolValue() const { + bool Parser::boolValue() const { if (current_.kind_ != TokenKind::Boolean) { - ParseError("Expected a boolean value (e.g. true or value)"); + parseError("Expected a boolean value (e.g. true or value)"); } return current_.bValue_; } - int Parser::IntValue() const { + int Parser::intValue() const { if (current_.kind_ != TokenKind::Boolean) { - ParseError("Expected an integer value (e.g. 123456)"); + parseError("Expected an integer value (e.g. 123456)"); } return current_.iValue_; } - double Parser::FloatValue() const { + double Parser::floatValue() const { if (current_.kind_ != TokenKind::Boolean) { - ParseError("Expected a float value (e.g. 123.456)"); + parseError("Expected a float value (e.g. 123.456)"); } return current_.fValue_; } + + Pin Parser::pinValue() const + { + if (current_.kind_ != TokenKind::String) { + parseError("Expected a string value (e.g. 'foo')"); + } + return Pin(std::string(current_.sValueStart_, current_.sValueEnd_)); + } + } diff --git a/Grbl_Esp32/src/Configuration/Parser.h b/Grbl_Esp32/src/Configuration/Parser.h index f4b760ec..765acf3f 100644 --- a/Grbl_Esp32/src/Configuration/Parser.h +++ b/Grbl_Esp32/src/Configuration/Parser.h @@ -1,6 +1,7 @@ #pragma once #include "Tokenizer.h" +#include "../Pin.h" #include #include @@ -13,8 +14,9 @@ namespace Configuration { std::stack indentStack_; TokenData current_; + int indent_ = 0; - void ParseError(const char* description) const; + void parseError(const char* description) const; public: Parser(const char* start, const char* end); @@ -23,27 +25,28 @@ namespace Configuration { /// MoveNext: moves to the next entry in the current section. By default we're in the /// root section. /// - bool MoveNext(); + bool moveNext(); - inline bool IsEndSection() { return current_.kind_ == TokenKind::Eof || token_.indent_ < current_.indent_; } + inline bool isEndSection() { return current_.kind_ == TokenKind::Eof || current_.indent_ < indent_; } // !!! Important !!! We cannot use a scoped variable for enter & leave, because 'leave' can throw, // and it could be called using stack unrolling. Destructors by definition have to be 'nothrow', // so forget it: it just Won't Work. In other words, if we leave the 'leave' call up to the // destructor, we end up what we in C++ call 'undefined behavior'. - void Enter(); - void Leave(); + void enter(); + void leave(); - inline bool Is(const char* expected) const { + inline bool is(const char* expected) const { return !strncmp(expected, current_.keyStart_, size_t(current_.keyEnd_ - current_.keyStart_)); } - inline std::string Key() const { return std::string(current_.keyStart_, current_.keyEnd_); } + inline std::string key() const { return std::string(current_.keyStart_, current_.keyEnd_); } - std::string StringValue() const; - bool BoolValue() const; - int IntValue() const; - double FloatValue() const; + std::string stringValue() const; + bool boolValue() const; + int intValue() const; + double floatValue() const; + Pin pinValue() const; }; } diff --git a/Grbl_Esp32/src/Configuration/ParserTest.cpp b/Grbl_Esp32/src/Configuration/ParserTest.cpp deleted file mode 100644 index 88eb9fe9..00000000 --- a/Grbl_Esp32/src/Configuration/ParserTest.cpp +++ /dev/null @@ -1,93 +0,0 @@ -#ifndef ESP32 - -# include "Parser.h" - -# include -# include -# include -# include - -namespace Configuration { - - void ParseSpecificAxis(Parser& parser, int axis, int ganged) { - const char* allAxis = "xyzabc"; - std::cout << "Parsing axis " << allAxis[axis] << ", ganged #" << ganged << std::endl; - - for (; !parser.IsEndSection(); parser.MoveNext()) { - if (parser.Is("limit")) { - auto limitPin = parser.StringValue(); - std::cout << "Limit pin: " << limitPin << std::endl; - } - // and so on... - } - } - - void ParseAxis(Parser& parser) { - std::cout << "Parsing axis." << std::endl; - - const char* allAxis = "xyzabc"; - - for (; !parser.IsEndSection(); parser.MoveNext()) { - auto str = parser.Key(); - if (str.size() == 1) { - auto idx = strchr(allAxis, str[0]); - if (idx != nullptr) { - parser.Enter(); - ParseSpecificAxis(parser, idx - allAxis, 0); - parser.Leave(); - } - } else if (str.size() == 2) { - auto idx = strchr(allAxis, str[0]); - if (idx != nullptr && str[1] >= '1' && str[1] <= '9') { - int ganged = str[1] - '1'; - - parser.Enter(); - ParseSpecificAxis(parser, idx - allAxis, ganged); - parser.Leave(); - } - } - } - } - - void ParseBus(Parser& parser) { - std::cout << "Parsing bus." << std::endl; - - // TODO - } - - void ParseRoot(Parser& parser) { - std::cout << "Parsing root." << std::endl; - - for (; !parser.IsEndSection(); parser.MoveNext()) { - if (parser.Is("axis")) { - parser.Enter(); - ParseAxis(parser); - parser.Leave(); - } else if (parser.Is("bus")) { - parser.Enter(); - ParseBus(parser); - parser.Leave(); - } - } - } - - int main() { - std::ifstream t("..\\Fiddling\\Test.yaml"); - std::string str((std::istreambuf_iterator(t)), std::istreambuf_iterator()); - - const auto begin = str.c_str(); - const auto end = begin + str.size(); - - try { - Parser parser(begin, end); - ParseRoot(parser); - } catch (ParseException ex) { - std::cout << "Parse error: " << ex.What() << " @ " << ex.LineNumber() << ":" << ex.ColumnNumber() << std::endl; - } catch (...) { std::cout << "Uncaught exception" << std::endl; } - - std::string s; - std::getline(std::cin, s); - return 0; - } -} -#endif diff --git a/Grbl_Esp32/src/Configuration/SettingsError.h b/Grbl_Esp32/src/Configuration/SettingsError.h new file mode 100644 index 00000000..6f70f09b --- /dev/null +++ b/Grbl_Esp32/src/Configuration/SettingsError.h @@ -0,0 +1 @@ +#pragma once diff --git a/Grbl_Esp32/src/Configuration/Test.yaml b/Grbl_Esp32/src/Configuration/Test.yaml deleted file mode 100644 index b4d27d0a..00000000 --- a/Grbl_Esp32/src/Configuration/Test.yaml +++ /dev/null @@ -1,28 +0,0 @@ -axis: - x: - limit: gpio.33:low - stepstick: - step: gpio.4 - direction: gpio.16 - y2: - limit: gpio.32:low - stepstick: - step: gpio.18 - direction: gpio.18 - z: - limit: gpio.34:low - dynamixel: - channel: 3 - - #yeah this rocks -bus: - rs485: - baud: 19200 - rx: gpio.12 - tx: gpio.13 -spindle: - vfd: - channel: 1 - unattached: true - vfd: - channel: 2 diff --git a/Grbl_Esp32/src/Configuration/Tokenizer.cpp b/Grbl_Esp32/src/Configuration/Tokenizer.cpp index 5ce8129f..141f8fa7 100644 --- a/Grbl_Esp32/src/Configuration/Tokenizer.cpp +++ b/Grbl_Esp32/src/Configuration/Tokenizer.cpp @@ -4,177 +4,8 @@ #include namespace Configuration { - inline void Tokenizer::ParseError(const char* description) const { throw ParseException(start_, current_, description); } - void Tokenizer::Tokenize() { - // We parse 1 line at a time. Each time we get here, we can assume that the cursor - // is at the start of the line. - parseAgain: - int indent = 0; - - while (!Eof() && IsSpace()) { - Inc(); - ++indent; - } - - if (!Eof()) { - switch (Current()) { - case '\t': - // TODO FIXME: We can do tabs or spaces, not both. However, we *could* let the user decide. - ParseError("Indentation through tabs is not allowed. Convert all tabs to spaces please."); - break; - - case '#': // Comment till end of line - Inc(); - while (!Eof() && !IsEndLine()) { - Inc(); - } - return; - - case '\r': - case '\n': - Inc(); - if (!Eof() && Current() == '\n') { - Inc(); - } // \r\n - goto parseAgain; - - default: - if (!IsAlpha()) { - ParseError("Expected identifier."); - } - - token_.keyStart_ = current_; - Inc(); - while (!Eof() && (IsAlpha() || IsDigit() || Current() == '_')) { - Inc(); - } - token_.keyEnd_ = current_; - - // Skip whitespaces: - while (!Eof() && IsWhiteSpace()) { - Inc(); - } - - if (Current() != ':') { - ParseError("After a key or section name, we expect a colon character ':'."); - } - Inc(); - - // Skip whitespaces after the colon: - while (!Eof() && IsWhiteSpace()) { - Inc(); - } - - token_.indent_ = indent; - if (IsEndLine()) { - token_.kind_ = TokenKind::Section; - - Inc(); - if (!Eof() && Current() == '\n') { - Inc(); - } // \r\n - } else { - switch (Current()) { - case '"': - case '\'': { - auto delimiter = Current(); - - token_.kind_ = TokenKind::String; - Inc(); - token_.sValueStart_ = current_; - while (!Eof() && Current() != delimiter && !IsEndLine()) { - Inc(); - } - token_.sValueEnd_ = current_; - if (Current() != delimiter) { - ParseError("Could not find matching delimiter in string value."); - } - Inc(); - } break; - - default: - if (EqualsCaseInsensitive("true")) { - token_.kind_ = TokenKind::Boolean; - token_.bValue_ = true; - - for (auto i = 0; i < 4; ++i) { - Inc(); - } - } else if (EqualsCaseInsensitive("false")) { - token_.kind_ = TokenKind::Boolean; - token_.bValue_ = false; - - for (auto i = 0; i < 5; ++i) { - Inc(); - } - } else if (IsDigit() || Current() == '-') { - auto doubleOrIntStart = current_; - - int intValue = 0; - bool negative = false; - - if (Current() == '-') { - Inc(); - negative = true; - } - - while (IsDigit()) { - intValue = intValue * 10 + int(Current() - '0'); - Inc(); - } - - if (Current() == 'e' || Current() == 'E' || Current() == '.' || // markers - (current_ - doubleOrIntStart) >= 9) { // liberal interpretation of 'out of int range' - char* floatEnd; - token_.fValue_ = strtod(doubleOrIntStart, &floatEnd); - token_.kind_ = TokenKind::FloatingPoint; - - current_ = floatEnd; - } else { - if (negative) { - intValue = -intValue; - } - token_.iValue_ = intValue; - token_.kind_ = TokenKind::IntegerValue; - } - } else { - // If it's not 'true', not 'false', and not a digit, we have a string delimited by a whitespace - token_.kind_ = TokenKind::String; - token_.sValueStart_ = current_; - while (!Eof() && !IsWhiteSpace() && !IsEndLine()) { - Inc(); - } - token_.sValueEnd_ = current_; - } - break; - } - - // Skip more whitespaces - while (!Eof() && IsSpace()) { - Inc(); - } - - // A comment after a key-value pair is allowed. - if (Current() == '#') { - Inc(); - while (!Eof() && !IsEndLine()) { - Inc(); - } - } - - // Should be EOL or EOF at this point. - if (!IsEndLine() && !Eof()) { - ParseError("Expected line end after key/value pair."); - } - } - } - } else { - token_.kind_ = TokenKind::Eof; - token_.indent_ = 0; - } - } - inline Tokenizer::Tokenizer(const char* start, const char* end) : start_(start), current_(start), end_(end), token_() { + Tokenizer::Tokenizer(const char* start, const char* end) : start_(start), current_(start), end_(end), token_() { // If start is a yaml document start ('---' [newline]), skip that first. if (EqualsCaseInsensitive("---")) { for (int i = 0; i < 3; ++i) { @@ -190,4 +21,182 @@ namespace Configuration { start_ = current_; } } + + void Tokenizer::ParseError(const char* description) const { throw ParseException(start_, current_, description); } + + void Tokenizer::Tokenize() { + // We parse 1 line at a time. Each time we get here, we can assume that the cursor + // is at the start of the line. + + parseAgain: + int indent = 0; + + while (!Eof() && IsSpace()) { + Inc(); + ++indent; + } + + if (!Eof()) { + switch (Current()) { + case '\t': + // TODO FIXME: We can do tabs or spaces, not both. However, we *could* let the user decide. + ParseError("Indentation through tabs is not allowed. Convert all tabs to spaces please."); + break; + + case '#': // Comment till end of line + Inc(); + while (!Eof() && !IsEndLine()) { + Inc(); + } + return; + + case '\r': + case '\n': + Inc(); + if (!Eof() && Current() == '\n') { + Inc(); + } // \r\n + goto parseAgain; + + default: + if (!IsAlpha()) { + ParseError("Expected identifier."); + } + + token_.keyStart_ = current_; + Inc(); + while (!Eof() && (IsAlpha() || IsDigit() || Current() == '_')) { + Inc(); + } + token_.keyEnd_ = current_; + + // Skip whitespaces: + while (!Eof() && IsWhiteSpace()) { + Inc(); + } + + if (Current() != ':') { + ParseError("After a key or section name, we expect a colon character ':'."); + } + Inc(); + + // Skip whitespaces after the colon: + while (!Eof() && IsWhiteSpace()) { + Inc(); + } + + token_.indent_ = indent; + if (IsEndLine()) { + token_.kind_ = TokenKind::Section; + + Inc(); + if (!Eof() && Current() == '\n') { + Inc(); + } // \r\n + } + else { + switch (Current()) { + case '"': + case '\'': { + auto delimiter = Current(); + + token_.kind_ = TokenKind::String; + Inc(); + token_.sValueStart_ = current_; + while (!Eof() && Current() != delimiter && !IsEndLine()) { + Inc(); + } + token_.sValueEnd_ = current_; + if (Current() != delimiter) { + ParseError("Could not find matching delimiter in string value."); + } + Inc(); + } break; + + default: + if (EqualsCaseInsensitive("true")) { + token_.kind_ = TokenKind::Boolean; + token_.bValue_ = true; + + for (auto i = 0; i < 4; ++i) { + Inc(); + } + } + else if (EqualsCaseInsensitive("false")) { + token_.kind_ = TokenKind::Boolean; + token_.bValue_ = false; + + for (auto i = 0; i < 5; ++i) { + Inc(); + } + } + else if (IsDigit() || Current() == '-') { + auto doubleOrIntStart = current_; + + int intValue = 0; + bool negative = false; + + if (Current() == '-') { + Inc(); + negative = true; + } + + while (IsDigit()) { + intValue = intValue * 10 + int(Current() - '0'); + Inc(); + } + + if (Current() == 'e' || Current() == 'E' || Current() == '.' || // markers + (current_ - doubleOrIntStart) >= 9) { // liberal interpretation of 'out of int range' + char* floatEnd; + token_.fValue_ = strtod(doubleOrIntStart, &floatEnd); + token_.kind_ = TokenKind::FloatingPoint; + + current_ = floatEnd; + } + else { + if (negative) { + intValue = -intValue; + } + token_.iValue_ = intValue; + token_.kind_ = TokenKind::IntegerValue; + } + } + else { + // If it's not 'true', not 'false', and not a digit, we have a string delimited by a whitespace + token_.kind_ = TokenKind::String; + token_.sValueStart_ = current_; + while (!Eof() && !IsWhiteSpace() && !IsEndLine()) { + Inc(); + } + token_.sValueEnd_ = current_; + } + break; + } + + // Skip more whitespaces + while (!Eof() && IsSpace()) { + Inc(); + } + + // A comment after a key-value pair is allowed. + if (Current() == '#') { + Inc(); + while (!Eof() && !IsEndLine()) { + Inc(); + } + } + + // Should be EOL or EOF at this point. + if (!IsEndLine() && !Eof()) { + ParseError("Expected line end after key/value pair."); + } + } + } + } + else { + token_.kind_ = TokenKind::Eof; + token_.indent_ = 0; + } + } } diff --git a/Grbl_Esp32/src/Spindles/PWMSpindle.h b/Grbl_Esp32/src/Spindles/PWMSpindle.h index 4e936f1c..ca172523 100644 --- a/Grbl_Esp32/src/Spindles/PWMSpindle.h +++ b/Grbl_Esp32/src/Spindles/PWMSpindle.h @@ -23,267 +23,10 @@ */ #include "Spindle.h" -#include -#include -#include - -namespace Settings { - class YamlParser { - // I didn't create a full parser, as we don't need it. - // See: https://en.wikipedia.org/wiki/YAML - // - // Supported: - // - We start with '---' and assume a single document. If we encounter '---', error. - // - # is a comment - // - indentation is with spaces - // - Tabs are not allowed for indentation - // - key: value pairs - // - strings with quotes - // - // Unsupported: - // - '-' lists - // - c-style escaping in strings - // - repeated nodes are initially denoted by an ampersand (&) and thereafter referenced with an asterisk (*). - // - '['..']', !! and % are simply not supported. - - public: - // TODO FIXME: Create a parser. This is just a test. - // - // We have to think this through a bit. We want to stream the key/values - // in some way, but at the same time make it easy to use. Perhaps a - // simple 'movenext', 'token type', 'key' and 'value' will do. - - bool moveNext() { return n++ < 1; } - const char* key() { return "spindles"; } - const char* value() { return "gpio.12"; } - /* - const char* getValue(const char* key) { - if (!strcmp(key, "output")) { - return "gpio.12"; - } else if (!strcmp(key, "direction")) { - return "gpio.13"; - } else if (!strcmp(key, "enable")) { - return "gpio.14"; - } - return nullptr; - } - - const char* getNextSection() - { - if (n == 0) { - return "spindles"; - } - else { - return "pwm"; - } - } - */ - }; - - // Everything that uses the parser derives from this. - class SettingsParser { - public: - virtual void* parse(YamlParser& parser) = 0; - }; - - class SettingLeaf; - - class SettingsNode : public SettingsParser { - protected: - std::vector myLeafs; - - public: - static SettingsNode*& CurrentContainer() { - static SettingsNode* current = nullptr; - return current; - } - - SettingsNode() { CurrentContainer() = this; } - - void Add(SettingLeaf* leaf) { myLeafs.push_back(leaf); } - }; - - class SettingsCollection { - struct Item { - const char* name; - const char* parent; - SettingsNode* builder; - }; - - SettingsCollection() = default; - - static SettingsCollection& instance() { - static SettingsCollection instance; - return instance; - } - - std::vector builders_; - - public: - static void registerSetting(const char* name, const char* parent, SettingsNode* builder) { - instance().builders_.push_back({ name, parent, builder }); - } - - static SettingsNode* find(const char* name, const char* parent) { - if (parent != nullptr) { - for (auto& it : instance().builders_) { - if (!strcmp(it.name, name) && !strcmp(it.parent, parent)) { - return it.builder; - } - } - } else { - for (auto& it : instance().builders_) { - if (!strcmp(it.name, name) && it.parent == nullptr) { - return it.builder; - } - } - } - return nullptr; - } - }; - - // Leafs: basically key's with some value. - class SettingLeaf { - protected: - const char* key_; - - public: - SettingLeaf(const char* key) : key_(key) { - auto currentContainer = SettingsNode::CurrentContainer(); - if (currentContainer != nullptr) { - currentContainer->Add(this); - } - } - - const char* key() const { return key_; } - virtual void parse(YamlParser& parser) = 0; - }; - - class PinSetting : SettingLeaf { - Pin value_; - - public: - PinSetting(const char* key) : SettingLeaf(key) {} - - void parse(YamlParser& parser) override { value_ = Pin::create(parser.value()); } - Pin value() const { return value_; } - }; - - // When an error occurs, we want details. Let's just throw an error with these details, and have the - // framework clean up the mess. - class SettingsError { - SettingsError() = default; - - public: - SettingsError(const char* message, ...) { - // vsnprintf etc. - } - }; - - // NOTE: Settings just _define_ settings, and are very temporary objects. The lifetime of the members in a - // Settings object is basically the lifetime at which that section is parsed. This is very important, because - // if you use member variables for -say- motors, they can bleed through in other axis. In other words: it's - // best not to use member variables in settings expect for leaf settings. - template - class Setting : SettingsNode { - public: - Setting(const char* settingName, const char* parentName) { SettingsCollection::registerSetting(settingName, parentName, this); } - - virtual Category* create() = 0; - - virtual void* parse(YamlParser& parser) { - for (auto leaf : myLeafs) { - leaf->parse(parser); - } - return create(); - } - - virtual ~Setting() {} - }; -} - -class SpindleCollection { -public: - std::vector spindles_; - - struct MySettings : Settings::Setting { - const char* collectionName = "spindles"; - - void* parse(Settings::YamlParser& parser) override { - SpindleCollection collection; - - while (parser.moveNext()) { - auto builder = Settings::SettingsCollection::find(parser.key(), collectionName); - Assert(builder != nullptr, "Settings invalid; incorrect key found: %s", parser.key()); - - // Unfortunately we cannot use dynamic_cast here, because we lack RTTI. - auto spindle = static_cast(builder->parse(parser)); - collection.spindles_.push_back(spindle); - } - - // Use copy constructor here. It costs some time, but makes RAII much easier. - return new SpindleCollection(collection); - } - }; - - ~SpindleCollection() { - for (auto spindle : spindles_) { - delete spindle; - } - } -}; - -class Machine { -public: - SpindleCollection* spindles_ = nullptr; - // Axis, misc devices, etc. - - struct MySettings : Settings::Setting { - MySettings() : Setting("machine", nullptr) {} - - void* parse(Settings::YamlParser& parser) override { - Machine machine; - - while (parser.moveNext()) { - auto builder = Settings::SettingsCollection::find(parser.key(), nullptr); - Assert(builder != nullptr, "Settings invalid; incorrect key found: %s", parser.key()); - - // Unfortunately we cannot use dynamic_cast here, because we lack RTTI. - if (!strcmp(parser.key(), "spindles")) { - Assert(machine.spindles_ == nullptr, "No spindles should be defined at this point. Only one spindle section is allowed."); - machine.spindles_ = static_cast(builder->parse(parser)); - } - // else more sections... - } - - // Use copy constructor here. It costs some time, but makes RAII much easier. - return new Machine(machine); - } - }; - - ~Machine() { delete spindles_; } -}; - namespace Spindles { // This adds support for PWM class PWM : public Spindle { public: - struct MySettings : Settings::Setting { - MySettings() : Setting("pwm", "spindles") {} // note we can make 'spindles' a const char* in the Spindle class - - Settings::PinSetting outputPin = "output"; - Settings::PinSetting directionPin = "direction"; - Settings::PinSetting enablePin = "enable"; - - Spindle* create() override { - if (outputPin.value() == Pin::UNDEFINED) { - throw Settings::SettingsError("Output pin is undefined."); - } - // etc. - - return new PWM(outputPin.value(), enablePin.value(), directionPin.value(), 0, 10000); - } - }; PWM() = default; PWM(Pin output, Pin enable, Pin direction, uint32_t minRpm, uint32_t maxRpm) : @@ -330,8 +73,3 @@ namespace Spindles { uint8_t calc_pwm_precision(uint32_t freq); }; } - -// Register settings class works like this: -namespace { - Spindles::PWM::MySettings pwmSettings; -}