commit
3e4181f18e
@ -1 +1 @@ |
||||
Subproject commit d2fabc40f1874cabd7f7ceca690a5874887c3c5d |
||||
Subproject commit a96b522ca899c687d70b9ad92f708cd416b8d460 |
@ -1 +1 @@ |
||||
Subproject commit e4ecf0724e36c828be5222eddce58a6a5cd2386f |
||||
Subproject commit d6651ca0d11b7950078247b8305f5b23a6be6c6c |
@ -0,0 +1,3 @@ |
||||
#!/usr/bin/env bash |
||||
|
||||
xtensa-lx106-elf-gcc -E -Iuser -Ilibesphttpd/include -Iesp_iot_sdk_v1.5.2/include -Iinclude $@ |
@ -0,0 +1,144 @@ |
||||
//
|
||||
// Created by MightyPork on 2017/10/22.
|
||||
//
|
||||
|
||||
#include "config_xmacros.h" |
||||
#include "cgi_logging.h" |
||||
|
||||
void ICACHE_FLASH_ATTR xget_dec(char *buff, u32 value) |
||||
{ |
||||
sprintf(buff, "%d", value); |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR xget_bool(char *buff, bool value) |
||||
{ |
||||
sprintf(buff, "%d", value?1:0); |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR xget_ustring(char *buff, const u8 *value) |
||||
{ |
||||
sprintf(buff, "%s", (const char *) value); |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR xget_string(char *buff, const char *value) |
||||
{ |
||||
sprintf(buff, "%s", value); |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR xget_ip(char *buff, const struct ip_addr *value) |
||||
{ |
||||
sprintf(buff, IPSTR, GOOD_IP2STR(value->addr)); |
||||
} |
||||
|
||||
// ------------- XSET -------------
|
||||
|
||||
enum xset_result ICACHE_FLASH_ATTR |
||||
xset_ip(const char *name, struct ip_addr *field, const char *buff, const void *arg) |
||||
{ |
||||
cgi_dbg("Setting %s = %s", name, buff); |
||||
u32 ip = ipaddr_addr(buff); |
||||
if (ip != 0 && ip != 0xFFFFFFFFUL) { |
||||
if (field->addr != ip) { |
||||
field->addr = ip; |
||||
return XSET_SET; |
||||
} |
||||
return XSET_UNCHANGED; |
||||
} else { |
||||
cgi_warn("Bad IP: %s", buff); |
||||
return XSET_FAIL; |
||||
} |
||||
} |
||||
|
||||
enum xset_result ICACHE_FLASH_ATTR |
||||
xset_bool(const char *name, bool *field, const char *buff, const void *arg) |
||||
{ |
||||
cgi_dbg("Setting %s = %s", name, buff); |
||||
bool enable = (atoi(buff) != 0); |
||||
|
||||
if (*field != enable) { |
||||
*field = enable; |
||||
return XSET_SET; |
||||
} |
||||
return XSET_UNCHANGED; |
||||
} |
||||
|
||||
enum xset_result ICACHE_FLASH_ATTR |
||||
xset_u8(const char *name, u8 *field, const char *buff, const void *arg) |
||||
{ |
||||
cgi_dbg("Setting %s = %s", name, buff); |
||||
u32 val = (u32) atoi(buff); |
||||
|
||||
if (val <= 255) { |
||||
if (*field != val) { |
||||
*field = (u8) val; |
||||
return XSET_SET; |
||||
} |
||||
return XSET_UNCHANGED; |
||||
} else { |
||||
cgi_warn("Bad value, max 255: %s", buff); |
||||
return XSET_FAIL; |
||||
} |
||||
} |
||||
|
||||
enum xset_result ICACHE_FLASH_ATTR |
||||
xset_u32(const char *name, u32 *field, const char *buff, const void *arg) |
||||
{ |
||||
cgi_dbg("Setting %s = %s", name, buff); |
||||
u32 val = (u32) atoi(buff); |
||||
|
||||
if (*field != val) { |
||||
*field = (u32) val; |
||||
return XSET_SET; |
||||
} |
||||
return XSET_UNCHANGED; |
||||
} |
||||
|
||||
enum xset_result ICACHE_FLASH_ATTR |
||||
xset_u16(const char *name, u16 *field, const char *buff, const void *arg) |
||||
{ |
||||
cgi_dbg("Setting %s = %s", name, buff); |
||||
u16 val = (u16) atoi(buff); |
||||
|
||||
if (*field != val) { |
||||
*field = (u16) val; |
||||
return XSET_SET; |
||||
} |
||||
return XSET_UNCHANGED; |
||||
} |
||||
|
||||
enum xset_result ICACHE_FLASH_ATTR |
||||
xset_string(const char *name, char *field, const char *buff, const void *arg) |
||||
{ |
||||
cgi_dbg("Setting %s = %s", name, buff); |
||||
u32 maxlen = (u32) arg; |
||||
|
||||
if (arg > 0 && (u32)strlen(buff) > maxlen) { |
||||
cgi_warn("String too long, max %d", maxlen); |
||||
return XSET_FAIL; |
||||
} |
||||
|
||||
if (!streq(field, buff)) { |
||||
strncpy_safe(field, buff, (u32)arg); |
||||
return XSET_SET; |
||||
} |
||||
return XSET_UNCHANGED; |
||||
} |
||||
|
||||
|
||||
enum xset_result ICACHE_FLASH_ATTR |
||||
xset_ustring(const char *name, uchar *field, const char *buff, const void *arg) |
||||
{ |
||||
cgi_dbg("Setting %s = %s", name, buff); |
||||
u32 maxlen = (u32) arg; |
||||
|
||||
if (arg > 0 && (u32)strlen(buff) > maxlen) { |
||||
cgi_warn("String too long, max %d", maxlen); |
||||
return XSET_FAIL; |
||||
} |
||||
|
||||
if (!streq(field, buff)) { |
||||
strncpy_safe(field, buff, (u32)arg); |
||||
return XSET_SET; |
||||
} |
||||
return XSET_UNCHANGED; |
||||
} |
@ -0,0 +1,106 @@ |
||||
//
|
||||
// Created by MightyPork on 2017/10/22.
|
||||
//
|
||||
|
||||
#ifndef ESPTERM_CONFIG_XMACROS_H |
||||
#define ESPTERM_CONFIG_XMACROS_H |
||||
|
||||
#include <esp8266.h> |
||||
#include <helpers.h> |
||||
|
||||
typedef unsigned char uchar; |
||||
|
||||
#define XJOIN(a, b) a##b |
||||
|
||||
/**Do nothing xnotify */ |
||||
#define xnoop() |
||||
|
||||
/**
|
||||
* XGET interface |
||||
* |
||||
* @param buff - buffer where the value should be printed |
||||
* @param value - value to render to the buffer |
||||
*/ |
||||
|
||||
static inline bool xget_dummy(char *buff, u32 value) |
||||
{ |
||||
sprintf(buff, "unused %d", value); |
||||
return false; |
||||
} |
||||
|
||||
void xget_dec(char *buff, u32 value); |
||||
void xget_bool(char *buff, bool value); |
||||
void xget_ustring(char *buff, const u8 *value); |
||||
void xget_string(char *buff, const char *value); |
||||
void xget_ip(char *buff, const struct ip_addr *value); |
||||
void xget_dhcp(char *buff, const struct dhcps_lease *value); |
||||
|
||||
|
||||
/**
|
||||
* XSET interface |
||||
* |
||||
* @param name - field name (for debug) |
||||
* @param field - pointer to the target field |
||||
* @param buff - field with the value to be set |
||||
* @param arg - arbitrary argument, used to modify behavior |
||||
* |
||||
* @return xset_result |
||||
*/ |
||||
|
||||
enum xset_result { |
||||
XSET_FAIL = 0, |
||||
XSET_SET = 1, |
||||
XSET_UNCHANGED = 2 |
||||
}; |
||||
|
||||
// Dummy for unimplemented setters
|
||||
static inline enum xset_result xset_dummy(const char *name, void *field, const char *buff, const void *arg) |
||||
{ |
||||
return XSET_UNCHANGED; |
||||
} |
||||
|
||||
enum xset_result xset_ip(const char *name, struct ip_addr *field, const char *buff, const void *arg); |
||||
enum xset_result xset_bool(const char *name, bool *field, const char *buff, const void *arg); |
||||
enum xset_result xset_u8(const char *name, u8 *field, const char *buff, const void *arg); |
||||
enum xset_result xset_u32(const char *name, u32 *field, const char *buff, const void *arg); |
||||
enum xset_result xset_u16(const char *name, u16 *field, const char *buff, const void *arg); |
||||
|
||||
// static string arrays are not &'d, so we don't get **
|
||||
/** @param arg - max string length */ |
||||
enum xset_result xset_string(const char *name, char *field, const char *buff, const void *arg); |
||||
enum xset_result xset_ustring(const char *name, u8 *field, const char *buff, const void *arg); |
||||
|
||||
/**
|
||||
* Helper template macro for CGI functions that load GET args to structs using XTABLE |
||||
* |
||||
* If 'name' is found in connData->getArgs, xset() is called. |
||||
* If the result is SET, xnotify() is fired. Else, 'name,' is appended to the redir_url buffer. |
||||
*/ |
||||
#define XSET_CGI_FUNC(type, name, suffix, deref, xget, xset, xsarg, xnotify, allow) \ |
||||
if ((allow) && GET_ARG(#name)) { \
|
||||
type *_p = (type *) &XSTRUCT->name; \
|
||||
enum xset_result res = xset(#name, _p, buff, (const void*) (xsarg)); \
|
||||
if (res == XSET_SET) { xnotify; } \
|
||||
else if (res == XSET_FAIL) { redir_url += sprintf(redir_url, #name","); } \
|
||||
} |
||||
|
||||
/** used for INI */ |
||||
#define XSET_ASSIGN(type, name, suffix, deref, xget, xset, xsarg, xnotify, allow) \ |
||||
if (streq(#name, key)) { \
|
||||
found = true; \
|
||||
type *_p = (type *) &XSTRUCT->name; \
|
||||
enum xset_result res = xset(#name, _p, value, (const void*) (xsarg)); \
|
||||
if (res == XSET_SET) { xnotify; } \
|
||||
else if (res == XSET_FAIL) { suc = false; } \
|
||||
} |
||||
|
||||
#define XGET_CGI_FUNC(type, name, suffix, deref, xget, xset, xsarg, xnotify, allow) \ |
||||
if ((allow) && streq(token, #name)) xget(buff, deref XSTRUCT->name); |
||||
|
||||
#define XSTRUCT_FIELD(type, name, suffix, deref, xget, xset, xsarg, xnotify, allow) \ |
||||
type name suffix; |
||||
|
||||
#define XDUMP_FIELD(type, name, suffix, deref, xget, allow, xset, xsarg, xnotify) \ |
||||
{ xget(buff, deref XSTRUCT->name); dbg(#name " = %s", buff); } |
||||
|
||||
#endif //ESPTERM_CONFIG_XMACROS_H
|
@ -0,0 +1,526 @@ |
||||
|
||||
/* #line 1 "user/ini_parser.rl" */ |
||||
|
||||
/* Ragel constants block */ |
||||
#include "ini_parser.h" |
||||
|
||||
// Ragel setup
|
||||
|
||||
/* #line 10 "user/ini_parser.c" */ |
||||
static const char _ini_actions[] ESP_CONST_DATA = { |
||||
0, 1, 1, 1, 2, 1, 3, 1,
|
||||
4, 1, 5, 1, 6, 1, 7, 1,
|
||||
8, 1, 9, 1, 10, 1, 11, 1,
|
||||
13, 2, 0, 4, 2, 12, 4 |
||||
}; |
||||
|
||||
static const char _ini_eof_actions[] ESP_CONST_DATA = { |
||||
0, 23, 5, 5, 15, 15, 15, 15,
|
||||
19, 19, 0, 0, 0, 0, 0, 0,
|
||||
0 |
||||
}; |
||||
|
||||
static const int ini_start = 1; |
||||
static const int ini_first_final = 12; |
||||
static const int ini_error = 0; |
||||
|
||||
static const int ini_en_section = 2; |
||||
static const int ini_en_keyvalue = 4; |
||||
static const int ini_en_comment = 8; |
||||
static const int ini_en_discard2eol = 10; |
||||
static const int ini_en_main = 1; |
||||
|
||||
|
||||
/* #line 10 "user/ini_parser.rl" */ |
||||
|
||||
|
||||
// Persistent state
|
||||
static int8_t cs = -1; //!< Ragel's Current State variable
|
||||
static uint32_t buff_i = 0; //!< Write pointer for the buffers
|
||||
static char value_quote = 0; //!< Quote character of the currently collected value
|
||||
static bool value_nextesc = false; //!< Next character is escaped, trated specially, and if quote, as literal quote character
|
||||
static IniParserCallback keyCallback = NULL; //!< Currently assigned callback
|
||||
static void *userdata = NULL; //!< Currently assigned user data for the callback
|
||||
|
||||
// Buffers
|
||||
static char keybuf[INI_KEY_MAX]; |
||||
static char secbuf[INI_KEY_MAX]; |
||||
static char valbuf[INI_VALUE_MAX]; |
||||
|
||||
// See header for doxygen!
|
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
ini_parse_reset_partial(void) |
||||
{ |
||||
buff_i = 0; |
||||
value_quote = 0; |
||||
value_nextesc = false; |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
ini_parse_reset(void) |
||||
{ |
||||
ini_parse_reset_partial(); |
||||
keybuf[0] = secbuf[0] = valbuf[0] = 0; |
||||
|
||||
/* #line 67 "user/ini_parser.c" */ |
||||
{ |
||||
cs = ini_start; |
||||
} |
||||
|
||||
/* #line 41 "user/ini_parser.rl" */ |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
ini_parser_error(const char* msg) |
||||
{ |
||||
ini_error("Parser error: %s", msg); |
||||
ini_parse_reset_partial(); |
||||
} |
||||
|
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
ini_parse_begin(IniParserCallback callback, void *userData) |
||||
{ |
||||
keyCallback = callback; |
||||
userdata = userData; |
||||
ini_parse_reset(); |
||||
} |
||||
|
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
*ini_parse_end(void) |
||||
{ |
||||
ini_parse("\n", 1); |
||||
if (keyCallback) { |
||||
keyCallback = NULL; |
||||
} |
||||
|
||||
void *ud = userdata; |
||||
userdata = NULL; |
||||
return ud; |
||||
} |
||||
|
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
ini_parse_file(const char *text, size_t len, IniParserCallback callback, void *userData) |
||||
{ |
||||
ini_parse_begin(callback, userData); |
||||
ini_parse(text, len); |
||||
ini_parse_end(); |
||||
} |
||||
|
||||
static void ICACHE_FLASH_ATTR |
||||
rtrim_buf(char *buf, int32_t end) |
||||
{ |
||||
if (end > 0) { |
||||
while ((uint8_t)buf[--end] < 33); |
||||
end++; // go past the last character
|
||||
} |
||||
|
||||
buf[end] = 0; |
||||
} |
||||
|
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
ini_parse(const char *newstr, size_t len) |
||||
{ |
||||
int32_t i; |
||||
char c; |
||||
bool isnl; |
||||
bool isquot; |
||||
|
||||
// Load new data to Ragel vars
|
||||
const uint8_t *p; |
||||
const uint8_t *eof; |
||||
const uint8_t *pe; |
||||
|
||||
if (len == 0) while(newstr[++len] != 0); // alternative to strlen
|
||||
|
||||
p = (const uint8_t *) newstr; |
||||
eof = NULL; |
||||
pe = (const uint8_t *) (newstr + len); |
||||
|
||||
// Init Ragel on the first run
|
||||
if (cs == -1) { |
||||
ini_parse_reset(); |
||||
} |
||||
|
||||
// The parser
|
||||
|
||||
/* #line 152 "user/ini_parser.c" */ |
||||
{ |
||||
const char *_acts; |
||||
unsigned int _nacts; |
||||
|
||||
if ( p == pe ) |
||||
goto _test_eof; |
||||
if ( cs == 0 ) |
||||
goto _out; |
||||
_resume: |
||||
switch ( cs ) { |
||||
case 1: |
||||
switch( (*p) ) { |
||||
case 32u: goto tr1; |
||||
case 35u: goto tr3; |
||||
case 58u: goto tr0; |
||||
case 59u: goto tr3; |
||||
case 61u: goto tr0; |
||||
case 91u: goto tr4; |
||||
} |
||||
if ( (*p) < 9u ) { |
||||
if ( (*p) <= 8u ) |
||||
goto tr0; |
||||
} else if ( (*p) > 13u ) { |
||||
if ( 14u <= (*p) && (*p) <= 31u ) |
||||
goto tr0; |
||||
} else |
||||
goto tr1; |
||||
goto tr2; |
||||
case 0: |
||||
goto _out; |
||||
case 12: |
||||
goto tr0; |
||||
case 2: |
||||
switch( (*p) ) { |
||||
case 9u: goto tr6; |
||||
case 32u: goto tr6; |
||||
case 93u: goto tr5; |
||||
} |
||||
if ( (*p) <= 31u ) |
||||
goto tr5; |
||||
goto tr7; |
||||
case 3: |
||||
if ( (*p) == 93u ) |
||||
goto tr8; |
||||
if ( (*p) > 8u ) { |
||||
if ( 10u <= (*p) && (*p) <= 31u ) |
||||
goto tr5; |
||||
} else |
||||
goto tr5; |
||||
goto tr7; |
||||
case 13: |
||||
goto tr5; |
||||
case 4: |
||||
switch( (*p) ) { |
||||
case 10u: goto tr10; |
||||
case 58u: goto tr11; |
||||
case 61u: goto tr11; |
||||
} |
||||
goto tr9; |
||||
case 5: |
||||
switch( (*p) ) { |
||||
case 9u: goto tr13; |
||||
case 10u: goto tr14; |
||||
case 13u: goto tr15; |
||||
case 32u: goto tr13; |
||||
} |
||||
goto tr12; |
||||
case 6: |
||||
switch( (*p) ) { |
||||
case 10u: goto tr14; |
||||
case 13u: goto tr15; |
||||
} |
||||
goto tr12; |
||||
case 14: |
||||
goto tr10; |
||||
case 7: |
||||
if ( (*p) == 10u ) |
||||
goto tr14; |
||||
goto tr10; |
||||
case 8: |
||||
switch( (*p) ) { |
||||
case 10u: goto tr17; |
||||
case 13u: goto tr18; |
||||
} |
||||
goto tr16; |
||||
case 15: |
||||
goto tr19; |
||||
case 9: |
||||
if ( (*p) == 10u ) |
||||
goto tr17; |
||||
goto tr19; |
||||
case 10: |
||||
switch( (*p) ) { |
||||
case 10u: goto tr21; |
||||
case 13u: goto tr22; |
||||
} |
||||
goto tr20; |
||||
case 16: |
||||
goto tr23; |
||||
case 11: |
||||
if ( (*p) == 10u ) |
||||
goto tr21; |
||||
goto tr23; |
||||
} |
||||
|
||||
tr23: cs = 0; goto _again; |
||||
tr0: cs = 0; goto f0; |
||||
tr5: cs = 0; goto f4; |
||||
tr10: cs = 0; goto f7; |
||||
tr19: cs = 0; goto f11; |
||||
tr1: cs = 1; goto _again; |
||||
tr6: cs = 2; goto _again; |
||||
tr7: cs = 3; goto f5; |
||||
tr9: cs = 4; goto f8; |
||||
tr13: cs = 5; goto _again; |
||||
tr11: cs = 5; goto f9; |
||||
tr12: cs = 6; goto f10; |
||||
tr15: cs = 7; goto _again; |
||||
tr16: cs = 8; goto _again; |
||||
tr18: cs = 9; goto _again; |
||||
tr20: cs = 10; goto _again; |
||||
tr22: cs = 11; goto _again; |
||||
tr2: cs = 12; goto f1; |
||||
tr3: cs = 12; goto f2; |
||||
tr4: cs = 12; goto f3; |
||||
tr8: cs = 13; goto f6; |
||||
tr14: cs = 14; goto f10; |
||||
tr17: cs = 15; goto f12; |
||||
tr21: cs = 16; goto f13; |
||||
|
||||
f5: _acts = _ini_actions + 1; goto execFuncs; |
||||
f6: _acts = _ini_actions + 3; goto execFuncs; |
||||
f4: _acts = _ini_actions + 5; goto execFuncs; |
||||
f1: _acts = _ini_actions + 7; goto execFuncs; |
||||
f8: _acts = _ini_actions + 9; goto execFuncs; |
||||
f9: _acts = _ini_actions + 11; goto execFuncs; |
||||
f10: _acts = _ini_actions + 13; goto execFuncs; |
||||
f7: _acts = _ini_actions + 15; goto execFuncs; |
||||
f12: _acts = _ini_actions + 17; goto execFuncs; |
||||
f11: _acts = _ini_actions + 19; goto execFuncs; |
||||
f13: _acts = _ini_actions + 21; goto execFuncs; |
||||
f0: _acts = _ini_actions + 23; goto execFuncs; |
||||
f3: _acts = _ini_actions + 25; goto execFuncs; |
||||
f2: _acts = _ini_actions + 28; goto execFuncs; |
||||
|
||||
execFuncs: |
||||
_nacts = *_acts++; |
||||
while ( _nacts-- > 0 ) { |
||||
switch ( *_acts++ ) { |
||||
case 0: |
||||
/* #line 130 "user/ini_parser.rl" */ |
||||
{ |
||||
buff_i = 0; |
||||
{cs = 2;goto _again;} |
||||
} |
||||
break; |
||||
case 1: |
||||
/* #line 135 "user/ini_parser.rl" */ |
||||
{ |
||||
if (buff_i >= INI_KEY_MAX) { |
||||
ini_parser_error("Section name too long"); |
||||
{cs = 10;goto _again;} |
||||
} |
||||
keybuf[buff_i++] = (*p); |
||||
} |
||||
break; |
||||
case 2: |
||||
/* #line 143 "user/ini_parser.rl" */ |
||||
{ |
||||
// we need a separate buffer for the result, otherwise a failed
|
||||
// partial parse would corrupt the section string
|
||||
rtrim_buf(keybuf, buff_i); |
||||
for (i = 0; (c = keybuf[i]) != 0; i++) secbuf[i] = c; |
||||
secbuf[i] = 0; |
||||
{cs = 1;goto _again;} |
||||
} |
||||
break; |
||||
case 3: |
||||
/* #line 155 "user/ini_parser.rl" */ |
||||
{ |
||||
ini_parser_error("Syntax error in [section]"); |
||||
if((*p) == '\n') {cs = 1;goto _again;} else {cs = 10;goto _again;} |
||||
} |
||||
break; |
||||
case 4: |
||||
/* #line 162 "user/ini_parser.rl" */ |
||||
{ |
||||
buff_i = 0; |
||||
keybuf[buff_i++] = (*p); // add the first char
|
||||
{cs = 4;goto _again;} |
||||
} |
||||
break; |
||||
case 5: |
||||
/* #line 168 "user/ini_parser.rl" */ |
||||
{ |
||||
if (buff_i >= INI_KEY_MAX) { |
||||
ini_parser_error("Key too long"); |
||||
{cs = 10;goto _again;} |
||||
} |
||||
keybuf[buff_i++] = (*p); |
||||
} |
||||
break; |
||||
case 6: |
||||
/* #line 176 "user/ini_parser.rl" */ |
||||
{ |
||||
rtrim_buf(keybuf, buff_i); |
||||
|
||||
// --- Value begin ---
|
||||
buff_i = 0; |
||||
value_quote = 0; |
||||
value_nextesc = false; |
||||
} |
||||
break; |
||||
case 7: |
||||
/* #line 185 "user/ini_parser.rl" */ |
||||
{ |
||||
isnl = ((*p) == '\r' || (*p) == '\n'); |
||||
isquot = ((*p) == '\'' || (*p) == '"'); |
||||
|
||||
// detect our starting quote
|
||||
if (isquot && !value_nextesc && buff_i == 0 && value_quote == 0) { |
||||
value_quote = (*p); |
||||
goto valueCharDone; |
||||
} |
||||
|
||||
if (buff_i >= INI_VALUE_MAX) { |
||||
ini_parser_error("Value too long"); |
||||
{cs = 10;goto _again;} |
||||
} |
||||
|
||||
// end of string - clean up and report
|
||||
if ((!value_nextesc && (*p) == value_quote) || isnl) { |
||||
if (isnl && value_quote) { |
||||
ini_parser_error("Unterminated string"); |
||||
{cs = 1;goto _again;} |
||||
} |
||||
|
||||
// unquoted: trim from the end
|
||||
if (!value_quote) { |
||||
rtrim_buf(valbuf, buff_i); |
||||
} else { |
||||
valbuf[buff_i] = 0; |
||||
} |
||||
|
||||
if (keyCallback) { |
||||
keyCallback(secbuf, keybuf, valbuf, userdata); |
||||
} |
||||
|
||||
// we don't want to discard to eol if the string was terminated by eol
|
||||
// - would delete the next line
|
||||
|
||||
if (isnl) {cs = 1;goto _again;} else {cs = 10;goto _again;} |
||||
} |
||||
|
||||
c = (*p); |
||||
// escape...
|
||||
if (value_nextesc) { |
||||
if ((*p) == 'n') c = '\n'; |
||||
else if ((*p) == 'r') c = '\r'; |
||||
else if ((*p) == 't') c = '\t'; |
||||
else if ((*p) == 'e') c = '\033'; |
||||
} |
||||
|
||||
// collecting characters...
|
||||
if (value_nextesc || (*p) != '\\') { // is quoted, or is not a quoting backslash - literal character
|
||||
valbuf[buff_i++] = c; |
||||
} |
||||
|
||||
value_nextesc = (!value_nextesc && (*p) == '\\'); |
||||
valueCharDone:; |
||||
} |
||||
break; |
||||
case 8: |
||||
/* #line 247 "user/ini_parser.rl" */ |
||||
{ |
||||
ini_parser_error("Syntax error in key=value"); |
||||
if((*p) == '\n') {cs = 1;goto _again;} else {cs = 10;goto _again;} |
||||
} |
||||
break; |
||||
case 9: |
||||
/* #line 257 "user/ini_parser.rl" */ |
||||
{ {cs = 1;goto _again;} } |
||||
break; |
||||
case 10: |
||||
/* #line 258 "user/ini_parser.rl" */ |
||||
{ |
||||
ini_parser_error("Syntax error in comment"); |
||||
if((*p) == '\n') {cs = 1;goto _again;} else {cs = 10;goto _again;} |
||||
} |
||||
break; |
||||
case 11: |
||||
/* #line 265 "user/ini_parser.rl" */ |
||||
{ {cs = 1;goto _again;} } |
||||
break; |
||||
case 12: |
||||
/* #line 273 "user/ini_parser.rl" */ |
||||
{ {cs = 8;goto _again;} } |
||||
break; |
||||
case 13: |
||||
/* #line 276 "user/ini_parser.rl" */ |
||||
{ |
||||
ini_parser_error("Syntax error in root"); |
||||
{cs = 10;goto _again;} |
||||
} |
||||
break; |
||||
/* #line 458 "user/ini_parser.c" */ |
||||
} |
||||
} |
||||
goto _again; |
||||
|
||||
_again: |
||||
if ( cs == 0 ) |
||||
goto _out; |
||||
if ( ++p != pe ) |
||||
goto _resume; |
||||
_test_eof: {} |
||||
if ( p == eof ) |
||||
{ |
||||
const char *__acts = _ini_actions + _ini_eof_actions[cs]; |
||||
unsigned int __nacts = (unsigned int) *__acts++; |
||||
while ( __nacts-- > 0 ) { |
||||
switch ( *__acts++ ) { |
||||
case 3: |
||||
/* #line 155 "user/ini_parser.rl" */ |
||||
{ |
||||
ini_parser_error("Syntax error in [section]"); |
||||
if((*p) == '\n') {cs = 1; if ( p == pe ) |
||||
goto _test_eof; |
||||
goto _again;} else {cs = 10; if ( p == pe ) |
||||
goto _test_eof; |
||||
goto _again;} |
||||
} |
||||
break; |
||||
case 8: |
||||
/* #line 247 "user/ini_parser.rl" */ |
||||
{ |
||||
ini_parser_error("Syntax error in key=value"); |
||||
if((*p) == '\n') {cs = 1; if ( p == pe ) |
||||
goto _test_eof; |
||||
goto _again;} else {cs = 10; if ( p == pe ) |
||||
goto _test_eof; |
||||
goto _again;} |
||||
} |
||||
break; |
||||
case 10: |
||||
/* #line 258 "user/ini_parser.rl" */ |
||||
{ |
||||
ini_parser_error("Syntax error in comment"); |
||||
if((*p) == '\n') {cs = 1; if ( p == pe ) |
||||
goto _test_eof; |
||||
goto _again;} else {cs = 10; if ( p == pe ) |
||||
goto _test_eof; |
||||
goto _again;} |
||||
} |
||||
break; |
||||
case 13: |
||||
/* #line 276 "user/ini_parser.rl" */ |
||||
{ |
||||
ini_parser_error("Syntax error in root"); |
||||
{cs = 10; if ( p == pe ) |
||||
goto _test_eof; |
||||
goto _again;} |
||||
} |
||||
break; |
||||
/* #line 517 "user/ini_parser.c" */ |
||||
} |
||||
} |
||||
} |
||||
|
||||
_out: {} |
||||
} |
||||
|
||||
/* #line 283 "user/ini_parser.rl" */ |
||||
|
||||
} |
@ -0,0 +1,65 @@ |
||||
#ifndef INIPARSE_STREAM_H |
||||
#define INIPARSE_STREAM_H |
||||
|
||||
#include <esp8266.h> |
||||
|
||||
#ifdef DEBUG_INI |
||||
#define ini_error(fmt, ...) error("[INI] "#fmt, ##__VA_ARGS__) |
||||
#else |
||||
#define ini_error(fmt, ...) |
||||
#endif |
||||
|
||||
// buffer sizes
|
||||
#define INI_KEY_MAX 64 |
||||
#define INI_VALUE_MAX 256 |
||||
|
||||
/**
|
||||
* INI parser callback, called for each found key-value pair. |
||||
* |
||||
* @param section - current section, empty string for global keys |
||||
* @param key - found key (trimmed of whitespace) |
||||
* @param value - value, trimmed of quotes or whitespace |
||||
* @param userData - opaque user data pointer, general purpose |
||||
*/ |
||||
typedef void (*IniParserCallback)(const char *section, const char *key, const char *value, void *userData); |
||||
|
||||
/**
|
||||
* Begin parsing a stream |
||||
* |
||||
* @param callback - key callback to assign |
||||
* @param userData - optional user data that willb e passed to the callback |
||||
*/ |
||||
void ini_parse_begin(IniParserCallback callback, void *userData); |
||||
|
||||
/**
|
||||
* End parse stream. |
||||
* Flushes what remains in the buffer and removes callback. |
||||
* |
||||
* @returns userData or NULL if none |
||||
*/ |
||||
void* ini_parse_end(void); |
||||
|
||||
/**
|
||||
* Parse a string (needn't be complete line or file) |
||||
* |
||||
* @param data - string to parse |
||||
* @param len - string length (0 = use strlen) |
||||
*/ |
||||
void ini_parse(const char *data, size_t len); |
||||
|
||||
/**
|
||||
* Parse a complete file loaded to string |
||||
* |
||||
* @param text - entire file as string |
||||
* @param len - file length (0 = use strlen) |
||||
* @param callback - key callback |
||||
* @param userData - optional user data for key callback |
||||
*/ |
||||
void ini_parse_file(const char *text, size_t len, IniParserCallback callback, void *userData); |
||||
|
||||
/**
|
||||
* Explicitly reset the parser |
||||
*/ |
||||
void ini_parse_reset(void); |
||||
|
||||
#endif // INIPARSE_STREAM_H
|
@ -0,0 +1,284 @@ |
||||
|
||||
/* Ragel constants block */ |
||||
#include "ini_parser.h" |
||||
|
||||
// Ragel setup |
||||
%%{ |
||||
machine ini; |
||||
write data; |
||||
alphtype unsigned char; |
||||
}%% |
||||
|
||||
// Persistent state |
||||
static int8_t cs = -1; //!< Ragel's Current State variable |
||||
static uint32_t buff_i = 0; //!< Write pointer for the buffers |
||||
static char value_quote = 0; //!< Quote character of the currently collected value |
||||
static bool value_nextesc = false; //!< Next character is escaped, trated specially, and if quote, as literal quote character |
||||
static IniParserCallback keyCallback = NULL; //!< Currently assigned callback |
||||
static void *userdata = NULL; //!< Currently assigned user data for the callback |
||||
|
||||
// Buffers |
||||
static char keybuf[INI_KEY_MAX]; |
||||
static char secbuf[INI_KEY_MAX]; |
||||
static char valbuf[INI_VALUE_MAX]; |
||||
|
||||
// See header for doxygen! |
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
ini_parse_reset_partial(void) |
||||
{ |
||||
buff_i = 0; |
||||
value_quote = 0; |
||||
value_nextesc = false; |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
ini_parse_reset(void) |
||||
{ |
||||
ini_parse_reset_partial(); |
||||
keybuf[0] = secbuf[0] = valbuf[0] = 0; |
||||
%% write init; |
||||
} |
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
ini_parser_error(const char* msg) |
||||
{ |
||||
ini_error("Parser error: %s", msg); |
||||
ini_parse_reset_partial(); |
||||
} |
||||
|
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
ini_parse_begin(IniParserCallback callback, void *userData) |
||||
{ |
||||
keyCallback = callback; |
||||
userdata = userData; |
||||
ini_parse_reset(); |
||||
} |
||||
|
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
*ini_parse_end(void) |
||||
{ |
||||
ini_parse("\n", 1); |
||||
if (keyCallback) { |
||||
keyCallback = NULL; |
||||
} |
||||
|
||||
void *ud = userdata; |
||||
userdata = NULL; |
||||
return ud; |
||||
} |
||||
|
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
ini_parse_file(const char *text, size_t len, IniParserCallback callback, void *userData) |
||||
{ |
||||
ini_parse_begin(callback, userData); |
||||
ini_parse(text, len); |
||||
ini_parse_end(); |
||||
} |
||||
|
||||
static void ICACHE_FLASH_ATTR |
||||
rtrim_buf(char *buf, int32_t end) |
||||
{ |
||||
if (end > 0) { |
||||
while ((uint8_t)buf[--end] < 33); |
||||
end++; // go past the last character |
||||
} |
||||
|
||||
buf[end] = 0; |
||||
} |
||||
|
||||
|
||||
void ICACHE_FLASH_ATTR |
||||
ini_parse(const char *newstr, size_t len) |
||||
{ |
||||
int32_t i; |
||||
char c; |
||||
bool isnl; |
||||
bool isquot; |
||||
|
||||
// Load new data to Ragel vars |
||||
const uint8_t *p; |
||||
const uint8_t *eof; |
||||
const uint8_t *pe; |
||||
|
||||
if (len == 0) while(newstr[++len] != 0); // alternative to strlen |
||||
|
||||
p = (const uint8_t *) newstr; |
||||
eof = NULL; |
||||
pe = (const uint8_t *) (newstr + len); |
||||
|
||||
// Init Ragel on the first run |
||||
if (cs == -1) { |
||||
ini_parse_reset(); |
||||
} |
||||
|
||||
// The parser |
||||
%%{ |
||||
#/ * |
||||
ispace = [ \t]; # inline space |
||||
wchar = any - 0..8 - 10..31; |
||||
#apos = '\''; |
||||
#quot = '\"'; |
||||
nonl = [^\r\n]; |
||||
nl = '\r'? '\n'; |
||||
|
||||
# ---- [SECTION] ---- |
||||
|
||||
action sectionStart { |
||||
buff_i = 0; |
||||
fgoto section; |
||||
} |
||||
|
||||
action sectionChar { |
||||
if (buff_i >= INI_KEY_MAX) { |
||||
ini_parser_error("Section name too long"); |
||||
fgoto discard2eol; |
||||
} |
||||
keybuf[buff_i++] = fc; |
||||
} |
||||
|
||||
action sectionEnd { |
||||
// we need a separate buffer for the result, otherwise a failed |
||||
// partial parse would corrupt the section string |
||||
rtrim_buf(keybuf, buff_i); |
||||
for (i = 0; (c = keybuf[i]) != 0; i++) secbuf[i] = c; |
||||
secbuf[i] = 0; |
||||
fgoto main; |
||||
} |
||||
|
||||
section := |
||||
( |
||||
ispace* <: ((wchar - ']')+ @sectionChar) ']' @sectionEnd |
||||
) $!{ |
||||
ini_parser_error("Syntax error in [section]"); |
||||
if(fc == '\n') fgoto main; else fgoto discard2eol; |
||||
}; |
||||
|
||||
# ---- KEY=VALUE ---- |
||||
|
||||
action keyStart { |
||||
buff_i = 0; |
||||
keybuf[buff_i++] = fc; // add the first char |
||||
fgoto keyvalue; |
||||
} |
||||
|
||||
action keyChar { |
||||
if (buff_i >= INI_KEY_MAX) { |
||||
ini_parser_error("Key too long"); |
||||
fgoto discard2eol; |
||||
} |
||||
keybuf[buff_i++] = fc; |
||||
} |
||||
|
||||
action keyEnd { |
||||
rtrim_buf(keybuf, buff_i); |
||||
|
||||
// --- Value begin --- |
||||
buff_i = 0; |
||||
value_quote = 0; |
||||
value_nextesc = false; |
||||
} |
||||
|
||||
action valueChar { |
||||
isnl = (fc == '\r' || fc == '\n'); |
||||
isquot = (fc == '\'' || fc == '"'); |
||||
|
||||
// detect our starting quote |
||||
if (isquot && !value_nextesc && buff_i == 0 && value_quote == 0) { |
||||
value_quote = fc; |
||||
goto valueCharDone; |
||||
} |
||||
|
||||
if (buff_i >= INI_VALUE_MAX) { |
||||
ini_parser_error("Value too long"); |
||||
fgoto discard2eol; |
||||
} |
||||
|
||||
// end of string - clean up and report |
||||
if ((!value_nextesc && fc == value_quote) || isnl) { |
||||
if (isnl && value_quote) { |
||||
ini_parser_error("Unterminated string"); |
||||
fgoto main; |
||||
} |
||||
|
||||
// unquoted: trim from the end |
||||
if (!value_quote) { |
||||
rtrim_buf(valbuf, buff_i); |
||||
} else { |
||||
valbuf[buff_i] = 0; |
||||
} |
||||
|
||||
if (keyCallback) { |
||||
keyCallback(secbuf, keybuf, valbuf, userdata); |
||||
} |
||||
|
||||
// we don't want to discard to eol if the string was terminated by eol |
||||
// - would delete the next line |
||||
|
||||
if (isnl) fgoto main; else fgoto discard2eol; |
||||
} |
||||
|
||||
c = fc; |
||||
// escape... |
||||
if (value_nextesc) { |
||||
if (fc == 'n') c = '\n'; |
||||
else if (fc == 'r') c = '\r'; |
||||
else if (fc == 't') c = '\t'; |
||||
else if (fc == 'e') c = '\033'; |
||||
} |
||||
|
||||
// collecting characters... |
||||
if (value_nextesc || fc != '\\') { // is quoted, or is not a quoting backslash - literal character |
||||
valbuf[buff_i++] = c; |
||||
} |
||||
|
||||
value_nextesc = (!value_nextesc && fc == '\\'); |
||||
valueCharDone:; |
||||
} |
||||
|
||||
# use * for key, first char is already consumed. |
||||
keyvalue := |
||||
( |
||||
([^\n=:]* @keyChar %keyEnd) |
||||
[=:] ispace* <: nonl* @valueChar nl @valueChar |
||||
) $!{ |
||||
ini_parser_error("Syntax error in key=value"); |
||||
if(fc == '\n') fgoto main; else fgoto discard2eol; |
||||
}; |
||||
|
||||
# ---- COMMENT ---- |
||||
|
||||
comment := |
||||
( |
||||
nonl* nl |
||||
@{ fgoto main; } |
||||
) $!{ |
||||
ini_parser_error("Syntax error in comment"); |
||||
if(fc == '\n') fgoto main; else fgoto discard2eol; |
||||
}; |
||||
|
||||
# ---- CLEANUP ---- |
||||
|
||||
discard2eol := nonl* nl @{ fgoto main; }; |
||||
|
||||
# ---- ROOT ---- |
||||
|
||||
main := |
||||
(space* |
||||
( |
||||
'[' @sectionStart | |
||||
[#;] @{ fgoto comment; } | |
||||
(wchar - [\t =:]) @keyStart |
||||
) |
||||
) $!{ |
||||
ini_parser_error("Syntax error in root"); |
||||
fgoto discard2eol; |
||||
}; |
||||
|
||||
write exec; |
||||
#*/ |
||||
}%% |
||||
} |
Loading…
Reference in new issue