// https://wokwi.com/projects/411834422059067393
//
// https://wokwi.com/projects/411769421805937665
// a modification of
// https://wokwi.com/projects/409963746798376961
// a modificaton of:
// https://wokwi.com/projects/409963275966630913
// to use a post-fix/Reverse Polish Notation protocol
// Code from https://www.gammon.com.au/forum/?id=11425&reply=1#reply1
// Other simulations https://forum.arduino.cc/t/wokwi-simulations-for-arduino-built-in-examples/1304754

// See also Nick Gammon's Serial examples
//   Serial Example 1: https://wokwi.com/projects/409963075177910273
//   Serial Example 2: https://wokwi.com/projects/409963275966630913
// & @gcjr's pcRead https://wokwi.com/projects/412448490504844289

/*
  Non-Blocking Serial Parsing, RPN-style with switch-case

  Another way of processing incoming data without blocking, is to
  use single letter commands in a Post-fix/Reverse Polish Notation
  form.

  Effectively this means looking at each byte in the
  input stream, and handling it with the current state.

  As an example, say you had this coming into the serial port:

        4500R 80S 3G

  Where R is RPM, S is speed, and G is the gear setting, using
  the current accumulated number.

  The code below  acts on the bytes as they come in.  When it gets
  a letter "R", "S" or "G" it copies the number ot the register and
  acts. Otherwise it processes incoming digits by multiplying the
  previous result by 10, and adding in the new one.  Whitespace
  is skipped.

  This has the advantage of handling long messages without even
  needing any buffer, thus saving RAM. You can also process message
  as soon as the state changes, rather than waiting for end-of-line.

*/


// Example state machine reading serial input
// Author: DaveX
// Date: 2024-19-24
// Modified from https://www.gammon.com.au/forum/?id=11425&reply=1#reply1

// For demo
#include <AccelStepper.h> // https://www.airspayce.com/mikem/arduino/AccelStepper/

// #########  Global variables
//
long currentValue;  // accumulator for number parsing
bool numberMode = false; // activly parsing a number

int verbose = 0 ; //
long Svalue, Avalue, Target;

struct XYZTstack {  // HP41-like calculator stack
  long X;
  long Y;
  long Z;
  long T;
} stack;

AccelStepper stepper(AccelStepper::DRIVER,9,8);

void usage() {
  Serial.print(F(R"(
RPN single char parser for controlling a stepper
 Commands: SATR0-9cp+-*/%srdv
  S Speed
  A Accelleration
  T Target
  R Relative
  0-9 Assemble a number
  c Change sign
  +-*/%  arithmetic
  p print it
  srd  swap, roll, dup on the XYZT stack
  v set verbosity

Try "100 2 20 SAT" or "100S1000A100T" or "10 60*21+T"
or "200 10 c*R"
2000S 20000A 200 10 * T  for 10 turns fast
0T                       to return to 0
100c 2 2 SAR              move slow 1/2 turn CCW
)"
                ));
}

// ########   Character parsing

void processLine () // Act on a \n newline
{
  // do something with line
  char buff[60];
  snprintf(buff, 60, "Settings are %ldstep/sec %ldstep/s^2 %ldTarget ", Svalue, Avalue, Target);
  Serial.println(buff);
  if (verbose > 0) {
    snprintf(buff, 60, "X:%ld Y:%ld Z:%ld T:%ld\n", stack.X, stack.Y, stack.Z, stack.T);
    Serial.println(buff);
  }
} // end of processGear


// Calculator stack operations ########################
long popValue() {
  long retval = stack.X;
  // Serial.print("Popping:");
  // Serial.println(retval);
  stack.X = stack.Y;
  stack.Y = stack.Z;
  stack.Z = stack.T;
  return retval;
  // currentValue = 0;
}

void pushValue() {
  // Serial.print("Pushing:");
  // Serial.println(currentValue);
  stack.T = stack.Z;
  stack.Z = stack.Y;
  stack.Y = stack.X;
  stack.X = currentValue;
  currentValue = 0;
}

void completeNumber() {
  if (numberMode) {
    pushValue();
    numberMode = false;
  }
}

void processIncomingByte (const byte c)
{
  // non-blocking serial parsing with single-character post-fix/RPN actions
  //
  if ( ! ((c >= '0') && (c <= '9')) ) {
    completeNumber();
  }
  switch (c)
  {
    case 'S':  // Speed command
      Svalue = popValue();
      stepper.setMaxSpeed(Svalue);
      break;
    case 'A':  // Acceleration command
      Avalue = popValue();
      stepper.setAcceleration(Avalue);
      break;
    case 'T':  // Target command
      Target = popValue();
      stepper.moveTo(Target);
      break;
    case 'R':  // RelativeMove command
      stepper.move(popValue());
      break;
    // ##### Number and command parsing for caclulator
    case '0' ... '9': // digits -- collect a series of them
      numberMode = true;
      currentValue *= 10;
      currentValue += c - '0';
      break;
    case 'c': // change sign
      stack.X *= -1;
      break;
    case 'p': // print
      Serial.print(stack.X);
      break;
    case 's': // swap
      currentValue = stack.Y;
      stack.Y = stack.X;
      stack.X = currentValue;
      currentValue = 0;
      break;
    case 'd': // duplicate
      currentValue = stack.X;
      pushValue();
      break;
    case 'r': // roll
      currentValue = stack.X;
      stack.X = stack.Y;
      stack.Y = stack.Z;
      stack.Z = stack.T;
      stack.T = currentValue;
      currentValue = 0;
      break;
    case '+':
      { long x1 = popValue();
        long x2 = popValue();
        currentValue = x1 + x2;
        pushValue();
      }
      break;
    case '-':
      currentValue -= popValue();
      pushValue();
      break;
    case '*':
      currentValue = popValue() * popValue();
      pushValue();
      break;
    case '/': // divide
      {
        long dividend;
        long divisor = popValue();
        if (divisor != 0 ) {
          dividend = popValue();
          currentValue = dividend / divisor;
        } else { // don't do a divide by zero
          currentValue = divisor;
        }
        pushValue();
      }
      break;
    case '%': // modulus
      {
        long divisor = popValue();
        long dividend = popValue();
        currentValue = dividend % divisor;
        pushValue();
      }
      break;
    case ' ': // ignore whitespace
    case '\t':
      break;
    case '\n': // end-of-line tag
      processLine();
      break;
    case 'v': // set verbose mode
      verbose = popValue();
      break;
    default:
      Serial.print("Character '");
      Serial.print(char(c));
      Serial.println("' is unknown.");
      break;
  }  // end of switch on incoming byte
} // end of processIncomingByte


// ###########   Arduino Basics #############################

void setup ()
{
  Serial.begin (115200);
  usage();
}  // end of setup

void loop ()
{
  while (Serial.available ())
    processIncomingByte (Serial.read ());

  // do other stuff in loop as required
  stepper.run();
}  // end of loop
A4988