text: #include "c_types.h"
  #include "mem.h"
  #include "osapi.h"
  #include "lang.h"
  #include "global.h"
  
  #ifdef SCRIPTED
  
  #ifdef NTP
  #include "ntp.h"
  #endif
  
  #ifdef GPIO
  #include "easygpio.h"
  #ifdef GPIO_PWM
  #include "pwm.h"
  #endif
  #endif
  
  #ifdef ADC
  #include "adc.h"
  #endif
  
  #ifdef JSON_PARSE
  #include "json_path.h"
  #endif
  
  #define lang_debug	//os_printf
  
  #define lang_log(...) 	{if (lang_logging){char log_buffer[256]; os_sprintf (log_buffer, "%s: ", get_timestr()); con_print(log_buffer); os_sprintf (log_buffer, __VA_ARGS__); con_print(log_buffer);}}
  //#define lang_log	//os_printf
  
  static char EOT[] = "end of text";
  #define len_check(x) \
  if (interpreter_status==SYNTAX_CHECK && next_token+(x) >= max_token) \
    return syntax_error(next_token+(x), EOT)
  #define syn_chk (interpreter_status==SYNTAX_CHECK)
  
  typedef enum {INVALID = 0, HAPPENED, NOT_HAPPENED, UNDEFINED} Alarm_State;
  typedef struct _timestamp_entry_t {
      uint8_t ts[9];
      Alarm_State state;
  } timestamp_entry_t;
  
  #ifdef GPIO
  typedef struct _gpio_entry_t {
      os_timer_t inttimer;
      uint8_t no;
      bool val;
  } gpio_entry_t;
  static gpio_entry_t gpios[MAX_GPIOS];
  int gpio_counter;
  
  #ifdef GPIO_PWM
  static uint8_t pwm_channels[PWM_MAX_CHANNELS];
  int pwm_counter;
  #endif
  #endif
  
  bool lang_logging = false;
  char **my_token;
  int max_token;
  bool script_enabled = false;
  bool in_topic_statement;
  Interpreter_Status interpreter_status;
  char *interpreter_topic;
  char *interpreter_data;
  int interpreter_data_len;
  int interpreter_timer;
  int interpreter_timestamp;
  bool in_serial_statement;
  char *interpreter_serial_data;
  int interpreter_serial_data_len;
  #ifdef GPIO
  bool in_gpio_statement;
  int interpreter_gpio;
  int interpreter_gpioval;
  #endif
  #ifdef HTTPC
  bool in_http_statement;
  int interpreter_http_status;
  char *interpreter_http_path;
  char *interpreter_http_hostname;
  
  void interpreter_http_reply(char *hostname, char *path, char *response_body, int http_status, char *response_headers, int body_size);
  #endif
  
  static os_timer_t timers[MAX_TIMERS];
  var_entry_t vars[MAX_VARS];
  static timestamp_entry_t timestamps[MAX_TIMESTAMPS];
  
  var_entry_t ICACHE_FLASH_ATTR *find_var(const uint8_t *name, var_entry_t **free_var) {
      int i;
  
      *free_var = NULL;
      for (i = 0; i<MAX_VARS; i++) {
  	if (!vars[i].free) {
  	    if (os_strncmp(name, vars[i].name, 14) == 0) {
  		lang_debug("var %s found at %d\r\n", vars[i].name, i);
  		return &vars[i];
  	    }
  	} else {
  	    if (*free_var == NULL)
  		*free_var = &vars[i];
  	}
      }
      return NULL;
  }
  
  static void ICACHE_FLASH_ATTR lang_timers_timeout(void *arg) {
  
      interpreter_timer = (int)arg;
      os_timer_disarm(&timers[interpreter_timer]);
      if (!script_enabled)
  	return;
  
      lang_debug("timer %d expired\r\n", interpreter_timer + 1);
  
      interpreter_topic = interpreter_data = "";
      interpreter_data_len = 0;
      interpreter_status = TIMER;
      parse_statement(0);
  }
  
  void ICACHE_FLASH_ATTR check_timestamps(uint8_t * curr_time) {
      int i;
  
      if (!script_enabled)
  	return;
  
      for (i = 0; i < MAX_TIMESTAMPS; i++) {
  	if (timestamps[i].state == INVALID)
  	    continue;
  
  	if (os_strcmp(curr_time, timestamps[i].ts) >= 0) {
  	    if (timestamps[i].state == UNDEFINED) {
  		timestamps[i].state = HAPPENED;
  		continue;
  	    }
  	    if (timestamps[i].state == HAPPENED)
  		continue;
  	    lang_debug("timerstamp %s happened\r\n", timestamps[i].ts);
  
  	    interpreter_topic = interpreter_data = "";
  	    interpreter_data_len = 0;
  	    interpreter_status = ALARM;
  	    interpreter_timestamp = i;
  	    parse_statement(0);
  	    timestamps[i].state = HAPPENED;
  	} else {
  	    timestamps[i].state = NOT_HAPPENED;
  	}
      }
  }
  
  #ifdef GPIO
  // GPIO int timer - debouncing
  void ICACHE_FLASH_ATTR inttimer_func(void *arg){
  
  	gpio_entry_t *my_gpio_entry = (gpio_entry_t *)arg;
  	interpreter_gpioval = easygpio_inputGet(my_gpio_entry->no);
  
  	//os_printf("GPIO %d %d\r\n", my_gpio_entry->no, interpreter_gpioval);
  
          // Reactivate interrupts for GPIO
          gpio_pin_intr_state_set(GPIO_ID_PIN(my_gpio_entry->no), GPIO_PIN_INTR_ANYEDGE);
  
  	if (script_enabled) {
  	    lang_debug("interpreter GPIO %d %d\r\n", my_gpio_entry->no, interpreter_gpioval);
  
  	    interpreter_status = GPIO_INT;
  	    interpreter_topic = interpreter_data = "";
  	    interpreter_data_len = 0;
  	    interpreter_gpio = my_gpio_entry->no;
  	    my_gpio_entry->val = interpreter_gpioval;
  	    parse_statement(0);
  	}
  }
  
  // Interrupt handler - this function will be executed on any edge of a GPIO
  LOCAL void gpio_intr_handler(void *arg)
  {
      int i;
  
      uint32 gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS);
      for (i = 0; i < gpio_counter; i++) {
  	if (gpio_status & BIT(gpios[i].no)) {
  
  	    // Disable interrupt for GPIO
  	    gpio_pin_intr_state_set(GPIO_ID_PIN(gpios[i].no), GPIO_PIN_INTR_DISABLE);
  
  	    // Clear interrupt status for GPIO
  	    GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, gpio_status & BIT(gpios[i].no));
  
  	    // Start the timer
  	    os_timer_setfn(&gpios[i].inttimer, inttimer_func, &gpios[i]);
  	    os_timer_arm(&gpios[i].inttimer, 50, 0); 
  	}
      }
  }
  
  void ICACHE_FLASH_ATTR init_gpios() {
      int i;
  
      if (!script_enabled)
  	return;
  
      for (i = 0; i < gpio_counter; i++) {
  	gpio_pin_intr_state_set(GPIO_ID_PIN(gpios[i].no), GPIO_PIN_INTR_ANYEDGE);
      }
  }
  
  void ICACHE_FLASH_ATTR stop_gpios() {
      int i;
  
      for (i = 0; i < gpio_counter; i++) {
  	gpio_pin_intr_state_set(GPIO_ID_PIN(gpios[i].no), GPIO_PIN_INTR_DISABLE);
      }
  }
  
  #ifdef GPIO_PWM
  int32_t ICACHE_FLASH_ATTR pwm_channel_from_pin(int pin) {
  
      int32_t i;
      for (i = 0; i < pwm_counter; i++) {
  	if (pin == pwm_channels[i])
  	    return i;
      }
      return -1;
  }
  
  void ICACHE_FLASH_ATTR init_pwm() {
      uint32 io_info[pwm_counter][3];
      int32_t i;
  
      if (!script_enabled || pwm_counter == 0)
  	return;
  
      for (i = 0; i < pwm_counter; i++) {
  	io_info[i][2] = pwm_channels[i];
      }
  
      // initial duty: all off
      pwm_init(config.pwm_period, NULL, pwm_counter, io_info);
      pwm_start();
  }
  #endif /* GPIO_PWM */
  #endif /* GPIO */
  
  void ICACHE_FLASH_ATTR test_tokens(void) {
      int i;
  
      for (i = 0; i < max_token; i++) {
  	lang_debug("<%s>", my_token[i]);
      }
      lang_debug("\r\n");
  }
  
  int ICACHE_FLASH_ATTR text_into_tokens(char *str) {
      char *p, *q;
      int token_count = 0;
      bool in_token = false;
  
      // preprocessing
      lang_debug("lexxer preprocessing (prog_len: %d)\r\n", os_strlen(str));
  
      for (p = q = str; *p != 0; p++) {
  
  	if (*p == '\\') {
  	    // next char is quoted, copy that - skip this one
  	    if (*(p + 1) != 0)
  		*q++ = *++p;
  	} else if (*p == '"') {
  	    // string quotation
  	    if (in_token)
  		*q++ = 1; // end of string
  	    else
  		*q++ = *p; // start of string - copy "
  	    in_token = !in_token;
  	} else if (*p == '%' && !in_token) {
  	    // comment till eol
  	    for (; *p != 0; p++)
  		if (*p == '\n')
  		    break;
  	}  
  
  	else if (*p <= ' ' && !in_token) {
  	    // mark this as whitespace
  	    *q++ = 1;
  	} else if (*p == '|' && !in_token) {
  	    // mark this as concat
  	    *q++ = 2;
  	} else if (*p == '+' && !in_token) {
  	    // mark this as add
  	    *q++ = 3;
  	} else if (*p == '-' && !in_token) {
  	    // mark this as sub
  	    *q++ = 4;
  	} else if (*p == '*' && !in_token) {
  	    // mark this as mult
  	    *q++ = 5;
  	} else if (*p == '=' && !in_token) {
  	    // mark this as div
  	    *q++ = 6;
  	} else if (*p == '>' && !in_token) {
  	    // mark this as gt
  	    *q++ = 7;
  	} else if (*p == '(' && !in_token) {
  	    // mark this as bracket open
  	    *q++ = 8;
  	} else if (*p == ')' && !in_token) {
  	    // mark this as bracket close
  	    *q++ = 9;
  	} else if (*p == ',' && !in_token) {
  	    // mark this as colon
  	    *q++ = 10;
  	} else {
  	    *q++ = *p;
  	}
      }
      *q = 0;
  
      // eliminate double whitespace, detect operators and count tokens
      lang_debug("lexxer whitespaces\r\n");
  
      in_token = false;
      for (p = q = str; *p != 0; p++) {
  	if (*p < ' ') {
  	    // it is a whitespace or an operator
  	    if (*p == 1 && in_token) {
  		// it is a whitespace
  		*q++ = *p;
  		in_token = false;
  	    }
  	    if (*p > 1) {
  		// it is an operator
  		*q++ = *p;
  		token_count++;
  		in_token = false;
  	    }
  	} else {
  	    if (!in_token) {
  		token_count++;
  		in_token = true;
  	    }
  	    *q++ = *p;
  	}
      }
      *q = 0;
      lang_debug("found %d tokens\r\n", token_count);
  
      // resize (shrink) the mem needed for the script
      lang_debug("prog_len compact: %d\r\n", (q-str)+1);
      my_script = (char *)os_realloc(my_script, (q-str)+5);
      str = &my_script[4];
  
      my_token = (char **)os_malloc(token_count * sizeof(char *));
      if (my_token == 0)
  	return 0;
  
      // assign tokens
      lang_debug("lexxer tokenize\r\n");
  
      in_token = false;
      token_count = 0;
      for (p = str; *p != 0; p++) {
  	if (*p == 1) {
  	    *p = '\0';
  	    in_token = false;
  	} 
  	else if (*p == 2) {
  	    my_token[token_count++] = "|";
  	    *p = '\0';
  	    in_token = false;
  	}
  	else if (*p == 3) {
  	    my_token[token_count++] = "+";
  	    *p = '\0';
  	    in_token = false;
  	}
  	else if (*p == 4) {
  	    my_token[token_count++] = "-";
  	    *p = '\0';
  	    in_token = false;
  	}
  	else if (*p == 5) {
  	    my_token[token_count++] = "*";
  	    *p = '\0';
  	    in_token = false;
  	}
  	else if (*p == 6) {
  	    my_token[token_count++] = "=";
  	    *p = '\0';
  	    in_token = false;
  	}
  	else if (*p == 7) {
  	    my_token[token_count++] = ">";
  	    *p = '\0';
  	    in_token = false;
  	}
  	else if (*p == 8) {
  	    my_token[token_count++] = "(";
  	    *p = '\0';
  	    in_token = false;
  	}
  	else if (*p == 9) {
  	    my_token[token_count++] = ")";
  	    *p = '\0';
  	    in_token = false;
  	}
  	else if (*p == 10) {
  	    my_token[token_count++] = ",";
  	    *p = '\0';
  	    in_token = false;
  	}
  	else {
  	    if (!in_token) {
  		my_token[token_count++] = p;
  		in_token = true;
  	    }
  	}
      }
  
      max_token = token_count;
      return max_token;
  }
  
  void ICACHE_FLASH_ATTR free_tokens(void) {
      if (my_token != NULL)
  	os_free((uint32_t *) my_token);
      my_token = NULL;
  }
  
  bool ICACHE_FLASH_ATTR is_token(int i, char *s) {
      if (i >= max_token)
  	return false;
  //os_printf("cmp: %s %s\r\n", s, my_token[i]);
      return os_strcmp(my_token[i], s) == 0;
  }
  
  int ICACHE_FLASH_ATTR search_token(int i, char *s) {
      for (; i < max_token; i++)
  	if (is_token(i, s))
  	    return i;
      return max_token;
  }
  
  int ICACHE_FLASH_ATTR syntax_error(int i, char *message) {
      int j;
  
      os_sprintf(tmp_buffer, "Error (%s) at >>", message);
      for (j = i; j < i + 8 && j < max_token; j++) {
  	int pos = os_strlen(tmp_buffer);
  	if (sizeof(tmp_buffer) - pos - 2 > os_strlen(my_token[j])) {
  	    os_sprintf(tmp_buffer + pos, "%s ", my_token[j]);
  	}
      }
      return -1;
  }
  
  int ICACHE_FLASH_ATTR parse_statement(int next_token) {
      bool event_happened;
      int on_token;
  
      uint32_t start = system_get_time();
  
      while ((next_token = syn_chk ? next_token : search_token(next_token, "on")) < max_token) {
  
  	in_topic_statement = false;
  	in_serial_statement = false;
  #ifdef GPIO
  	in_gpio_statement = false;
  #endif
  #ifdef HTTPC
  	in_http_statement = false;
  #endif
  
  	if (is_token(next_token, "on")) {
  	    lang_debug("statement on\r\n");
  
  	    if ((next_token = parse_event(next_token + 1, &event_happened)) == -1)
  		return -1;
  	    if (!syn_chk && !event_happened)
  		continue;
  
  	    if (syn_chk && !is_token(next_token, "do"))
  		return syntax_error(next_token, "'do' expected");
  	    if ((next_token = parse_action(next_token + 1, event_happened)) == -1)
  		return -1;
  	} else if (is_token(next_token, "config")) {
  	    next_token += 3;
  	} else {
  	    return syntax_error(next_token, "'on' or 'config' expected");
  	}
      }
  
      loop_count++;
      lang_debug("Interpreter loop: %d us\r\n", (system_get_time()-start));
      if (interpreter_status == INIT)
  	loop_time = system_get_time()-start;
      else
  	loop_time = (loop_time * 7 + (system_get_time()-start)) / 8;
  
      return next_token;
  }
  
  int ICACHE_FLASH_ATTR parse_event(int next_token, bool * happend) {
      *happend = false;
  
      if (is_token(next_token, "init")) {
  	lang_debug("event init\r\n");
  
  	*happend = (interpreter_status == INIT);
  	if (*happend)
  	    lang_log("on init\r\n");
  	return next_token + 1;
      }
  
      if (is_token(next_token, "mqttconnect")) {
  	lang_debug("event mqttconnect\r\n");
  
  	*happend = (interpreter_status == MQTT_CLIENT_CONNECT);
  	if (*happend)
  	    lang_log("on mqttconnect\r\n");
  	return next_token + 1;
      }
  
      if (is_token(next_token, "wificonnect")) {
  	lang_debug("event wificonnect\r\n");
  
  	*happend = (interpreter_status == WIFI_CONNECT);
  	if (*happend)
  	    lang_log("on wificonnect\r\n");
  	return next_token + 1;
      }
  
      if (is_token(next_token, "wifidisconnect")) {
  	lang_debug("event wifidisconnect\r\n");
  
  	*happend = (interpreter_status == WIFI_DISCONNECT);
  	if (*happend)
  	    lang_log("on wifidisconnect\r\n");
  	return next_token + 1;
      }
  
      if (is_token(next_token, "topic")) {
  	char *topic;
  	int topic_len;
  	Value_Type topic_type;
  	int lr_token = next_token + 1;
  
  	lang_debug("event topic\r\n");
  	in_topic_statement = true;
  
  	len_check(2);
  	if ((next_token = parse_value(next_token + 2, &topic, &topic_len, &topic_type)) == -1)
  	    return -1;
  
  	if (is_token(lr_token, "remote")) {
  	    if (interpreter_status != TOPIC_REMOTE)
  		return next_token;
  	} else if (is_token(lr_token, "local")) {
  	    if (interpreter_status != TOPIC_LOCAL)
  		return next_token;
  	} else {
  	    return syntax_error(next_token + 1, "'local' or 'remote' expected");
  	}
  
  	*happend = Topics_matches(topic, true, interpreter_topic);
  
  	if (*happend)
  	    lang_log("on topic %s %s matched %s\r\n", my_token[lr_token],
  		      topic, interpreter_topic);
  
  	return next_token;
      }
  
      if (is_token(next_token, "timer")) {
  	lang_debug("event timer\r\n");
  
  	len_check(1);
  	uint32_t timer_no = atoi(my_token[next_token + 1]);
  	if (timer_no == 0 || timer_no > MAX_TIMERS)
  	    return syntax_error(next_token + 1, "invalid timer number");
  	if (interpreter_status == TIMER && interpreter_timer == --timer_no) {
  	    lang_log("on timer %s\r\n", my_token[next_token + 1]);
  	    *happend = true;
  	}
  	return next_token + 2;
      }
  
      if (is_token(next_token, "alarm")) {
  	lang_debug("event alarm\r\n");
  
  	len_check(1);
  	uint32_t timer_no = atoi(my_token[next_token + 1]);
  	if (timer_no == 0 || timer_no > MAX_TIMESTAMPS)
  	    return syntax_error(next_token + 1, "invalid alarm number");
  	if (interpreter_status == ALARM && interpreter_timestamp == --timer_no) {
  	    lang_log("on alarm %s\r\n", my_token[next_token + 1]);
  	    *happend = true;
  	}
  	return next_token + 2;
      }
  
      if (is_token(next_token, "serial")) {
  	lang_debug("event serial\r\n");
  	in_serial_statement = true;
  
  	*happend = (interpreter_status == SERIAL_INPUT);
  	if (*happend)
  	    lang_log("on serial\r\n");
  	return next_token + 1;
      }
  #ifdef GPIO
      if (is_token(next_token, "gpio_interrupt")) {
  	lang_debug("event gpio\r\n");
  
  	in_gpio_statement = true;
  	len_check(2);
  	uint32_t gpio_no = atoi(my_token[next_token + 1]);
  
  	if (syn_chk) {
  	    if (gpio_no > 16)
  		return syntax_error(next_token + 1, "invalid gpio number");
  #ifdef GPIO_PWM
  	    if (pwm_channel_from_pin(gpio_no) != -1)
  		return syntax_error(next_token, "pin defined as pwm before");	
  #endif
  	    if (!is_token(next_token+2, "pullup") && !is_token(next_token+2, "nopullup"))
  		return syntax_error(next_token + 2, "expected 'pullup' or 'nopullup'");
  	    int pullup = is_token(next_token+2, "pullup") ? EASYGPIO_PULLUP : EASYGPIO_NOPULL;
  	    if (gpio_counter >= MAX_GPIOS)
  		return syntax_error(next_token, "too many gpio_interrupt");
  	    gpios[gpio_counter].no = gpio_no;
  	    easygpio_pinMode(gpio_no, pullup, EASYGPIO_INPUT);
  	    easygpio_attachInterrupt(gpio_no, pullup, gpio_intr_handler, NULL);
  
  	    gpio_counter++;
  	}
  	if (interpreter_status == GPIO_INT && interpreter_gpio == gpio_no) {
  	    lang_log("on gpio_interrupt %s\r\n", my_token[next_token + 1]);
  	    *happend = true;
  	}
  	return next_token + 3;
      }
  #endif
  #ifdef HTTPC
      if (is_token(next_token, "http_response")) {
  	lang_debug("event http_response\r\n");
  	in_http_statement = true;
  
  	*happend = (interpreter_status == HTTP_RESPONSE);
  	if (*happend)
  	    lang_log("on http_response\r\n");
  	return next_token + 1;
      }
  #endif
      return syntax_error(next_token, "event spec (like 'topic') expected");
  }
  
  int ICACHE_FLASH_ATTR parse_action(int next_token, bool doit) {
  
      while (next_token < max_token && !is_token(next_token, "on")
  	   && !is_token(next_token, "config") && !is_token(next_token, "else")
  	   && !is_token(next_token, "endif") && !is_token(next_token, "done")) {
  	bool is_nl = false;
  
  	if (doit) {
  	    lang_debug("action %s\r\n", my_token[next_token]);
  	}
  	//os_printf("action %s %s\r\n", my_token[next_token], doit ? "do" : "ignore");
  
  	if ((is_nl = is_token(next_token, "println")) || is_token(next_token, "print")) {
  	    char *p_char;
  	    int p_len;
  	    Value_Type p_type;
  
  	    len_check(1);
  	    if ((next_token = parse_expression(next_token + 1, &p_char, &p_len, &p_type, doit)) == -1)
  		return -1;
  	    if (doit) {
  		con_print(p_char);
  		if (is_nl)
  		    con_print("\r\n");
  	    }
  	}
  
  	else if (is_token(next_token, "serial_out")) {
  	    char *p_char;
  	    int p_len;
  	    Value_Type p_type;
  
  	    len_check(1);
  	    if ((next_token = parse_expression(next_token + 1, &p_char, &p_len, &p_type, doit)) == -1)
  		return -1;
  	    if (doit) {
  		lang_log("serial_out '%s'\r\n", p_char);
  		serial_out(p_char);
  	    }
  	}
  
  	else if (is_token(next_token, "system")) {
  	    char *p_char;
  	    int p_len;
  	    Value_Type p_type;
  
  	    len_check(1);
  	    if ((next_token = parse_expression(next_token + 1, &p_char, &p_len, &p_type, doit)) == -1)
  		return -1;
  	    if (doit) {
  		lang_log("system '%s'\r\n", p_char);
  		do_command(p_char, "", "");
  	    }
  	}
  
  	else if (is_token(next_token, "publish")) {
  	    bool retained = false;
  	    char *data;
  	    int data_len;
  	    Value_Type data_type;
  	    char *topic;
  	    int topic_len;
  	    Value_Type topic_type;
  	    int lr_token = next_token + 1;
  
  	    len_check(3);
  	    if ((next_token = parse_value(next_token + 2, &topic, &topic_len, &topic_type)) == -1)
  		return -1;
  	    if ((next_token = parse_expression(next_token, &data, &data_len, &data_type, doit)) == -1)
  		return -1;
  	    if (next_token < max_token && is_token(next_token, "retained")) {
  		retained = true;
  		next_token++;
  	    }
  
  	    if (doit) {
  		if (topic_type != STRING_T || Topics_hasWildcards(topic)) {
  		    os_printf("invalid topic string\r\n");
  		    return next_token;
  		}
  	    }
  
  #ifdef MQTT_CLIENT
  	    if (is_token(lr_token, "remote")) {
  		if (doit && mqtt_connected) {
  		    if (data_type == STRING_T) {
  		    	lang_log("publish remote %s %s\r\n", topic, data);
  		    } else {
  			lang_log("publish remote %s binary (%d bytes)\r\n", topic, data_len);
  		    }
  		    MQTT_Publish(&mqttClient, topic, data, data_len, 0, retained);
  		}
  	    } else
  #endif
  	    if (is_token(lr_token, "local")) {
  		if (doit) {
  		    if (data_type == STRING_T) {
  		    	lang_log("publish local %s %s\r\n", topic, data);
  		    } else {
  			lang_log("publish local %s binary (%d bytes)\r\n", topic, data_len);
  		    }
  		    MQTT_local_publish(topic, data, data_len, 0, retained);
  		}
  	    } else {
  		return syntax_error(lr_token, "'local' or 'remote' expected");
  	    }
  	}
  
  	else if (is_token(next_token, "subscribe")) {
  	    char *topic;
  	    int topic_len;
  	    Value_Type topic_type;
  	    bool retval;
  	    int rl_token = next_token + 1;
  
  	    len_check(2);
  	    if ((next_token = parse_value(next_token + 2, &topic, &topic_len, &topic_type)) == -1)
  		return -1;
  #ifdef MQTT_CLIENT
  	    if (is_token(rl_token, "remote")) {
  		if (doit && mqtt_connected) {
  		    lang_log("subscribe remote %s\r\n", topic);
  		    retval = MQTT_Subscribe(&mqttClient, topic, 0);
  		}
  	    } else 
  #endif
  	    if (is_token(rl_token, "local")) {
  		if (doit) {
  		    lang_log("subscribe local %s\r\n", topic);
  		    retval = MQTT_local_subscribe(topic, 0);
  		}
  	    } else {
  		return syntax_error(next_token + 1, "'local' or 'remote' expected");
  	    }
  	}
  
  	else if (is_token(next_token, "unsubscribe")) {
  	    char *topic;
  	    int topic_len;
  	    Value_Type topic_type;
  	    bool retval;
  	    int rl_token = next_token + 1;
  
  	    len_check(2);
  	    if ((next_token = parse_value(next_token + 2, &topic, &topic_len, &topic_type)) == -1)
  		return -1;
  #ifdef MQTT_CLIENT
  	    if (is_token(rl_token, "remote")) {
  		if (doit && mqtt_connected) {
  		    lang_log("unsubscribe remote %s\r\n", topic);
  		    retval = MQTT_UnSubscribe(&mqttClient, topic);
  		}
  	    } else
  #endif
  	    if (is_token(rl_token, "local")) {
  		if (doit) {
  		    lang_log("subscribe local %s\r\n", topic);
  		    retval = MQTT_local_unsubscribe(topic);
  		}
  	    } else {
  		return syntax_error(next_token + 1, "'local' or 'remote' expected");
  	    }
  	}
  
  	else if (is_token(next_token, "if")) {
  	    uint32_t if_val = 0;
  	    char *if_char;
  	    int if_len;
  	    Value_Type if_type;
  	    int exp_token;
  
  	    len_check(3);
  	    exp_token = next_token + 1;
  	    if ((next_token = parse_expression(next_token + 1, &if_char, &if_len, &if_type, doit)) == -1)
  		return -1;
  	    if (syn_chk && !is_token(next_token, "then"))
  		return syntax_error(next_token, "'then' expected");
  
  	    if (doit) {
  		if_val = atoi(if_char);
  		if (if_val != 0)
  		    lang_log("if %s %s... (done) \r\n", my_token[exp_token++], my_token[exp_token]);
  	    }
  	    if ((next_token = parse_action(next_token + 1, doit && if_val != 0)) == -1)
  		return -1;
  	    if (is_token(next_token, "else")) {
  		if (doit && if_val == 0)
  		    lang_log("if %s %s... else (done) \r\n", my_token[exp_token++], my_token[exp_token]);
  		if ((next_token = parse_action(next_token + 1, doit && if_val == 0)) == -1)
  		    return -1;
  		if (syn_chk && !is_token(next_token - 1, "endif"))
  		    return syntax_error(next_token - 1, "'endif' expected");
  	    }
  	}
  
  
  	else if (is_token(next_token, "while")) {
  	    uint32_t if_val = 0;
  	    char *if_char;
  	    int if_len;
  	    Value_Type if_type;
  	    int exp_token;
  	    int repeat_token = next_token;
  
  	    len_check(3);
  	    exp_token = next_token + 1;
  	    if ((next_token = parse_expression(next_token + 1, &if_char, &if_len, &if_type, doit)) == -1)
  		return -1;
  	    if (syn_chk && !is_token(next_token, "do"))
  		return syntax_error(next_token, "'do' expected");
  
  	    if (doit) {
  		if_val = atoi(if_char);
  		if (if_val != 0)
  		    lang_log("while %s %s... (done) \r\n", my_token[exp_token++], my_token[exp_token]);
  	    }
  	    if ((next_token = parse_action(next_token + 1, doit && if_val != 0)) == -1)
  		return -1;
  	    if (syn_chk && !is_token(next_token - 1, "done")) {
  		return syntax_error(next_token - 1, "'done' expected");
  	    }
  	    if (!syn_chk && doit && if_val != 0) {
  		next_token = repeat_token;
  	    }
  	}
  
  
  	else if (is_token(next_token, "settimer")) {
  	    len_check(2);
  	    uint32_t timer_no = atoi(my_token[next_token + 1]);
  	    if (timer_no == 0 || timer_no > MAX_TIMERS)
  		return syntax_error(next_token + 1, "invalid timer number");
  
  	    uint32_t timer_val;
  	    char *timer_char;
  	    int timer_len;
  	    Value_Type timer_type;
  	    if ((next_token = parse_expression(next_token + 2, &timer_char, &timer_len, &timer_type, doit)) == -1)
  		return -1;
  
  	    if (doit) {
  		timer_val = atoi(timer_char);
  		lang_log("settimer %d %d\r\n", timer_no, timer_val);
  
  		timer_no--;
  		os_timer_disarm(&timers[timer_no]);
  		if (timer_val != 0) {
  		    os_timer_setfn(&timers[timer_no], (os_timer_func_t *) lang_timers_timeout, (void *)timer_no);
  		    os_timer_arm(&timers[timer_no], timer_val, 0);
  		}
  	    }
  	}
  
  	else if (is_token(next_token, "setalarm")) {
  	    len_check(2);
  	    uint32_t alarm_no = atoi(my_token[next_token + 1]);
  	    if (alarm_no == 0 || alarm_no > MAX_TIMESTAMPS)
  		return syntax_error(next_token + 1, "invalid alarm number");
  
  	    char *timer_char;
  	    int timer_len;
  	    Value_Type timer_type;
  	    if ((next_token = parse_expression(next_token + 2, &timer_char, &timer_len, &timer_type, doit)) == -1)
  		return -1;
  
  	    if (doit) {
  		lang_log("setalarm %d %d\r\n", alarm_no, timer_char);
  
  		alarm_no--;
  		os_strncpy(timestamps[alarm_no].ts, timer_char, 9);
  		timestamps[alarm_no].state = UNDEFINED;
  	    }
  	}
  
  	else if (is_token(next_token, "setvar")) {
  	    len_check(3);
  	    bool flash_var = false;
  	    uint32_t slot_no;
  	    var_entry_t *this_var, *free_var;
  	    uint8_t *var_id = my_token[next_token + 1];
  
  	    if (var_id[0] == '@') {
  		slot_no = atoi(&var_id[1]);
  		if (syn_chk && (slot_no == 0 || slot_no > MAX_FLASH_SLOTS))
  		    return syntax_error(next_token + 1, "invalid flash var number");
  		flash_var = true;
  	    }
  
  	    else if (var_id[0] == '$') {
  		if (syn_chk && var_id[1] == '\0')
  		    return syntax_error(next_token, "invalid var identifier");
  
  		this_var = find_var(&var_id[1], &free_var);
  		if (this_var == NULL) {
  		    if (free_var == NULL)
  			return syntax_error(next_token, "too many vars used");
  
  		    this_var = free_var;
  		    this_var->free = 0;
  		    os_strncpy(this_var->name, &var_id[1], 14);
  		    this_var->name[14] = '\0';
  		    this_var->data = (uint8_t *)os_malloc(DEFAULT_VAR_LEN);
  		    this_var->buffer_len = DEFAULT_VAR_LEN;
  		}
  	    } else {
  		return syntax_error(next_token, "invalid var identifier");
  	    }
  
  	    if (syn_chk && os_strcmp(my_token[next_token + 2], "=") != 0)
  		return syntax_error(next_token + 2, "'=' expected");
  
  	    char *var_data;
  	    int var_len;
  	    Value_Type var_type;
  	    if ((next_token = parse_expression(next_token + 3, &var_data, &var_len, &var_type, doit)) == -1)
  		return -1;
  
  	    if (doit) {
  		if (var_type == STRING_T) {
  		    lang_log("setvar %s = %s\r\n", var_id, var_data);
  		} else {
  		    lang_log("setvar %s = binary (%d bytes)\r\n", var_id, var_len);
  		}
  		lang_debug("setvar $%s\r\n", var_id);
  
  		if (flash_var) {
  		    if (var_len > FLASH_SLOT_LEN-1)
  			var_len = FLASH_SLOT_LEN-1;
  
  		    slot_no--;
  		    uint8_t slots[MAX_FLASH_SLOTS*FLASH_SLOT_LEN];
  		    blob_load(1, (uint32_t *)slots, sizeof(slots));
  		    os_memcpy(&slots[slot_no*FLASH_SLOT_LEN], var_data, var_len);
  		    slots[slot_no*FLASH_SLOT_LEN+var_len] = '\0';
  		    blob_save(1, (uint32_t *)slots, sizeof(slots));
  		} else {
  
  		    if (var_len > this_var->buffer_len - 1) {
  			os_free(this_var->data);
  			this_var->data = (uint8_t *)os_malloc(var_len+1);
  			this_var->buffer_len = var_len+1;
  			if (this_var->data == NULL) {
  			    os_printf("Out of mem for var $%s\r\n", this_var->name);
  			    this_var->data = (uint8_t *)os_malloc(DEFAULT_VAR_LEN);
  			    this_var->buffer_len = DEFAULT_VAR_LEN;
  			    return next_token;
  			}
  		    }
  		    os_memcpy(this_var->data, var_data, var_len);
  		    this_var->data[var_len] = '\0';
  		    this_var->data_len = var_len;
  		    this_var->data_type = var_type;
  		}
  	    }
  	}
  #ifdef HTTPC
  	else if (is_token(next_token, "http_get")) {
  	    len_check(1);
  
  	    char *url_data;
  	    int url_len;
  	    Value_Type url_type;
  	    if ((next_token = parse_expression(next_token + 1, &url_data, &url_len, &url_type, doit)) == -1)
  		return -1;
  
  	    if (doit) {
  		http_get(url_data, "", interpreter_http_reply);
  	    }
  	}
  
  	else if (is_token(next_token, "http_post")) {
  	    len_check(2);
  
  	    char *url_data;
  	    int url_len;
  	    Value_Type url_type;
  	    if ((next_token = parse_expression(next_token + 1, &url_data, &url_len, &url_type, doit)) == -1)
  		return -1;
  
  	    // Copy, in case it is in tmp_buffer
  	    if (!doit)
  		url_len = 0;
  	    char post_url[url_len+1];
  	    os_memcpy(post_url, url_data, url_len);
  	    post_url[url_len] = '\0';
  
  	    char *post_data;
  	    int post_len;
  	    Value_Type post_type;
  	    if ((next_token = parse_expression(next_token + 1, &post_data, &post_len, &post_type, doit)) == -1)
  		return -1;
  
  	    if (doit) {
  		http_post(post_url, post_data, "", interpreter_http_reply);
  	    }
  	}
  #endif
  #ifdef GPIO
  	else if (is_token(next_token, "gpio_pinmode")) {
  	    len_check(2);
  
  	    uint32_t gpio_no = atoi(my_token[next_token + 1]);
  	    if (gpio_no > 16)
  		return syntax_error(next_token + 1, "invalid gpio number");
  #ifdef GPIO_PWM
  	    if (syn_chk && pwm_channel_from_pin(gpio_no) != -1)
  		return syntax_error(next_token, "pin defined as pwm before");	
  #endif
  	    int pullup = EASYGPIO_NOPULL;
  	    int inout = EASYGPIO_OUTPUT;
  	    if (is_token(next_token+2, "input")) {
  		inout = EASYGPIO_INPUT;
  		if (is_token(next_token+3, "pullup")) {
  		    pullup = EASYGPIO_PULLUP;
  		    next_token++;
  		}
  	    } else if (syn_chk && !is_token(next_token+2, "output")) {
  		return syntax_error(next_token + 2, "expected 'input' or 'output'");	
  	    }
  
  	    if (doit) {
  		lang_log("gpio_pinmode %d %s\r\n", gpio_no, inout == EASYGPIO_INPUT ? "input" : "output");
  		easygpio_pinMode(gpio_no, pullup, inout);
  	    }
  	    next_token += 3;
  	}
  
  	else if (is_token(next_token, "gpio_out")) {
  	    len_check(2);
  
  	    uint32_t gpio_no = atoi(my_token[next_token + 1]);
  	    if (gpio_no > 16)
  		return syntax_error(next_token + 1, "invalid gpio number");
  
  	    char *gpio_data;
  	    int gpio_len;
  	    Value_Type gpio_type;
  	    if ((next_token = parse_expression(next_token + 2, &gpio_data, &gpio_len, &gpio_type, doit)) == -1)
  		return -1;
  
  	    if (doit) {
  		lang_log("gpio_out %d %d\r\n", gpio_no, atoi(gpio_data) != 0);
  		if (easygpio_pinMode(gpio_no, EASYGPIO_NOPULL, EASYGPIO_OUTPUT))
  		    easygpio_outputSet(gpio_no, atoi(gpio_data) != 0);
  	    }
  	}
  #ifdef GPIO_PWM
  	else if (is_token(next_token, "gpio_pwm")) {
  	    len_check(1);
  
  	    uint32_t gpio_no = atoi(my_token[next_token + 1]);
  	    if (gpio_no > 16)
  		return syntax_error(next_token + 1, "invalid gpio number");
  	    if (syn_chk && pwm_channel_from_pin(gpio_no) == -1) {
  		if (pwm_counter >= PWM_MAX_CHANNELS)
  		    return syntax_error(next_token, "too many pwm channels");
  		pwm_channels[pwm_counter] = gpio_no;
  		pwm_counter++;
  	    }
  
  	    char *pwm_data;
  	    int pwm_len;
  	    Value_Type pwm_type;
  	    if ((next_token = parse_expression(next_token + 2, &pwm_data, &pwm_len, &pwm_type, doit)) == -1)
  		return -1;
  
  	    uint32_t pwm_channel = pwm_channel_from_pin(gpio_no);
  
  	    if (doit && pwm_channel != -1) {
  		lang_log("gpio_pwm %d %s\r\n", gpio_no, pwm_data);
  		pwm_set_duty((atoi(pwm_data)*config.pwm_period)/1000, pwm_channel);
  		pwm_start();  
  	    }
  	}
  #endif
  #endif
  	else
  	    return syntax_error(next_token, "action command expected");
  
      }
  
      if (is_token(next_token, "endif") || is_token(next_token, "done"))
  	next_token++;
  
      return next_token;
  }
  
  bool ICACHE_FLASH_ATTR retained_cb(retained_entry *topic, void *user_data) {
      *(retained_entry **)user_data = topic;
      return true;
  }
  
  int ICACHE_FLASH_ATTR parse_expression(int next_token, char **data, int *data_len, Value_Type * data_type, bool doit) {
  
      if (is_token(next_token, "not")) {
  	lang_debug("expr not\r\n");
  
  	len_check(3);
  	if (syn_chk && !is_token(next_token+1, "("))
  	    return syntax_error(next_token, "expected '('");
  	if ((next_token = parse_expression(next_token + 2, data, data_len, data_type, doit)) == -1)
  	    return -1;
  	if (syn_chk && !is_token(next_token, ")"))
  	    return syntax_error(next_token, "expected ')'");
  
  	next_token++;
  	*data = atoi(*data) ? "0" : "1";
  	*data_len = 1;
  	*data_type = STRING_T;
      }
  
      else if (is_token(next_token, "retained_topic")) {
  	lang_debug("val retained_topic\r\n");
  
  	len_check(3);
  	if (syn_chk && !is_token(next_token+1, "("))
  	    return syntax_error(next_token, "expected '('");
  
  	char *topic_data;
  	int topic_data_len;
  	Value_Type topic_data_type;
  	// parse path string
  	if ((next_token = parse_expression(next_token + 2, &topic_data, &topic_data_len, &topic_data_type, doit)) == -1)
  	    return -1;
  
  	if (syn_chk && !is_token(next_token, ")"))
  	    return syntax_error(next_token, "expected ')'");
  	next_token += 1;
  
  	*data = "";
  	*data_len = 0;
  	*data_type = DATA_T;
  	if (doit) {
  	    retained_entry *retained_entry_p;
  	    if (find_retainedtopic(topic_data, retained_cb, &retained_entry_p)) {
  		*data_len = retained_entry_p->data_len > sizeof(tmp_buffer)-1? sizeof(tmp_buffer)-1 : retained_entry_p->data_len;
  		os_memcpy(tmp_buffer, retained_entry_p->data, *data_len);
  		tmp_buffer[*data_len] = '\0';
  		*data = tmp_buffer;
  	    }
  	}
      }
  
      else if (is_token(next_token, "eatwhite")) {
  	lang_debug("val eatwhite\r\n");
  
  	len_check(3);
  	if (syn_chk && !is_token(next_token+1, "("))
  	    return syntax_error(next_token+1, "expected '('");
  
  	char *str_data;
  	int str_data_len;
  	Value_Type str_data_type;
  	// parse path string
  	if ((next_token = parse_expression(next_token + 2, &str_data, &str_data_len, &str_data_type, doit)) == -1)
  	    return -1;
  	if (!doit)
  	    str_data_len = 0;
  	char str[str_data_len+1];
  	if (doit)
  	    os_strcpy(str, str_data);
  
  	if (syn_chk && !is_token(next_token, ")"))
  	    return syntax_error(next_token, "expected ')'");
  	next_token++;
  
  	if (doit) {
  	    int i, j;
  	
  	    for (i=0, j=0; i<str_data_len; i++){
  		if (!isspace(str[i])) {
  		    tmp_buffer[j] = str[i];
  		    j++;
  		}
  	    }
  	    tmp_buffer[j] = '\0';
  
  	    *data_len = j;
  	    *data = tmp_buffer;
  	    *data_type = STRING_T;
  	}
      }
  
      else if (is_token(next_token, "substr")) {
  	lang_debug("val substr\r\n");
  
  	len_check(7);
  	if (syn_chk && !is_token(next_token+1, "("))
  	    return syntax_error(next_token+1, "expected '('");
  
  	char *str_data;
  	int str_data_len;
  	Value_Type str_data_type;
  	// parse path string
  	if ((next_token = parse_expression(next_token + 2, &str_data, &str_data_len, &str_data_type, doit)) == -1)
  	    return -1;
  	if (!doit)
  	    str_data_len = 0;
  	char str[str_data_len+1];
  	if (doit)
  	    os_strcpy(str, str_data);
  
  	if (syn_chk && !is_token(next_token, ","))
  	    return syntax_error(next_token, "expected ','");
  	next_token++;
  
  	int16_t from = atoi(my_token[next_token]);
  	// if as string const
  	if (my_token[next_token][0] == '"')
  	    from = atoi(&my_token[next_token][1]);
  	next_token++;
  
  	if (syn_chk && !is_token(next_token, ","))
  	    return syntax_error(next_token, "expected ','");
  	next_token++;
  
  	uint16_t len = atoi(my_token[next_token]);
  	next_token++;
  
  	if (syn_chk && !is_token(next_token, ")"))
  	    return syntax_error(next_token, "expected ')'");
  	next_token++;
  
  	if (doit) {
  	    if (from < 0) {
  		from = str_data_len+from;
  		if (from < 0)
  		    from = 0;
  	    }
  
  	    if (len > sizeof(tmp_buffer)-1)
  		len = sizeof(tmp_buffer)-1;
  
  	    os_strncpy(tmp_buffer, &str[from], len);
  	    tmp_buffer[len] = '\0';
  
  	    *data_len = os_strlen(tmp_buffer);
  	    *data = tmp_buffer;
  	    *data_type = STRING_T;
  	}
      }
  
      else if (is_token(next_token, "csvstr")) {
  	lang_debug("val csvstr\r\n");
  
  	len_check(7);
  	if (syn_chk && !is_token(next_token+1, "("))
  	    return syntax_error(next_token+1, "expected '('");
  
  	char *str_data;
  	int str_data_len;
  	Value_Type str_data_type;
  	// parse path string
  	if ((next_token = parse_expression(next_token + 2, &str_data, &str_data_len, &str_data_type, doit)) == -1)
  	    return -1;
  	if (!doit)
  	    str_data_len = 0;
  	char str[str_data_len+1];
  	if (doit)
  	    os_strcpy(str, str_data);
  
  	if (syn_chk && !is_token(next_token, ","))
  	    return syntax_error(next_token, "expected ','");
  	next_token++;
  
  	int16_t num = atoi(my_token[next_token]);
  	next_token++;
  
  	if (syn_chk && !is_token(next_token, ","))
  	    return syntax_error(next_token, "expected ','");
  	next_token++;
  
  	uint8_t ch = my_token[next_token][0];
  	// if as string const
  	if (my_token[next_token][0] == '"')
  	    ch = my_token[next_token][1];
  	next_token++;
  
  	if (syn_chk && !is_token(next_token, ")"))
  	    return syntax_error(next_token, "expected ')'");
  	next_token++;
  
  	if (doit) {
  	    int i;
  	    char *p, *q;
  
  	    for (i=0, p=q=str; i<=num; p++) {
  		if (*p == ch || *p == '\0') {
  		    i++;
  		    if (i > num) {
  			break;
  		    }
  		    q=p+1;
  		}
  		if (*p == '\0')
  		    break;
  	    }
  
  	    if (i<=num) {
  		*data_len = 0;
  		*data = "";		
  	    } else {
  		uint16_t len = p-q;
  		if (len > sizeof(tmp_buffer)-1)
  		    len = sizeof(tmp_buffer)-1;
  		os_strncpy(tmp_buffer, q, len);
  		tmp_buffer[len] = '\0';
  
  		*data_len = len;
  		*data = tmp_buffer;
  	    }
  	    *data_type = STRING_T;
  	}
      }
  
      else if (is_token(next_token, "byte_val")) {
  	lang_debug("val byte_val\r\n");
  
  	len_check(5);
  	if (syn_chk && !is_token(next_token+1, "("))
  	    return syntax_error(next_token+1, "expected '('");
  
  	char *str_data;
  	int str_data_len;
  	Value_Type str_data_type;
  	// parse path string
  	if ((next_token = parse_expression(next_token + 2, &str_data, &str_data_len, &str_data_type, doit)) == -1)
  	    return -1;
  
  	if (syn_chk && !is_token(next_token, ","))
  	    return syntax_error(next_token, "expected ','");
  	next_token++;
  
  	int16_t num = atoi(my_token[next_token]);
  	next_token++;
  
  	if (syn_chk && !is_token(next_token, ")"))
  	    return syntax_error(next_token, "expected ')'");
  	next_token++;
  
  	if (doit) {
  	    if (num >= str_data_len) {
  		*data_len = 0;
  		*data = "";
  	    } else {
  		os_sprintf(tmp_buffer, "%d", str_data[num]);
  		*data_len = os_strlen(tmp_buffer);
  		*data = tmp_buffer;
  	    }
  	    *data_type = STRING_T;
  	}
      }
  
      else if (is_token(next_token, "binary")) {
  	lang_debug("val binary\r\n");
  
  	len_check(3);
  	if (syn_chk && !is_token(next_token+1, "("))
  	    return syntax_error(next_token+1, "expected '('");
  
  	char *str_data;
  	int str_data_len;
  	Value_Type str_data_type;
  	// parse path string
  	if ((next_token = parse_expression(next_token + 2, &str_data, &str_data_len, &str_data_type, doit)) == -1)
  	    return -1;
  
  	if (syn_chk && !is_token(next_token, ")"))
  	    return syntax_error(next_token, "expected ')'");
  	next_token++;
  
  	if (doit) {
  	    tmp_buffer[0] = atoi(str_data);
  	    tmp_buffer[1] = '\0';
  	    *data = tmp_buffer;
  	    *data_len = 1;
  	    *data_type = DATA_T;
  	}
      }
  #ifdef GPIO
      else if (is_token(next_token, "gpio_in")) {
  	lang_debug("val gpio_in\r\n");
  
  	len_check(3);
  	if (syn_chk && !is_token(next_token+1, "("))
  	    return syntax_error(next_token, "expected '('");
  
  	uint32_t gpio_no = atoi(my_token[next_token + 2]);
  	if (gpio_no > 16)
  	    return syntax_error(next_token+2, "invalid gpio number");
  
  	if (syn_chk && !is_token(next_token+3, ")"))
  	    return syntax_error(next_token+3, "expected ')'");
  
  	next_token += 4;
  	*data = "0";
  	*data_len = 1;
  	*data_type = STRING_T;
  	if (doit && easygpio_inputGet(gpio_no)) {
  	    *data = "1";
  	}
      }
  #endif
  #ifdef JSON_PARSE
      else if (is_token(next_token, "json_parse")) {
  	lang_debug("val json_parse\r\n");
  
  	len_check(5);
  	if (syn_chk && !is_token(next_token+1, "("))
  	    return syntax_error(next_token+1, "expected '('");
  
  	char *path_data;
  	int path_data_len;
  	Value_Type path_data_type;
  	// parse path string
  	if ((next_token = parse_expression(next_token + 2, &path_data, &path_data_len, &path_data_type, doit)) == -1)
  	    return -1;
  	if (!doit)
  	    path_data_len = 0;
  	char path[path_data_len+1];
  	if (doit)
  	    os_strcpy(path, path_data);
  
  	if (syn_chk && !is_token(next_token, ","))
  	    return syntax_error(next_token, "expected ','");
  
  	char *json_data;
  	int json_data_len;
  	Value_Type json_data_type;
  	// parse json string
  	if ((next_token = parse_expression(next_token + 1, &json_data, &json_data_len, &json_data_type, doit)) == -1)
  	    return -1;
  	if (!doit)
  	    json_data_len = 0;
  	char json[json_data_len+1];
  	if (doit)
  	    os_strcpy(json, json_data);
  
  	if (syn_chk && !is_token(next_token, ")"))
  	    return syntax_error(next_token, "expected ')'");
  
  	next_token += 1;
  
  	if (doit) {
  	    *data_len = sizeof(tmp_buffer);
  	    json_path(json, path, tmp_buffer, data_len);
  	    *data = tmp_buffer;
  	    *data_type = STRING_T;
  	}
      }
  #endif
      else if (is_token(next_token, "(")) {
  	lang_debug("expr (\r\n");
  
  	len_check(2);
  	if ((next_token = parse_expression(next_token + 1, data, data_len, data_type, doit)) == -1)
  	    return -1;
  
  	if (!is_token(next_token, ")"))
  	    return syntax_error(next_token, "expected ')'");
  	next_token++;
      }
  
      else {
  	if ((next_token = parse_value(next_token, data, data_len, data_type)) == -1)
  	    return -1;
      }
  
      // if it is not some kind of binary operation - finished
      if (!is_token(next_token, "=") 
  	&& !is_token(next_token, "+")
  	&& !is_token(next_token, "-")
  	&& !is_token(next_token, "*")
  	&& !is_token(next_token, "div")
  	&& !is_token(next_token, "|")
  	&& !is_token(next_token, ">")
  	&& !is_token(next_token, "gte")
  	&& !is_token(next_token, "str_gt")
  	&& !is_token(next_token, "str_gte"))
  	return next_token;
  
      // okay, it is an operation
      int op = next_token;
  
      char *r_data;
      int r_data_len;
      Value_Type r_data_type;
  
      if (!doit) {
  	*data_len = 0;
      }
      char l_data[*data_len+1];
      if (doit) {
  	os_memcpy(l_data, *data, *data_len);
      }
      l_data[*data_len] = '\0';
  
      // parse second operand
      if ((next_token = parse_expression(next_token + 1, &r_data, &r_data_len, &r_data_type, doit)) == -1)
  	return -1;
      //os_printf("l:%s(%d) r:%s(%d)\r\n", *data, *data_len, r_data, r_data_len);
  
      if (!doit)
  	return next_token;
  
      *data_type = STRING_T;
      if (is_token(op, "=")) {
  	*data = os_strcmp(l_data, r_data) ? "0" : "1";
      } else if (is_token(op, "+")) {
  	 os_sprintf(tmp_buffer, "%d", atoi(l_data) + atoi(r_data));
  	 *data = tmp_buffer;
  	 *data_len = os_strlen(tmp_buffer);
      } else if (is_token(op, "-")) {
  	os_sprintf(tmp_buffer, "%d", atoi(l_data) - atoi(r_data));
  	*data = tmp_buffer;
  	*data_len = os_strlen(tmp_buffer);
      } else if (is_token(op, "*")) {
  	os_sprintf(tmp_buffer, "%d", atoi(l_data) * atoi(r_data));
  	*data = tmp_buffer;
  	*data_len = os_strlen(tmp_buffer);
      } else if (is_token(op, "div")) {
  	os_sprintf(tmp_buffer, "%d", atoi(l_data) / atoi(r_data));
  	*data = tmp_buffer;
  	*data_len = os_strlen(tmp_buffer);
      } else if (is_token(op, "|")) {
  /*
  	uint16_t len = os_strlen(l_data) + os_strlen(r_data);
  	char catbuf[len+1];
  	os_sprintf(catbuf, "%s%s", l_data, r_data);
  */
  	uint16_t len = *data_len + r_data_len;
  	char catbuf[len+1];
  	os_memcpy(catbuf, l_data, *data_len);
  	os_memcpy(&catbuf[*data_len], r_data, r_data_len);
  
  	if (len > sizeof(tmp_buffer)-1) {
  	    len = sizeof(tmp_buffer);
  	}
  	catbuf[len] = '\0';
  	*data_len = len;
  	os_memcpy(tmp_buffer, catbuf, *data_len + 1);
  	*data = tmp_buffer;
  
      } else if (is_token(op, ">")) {
  	*data = atoi(l_data) > atoi(r_data) ? "1" : "0";
  	*data_len = 1;
      } else if (is_token(op, "gte")) {
  	*data = atoi(l_data) >= atoi(r_data) ? "1" : "0";
  	*data_len = 1;
      } else if (is_token(op, "str_gt")) {
  	*data = os_strcmp(l_data, r_data) > 0 ? "1" : "0";
  	*data_len = 1;
      } else if (is_token(op, "str_gte")) {
  	*data = os_strcmp(l_data, r_data) >= 0 ? "1" : "0";
  	*data_len = 1;
      }
  
      return next_token;
  }
  
  int ICACHE_FLASH_ATTR parse_value(int next_token, char **data, int *data_len, Value_Type * data_type) {
      if (my_token[next_token][0] == '"') {
  	lang_debug("val str(%s)\r\n", &my_token[next_token][1]);
  
  	*data = &my_token[next_token][1];
  	*data_len = os_strlen(&my_token[next_token][1]);
  	*data_type = STRING_T;
  	return next_token + 1;
      }
  
      else if (my_token[next_token][0] == '#') {
  	lang_debug("val hexbinary\r\n");
  
  	// Convert it in place to binary once during syntax check
  	// Format: Byte 0: '#', Byte 1: len (max 255), Byte 2 to len: binary data, Byte len+1: 0
  	if (syn_chk) {
  	    int i, j, len = os_strlen(my_token[next_token])-1;
  	    uint8_t a, *p = &(my_token[next_token][1]);
  
  	    if (len == 0 || len % 2)
  		return syntax_error(next_token, "number of hexdigits must be multiple of 2");
  	    if (len > 511)
  		return syntax_error(next_token, "hexbinary too long");
  	    for (i = 0, j = 1; i < len; i += 2, j++) {
  		if (p[i] <= '9')
  		    a = p[i] - '0';
  		else
  		    a = toupper(p[i]) - 'A' + 10;
  		a <<= 4;
  		if (p[i + 1] <= '9')
  		    a += p[i + 1] - '0';
  		else
  		    a += toupper(p[i + 1]) - 'A' + 10;
  		p[j] = a;
  	    }
  	    p[j] = '\0';
  	    p[0] = (uint8_t) j - 1;
  	}
  
  	*data = &my_token[next_token][2];
  	*data_len = my_token[next_token][1];
  	*data_type = DATA_T;
  
  	return next_token + 1;
      }
  
      else if (is_token(next_token, "$this_data")) {
  	lang_debug("val $this_data\r\n");
  
  	if (!in_topic_statement)
  	    return syntax_error(next_token, "undefined $this_data");
  	*data = interpreter_data;
  	*data_len = interpreter_data_len;
  	*data_type = DATA_T;
  	return next_token + 1;
      }
  
      else if (is_token(next_token, "$this_topic")) {
  	lang_debug("val $this_topic\r\n");
  
  	if (!in_topic_statement)
  	    return syntax_error(next_token, "undefined $this_topic");
  	*data = interpreter_topic;
  	*data_len = os_strlen(interpreter_topic);
  	*data_type = STRING_T;
  	return next_token + 1;
      }
  
      else if (is_token(next_token, "$this_serial")) {
  	lang_debug("val $this_serial\r\n");
  
  	if (!in_serial_statement)
  	    return syntax_error(next_token, "undefined $this_serial");
  	*data = interpreter_serial_data;
  	*data_len = interpreter_serial_data_len;
  	*data_type = DATA_T;
  	return next_token + 1;
      }
  #ifdef GPIO
      else if (is_token(next_token, "$this_gpio")) {
  	lang_debug("val $this_gpio\r\n");
  
  	if (!in_gpio_statement)
  	    return syntax_error(next_token, "undefined $this_gpio");
  	*data = interpreter_gpioval == 0 ? "0" : "1";
  	*data_len = 1;
  	*data_type = STRING_T;
  	return next_token + 1;
      }
  #endif
  #ifdef HTTPC
      else if (is_token(next_token, "$this_http_body")) {
  	lang_debug("val $this_http_body\r\n");
  
  	if (!in_http_statement)
  	    return syntax_error(next_token, "undefined $this_http_body");
  	*data = interpreter_data;
  	*data_len = interpreter_data_len;
  	*data_type = STRING_T;
  	return next_token + 1;
      }
  
      else if (is_token(next_token, "$this_http_code")) {
  	static char codebuf[4];
  	lang_debug("val $this_http_code\r\n");
  
  	if (!in_http_statement)
  	    return syntax_error(next_token, "undefined $this_http_code");
  	
  	os_sprintf(codebuf, "%3d", interpreter_http_status);
  	*data = codebuf;
  	*data_len = os_strlen(codebuf);
  	*data_type = STRING_T;
  	return next_token + 1;
      }
  
      else if (is_token(next_token, "$this_http_host")) {
  	static char codebuf[4];
  	lang_debug("val $this_http_host\r\n");
  
  	if (!in_http_statement)
  	    return syntax_error(next_token, "undefined $this_http_host");
  	if (interpreter_status == HTTP_RESPONSE) {
  	    *data = interpreter_http_hostname;
  	    *data_len = os_strlen(interpreter_http_hostname);
  	    *data_type = STRING_T;
  	}
  	return next_token + 1;
      }
  
      else if (is_token(next_token, "$this_http_path")) {
  	static char codebuf[4];
  	lang_debug("val $this_http_path\r\n");
  
  	if (!in_http_statement)
  	    return syntax_error(next_token, "undefined $this_http_path");
  	if (interpreter_status == HTTP_RESPONSE) {
  	    *data = interpreter_http_path;
  	    *data_len = os_strlen(interpreter_http_path);
  	    *data_type = STRING_T;
  	}
  	return next_token + 1;
      }
  #endif
  #ifdef NTP
      else if (is_token(next_token, "$timestamp")) {
  	lang_debug("val $timestamp\r\n");
  
  	if (ntp_sync_done())
  	    *data = get_timestr();
  	else
  	    *data = "99:99:99";
  	*data_len = os_strlen(*data);
  	*data_type = STRING_T;
  	return next_token + 1;
      }
  
      else if (is_token(next_token, "$weekday")) {
  	lang_debug("val $weekday\r\n");
  
  	if (ntp_sync_done())
  	    *data = get_weekday();
  	else
  	    *data = "xxx";
  	*data_len = os_strlen(*data);
  	*data_type = STRING_T;
  	return next_token + 1;
      }
  #endif
  #ifdef ADC
      else if (is_token(next_token, "$adc")) {
  	static char adcbuf[8];
  	lang_debug("val $adc\r\n");
  
  	os_sprintf(adcbuf, "%d", adc_read());
  	*data = adcbuf;
  	*data_len = os_strlen(*data);
  	*data_type = STRING_T;
  	return next_token + 1;
      }
  #endif
      else if (my_token[next_token][0] == '$' && my_token[next_token][1] != '\0') {
  	lang_debug("val var %s\r\n", &(my_token[next_token][1]));
  
  	var_entry_t *this_var, *free_var;
  
  	this_var = find_var(&(my_token[next_token][1]), &free_var);
  	if (this_var == NULL)
  	    return syntax_error(next_token, "unknown var name");
  
  	*data = this_var->data;
  	*data_len = this_var->data_len;
  	*data_type = this_var->data_type;
  	return next_token + 1;
      }
  
      else if (my_token[next_token][0] == '@' && my_token[next_token][1] != '\0') {
  	lang_debug("val flashvar %s\r\n", my_token[next_token]);
  
  	uint32_t slot_no = atoi(&my_token[next_token][1]);
  	if (slot_no == 0 || slot_no > MAX_FLASH_SLOTS)
  	    return syntax_error(next_token+2, "invalid flash slot number");
  
  	*data = "0";
  	*data_len = 1;
  	*data_type = STRING_T;
  
  	if (!syn_chk) {
  	    slot_no--;
  	    uint8_t slots[MAX_FLASH_SLOTS*FLASH_SLOT_LEN];
  	    blob_load(1, (uint32_t *)slots, sizeof(slots));
  	    os_memcpy(tmp_buffer, &slots[slot_no*FLASH_SLOT_LEN], FLASH_SLOT_LEN);
  	    *data = tmp_buffer;
  	    *data_len = os_strlen(tmp_buffer);
  	}
  	return next_token + 1;
      }
  
      else {
  	lang_debug("val num/str(%s)\r\n", my_token[next_token]);
  
  	*data = my_token[next_token];
  	*data_len = os_strlen(my_token[next_token]);
  	*data_type = STRING_T;
  	return next_token + 1;
      }
  }
  
  int ICACHE_FLASH_ATTR interpreter_syntax_check() {
      lang_debug("interpreter_syntax_check\r\n");
  
      int i;
      for (i = 0; i<MAX_VARS; i++) {
  	vars[i].free = 1;
  	vars[i].data = "";//(uint8_t *)os_malloc(MAX_VAR_LEN);
  	vars[i].data_len = 0;
      }
  
      os_sprintf(tmp_buffer, "Syntax okay");
      interpreter_status = SYNTAX_CHECK;
      interpreter_topic = interpreter_data = "";
      interpreter_data_len = 0;
      os_bzero(&timestamps, sizeof(timestamps));
  #ifdef GPIO
      gpio_counter = 0;
  #ifdef GPIO_PWM
      pwm_counter = 0;
  #endif
  #endif
      return parse_statement(0);
  }
  
  int ICACHE_FLASH_ATTR interpreter_config() {
      int next_token = 0;
  
      while ((next_token = search_token(next_token, "config")) < max_token) {
  	lang_debug("statement config\r\n");
  	uint8_t *val;
  
  	len_check(2);
  
  	if (my_token[next_token + 2][0] == '@') {
  	    uint32_t slot_no = atoi(&my_token[next_token + 2][1]);
  	    if (slot_no == 0 || slot_no > MAX_FLASH_SLOTS)
  		return syntax_error(next_token + 1, "invalid flash slot number");
  
  	    slot_no--;
  	    uint8_t slots[MAX_FLASH_SLOTS*FLASH_SLOT_LEN];
  	    blob_load(1, (uint32_t *)slots, sizeof(slots));
  	    val = &slots[slot_no*FLASH_SLOT_LEN];
  	    if (val[0] == '\0')
  		val = "_undefined_";
  	} else {
  	    val = my_token[next_token + 2];
  	}
  
  	do_command("set", my_token[next_token + 1], val);
  	next_token += 3;
      }
      return next_token;
  }
  
  int ICACHE_FLASH_ATTR interpreter_init() {
      if (!script_enabled)
  	return -1;
  
      lang_debug("interpreter_init\r\n");
  #ifdef GPIO
  #ifdef GPIO_PWM
      init_pwm();
  #endif
  #endif
      interpreter_status = INIT;
      interpreter_topic = interpreter_data = "";
      interpreter_data_len = 0;
      int ret_val = parse_statement(0);
  #ifdef GPIO
      init_gpios();
  #endif
  
      return ret_val;
  }
  
  int ICACHE_FLASH_ATTR interpreter_wifi_connect(void) {
      if (!script_enabled)
  	return -1;
  
      lang_debug("interpreter_wifi_connect\r\n");
  
      interpreter_status = WIFI_CONNECT;
      interpreter_topic = interpreter_data = "";
      interpreter_data_len = 0;
      return parse_statement(0);
  }
  
  int ICACHE_FLASH_ATTR interpreter_wifi_disconnect(void) {
      if (!script_enabled)
  	return -1;
  
      lang_debug("interpreter_wifi_disconnect\r\n");
  
      interpreter_status = WIFI_DISCONNECT;
      interpreter_topic = interpreter_data = "";
      interpreter_data_len = 0;
      return parse_statement(0);
  }
  
  int ICACHE_FLASH_ATTR interpreter_mqtt_connect(void) {
      if (!script_enabled)
  	return -1;
  
      lang_debug("interpreter_mqtt_connect\r\n");
  
      interpreter_status = MQTT_CLIENT_CONNECT;
      interpreter_topic = interpreter_data = "";
      interpreter_data_len = 0;
      return parse_statement(0);
  }
  
  int ICACHE_FLASH_ATTR interpreter_topic_received(const char *topic, const char *data, int data_len, bool local) {
      if (!script_enabled)
  	return -1;
  
      uint8_t data_null[data_len+1];
      os_memcpy(data_null, data, data_len);
      data_null[data_len] = '\0';
  
      lang_debug("interpreter_topic_received\r\n");
  
      interpreter_status = (local) ? TOPIC_LOCAL : TOPIC_REMOTE;
      interpreter_topic = (char *)topic;
      interpreter_data = data_null;
      interpreter_data_len = data_len;
  
      return parse_statement(0);
  }
  
  int ICACHE_FLASH_ATTR interpreter_serial_input(const char *data, int data_len) {
      if (!script_enabled)
  	return -1;
  
      lang_debug("interpreter_serial_input\r\n");
  
      interpreter_status = SERIAL_INPUT;
      interpreter_serial_data = (char *)data;
      interpreter_serial_data_len = data_len;
  
      return parse_statement(0);
  }
  
  #ifdef HTTPC
  void ICACHE_FLASH_ATTR interpreter_http_reply(char *hostname, char *path, char *response_body, int http_status, char *response_headers, int body_size) {
      if (!script_enabled)
  	return;
  
      lang_debug("interpreter_http_reply\r\n");
  
      interpreter_status = HTTP_RESPONSE;
      interpreter_topic = response_headers;
      interpreter_http_status = http_status;
      interpreter_http_hostname = hostname;
      interpreter_http_path = path;
      interpreter_data = response_body;
      interpreter_data_len = body_size;
  
      parse_statement(0);
  }
  #endif
  
  #endif /* SCRIPTED */
  
  