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