mirror of https://github.com/meshcore-dev/MeshCore
7 changed files with 485 additions and 69 deletions
@ -0,0 +1,278 @@ |
|||
#include "ConfigSerializer.h" |
|||
|
|||
bool ConfigSerializer::saveSerial(Stream& s) { |
|||
Context context(&s, OP::WRITE); |
|||
_context = &context; // set the context for structure() call
|
|||
s.print("{"); // root object
|
|||
_first = true; |
|||
structure(); |
|||
if (s.print("}") != 1) context.success = false; // failure detect
|
|||
_context = NULL; |
|||
return context.success; |
|||
} |
|||
|
|||
#define TOK_ERROR -1 |
|||
#define TOK_EOF 0 |
|||
#define TOK_KEY 1 |
|||
#define TOK_VALUE 2 |
|||
#define TOK_START_OBJ 3 |
|||
#define TOK_END_OBJ 4 |
|||
#define TOK_WHITESPACE 5 |
|||
|
|||
static bool is_whitespace(char c) { |
|||
return c == ' ' || c == '\t' || c == '\r' || c == '\n'; |
|||
} |
|||
static bool is_key_char(char c) { |
|||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_'; |
|||
} |
|||
static bool is_value_char(char c) { |
|||
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || c == '-' || c == '.'; |
|||
} |
|||
|
|||
#define EXPECT_OPEN_BRACE 0 |
|||
#define EXPECT_KEY 1 |
|||
#define EXPECT_VAL_OR_OBJ 2 |
|||
#define EXPECT_STRING_VAL 3 |
|||
#define EXPECT_STRING_ESCAPE 4 |
|||
#define EXPECT_COMMA_OR_CLOSE 5 |
|||
#define EXPECT_COMMA_OR_KEY 6 |
|||
#define EXPECT_COMMA_OR_KEY_OR_CLOSE 7 |
|||
|
|||
int ConfigSerializer::Context::readNext() { |
|||
char c; |
|||
if (pending) { |
|||
c = pending; |
|||
pending = 0; |
|||
} else { |
|||
if (_f->available() == 0) return TOK_EOF; |
|||
|
|||
int n = _f->read(); |
|||
if (n < 0) return TOK_EOF; |
|||
c = (char)n; |
|||
} |
|||
|
|||
switch (rd_mode) { |
|||
case EXPECT_OPEN_BRACE: |
|||
if (c == '{') { rd_mode = EXPECT_KEY; return TOK_START_OBJ; } |
|||
if (is_whitespace(c)) return TOK_WHITESPACE; |
|||
return TOK_ERROR; |
|||
|
|||
case EXPECT_COMMA_OR_KEY_OR_CLOSE: |
|||
if (c == '}') { rd_mode = EXPECT_COMMA_OR_KEY_OR_CLOSE; return TOK_END_OBJ; } |
|||
case EXPECT_COMMA_OR_KEY: |
|||
if (c == ',') { rd_mode = EXPECT_KEY; return TOK_WHITESPACE; } |
|||
case EXPECT_KEY: |
|||
if (rd_len > 0 && c == ':') { rd_buf[rd_len] = 0; rd_len = 0; rd_mode = EXPECT_VAL_OR_OBJ; return TOK_KEY; } |
|||
if (rd_len == 0 && is_whitespace(c)) return TOK_WHITESPACE; |
|||
if (rd_len < CONFIG_MAX_KEYLEN-1 && is_key_char(c)) { rd_buf[rd_len++] = c; return TOK_WHITESPACE; } |
|||
return TOK_ERROR; |
|||
|
|||
case EXPECT_VAL_OR_OBJ: |
|||
if (rd_len == 0 && is_whitespace(c)) return TOK_WHITESPACE; |
|||
if (rd_len == 0 && c == '"') { rd_mode = EXPECT_STRING_VAL; return TOK_WHITESPACE; } |
|||
if (rd_len == 0 && c == '{') { rd_mode = EXPECT_KEY; return TOK_START_OBJ; } |
|||
if (is_value_char(c) && rd_len < CONFIG_MAX_TOKEN_LEN-1) { rd_buf[rd_len++] = c; return TOK_WHITESPACE; } |
|||
if (rd_len > 0 && (c == ',' || c == '}' || is_whitespace(c))) { pending = c; rd_buf[rd_len] = 0; rd_len = 0; rd_mode = EXPECT_COMMA_OR_CLOSE; return TOK_VALUE; } |
|||
return TOK_ERROR; |
|||
|
|||
case EXPECT_STRING_ESCAPE: |
|||
if ((c == '"' || c == '\\' || c == '/') && rd_len < CONFIG_MAX_TOKEN_LEN-1) { rd_buf[rd_len++] = c; rd_mode = EXPECT_STRING_VAL; return TOK_WHITESPACE; } |
|||
return TOK_ERROR; // unsupport escape
|
|||
|
|||
case EXPECT_STRING_VAL: |
|||
if (c == '"') { rd_buf[rd_len] = 0; rd_len = 0; rd_mode = EXPECT_COMMA_OR_CLOSE; return TOK_VALUE; } |
|||
if (c == '\\') { rd_mode = EXPECT_STRING_ESCAPE; return TOK_WHITESPACE; } |
|||
if (rd_len < CONFIG_MAX_TOKEN_LEN-1) { rd_buf[rd_len++] = c; return TOK_WHITESPACE; } |
|||
return TOK_ERROR; |
|||
|
|||
case EXPECT_COMMA_OR_CLOSE: |
|||
if (c == ',') { rd_mode = EXPECT_KEY; return TOK_WHITESPACE; } |
|||
if (c == '}') { rd_mode = EXPECT_COMMA_OR_KEY_OR_CLOSE; return TOK_END_OBJ; } |
|||
if (is_whitespace(c)) return TOK_WHITESPACE; |
|||
return TOK_ERROR; |
|||
} |
|||
return TOK_ERROR; // unknown mode
|
|||
} |
|||
|
|||
bool ConfigSerializer::loadSerial(Stream& s) { |
|||
Context context(&s, OP::READ); |
|||
_context = &context; // set the context for structure() call
|
|||
// parse the Json file
|
|||
uint8_t sp = 0; |
|||
int next_tok; |
|||
|
|||
while ((next_tok = context.readNext()) > TOK_EOF) { |
|||
if (next_tok == TOK_KEY) { |
|||
context.setKey(sp, context.getToken()); |
|||
} else if (next_tok == TOK_VALUE) { |
|||
_depth = 1; // re-run the structure() hierarchy again (looking for specific key, at specific depth)
|
|||
structure(); |
|||
} else if (next_tok == TOK_START_OBJ) { |
|||
if (sp < CONFIF_MAX_DEPTH - 1) { |
|||
sp++; |
|||
} else { |
|||
Serial.printf("Error: max nesting reached"); // TODO: debug logging
|
|||
context.success = false; |
|||
break; |
|||
} |
|||
} else if (next_tok == TOK_END_OBJ) { |
|||
if (sp > 0) { |
|||
sp--; |
|||
} else { |
|||
Serial.printf("Error: too many closing '}'"); // TODO: debug logging
|
|||
context.success = false; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
if (sp != 0 || next_tok == TOK_ERROR) { |
|||
context.success = false; // unmatched { }, or other parse error
|
|||
} |
|||
_context = NULL; |
|||
return context.success; |
|||
} |
|||
|
|||
void ConfigSerializer::writeComma() { |
|||
if (_first) { |
|||
_first = false; |
|||
} else { |
|||
_context->file()->print(","); // comma separated properties
|
|||
} |
|||
} |
|||
|
|||
void ConfigSerializer::def(const char* key, char* value, size_t max_len) { |
|||
if (_context->op() == OP::WRITE) { |
|||
writeComma(); |
|||
_context->file()->print(key); |
|||
_context->file()->print(":\""); |
|||
_context->file()->print(value); // TODO: escape quotes
|
|||
_context->file()->print("\""); |
|||
} else { |
|||
if (_context->keyMatch(_depth, key)) { |
|||
strncpy(value, _context->getToken(), max_len - 1); |
|||
value[max_len - 1] = 0; |
|||
} |
|||
} |
|||
} |
|||
|
|||
void ConfigSerializer::def(const char* key, int32_t& value) { |
|||
if (_context->op() == OP::WRITE) { |
|||
writeComma(); |
|||
_context->file()->print(key); |
|||
_context->file()->print(":"); |
|||
_context->file()->print(value); |
|||
} else { |
|||
if (_context->keyMatch(_depth, key)) { |
|||
value = atol(_context->getToken()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void ConfigSerializer::def(const char* key, uint32_t& value) { |
|||
if (_context->op() == OP::WRITE) { |
|||
writeComma(); |
|||
_context->file()->print(key); |
|||
_context->file()->print(":"); |
|||
_context->file()->print(value); |
|||
} else { |
|||
if (_context->keyMatch(_depth, key)) { |
|||
value = atol(_context->getToken()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void ConfigSerializer::def(const char* key, int16_t& value) { |
|||
if (_context->op() == OP::WRITE) { |
|||
writeComma(); |
|||
_context->file()->print(key); |
|||
_context->file()->print(":"); |
|||
_context->file()->print((int32_t) value, 10); |
|||
} else { |
|||
if (_context->keyMatch(_depth, key)) { |
|||
value = atol(_context->getToken()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void ConfigSerializer::def(const char* key, uint16_t& value) { |
|||
if (_context->op() == OP::WRITE) { |
|||
writeComma(); |
|||
_context->file()->print(key); |
|||
_context->file()->print(":"); |
|||
_context->file()->print((uint32_t) value, 10); |
|||
} else { |
|||
if (_context->keyMatch(_depth, key)) { |
|||
value = atoi(_context->getToken()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void ConfigSerializer::def(const char* key, uint8_t& value) { |
|||
if (_context->op() == OP::WRITE) { |
|||
writeComma(); |
|||
_context->file()->print(key); |
|||
_context->file()->print(":"); |
|||
_context->file()->print((uint32_t) value, 10); |
|||
} else { |
|||
if (_context->keyMatch(_depth, key)) { |
|||
value = atoi(_context->getToken()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void ConfigSerializer::def(const char* key, int8_t& value) { |
|||
if (_context->op() == OP::WRITE) { |
|||
writeComma(); |
|||
_context->file()->print(key); |
|||
_context->file()->print(":"); |
|||
_context->file()->print((int32_t) value, 10); |
|||
} else { |
|||
if (_context->keyMatch(_depth, key)) { |
|||
value = atoi(_context->getToken()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void ConfigSerializer::def(const char* key, double& value) { |
|||
if (_context->op() == OP::WRITE) { |
|||
writeComma(); |
|||
_context->file()->print(key); |
|||
_context->file()->print(":"); |
|||
_context->file()->print(value, 6); // REVISIT: how many dec places?
|
|||
} else { |
|||
if (_context->keyMatch(_depth, key)) { |
|||
value = atof(_context->getToken()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void ConfigSerializer::def(const char* key, float& value) { |
|||
if (_context->op() == OP::WRITE) { |
|||
writeComma(); |
|||
_context->file()->print(key); |
|||
_context->file()->print(":"); |
|||
_context->file()->print(value, 4); // REVISIT: how many dec places?
|
|||
} else { |
|||
if (_context->keyMatch(_depth, key)) { |
|||
value = (float) atof(_context->getToken()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void ConfigSerializer::def(const char* key, ConfigSerializer& sub_obj) { |
|||
if (_context->op() == OP::WRITE) { |
|||
writeComma(); |
|||
_context->file()->print(key); |
|||
_context->file()->print(":{"); |
|||
sub_obj._context = _context; // inherit the Context
|
|||
sub_obj._first = true; |
|||
sub_obj.structure(); // recurse into sub object
|
|||
if (_context->file()->print("}") != 1) _context->success = false; // failure detect
|
|||
} else { |
|||
if (_context->keyMatch(_depth, key)) { |
|||
sub_obj._context = _context; // inherit the Context
|
|||
sub_obj._depth = _depth + 1; |
|||
sub_obj.structure(); // recurse into sub object
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,67 @@ |
|||
#pragma once |
|||
|
|||
#include <Arduino.h> |
|||
|
|||
#ifndef CONFIF_MAX_DEPTH |
|||
#define CONFIF_MAX_DEPTH 8 |
|||
#endif |
|||
|
|||
#ifndef CONFIG_MAX_KEYLEN |
|||
#define CONFIG_MAX_KEYLEN 16 |
|||
#endif |
|||
|
|||
#ifndef CONFIG_MAX_TOKEN_LEN |
|||
#define CONFIG_MAX_TOKEN_LEN 128 |
|||
#endif |
|||
|
|||
class ConfigSerializer { |
|||
bool _first; |
|||
int8_t _depth; |
|||
|
|||
enum OP { READ, WRITE }; |
|||
|
|||
class Context { |
|||
Stream* _f; |
|||
OP _op; |
|||
uint8_t rd_len; |
|||
uint8_t rd_mode; |
|||
char pending; |
|||
char rd_buf[CONFIG_MAX_TOKEN_LEN]; |
|||
char _keys[CONFIF_MAX_DEPTH][CONFIG_MAX_KEYLEN]; |
|||
|
|||
public: |
|||
bool success = true; |
|||
Context(Stream* f, OP op) : _f(f), _op(op) { rd_buf[rd_len = 0] = 0; rd_mode = 0; pending = 0; } |
|||
OP op() const { return _op; } |
|||
Stream* file() const { return _f; } |
|||
int readNext(); |
|||
const char* getToken() const { return rd_buf; } |
|||
bool keyMatch(int8_t depth, const char* key) { return strcmp(key, _keys[depth]) == 0; } |
|||
void setKey(uint8_t depth, const char* key) { strcpy(_keys[depth], key); } |
|||
}; |
|||
|
|||
Context* _context = NULL; |
|||
|
|||
void writeComma(); |
|||
|
|||
protected: |
|||
ConfigSerializer() { } |
|||
|
|||
void def(const char* key, char* value, size_t max_len); // max_len inclusive of null
|
|||
void def(const char* key, int32_t& value); |
|||
void def(const char* key, int16_t& value); |
|||
void def(const char* key, int8_t& value); |
|||
void def(const char* key, uint32_t& value); |
|||
void def(const char* key, uint16_t& value); |
|||
void def(const char* key, uint8_t& value); |
|||
void def(const char* key, float& value); |
|||
void def(const char* key, double& value); |
|||
void def(const char* key, bool& value); |
|||
void def(const char* key, ConfigSerializer& sub_obj); |
|||
|
|||
virtual void structure() = 0; |
|||
|
|||
public: |
|||
bool loadSerial(Stream& s); |
|||
bool saveSerial(Stream& s); |
|||
}; |
|||
Loading…
Reference in new issue