mirror of
https://github.com/bdring/Grbl_Esp32.git
synced 2025-09-03 11:22:38 +02:00
Implemented parser for configuration. A small x64 test can be found in ParserTest.cpp; haven't ran it on ESP32 yet.
This commit is contained in:
29
Grbl_Esp32/src/Configuration/ParseException.h
Normal file
29
Grbl_Esp32/src/Configuration/ParseException.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Configuration {
|
||||||
|
class ParseException {
|
||||||
|
int line_;
|
||||||
|
int column_;
|
||||||
|
const char* description_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ParseException() = default;
|
||||||
|
ParseException(const ParseException&) = default;
|
||||||
|
|
||||||
|
ParseException(const char* start, const char* current, const char* description) : description_(description) {
|
||||||
|
line_ = 1;
|
||||||
|
column_ = 1;
|
||||||
|
while (start != current) {
|
||||||
|
if (*start == '\n') {
|
||||||
|
++line_;
|
||||||
|
column_ = 1;
|
||||||
|
}
|
||||||
|
++start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int LineNumber() const { return line_; }
|
||||||
|
inline int ColumnNumber() const { return column_; }
|
||||||
|
inline const char* What() const { return description_; }
|
||||||
|
};
|
||||||
|
}
|
112
Grbl_Esp32/src/Configuration/Parser.cpp
Normal file
112
Grbl_Esp32/src/Configuration/Parser.cpp
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
#include "Parser.h"
|
||||||
|
|
||||||
|
#include "ParseException.h"
|
||||||
|
|
||||||
|
namespace Configuration {
|
||||||
|
Parser::Parser(const char* start, const char* end) : Tokenizer(start, end), current_() {
|
||||||
|
Tokenize();
|
||||||
|
current_ = token_;
|
||||||
|
if (current_.kind_ != TokenKind::Eof) {
|
||||||
|
Tokenize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Tokenizer::ParseError(description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MoveNext: moves to the next entry in the current section. By default we're in the
|
||||||
|
/// root section.
|
||||||
|
/// </summary>
|
||||||
|
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_) {
|
||||||
|
Tokenize();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the indent is the same, we're in the same section. Update current, move to next
|
||||||
|
// token.
|
||||||
|
if (token_.indent_ == current_.indent_) {
|
||||||
|
current_ = token_;
|
||||||
|
Tokenize();
|
||||||
|
} else {
|
||||||
|
// Apparently token_.indent < current_.indent_, which means we have no more items
|
||||||
|
// in our tokenizer that are relevant.
|
||||||
|
//
|
||||||
|
// Note that we want to preserve current_.indent_!
|
||||||
|
current_.kind_ = TokenKind::Eof;
|
||||||
|
}
|
||||||
|
|
||||||
|
return current_.kind_ != TokenKind::Eof;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Parser::Enter() {
|
||||||
|
indentStack_.push(current_.indent_);
|
||||||
|
|
||||||
|
// If we can enter, token_.indent_ > current_.indent_:
|
||||||
|
if (token_.indent_ > current_.indent_) {
|
||||||
|
current_ = token_;
|
||||||
|
Tokenize();
|
||||||
|
} else {
|
||||||
|
current_ = TokenData();
|
||||||
|
current_.indent_ = INT_MAX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
indentStack_.pop();
|
||||||
|
|
||||||
|
if (last == token_.indent_) {
|
||||||
|
// Yes, the token continues where we left off:
|
||||||
|
current_ = token_;
|
||||||
|
// Tokenize(); --> No need, this is handled by MoveNext!
|
||||||
|
} else {
|
||||||
|
current_ = TokenData();
|
||||||
|
current_.indent_ = last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Parser::StringValue() const {
|
||||||
|
if (current_.kind_ != TokenKind::String) {
|
||||||
|
ParseError("Expected a string value (e.g. 'foo')");
|
||||||
|
}
|
||||||
|
return std::string(current_.sValueStart_, current_.sValueEnd_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Parser::BoolValue() const {
|
||||||
|
if (current_.kind_ != TokenKind::Boolean) {
|
||||||
|
ParseError("Expected a boolean value (e.g. true or value)");
|
||||||
|
}
|
||||||
|
return current_.bValue_;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Parser::IntValue() const {
|
||||||
|
if (current_.kind_ != TokenKind::Boolean) {
|
||||||
|
ParseError("Expected an integer value (e.g. 123456)");
|
||||||
|
}
|
||||||
|
return current_.iValue_;
|
||||||
|
}
|
||||||
|
|
||||||
|
double Parser::FloatValue() const {
|
||||||
|
if (current_.kind_ != TokenKind::Boolean) {
|
||||||
|
ParseError("Expected a float value (e.g. 123.456)");
|
||||||
|
}
|
||||||
|
return current_.fValue_;
|
||||||
|
}
|
||||||
|
}
|
49
Grbl_Esp32/src/Configuration/Parser.h
Normal file
49
Grbl_Esp32/src/Configuration/Parser.h
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Tokenizer.h"
|
||||||
|
|
||||||
|
#include <stack>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace Configuration {
|
||||||
|
class Parser : public Tokenizer {
|
||||||
|
// Parsing here might be a bit confusing, because the state of the tokenizer is one step
|
||||||
|
// ahead of the parser. That way we always have 2 tokens at our disposal, so we know when
|
||||||
|
// we're entering or exiting a section.
|
||||||
|
|
||||||
|
std::stack<int> indentStack_;
|
||||||
|
TokenData current_;
|
||||||
|
|
||||||
|
void ParseError(const char* description) const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Parser(const char* start, const char* end);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MoveNext: moves to the next entry in the current section. By default we're in the
|
||||||
|
/// root section.
|
||||||
|
/// </summary>
|
||||||
|
bool MoveNext();
|
||||||
|
|
||||||
|
inline bool IsEndSection() { return current_.kind_ == TokenKind::Eof || token_.indent_ < current_.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();
|
||||||
|
|
||||||
|
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_); }
|
||||||
|
|
||||||
|
std::string StringValue() const;
|
||||||
|
bool BoolValue() const;
|
||||||
|
int IntValue() const;
|
||||||
|
double FloatValue() const;
|
||||||
|
};
|
||||||
|
}
|
93
Grbl_Esp32/src/Configuration/ParserTest.cpp
Normal file
93
Grbl_Esp32/src/Configuration/ParserTest.cpp
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
#ifndef ESP32
|
||||||
|
|
||||||
|
# include "Parser.h"
|
||||||
|
|
||||||
|
# include <string>
|
||||||
|
# include <fstream>
|
||||||
|
# include <iostream>
|
||||||
|
# include <streambuf>
|
||||||
|
|
||||||
|
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<char>(t)), std::istreambuf_iterator<char>());
|
||||||
|
|
||||||
|
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
|
28
Grbl_Esp32/src/Configuration/Test.yaml
Normal file
28
Grbl_Esp32/src/Configuration/Test.yaml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
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
|
13
Grbl_Esp32/src/Configuration/TokenKind.h
Normal file
13
Grbl_Esp32/src/Configuration/TokenKind.h
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Configuration {
|
||||||
|
|
||||||
|
enum struct TokenKind {
|
||||||
|
Section,
|
||||||
|
Boolean,
|
||||||
|
String,
|
||||||
|
IntegerValue,
|
||||||
|
FloatingPoint,
|
||||||
|
Eof,
|
||||||
|
};
|
||||||
|
}
|
193
Grbl_Esp32/src/Configuration/Tokenizer.cpp
Normal file
193
Grbl_Esp32/src/Configuration/Tokenizer.cpp
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
#include "Tokenizer.h"
|
||||||
|
|
||||||
|
#include "ParseException.h"
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
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_() {
|
||||||
|
// If start is a yaml document start ('---' [newline]), skip that first.
|
||||||
|
if (EqualsCaseInsensitive("---")) {
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
Inc();
|
||||||
|
}
|
||||||
|
while (IsWhiteSpace()) {
|
||||||
|
Inc();
|
||||||
|
}
|
||||||
|
while (Current() == '\r' || Current() == '\n') {
|
||||||
|
Inc();
|
||||||
|
}
|
||||||
|
|
||||||
|
start_ = current_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
85
Grbl_Esp32/src/Configuration/Tokenizer.h
Normal file
85
Grbl_Esp32/src/Configuration/Tokenizer.h
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "TokenKind.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Configuration {
|
||||||
|
|
||||||
|
class Tokenizer {
|
||||||
|
const char* current_;
|
||||||
|
const char* end_;
|
||||||
|
|
||||||
|
inline void Inc() {
|
||||||
|
if (current_ != end_) {
|
||||||
|
++current_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inline char Current() const { return Eof() ? '\0' : (*current_); }
|
||||||
|
|
||||||
|
inline bool IsAlpha() {
|
||||||
|
char c = Current();
|
||||||
|
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool IsSpace() { return Current() == ' '; }
|
||||||
|
|
||||||
|
inline bool IsWhiteSpace() {
|
||||||
|
char c = Current();
|
||||||
|
return c == ' ' || c == '\t' || c == '\f';
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool IsEndLine() { return Current() == '\n'; }
|
||||||
|
|
||||||
|
inline bool IsDigit() {
|
||||||
|
char c = Current();
|
||||||
|
return (c >= '0' && c <= '9');
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline char ToLower(char c) { return (c >= 'A' && c <= 'Z') ? (char)(c + 32) : c; }
|
||||||
|
|
||||||
|
inline bool EqualsCaseInsensitive(const char* input) {
|
||||||
|
const char* tmp = current_;
|
||||||
|
while (ToLower(*input) == ToLower(Current()) && *input != '\0') {
|
||||||
|
Inc();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isSame = *input == '\0'; // Everything till the end of the input string is the same
|
||||||
|
current_ = tmp; // Restore situation
|
||||||
|
return isSame;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
const char* start_;
|
||||||
|
|
||||||
|
// Results:
|
||||||
|
struct TokenData {
|
||||||
|
TokenData() :
|
||||||
|
keyStart_(nullptr), keyEnd_(nullptr), indent_(0), kind_(TokenKind::Eof), sValueStart_(nullptr), sValueEnd_(nullptr) {}
|
||||||
|
|
||||||
|
const char* keyStart_;
|
||||||
|
const char* keyEnd_;
|
||||||
|
int indent_;
|
||||||
|
|
||||||
|
TokenKind kind_;
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
const char* sValueStart_;
|
||||||
|
const char* sValueEnd_;
|
||||||
|
};
|
||||||
|
int iValue_;
|
||||||
|
double fValue_;
|
||||||
|
bool bValue_;
|
||||||
|
};
|
||||||
|
} token_;
|
||||||
|
|
||||||
|
void ParseError(const char* description) const;
|
||||||
|
|
||||||
|
inline bool Eof() const { return current_ == end_; }
|
||||||
|
|
||||||
|
void Tokenize();
|
||||||
|
|
||||||
|
public:
|
||||||
|
Tokenizer(const char* start, const char* end);
|
||||||
|
};
|
||||||
|
}
|
Reference in New Issue
Block a user