text: /*
   Stream.cpp - adds parsing methods to Stream class
   Copyright (c) 2008 David A. Mellis.  All right reserved.
  
   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.
  
   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.
  
   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  
   Created July 2011
   parsing functions based on TextFinder library by Michael Margolis
  
   findMulti/findUntil routines written by Jim Leonard/Xuth
   */
  
  #include "Arduino.h"
  #include "Stream.h"
  
  #define PARSE_TIMEOUT 1000  // default number of milli-seconds to wait
  
  // protected method to read stream with timeout
  int Stream::timedRead()
  {
    int c;
    _startMillis = millis();
    do {
      c = read();
      if (c >= 0) return c;
    } while(millis() - _startMillis < _timeout);
    return -1;     // -1 indicates timeout
  }
  
  // protected method to peek stream with timeout
  int Stream::timedPeek()
  {
    int c;
    _startMillis = millis();
    do {
      c = peek();
      if (c >= 0) return c;
    } while(millis() - _startMillis < _timeout);
    return -1;     // -1 indicates timeout
  }
  
  // returns peek of the next digit in the stream or -1 if timeout
  // discards non-numeric characters
  int Stream::peekNextDigit(LookaheadMode lookahead, bool detectDecimal)
  {
    int c;
    while (1) {
      c = timedPeek();
  
      if( c < 0 ||
          c == '-' ||
          (c >= '0' && c <= '9') ||
          (detectDecimal && c == '.')) return c;
  
      switch( lookahead ){
          case SKIP_NONE: return -1; // Fail code.
          case SKIP_WHITESPACE:
              switch( c ){
                  case ' ':
                  case '\t':
                  case '\r':
                  case '\n': break;
                  default: return -1; // Fail code.
              }
          case SKIP_ALL:
              break;
      }
      read();  // discard non-numeric
    }
  }
  
  // Public Methods
  //////////////////////////////////////////////////////////////
  
  void Stream::setTimeout(unsigned long timeout)  // sets the maximum number of milliseconds to wait
  {
    _timeout = timeout;
  }
  
   // find returns true if the target string is found
  bool  Stream::find(char *target)
  {
    return findUntil(target, strlen(target), NULL, 0);
  }
  
  // reads data from the stream until the target string of given length is found
  // returns true if target string is found, false if timed out
  bool Stream::find(char *target, size_t length)
  {
    return findUntil(target, length, NULL, 0);
  }
  
  // as find but search ends if the terminator string is found
  bool  Stream::findUntil(char *target, char *terminator)
  {
    return findUntil(target, strlen(target), terminator, strlen(terminator));
  }
  
  // reads data from the stream until the target string of the given length is found
  // search terminated if the terminator string is found
  // returns true if target string is found, false if terminated or timed out
  bool Stream::findUntil(char *target, size_t targetLen, char *terminator, size_t termLen)
  {
    if (terminator == NULL) {
      MultiTarget t[1] = {{target, targetLen, 0}};
      return findMulti(t, 1) == 0 ? true : false;
    } else {
      MultiTarget t[2] = {{target, targetLen, 0}, {terminator, termLen, 0}};
      return findMulti(t, 2) == 0 ? true : false;
    }
  }
  
  // returns the first valid (long) integer value from the current position.
  // lookahead determines how parseInt looks ahead in the stream.
  // See LookaheadMode enumeration at the top of the file.
  // Lookahead is terminated by the first character that is not a valid part of an integer.
  // Once parsing commences, 'ignore' will be skipped in the stream.
  long Stream::parseInt(LookaheadMode lookahead, char ignore)
  {
    bool isNegative = false;
    long value = 0;
    int c;
  
    c = peekNextDigit(lookahead, false);
    // ignore non numeric leading characters
    if(c < 0)
      return 0; // zero returned if timeout
  
    do{
      if(c == ignore)
        ; // ignore this character
      else if(c == '-')
        isNegative = true;
      else if(c >= '0' && c <= '9')        // is c a digit?
        value = value * 10 + c - '0';
      read();  // consume the character we got with peek
      c = timedPeek();
    }
    while( (c >= '0' && c <= '9') || c == ignore );
  
    if(isNegative)
      value = -value;
    return value;
  }
  
  // as parseInt but returns a floating point value
  float Stream::parseFloat(LookaheadMode lookahead, char ignore)
  {
    bool isNegative = false;
    bool isFraction = false;
    long value = 0;
    int c;
    float fraction = 1.0;
  
    c = peekNextDigit(lookahead, true);
      // ignore non numeric leading characters
    if(c < 0)
      return 0; // zero returned if timeout
  
    do{
      if(c == ignore)
        ; // ignore
      else if(c == '-')
        isNegative = true;
      else if (c == '.')
        isFraction = true;
      else if(c >= '0' && c <= '9')  {      // is c a digit?
        value = value * 10 + c - '0';
        if(isFraction)
           fraction *= 0.1;
      }
      read();  // consume the character we got with peek
      c = timedPeek();
    }
    while( (c >= '0' && c <= '9')  || (c == '.' && !isFraction) || c == ignore );
  
    if(isNegative)
      value = -value;
    if(isFraction)
      return value * fraction;
    else
      return value;
  }
  
  // read characters from stream into buffer
  // terminates if length characters have been read, or timeout (see setTimeout)
  // returns the number of characters placed in the buffer
  // the buffer is NOT null terminated.
  //
  size_t Stream::readBytes(char *buffer, size_t length)
  {
    size_t count = 0;
    while (count < length) {
      int c = timedRead();
      if (c < 0) break;
      *buffer++ = (char)c;
      count++;
    }
    return count;
  }
  
  
  // as readBytes with terminator character
  // terminates if length characters have been read, timeout, or if the terminator character  detected
  // returns the number of characters placed in the buffer (0 means no valid data found)
  
  size_t Stream::readBytesUntil(char terminator, char *buffer, size_t length)
  {
    if (length < 1) return 0;
    size_t index = 0;
    while (index < length) {
      int c = timedRead();
      if (c < 0 || c == terminator) break;
      *buffer++ = (char)c;
      index++;
    }
    return index; // return number of characters, not including null terminator
  }
  
  String Stream::readString()
  {
    String ret;
    int c = timedRead();
    while (c >= 0)
    {
      ret += (char)c;
      c = timedRead();
    }
    return ret;
  }
  
  String Stream::readStringUntil(char terminator)
  {
    String ret;
    int c = timedRead();
    while (c >= 0 && c != terminator)
    {
      ret += (char)c;
      c = timedRead();
    }
    return ret;
  }
  
  int Stream::findMulti( struct Stream::MultiTarget *targets, int tCount) {
    // any zero length target string automatically matches and would make
    // a mess of the rest of the algorithm.
    for (struct MultiTarget *t = targets; t < targets+tCount; ++t) {
      if (t->len <= 0)
        return t - targets;
    }
  
    while (1) {
      int c = timedRead();
      if (c < 0)
        return -1;
  
      for (struct MultiTarget *t = targets; t < targets+tCount; ++t) {
        // the simple case is if we match, deal with that first.
        if (c == t->str[t->index]) {
          if (++t->index == t->len)
            return t - targets;
          else
            continue;
        }
  
        // if not we need to walk back and see if we could have matched further
        // down the stream (ie '1112' doesn't match the first position in '11112'
        // but it will match the second position so we can't just reset the current
        // index to 0 when we find a mismatch.
        if (t->index == 0)
          continue;
  
        int origIndex = t->index;
        do {
          --t->index;
          // first check if current char works against the new current index
          if (c != t->str[t->index])
            continue;
  
          // if it's the only char then we're good, nothing more to check
          if (t->index == 0) {
            t->index++;
            break;
          }
  
          // otherwise we need to check the rest of the found string
          int diff = origIndex - t->index;
          size_t i;
          for (i = 0; i < t->index; ++i) {
            if (t->str[i] != t->str[i + diff])
              break;
          }
  
          // if we successfully got through the previous loop then our current
          // index is good.
          if (i == t->index) {
            t->index++;
            break;
          }
  
          // otherwise we just try the next index
        } while (t->index);
      }
    }
    // unreachable
    return -1;
  }
  