#define DEFAULT_TOOL_WIDTH 2.4
#define ZERO_EVERY_TIME 1 // failed experiment.  need to remove ifdefs.

// TO DO: "install laser" "install spindle" "install 20k spindle"
// and refuse to execute commands incompatible with current hardware

// my 3018 is actually 270x157mm (previously was 266x154 until two of the endstops broke)

// Interesting... MPos seems to be reset to 0 by disconnecting and reconnecting.
// didn't expect that. Thought it was reset only by a power-off...

// to do - if spindle is on, use g1, otherwise g0

// or use cutto vs goto?

// assumes default of $20=0, $21=0, $22=0, $30=20000

// while executing gcode() calls, should check for immediate keyboard input, eg ~ or ! or ?  or ^C etc.

// cylander: need diameter option as well as radius! (DONE)

// need to add a global z step command...

/*
The $13 Grbl setting only determines the units Grbl sends back to the GUI for status reports (position, offsets, rate) and stored $# parameters. It does not effect how a gcode program is run.

Only G20 and G21 alter how the g-code program is interpreted in inches or mm, respectively. These must be set at the start of the g-code program, every time. UGS should refer to G20/21 modal state to determine the units to send to Grbl.

Grbl always defaults to G21 mm mode. So, after a power-cycle or reset, it's set back to G21. You can alter this default by programming a startup line by: $N0=G20. This will automatically run and set G20 after a reset or power cycle.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <error.h>
#include <errno.h>
#include <ctype.h>
#include <ncurses.h>
#include <unistd.h>
#include <fcntl.h>
#include <libgen.h>
#include <termios.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/ioctl.h>

// This file is a bit of R&D ... it was thrown together from various parts I had
// knocking around, so it's far from well structured.  Once I've finished experimenting
// I'll give it a well-deserved rewrite.

// This is a gcode sender - basically a terminal program talking to a serial line, which
// is connected to the GRBL controller on a Sainsmart 3018 CNC.  However it does a couple
// of things extra.
// 1) a trivial tweak - the GRBL controller can get in a state where it sends status
//    updates every few seconds.  We display those in a separate window at the top of the
//    page.
// 2) the input line is forced to always be at the foot of the screen, and anything that
//    comes from the CNC while you are entering a line is displayed in the main window
//    *above* the input line so that the line you are typing does not get corrupted.
// 3) Cursor keys can be used for x/y jogging at any time.
// 4) This is the big addition: as well as the G CODE commands accepted by the CNC,
//    this code also accepts arbitrary commands that were pre-defined in this file.
//    Those extensions can be of arbitrary complexity - or just short aliases for
//    harder to remember G CODE commands.

// The program is accessed over ssh (Putty, if from windows) with <esc>[n~ selected as
// the encoding under Terminal/Keyboard (only needed for curor-key jogging)

WINDOW *output, *input, *debugline;

// Note: to avoid always having a blank line at the foot of every window,
// we do the ugly old fashioned thing of printing '\nline' instead of 'line\n' ...

// simple low-level calls
static int gcode_errnum = 0;
extern void append_error (char *line, int gcode_errnum);
extern int gcode (char *format, ...);
extern int gcode_sendfile (char *fname);
extern int gcode_jog (char *direction, char *quantity, char *units);
extern int gcode_default_units (char *units);
extern int gcode_probe (void);
extern int gcode_display_info (void);
extern int gcode_gotoxy (float x, float y);
extern int gcode_cuttoxy (float x, float y);
extern int gcode_gotoz (float z);
extern int gcode_cuttoz (float z);
extern int gcode_spindle_on (void);
extern int gcode_spindle_off (void);

// slightly higher level procedures
extern void laser_test(void);
extern void laser_focus(void);
extern void laser_strength(void);

extern void laser_mode(void);
extern void cnc_mode(void);

extern void cnc_hammer_jig_peck_drill (float dz);
extern void cnc_rectangle (float from_x, float from_y,
			   float to_x, float to_y,
			   float step_x, float step_y, int want_dogbones);
extern void cnc_pocket (float from_x, float from_y, float from_z, float to_x, float to_y, float to_z, float step_x, float step_y, float step_z, float tool_width, int want_dogbones);	// e.g. 
																							// pocket 
																							// 0,0,-30 
																							// to 
																							// 79,28,-32.5 
																							// step 
																							// 1.4,1.4,0.5
extern void cnc_cylinder (float radius, float depth);
extern void cnc_pitrex_cartridge (void);
extern void cnc_pitrex_mailer (void);
extern void cnc_hammer_jig (void);

// CNC parameters here:
/*
$$ $# $G $I $N $x=val $Nx=line $J=line $SLP $C $X $H ~ ! ? ctrl-x

$$ (view Grbl settings) 
$# (view # parameters) 
$G (view parser state) 
$I (view build info) 
$N (view startup blocks) 
$x=value (save Grbl setting) 
$Nx=line (save startup block) 
$J=line (Run jogging motion)  (Immediate command char 0x85 to cancel queued jogging)
$SLP (Enable sleep mode)
$RST=$, $RST=#, and $RST=* (Restore Grbl settings and data to defaults)
$C (check gcode mode) 
$X (kill alarm lock) 
$H (run homing cycle) 
~ (cycle start) 
! (feed hold) 
? (current status) 
ctrl­x (reset Grbl)

https://github.com/gnea/grbl/wiki/Grbl-v1.1-Commands#grbl--commands
*/

typedef enum UNITS
{
  undefined,
  mm,
  in
} UNITS;

UNITS Default_Units = mm;

float xy_jog_default = 1.0;
float z_jog_default = 1.0;

// End of CNC data section.

#define Boolean int
Boolean expecting_ok = FALSE, status_requested = FALSE;

#define LOCAL_ECHO 1

#ifndef MAX_LINE
#define MAX_LINE 1024
#endif
#define BUF_SIZE 500
#define MAX_SNAME 1000

struct termios ttyOrig;

#include <unistd.h>
#include <stdarg.h>

fd_set inFds;

void ttyReset (void)
{
  wrefresh (debugline);
  wrefresh (output);
  wrefresh (input);
  echo ();
  delwin (output);
  delwin (input);
  endwin ();
  (void) tcsetattr (STDIN_FILENO, TCSANOW, &ttyOrig);	// restore
							// keyboard/stdin
  fflush (stderr);
  exit (EXIT_SUCCESS);
}

static char *ename[] = {
  /* 0 */ "",
  /* 1 */ "EPERM", "ENOENT", "ESRCH", "EINTR", "EIO", "ENXIO",
  /* 7 */ "E2BIG", "ENOEXEC", "EBADF", "ECHILD",
  /* 11 */ "EAGAIN/EWOULDBLOCK", "ENOMEM", "EACCES", "EFAULT",
  /* 15 */ "ENOTBLK", "EBUSY", "EEXIST", "EXDEV", "ENODEV",
  /* 20 */ "ENOTDIR", "EISDIR", "EINVAL", "ENFILE", "EMFILE",
  /* 25 */ "ENOTTY", "ETXTBSY", "EFBIG", "ENOSPC", "ESPIPE",
  /* 30 */ "EROFS", "EMLINK", "EPIPE", "EDOM", "ERANGE",
  /* 35 */ "EDEADLK/EDEADLOCK", "ENAMETOOLONG", "ENOLCK", "ENOSYS",
  /* 39 */ "ENOTEMPTY", "ELOOP", "", "ENOMSG", "EIDRM", "ECHRNG",
  /* 45 */ "EL2NSYNC", "EL3HLT", "EL3RST", "ELNRNG", "EUNATCH",
  /* 50 */ "ENOCSI", "EL2HLT", "EBADE", "EBADR", "EXFULL", "ENOANO",
  /* 56 */ "EBADRQC", "EBADSLT", "", "EBFONT", "ENOSTR", "ENODATA",
  /* 62 */ "ETIME", "ENOSR", "ENONET", "ENOPKG", "EREMOTE",
  /* 67 */ "ENOLINK", "EADV", "ESRMNT", "ECOMM", "EPROTO",
  /* 72 */ "EMULTIHOP", "EDOTDOT", "EBADMSG", "EOVERFLOW",
  /* 76 */ "ENOTUNIQ", "EBADFD", "EREMCHG", "ELIBACC", "ELIBBAD",
  /* 81 */ "ELIBSCN", "ELIBMAX", "ELIBEXEC", "EILSEQ", "ERESTART",
  /* 86 */ "ESTRPIPE", "EUSERS", "ENOTSOCK", "EDESTADDRREQ",
  /* 90 */ "EMSGSIZE", "EPROTOTYPE", "ENOPROTOOPT",
  /* 93 */ "EPROTONOSUPPORT", "ESOCKTNOSUPPORT",
  /* 95 */ "EOPNOTSUPP/ENOTSUP", "EPFNOSUPPORT", "EAFNOSUPPORT",
  /* 98 */ "EADDRINUSE", "EADDRNOTAVAIL", "ENETDOWN", "ENETUNREACH",
  /* 102 */ "ENETRESET", "ECONNABORTED", "ECONNRESET", "ENOBUFS",
  /* 106 */ "EISCONN", "ENOTCONN", "ESHUTDOWN", "ETOOMANYREFS",
  /* 110 */ "ETIMEDOUT", "ECONNREFUSED", "EHOSTDOWN", "EHOSTUNREACH",
  /* 114 */ "EALREADY", "EINPROGRESS", "ESTALE", "EUCLEAN",
  /* 118 */ "ENOTNAM", "ENAVAIL", "EISNAM", "EREMOTEIO", "EDQUOT",
  /* 123 */ "ENOMEDIUM", "EMEDIUMTYPE", "ECANCELED", "ENOKEY",
  /* 127 */ "EKEYEXPIRED", "EKEYREVOKED", "EKEYREJECTED",
  /* 130 */ "EOWNERDEAD", "ENOTRECOVERABLE", "ERFKILL", "EHWPOISON"
};

#define MAX_ENAME 133

__attribute__ ((__noreturn__))
     static void terminate (Boolean useExit3)
{
char *s;

  /* Dump core if EF_DUMPCORE environment variable is defined and is a
     nonempty string; otherwise call exit(3) or _exit(2), depending on the
     value of 'useExit3'. */

  s = getenv ("EF_DUMPCORE");

  if (s != NULL && *s != '\0')
    abort ();
  else if (useExit3)
    exit (EXIT_FAILURE);
  else
    _exit (EXIT_FAILURE);
}

/* Diagnose 'errno' error by:

 * outputting a string containing the error name (if available
   in 'ename' array) corresponding to the value in 'err', along
   with the corresponding error message from strerror(), and

 * outputting the caller-supplied error message specified in
   'format' and 'ap'. */

static void outputError (Boolean useErr, int err, Boolean flushStdout,
			 const char *format, va_list ap)
{
  char buf[BUF_SIZE], userMsg[BUF_SIZE], errText[BUF_SIZE];

  vsnprintf (userMsg, BUF_SIZE, format, ap);

  if (useErr)
    snprintf (errText, BUF_SIZE, " [%s %s]",
	      (err > 0
	       && err <= MAX_ENAME) ? ename[err] : "?UNKNOWN?",
	      strerror (err));
  else
    snprintf (errText, BUF_SIZE, ":");

  snprintf (buf, BUF_SIZE, "ERROR%s %s\n", errText, userMsg);

#ifdef OLDSTYLE
  if (flushStdout)
    fflush (stdout);		/* Flush any pending stdout */
  fputs (buf, stderr);
  fflush (stderr);		/* In case stderr is not line-buffered */
#else
  wprintw (output, "\nERROR%s %s", errText, userMsg);
  wrefresh (output);
  wrefresh (input);
#endif
}

/* Display error message including 'errno' diagnostic, and
   terminate the process */

static void errExit (const char *format, ...)
{
  va_list argList;

  va_start (argList, format);
  outputError (TRUE, errno, TRUE, format, argList);
  va_end (argList);

  terminate (TRUE);
}

/* Print an error message (without an 'errno' diagnostic) */

static void fatal (const char *format, ...)
{
  va_list argList;

  va_start (argList, format);
  outputError (FALSE, 0, TRUE, format, argList);
  va_end (argList);

  terminate (TRUE);
}

int Data_From_CNC_Fd, Data_To_CNC_Fd;

int interpret (char *command);

Boolean local_command (char *command)
{
  int rc;

  // contemplating using https://github.com/orangeduck/mpc rather than
  // rolling my own for once...?
  rc = interpret (command);
  if (rc < 0) {
    // was not able to parse a valid extended command
    // fprintf(stderr, "Can't\n");
    return FALSE;
  } else if (rc > 0) {
    // parsed, executed, failed :-(
    wprintw (output, "\nWon't");
    wrefresh (output);
    wrefresh (input);
    return FALSE;
  } else {
    // wprintw(output, "\nok"); wrefresh(output); wrefresh(input);
  }
  return TRUE;
}

// This is a quick & dirty top-down recursive-descent table-driven parser
// that I hacked out of another project.  The most generic version of the
// parser can support arbitrary languages, but I've cut this down to just
// enough to be useful for this project without a lot of extra features
// that would not by used here.

// Skip down to 'Phrase definitions' for the command language.

char *indents (int depth)
{
  static char indent[128];
  int i;

  for (i = 0; i < depth * 2 + 1; i++)
    indent[i] = ' ';
  indent[depth * 2 + 1] = '\0';
  return indent;
}

#define DEBUG_PARSER
#define DEBUG_RUNTIME

#include "gcode4.h"		// GENERATED GRAMMAR FROM takeon.c and
				// gcode4.g

#ifndef FALSE
#define FALSE (0!=0)
#define TRUE (0==0)
#endif

char *progname;

int debug_parser = FALSE;
int debug_runtime = FALSE;

// Built-in phrase codes.  Must match with grammar file.
#define TYPE_EOF 0
#define TYPE_CHAR 1
#define TYPE_KEYWORD 2

typedef struct sourceinfo
{				// ATOMS for processed input stream
  char *s;			// string contents
  int l;			// lineno
  int col;			// column
  int t;			// type - tag, char, or keyword
  char *f;			// source or includefile name
} sourceinfo;

int bestparse = -1;		// for error reporting.
char *looking_for = NULL;

// C is the source character token stream
static sourceinfo *c = NULL;
int nextfree = 0, arraysize = 0;
char *onecharstr = NULL;

// A is the Analysis record.  Contents point to a C[] structure when the item is a BIP,
// otherwise the format is [phraseno] [altno] [no-of-subphrases] [subphrases and/or BIPs...]
// eg A[25] = 10 means that A[25] is C[10].
static int *A = NULL;
int next_free_a = 0, a_size = 0;

static void *makespace_ (void *c, int nextfree, int *arraysize, int objsize)
{
  if (nextfree >= *arraysize) {
    *arraysize = (*arraysize * 2) + 1;
    c = (void *) realloc (c, (*arraysize + 1) * objsize);	// 0:arraysize, 
								// inclusive. 
								// eg 0:15,
								// 0:127 etc
    if (c == NULL) {
#ifdef OLDSTYLE
      fprintf (stderr, "parser: %s\n", strerror (errno));
#else
      wprintw (debugline, "\nparser: %s", strerror (errno));
      wrefresh (debugline);
#endif
      exit (errno);
    }
  }
  return c;
}

#define makespace(c, nextfree, arraysize) c = (typeof(c))makespace_(c, nextfree, &arraysize, sizeof(c[0]))

static void stores (char *s, int lineno, int col, int type, char *fname)
{
  makespace (c, nextfree, arraysize);
  c[nextfree].s = s;
  c[nextfree].l = lineno;
  c[nextfree].col = col;
  c[nextfree].f = fname;
  c[nextfree].t = type;
  nextfree++;
}

//static void storec(int ch, int lineno, int col, int type, char *fname) {
//  onecharstr[ch*2] = ch; onecharstr[ch*2+1] = '\0';  // convert char to 1-char string before saving.
//  stores(&onecharstr[ch*2], lineno, col, type, fname);
//}

static int iskeyword (char *s)
{
  int i;

  for (i = 0; i < MAX_KEYWORD; i++)
    if (strcmp (s, keyword[i]) == 0)
      return TRUE;
  return FALSE;
}

//--------------------------------------------------------------------------

FILE *sourcefile;
char *curfile;
int startline = TRUE, lineno = 1, col = 0, ch;

static void line_reconstruction (char *string)
{				// PRE-PROCESS INPUT READY FOR PARSING
// \n and \r removed.  All input restricted to single line ending in EOF token.
// done away with all built-in phrases.  Everything is a character now.  Not even
// handling spaces specially.  They're explicitly in the grammar...
#define fgetc(x) (*string == '\0' ? EOF : *string++)
#define ungetc(ch, x) if (ch != EOF) string--; else
  for (;;) {
  char *character = NULL;
  int nextfreech = 0, chsize = 0;

    ch = fgetc (sourcefile);
    if (ch == EOF)
      break;
    ch &= 255;			// int, positive.
    makespace (character, nextfreech, chsize);
    character[nextfreech++] = ch;
    character[nextfreech] = '\0';
    stores (strdup (character), lineno, col++, TYPE_CHAR, curfile);
    free (character);
    character = NULL;
  }
  // set up a dummy at the end because we sometimes look ahead by 1
  // in the parsing code and don't want to hit uninitialised data.
  makespace (c, nextfree, arraysize);
  c[nextfree].t = TYPE_EOF;
  c[nextfree].s = "<EOF>";
  c[nextfree].l = lineno++;
  c[nextfree].col = col;
#undef fgetc
#undef ungetc
}

//--------------------------------------------------------------------------

int cp = 0;			// code pointer.  Has to be global state.
int ap = 0;			// Analysis record pointer.

static int parse (int pp, int depth)	// depth is only for indentation in
					// diags
{
  int saved_cp, saved_ap, i, gp, alts, match;

  gp = phrase_start[pp - 512 - MAX_BIP];
  alts = gram[gp];

  if (debug_parser) {
#ifdef OLDSTYLE
    fprintf (stderr, "\n%sPhrase %s/%d (%d alternatives) = ", indents (depth),
	     phrasename[pp - 512], pp, alts);
    fflush (stderr);
#else
    wprintw (output, "\n%sPhrase %s/%d (%d alternatives) = ", indents (depth),
	     phrasename[pp - 512], pp, alts);
    wrefresh (output);
    wrefresh (input);
#endif
  }
  gp++;				// gp now points to first element (length) of 
				// first alt

  saved_cp = cp;
  saved_ap = ap;

  for (i = 0; i < alts; i++) {
  int each, phrases = gram[gp++], phrase_count, gap = 0;

    cp = saved_cp;
    ap = saved_ap;

    if (ap + 3 > next_free_a)
      next_free_a = ap + 3;
    makespace (A, next_free_a, a_size);
    A[ap++] = pp;		// record which phrase (could be done outside 
				// loop)
    A[ap++] = i;		// and which alt.

    // Count slots needed.  *Could* be precalculated and stored
    // in the grammar, either embedded (after the ALT) or as a
    // separate table

    for (each = 0; each < phrases; each++)
      if (gram[gp + each] >= 512)
	gap++;
    A[ap++] = gap;		// Count of alts (gap)
    // ap+gap now points to the slot after the space required, which
    // is where the first subphrase will be stored.
    ap = ap + gap;		// recursive subphrases are stored after this 
				// phrase.
    // ap is left updated if successful.

    // successfully parsed phrases are stored in A[saved_ap+3+n]

    if (saved_ap + 3 + gap > next_free_a)
      next_free_a = saved_ap + 3 + gap;
    makespace (A, next_free_a, a_size);

    // this loop is only for diagnostics
    if (debug_parser) {
      wprintw (output, "\n%sAlternative %d: (%d phrases) ", indents (depth),
	       i + 1, phrases);
      for (each = 0; each < phrases; each++) {
  int phrase = gram[gp + each];

	if (phrase < 256) {
	  wprintw (output, " '%c'", phrase);
	} else if (phrase < 512) {
	  wprintw (output, " \"%s\"/%d", keyword[phrase - 256], phrase - 256);
	} else if (phrase < 512 + MAX_BIP) {
	  wprintw (output, " {%s/BIP%d}", phrasename[phrase - 512],
		   BIP[phrase - 512]);
	} else {
	  wprintw (output, " <%s/%d>", phrasename[phrase - 512], phrase);
	}
      }

      if (debug_parser) {
	wprintw (output,
		 " [[cp=%d: '%s'%d '%s'%d '%s'%d '%s'%d '%s'%d ... ]]", cp,
		 (cp < nextfree ? c[cp].s : "EOF"),
		 (cp < nextfree ? c[cp].t : 0),
		 (cp + 1 < nextfree ? c[cp + 1].s : "EOF"),
		 (cp + 1 < nextfree ? c[cp + 1].t : 0),
		 (cp + 2 < nextfree ? c[cp + 2].s : "EOF"),
		 (cp + 2 < nextfree ? c[cp + 2].t : 0),
		 (cp + 3 < nextfree ? c[cp + 3].s : "EOF"),
		 (cp + 3 < nextfree ? c[cp + 3].t : 0),
		 (cp + 4 < nextfree ? c[cp + 4].s : "EOF"),
		 (cp + 4 < nextfree ? c[cp + 4].t : 0)
	 );
      }

      wrefresh (output);
      wrefresh (input);
      // fprintf(stderr, "\n"); fflush(stderr);
    }

    match = TRUE;		// stays true if all subphrases match
    phrase_count = 0;		// only phrases which make it into the A
				// record,
    // i.e. excluding literals and keywords
    for (each = 0; each < phrases; each++) {
  int phrase = gram[gp + each];

      if (debug_parser) {
	wprintw (debugline, "\n%sInput token stream = '%s' '%s' '%s' ...",
		 indents (depth),
		 (cp < nextfree ? c[cp].s : "EOF"),
		 (cp + 1 < nextfree ? c[cp + 1].s : "EOF"),
		 (cp + 2 < nextfree ? c[cp + 2].s : "EOF"));
	wrefresh (debugline);
	wrefresh (input);
      }
      if (cp > bestparse) {
  static char s[128];

	if (phrase < 256) {
	  sprintf (s, "'%c'", phrase);
	} else if (phrase < 512) {
	  sprintf (s, "\"%s\"", keyword[phrase - 256]);
	} else if (phrase < 512 + MAX_BIP) {
	  sprintf (s, "{%s}", phrasename[phrase - 512]);
	} else {
	  sprintf (s, "<%s>", phrasename[phrase - 512]);
	}
	looking_for = s;
	bestparse = cp;
      }

      if (phrase < 256) {
	if (debug_parser)
	  wprintw (output, "\n%s'%c'", indents (depth), phrase);
	if (debug_parser) {
	  wprintw (output, " [[ '%s'%d '%s'%d '%s'%d '%s'%d '%s'%d ... ]]",
		   (cp < nextfree ? c[cp].s : "EOF"),
		   (cp < nextfree ? c[cp].t : 0),
		   (cp + 1 < nextfree ? c[cp + 1].s : "EOF"),
		   (cp + 1 < nextfree ? c[cp + 1].t : 0),
		   (cp + 2 < nextfree ? c[cp + 2].s : "EOF"),
		   (cp + 2 < nextfree ? c[cp + 2].t : 0),
		   (cp + 3 < nextfree ? c[cp + 3].s : "EOF"),
		   (cp + 3 < nextfree ? c[cp + 3].t : 0),
		   (cp + 4 < nextfree ? c[cp + 4].s : "EOF"),
		   (cp + 4 < nextfree ? c[cp + 4].t : 0)
	   );
	}

	if (c[cp].s[0] != phrase)
	  match = FALSE;
	else
	  cp++;
	// Don't record literals
      } else if (phrase < 512) {
	if (debug_parser)
	  wprintw (output, "\n%s\"%s\"/%d", indents (depth),
		   keyword[phrase - 256], phrase - 256);
	if (debug_parser) {
	  wprintw (output, " [[ '%s'%d '%s'%d '%s'%d '%s'%d '%s'%d ... ]]",
		   (cp < nextfree ? c[cp].s : "EOF"),
		   (cp < nextfree ? c[cp].t : 0),
		   (cp + 1 < nextfree ? c[cp + 1].s : "EOF"),
		   (cp + 1 < nextfree ? c[cp + 1].t : 0),
		   (cp + 2 < nextfree ? c[cp + 2].s : "EOF"),
		   (cp + 2 < nextfree ? c[cp + 2].t : 0),
		   (cp + 3 < nextfree ? c[cp + 3].s : "EOF"),
		   (cp + 3 < nextfree ? c[cp + 3].t : 0),
		   (cp + 4 < nextfree ? c[cp + 4].s : "EOF"),
		   (cp + 4 < nextfree ? c[cp + 4].t : 0)
	   );
	}

	if (strcmp (keyword[phrase - 256], c[cp].s) != 0)
	  match = FALSE;
	else
	  cp++;
	// Don't record keywords
      } else if (phrase < 512 + MAX_BIP) {
  int where = ap;		// next phrase to be parsed will be stored at 
				// current 'ap'.
	if (debug_parser)
	  wprintw (output, "\n%s{%s/BIP%d}", indents (depth),
		   phrasename[phrase - 512], BIP[phrase - 512]);
	if (debug_parser) {
	  wprintw (output, " [[ '%s'%d '%s'%d '%s'%d '%s'%d '%s'%d ... ]]",
		   (cp < nextfree ? c[cp].s : "EOF"),
		   (cp < nextfree ? c[cp].t : 0),
		   (cp + 1 < nextfree ? c[cp + 1].s : "EOF"),
		   (cp + 1 < nextfree ? c[cp + 1].t : 0),
		   (cp + 2 < nextfree ? c[cp + 2].s : "EOF"),
		   (cp + 2 < nextfree ? c[cp + 2].t : 0),
		   (cp + 3 < nextfree ? c[cp + 3].s : "EOF"),
		   (cp + 3 < nextfree ? c[cp + 3].t : 0),
		   (cp + 4 < nextfree ? c[cp + 4].s : "EOF"),
		   (cp + 4 < nextfree ? c[cp + 4].t : 0)
	   );
	}

	if (c[cp].t != BIP[phrase - 512])
	  match = FALSE;
	else {
	  A[ap++] = phrase;
	  A[ap++] = 1;
	  A[ap++] = 1;
	  A[ap++] = cp++;
	  A[saved_ap + 3 + phrase_count++] = where;	// Record BIP
	}
      } else {
  int where = ap;		// next phrase to be parsed will be stored at 
				// current 'ap'.
	if (debug_parser)
	  wprintw (output, "\n%s<%s/%d>", indents (depth),
		   phrasename[phrase - 512], phrase);
	if (!parse (phrase, depth + 1))
	  match = FALSE;
	else {
	  A[saved_ap + 3 + phrase_count++] = where;
	}
      }
      if (debug_parser) {
	wprintw (output, "\n%sTried alternative %d: result was %s",
		 indents (depth), each + 1, (match ? "TRUE" : "FALSE"));
      }
      if (!match)
	break;
    }
    gp += phrases;		// move over all phrases, to next alt
    if (match)
      break;
    else if (debug_parser) {
      wprintw (output, "\n%s** Alternative %d FAILED.", indents (depth),
	       i + 1);
    }
    // gp now points to first element (length) of next alt, or start of next
    // phrase
  }
  wrefresh (output);
  wrefresh (input);
  return (match);
}

//--------------------------------------------------------------------------

typedef int TRIP;

//------------------------------------------------------------------------------------

/*
      In previous compilers, I had to write custom code for every tree-based
    optimisation, in order to walk down the tree to the right place to find
    the leaves to be optimised.  In this one, I have a generic tree-walking
    procedure which can walk the entire program, but it can be customised 
    so that it takes action only on specific phrases.  This is possible in
    this design only because each set of subphrases stores the count of
    subphrases before it - thus allowing a generic tree-walking procedure
    that doesn't have to know what each node consists of until it happens
    across a node of the type it is looking for.
 */

static void walk (int ap, int depth, int wanted (int phraseno),
		  void perform (int ap, int before, int depth))
{
  int i;

  if (wanted (A[ap]))
    perform (ap, TRUE, depth);
  for (i = 3; i < A[ap + 2] + 3; i++) {
    if (A[A[ap + i]] >= 512 + MAX_BIP)
      walk (A[ap + i], depth + 1, wanted, perform);
  }
  if (wanted (A[ap]))
    perform (ap, FALSE, depth);

}

static int want_all (int phraseno)
{
  return TRUE;
}

static void print_all (int ap, int before, int depth)
{
  int saved_ap = ap;
  int phrase = A[ap++];
  int alt = A[ap++];
  int phrases = A[ap++];	// defined subphrases
  int i;

  wprintw (output, "\n%s<%s%s/%d>  ", indents (depth), (before ? "" : "/"),
	   phrasename[phrase - 512], alt);
  for (i = 0; i < (3 + phrases); i++) {
    wprintw (output, "A[%d] = %d, ", saved_ap + i, A[saved_ap + i]);
  }
}

//--------------------------------------------------------------------------
int execute (int ap, int depth);

// Global state, only valid for one call before it is overwritten
char *last_units_ptr = "",
 *last_direction_ptr = "",
 *last_decimal_ptr = "", *last_decimal_endp,
 last_num[MAX_LINE],
 last_char[MAX_LINE],
 last_ident[MAX_LINE], last_quantity[MAX_LINE], fname[MAX_LINE], *fname_endp;

static float current_tool_width = 0.0;	// unknown until set.
static int current_feedrate = 0;	// make this global
static int current_spindle_speed = 20000;	// make this global too

int interpret (char *command)
{
  curfile = command;
  bestparse = -1;		// for error reporting.
  looking_for = "<UNKNOWN>";	// 'while looking for <PHRASENAME>' (or
				// literal) ... (Never freed)
  nextfree = 0, arraysize = 0;
  if (c != NULL) {
    free (c);
    c = NULL;
  }
  if (A != NULL) {
    free (A);
    A = NULL;
  }
  next_free_a = 0, a_size = 0;
  cp = ap = 0;
  startline = TRUE;
  if (onecharstr == NULL)
    onecharstr = (char *) malloc (512);

  // if (debug_parser) fprintf(stderr, "Entering line_reconstruction\n");
  line_reconstruction (command);

  if (c[0].t == TYPE_EOF)
    return -2;

  if (!parse (PHRASE_BASE, 0)) {
#ifdef DO_PARSER_DIAGS
    if (bestparse == nextfree) {
      wprintw (output,
	       "\n\"%s\", Line %d, Col %d: Premature end of file while looking for %s\n",
	       curfile, c[bestparse].l, c[bestparse].col + 1, looking_for);
    } else {
  int i;

      wprintw (output,
	       "\n\"%s\", Line %d, Col %d: Syntax error while looking for %s near: \"",
	       curfile, c[bestparse].l, c[bestparse].col + 1, looking_for);
      for (i = bestparse; i < bestparse + 3; i++) {
	if (i == nextfree) {
	  wprintw (output, "<End of line>");
	  break;
	}
	switch (c[i].t) {
	case TYPE_TAG:
	case TYPE_CHAR:
	case TYPE_KEYWORD:
	  wprintw (output, "%s", c[i].s);
	  break;
	}
	wprintw (output, (i == (bestparse + 2) ? " ..." : " "));
      }
      wprintw (output, "\"\n");
      wrefresh (output);
      wrefresh (input);
    }
#endif
    return -1;			// parse error
  }

  if (debug_parser)
    walk (0, 0, want_all, print_all);	// Diags: print final parse tree

#ifdef NEVER
  {
  int i;

    wprintw (output, "\nExecuting: ");
    for (i = 0; i < nextfree; i++) {
      wprintw (output, "%s ", c[i].s);
    }
    wprintw (output, "\n");
    wrefresh (output);
    wrefresh (input);
  }
#endif

  return execute (0, 0);	// 0 (for OK), or runtime error
}

// If the parser were being used in a compiler, we would probably want to build
// an AST here, but for this project it is simple enough to call the execution
// code directly.

int execute (int ap, int depth)
{
  int saved_ap = ap;
  int phrase = A[ap++];		// A[ap] is the phrase number. A[ap+1] is the 
				// alt.
  int alt = A[ap++];		// For consistency, in BIPs, the Alt should
				// always be 1
  // although that may not be the case at the moment :-(
#ifdef NEVER
  int phrases = A[ap++];	// defined subphrases
#else
  ap += 1;
#endif
  // int i;

  // The following ecce command executed on this file will generate
  // commands.g:
  // ecce -c "(v.//\\.s..(v/ /e)?m,k)0;%c" commands.c commands.g
  // May later tweak takeon.c to read from commands.c rather than commands.g
  // thus simplifying build process and becoming more like yacc.
#ifdef DEBUG_RUNTIME
  if (debug_runtime) {
    wprintw (output, "\n%s>> %d: execute(%s)", indents (depth), saved_ap,
	     (A[saved_ap] >= 512 ? phrasename[A[saved_ap] - 512] : "???"));
    wrefresh (output);
    wrefresh (input);
  }
#endif

  switch (phrase) {

//\\ # BIPS (Built-in Phrases)are linked to the type-code returned
//\\ # by the line-reconstruction code (aka lexer)
//\\#
//\\ # These *must* come first.

// See TYPE_* in first page for the values to use.

//\\ B<EOF>=0;
  case P_EOF:
    // fprintf(stderr, "%s", c[A[ap]].s);
    break;

//\\ B<CHAR>=1;
  case P_CHAR:			// Built-in-phrase 2 is "TYPE_CHAR".
    // fprintf(stderr, "%s", c[A[ap]].s);
    strcpy (last_char, c[A[ap]].s);
    break;

//\\#
//\\ # Phrase definitions.  PROGRAM is the main entry point.
//\\#

  case P_PROGRAM:
//\\ P<PROGRAM> =  <SIMPLE> <S> <EOF> ;
    // wprintw(debugline, "\nEntered P_PROGRAM"); wrefresh(debugline);
    execute (A[ap], depth + 1);
    break;

  case P_S:
//\\ P<S> = ' ' <S> | ;
    break;

  case P_INTEGER:
//\\ P<INTEGER> = <DIGIT> <OPT_DIGITS> ;
    execute (A[ap], depth + 1);
    // wprintw(output, "\n%sINTEGER recursing with another call to
    // OPT_DIGITS", indents(depth)); wrefresh(output); wrefresh(input);
    execute (A[ap + 1], depth + 1);
    break;

  case P_SNUM:
//\\ P<SNUM> = <S> <OPT_MINUS> <NUM> <S> ;
    {
      execute (A[ap + 2], depth + 1);
      if (A[A[ap + 1] + 1] == 0) {	// alt of OPT_MINUS
	memmove (last_num + 1, last_num, strlen (last_num) + 1);
	last_num[0] = '-';
      }
    }
    break;

  case P_OPT_MINUS:
//\\ P<OPT_MINUS> = '-' | ;
    // checked by parent
    break;

  case P_NUM:
//\\ P<NUM> = <INTEGER> <OPT_DECIMAL> ;
    {
  static char *p, tmp1[MAX_LINE], tmp2[MAX_LINE];

      *(last_decimal_ptr = last_decimal_endp = tmp1) = '\0';
      execute (A[ap], depth + 1);
      strcpy (last_num, last_decimal_ptr);
      *(last_decimal_ptr = last_decimal_endp = tmp2) = '\0';
      *last_decimal_endp++ = '.';
      *last_decimal_endp = '\0';
      execute (A[ap + 1], depth + 1);
      strcat (last_num, last_decimal_ptr);
      if (*(p = &last_num[strlen (last_num) - 1]) == '.')
	*p = '\0';		// strip trailing . if "xxx." 
    }
    break;

  case P_OPT_DECIMAL:
//\\ P<OPT_DECIMAL> = '.' <OPT_DIGITS> | ;
    if (alt == 0) {
      // wprintw(output, "\n%sOPT_DECIMAL recursing with a call to
      // OPT_DIGITS", indents(depth)); wrefresh(output); wrefresh(input);
      execute (A[ap], depth + 1);
    } else {
      // wprintw(output, "\n%sOPT_DECIMAL: NON-ZERO ALT %d", indents(depth),
      // alt); wrefresh(output); wrefresh(input);
    }
    break;

  case P_OPT_DIGITS:
//\\ P<OPT_DIGITS> = <DIGIT> <OPT_DIGITS> | ;
    if (alt == 0) {
      execute (A[ap], depth + 1);
      // wprintw(output, "\n%sOPT_DIGITS recursing with another call to
      // OPT_DIGITS", indents(depth)); wrefresh(output); wrefresh(input);
      execute (A[ap + 1], depth + 1);
    } else {
      // wprintw(output, "\n%sOPT_DIGITS: NON-ZERO ALT %d", indents(depth),
      // alt); wrefresh(output); wrefresh(input);
    }
    break;

  case P_DIGIT:
//\\ P<DIGIT> = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' ;
    *last_decimal_endp++ = alt + '0';
    *last_decimal_endp = '\0';
    // wprintw(output, "\n%sDIGIT: ALT %d", indents(depth), alt);
    // wrefresh(output); wrefresh(input);
    break;

  case P_OPT_SUBJECT:
//\\ P<OPT_SUBJECT> = <FILENAME> | ;
    break;

  case P_HELP:
//\\ P<HELP> = 'help' <S> <OPT_SUBJECT> ;
    wprintw (output,
	     "\nYou may enter any GCODE commands, immediate commands, or extended commands:");
    wprintw (output,
	     "\n\nA jog command:  left/right/nearer/farther/up/down <distance> [units: mm or in]");
    wprintw (output, "\nuse in/mm");
    wprintw (output, "\nfeedrate <rpm>");
    wprintw (output, "\nspindle speed <rpm>");
    wprintw (output, "\nspindle on/off");
    wprintw (output, "\nsend <filename>");
    wprintw (output,
	     "\ncylinder diameter/radius <distance> depth <distance>");
    wprintw (output, "\npocket from n,n,n to n,n,n step n,n,n [toolwidth n]");
    wprintw (output, "\ndrill <distance>");
    wprintw (output, "\nprobe");
    wprintw (output, "\ngpio");
    wprintw (output, "\nhome");
    wprintw (output, "\nreset xy");
    wprintw (output, "\ngoto x,y");
    wprintw (output, "\n[no]limits");
    wprintw (output, "\n");
    wrefresh (output);
    wrefresh (input);
    break;

  case P_SIMPLE:
    // wprintw(debugline, "\nEntered P_SIMPLE"); wrefresh(debugline);
//\\ P<SIMPLE> = <JOG> | <CANNED> | <USE> <S> <UNITS> | <TESTCODE> | <POCKET> | <SPINDLE> | <FEEDRATE> | <SENDER> | <HELP> | 'laser' ' ' <LASER> ;
    if (alt == 0) {		// jog
      execute (A[ap], depth + 1);
    } else if (alt == 1) {	// canned
      execute (A[ap], depth + 1);
    } else if (alt == 2) {	// use units
  char *u[] = { "mm", "in" };
  int units = A[ap + 2];

      wprintw (output, "\nDefault units: %s\nok", u[A[units + 1]]);	// 
      wrefresh (output);
      wrefresh (input);
      gcode_default_units (u[A[A[ap + 1] + 1]]);
    } else if (alt == 3) {	// testcode
      execute (A[ap], depth + 1);
    } else if (alt == 4) {	// pocket
      execute (A[ap], depth + 1);
    } else if (alt == 5) {	// spindle
      execute (A[ap], depth + 1);
    } else if (alt == 6) {	// feedrate
      execute (A[ap], depth + 1);
    } else if (alt == 7) {	// sender
      execute (A[ap], depth + 1);
    } else if (alt == 8) {	// help
      execute (A[ap], depth + 1);
    } else if (alt == 9) {	// laser
      execute (A[ap], depth + 1);
    }
    break;

  case P_SENDER:		// unfortunately due to super-hacky parser,
				// can't have filenames with spaces in them!
//\\ P<SENDER> = 'send' <S> <FILENAME> ;
    fname_endp = fname;
    execute (A[ap + 1], depth + 1);
    *fname_endp = '\0';
    wprintw (debugline, "\nSending %s", fname);
    wrefresh (debugline);
    wrefresh (input);
    gcode_sendfile (fname);
    break;

  case P_FILENAME:		// everything up to the end of line...
//\\ P<FILENAME> = <CHAR> <FILENAME> |  ;
    if (alt == 0) {
      execute (A[ap], depth + 1);
      *fname_endp++ = *last_char;
      execute (A[ap + 1], depth + 1);
    }
    break;

  case P_FEEDRATE:
//\\ P<FEEDRATE> = 'feedrate' <S> <INTEGER> ;
    {
  static char tmp1[MAX_LINE];

      *(last_decimal_ptr = last_decimal_endp = tmp1) = '\0';
      execute (A[ap + 1], depth + 1);
      strcpy (last_num, last_decimal_ptr);
      current_feedrate = atoi (last_num);
      gcode ("F%d", current_feedrate);
    }
    break;

  case P_SPINDLE:
//\\ P<SPINDLE> = 'spindle' <S> 'speed' <S> <INTEGER> | 'spindle' <S> 'on' | 'spindle' <S> 'off' ;
    {
  static char tmp1[MAX_LINE];

      switch (alt) {
      case 0:			// "spindle" <S> "speed" <S> <INTEGER>
	*(last_decimal_ptr = last_decimal_endp = tmp1) = '\0';
	execute (A[ap + 2], depth + 1);
	strcpy (last_num, last_decimal_ptr);
	current_spindle_speed = atoi (last_num);
	if (current_spindle_speed > 10000) {
	  gcode ("$30=20000");	// just in case...
	  gcode ("S4000");
	  gcode ("G4 P.25");
	  gcode ("S6000");
	  gcode ("G4 P.25");
	  gcode ("S8000");
	  gcode ("G4 P.25");
	  gcode ("S10000");
	  gcode ("G4 P.25");
	}
	gcode ("S%d", current_spindle_speed);
	break;
      case 1:			// "spindle" <S> "on"
	// I'm using the 20,000 RPM spindle so ramp it up slowly. ish.
	// Have already set $30=20000 parameter to say that 20000 == 100%
	// G4 delay?
	if (current_spindle_speed > 10000) {
	  gcode ("$30=20000");	// just in case...
	  gcode ("S4000");
	  gcode ("M3");
	  gcode ("G4 P.25");
	  gcode ("S6000");
	  gcode ("M3");
	  gcode ("G4 P.25");
	  gcode ("S8000");
	  gcode ("M3");
	  gcode ("G4 P.25");
	  gcode ("S10000");
	  gcode ("M3");
	  gcode ("G4 P.25");
	}
	gcode ("S%d", current_spindle_speed);
	gcode ("M3");
	break;
      case 2:			// "spindle" <S> "off"
	gcode ("M5");
	break;
      }
    }
    break;

#define child(parent, n) (A[(parent)+(n)]+3)

  case P_POCKET:
//\\ P<POCKET> = 'pocket' <SNUM> ',' <SNUM> ',' <SNUM> 'to' <SNUM> ',' <SNUM> ',' <SNUM> 'step' <SNUM> ',' <SNUM> ',' <SNUM> <OPT_TOOLWIDTH> ;
    {
  float from_x, from_y, from_z, to_x, to_y, to_z, step_x, step_y, step_z;

      execute (A[ap], depth + 1);
      from_x = (float) atof (last_num);
      execute (A[ap + 1], depth + 1);
      from_y = (float) atof (last_num);
      execute (A[ap + 2], depth + 1);
      from_z = (float) atof (last_num);

      execute (A[ap + 3], depth + 1);
      to_x = (float) atof (last_num);
      execute (A[ap + 4], depth + 1);
      to_y = (float) atof (last_num);
      execute (A[ap + 5], depth + 1);
      to_z = (float) atof (last_num);

      execute (A[ap + 6], depth + 1);
      step_x = (float) atof (last_num);
      execute (A[ap + 7], depth + 1);
      step_y = (float) atof (last_num);
      execute (A[ap + 8], depth + 1);
      step_z = (float) atof (last_num);

      current_tool_width = 0.0;
      execute (A[ap + 9], depth + 1);
      wprintw (debugline,
	       "\nCutting a pocket from (%f,%f,%f) to (%f,%f,%f) in steps of (%f,%f,%f)...\n",
	       from_x, from_y, from_z, to_x, to_y, to_z, step_x, step_y,
	       step_z);
      wrefresh (debugline);
      wrefresh (input);
      // coords are absolute, ie top z is > bottom z - but steps are all
      // positive, e.g. z step might be 0.2mm - NOT -0.2mm
      // I know this is all inconsistent and I *will* clean it up after I've
      // finished exploring the interface...
      cnc_pocket (from_x, from_y, from_z, to_x, to_y, to_z, step_x, step_y, step_z, current_tool_width, TRUE);	// TO 
														// DO: 
														// add 
														// dogbone 
														// parameter 
														// to 
														// syntax
    }
    break;

//\\ P<OPT_TOOLWIDTH> = 'toolwidth' <S> <NUM> | ;
  case P_OPT_TOOLWIDTH:
    if (alt == 0) {
      execute (A[ap + 1], depth + 1);
      current_tool_width = (float) atof (last_num);
    }
    break;

//\\ P<TESTCODE> = 'goto' <S> <NUM> <S> ',' <S> <NUM> ;
    // add an extra optional Z? 
  case P_TESTCODE:
    {
  char X[MAX_LINE], Y[MAX_LINE];

      execute (A[ap + 1], depth + 1);
      strcpy (X, last_num);
      execute (A[ap + 4], depth + 1);
      strcpy (Y, last_num);
      gcode_gotoxy ((float) atof (X), (float) atof (Y));
    }
    break;

  case P_JOG:
//\\ P<JOG> = <DIRECTION> <S> <QUANTITY> <S> <OPT_UNITS> ;
    // Units cannot be omitted unless a default unit has already been
    // selected.
    // wprintw(debugline, "\nEntered P_JOG for real"); wrefresh(debugline);
    {
      execute (A[ap], depth + 1);
      execute (A[ap + 2], depth + 1);
      execute (A[ap + 4], depth + 1);
      gcode_jog (last_direction_ptr, last_quantity, last_units_ptr);
    }
    break;

  case P_USE:
//\\ P<USE> = 'use' | 'units' ;
    break;

  case P_OPT_UNITS:
//\\ P<OPT_UNITS> = <UNITS> | ;
    if (alt == 0) {
      execute (A[ap], depth + 1);
    }
    break;

  case P_DIRECTION:
//\\ P<DIRECTION> = <UP> | <DOWN> | <LEFT> | <RIGHT> | 'farther' | 'nearer' ;
    // add closer, near, far, away?
    {
  char *directions[6] =
   { "up", "down", "left", "right", "farther", "nearer" };
      last_direction_ptr = directions[alt];
    }
    break;

//\\ P<UP> = 'up' | 'u' ;
  case P_UP:
    break;

//\\ P<DOWN> = 'down' | 'd' ;
  case P_DOWN:
    break;

//\\ P<LEFT> = 'left' | 'l' ;
  case P_LEFT:
    break;

//\\ P<RIGHT> = 'right' | 'r' ;
  case P_RIGHT:
    break;

  case P_QUANTITY:
//\\ P<QUANTITY> = <NUM> ;
    execute (A[ap], depth + 1);
    strcpy (last_quantity, last_num);
    break;

  case P_UNITS:
//\\ P<UNITS> = <mm> | <in> ;
    execute (A[ap], depth + 1);
    break;

  case P_mm:
//\\ P<mm> = 'mm' | 'mms' | 'millimeters' | 'millimetres' ;
    last_units_ptr = "mm";
    break;
  case P_in:
//\\ P<in> = 'in' | 'ins' | 'inches' ;
    last_units_ptr = "in";
    break;

  case P_SET_ORIGIN:
//\\ P<SET_ORIGIN> = 'zeroxy' | 'resetxy' | 'reset' <S> 'xy' | 'reset' <S> 'origin' ;
    // just in case I forget which command I defined...
    break;

  case P_CYLINDER:
//\\ P<CYLINDER> = 'cylinder' <S> 'diameter' <S> <NUM> <S> 'depth' <S> <NUM> | 'cylinder' <S> 'radius' <S> <NUM> <S> 'depth' <S> <NUM> ;
    if (alt == 0) {
      {				// cylinder
  float diameter, cutting_depth;

	execute (A[ap + 2], depth + 1);
	diameter = (float) atof (last_num);
	execute (A[ap + 5], depth + 1);
	cutting_depth = (float) atof (last_num);
	cnc_cylinder (diameter / 2, cutting_depth);
	// as of 8/9/2020, calling these procedures executes them OK but does 
	// not return to command loop.
	// Looking into why at the moment...
      }
    } else if (alt == 1) {
      {				// cylinder
  float radius, cutting_depth;

	execute (A[ap + 2], depth + 1);
	radius = (float) atof (last_num);
	execute (A[ap + 5], depth + 1);
	cutting_depth = (float) atof (last_num);
	cnc_cylinder (radius, cutting_depth);
      }
    }
    break;

  case P_LASER:
//\\ P<LASER> = 'test' | 'focus' | 'strength' ;
    if (alt == 0) {
      // wprintw(debugline, "\nEntered 'laser test'"); wrefresh(debugline);
      laser_mode();
      wprintw(output, "\nLASER TEST\nok"); wrefresh(output); wrefresh(input);
      laser_test();
      cnc_mode();
    } else if (alt == 1) {
      laser_mode();
      wprintw(output, "\nLASER FOCUS CHART AT 0,0\nok"); wrefresh(output); wrefresh(input);
      laser_focus();
      cnc_mode();
    } else if (alt == 2) {
      laser_mode();
      wprintw(output, "\nLASER CUT CHART AROUND Y=0\nok"); wrefresh(output); wrefresh(input);
      laser_strength();
      cnc_mode();
    }
    break;
    
  case P_CANNED:
//\\ P<CANNED> = 'probe' | <CYLINDER> | 'gpio' | 'home' | <SET_ORIGIN> | 'limits' | 'nolimits' | 'drill' <S> <NUM> | 'test' <SNUM> ',' <SNUM>  | <PITREX>;
    if (alt == 0) {
      // probe
      gcode_probe ();
    } else if (alt == 1) {
      // cylinder
      execute (A[ap + 0], depth + 1);
    } else if (alt == 2) {
      // gpio
      cnc_hammer_jig ();
    } else if (alt == 3) {
      // home
      // enable limits:
      gcode ("$20=1");
      gcode ("$21=1");
      gcode ("$22=1");
      // home:
      gcode ("$H");
      // reset x y 0 0:
      gcode ("G10 L2 P1 X0 Y0");
      gcode ("G92 X0 Y0");
      // turn off limits:
      gcode ("$20=0");
      gcode ("$21=0");
      gcode ("$22=0");
    } else if (alt == 4) {
      // zeroxy - not sure which is the best way to do this? Will try both...
      gcode ("G10 L2 P1 X0 Y0");
      gcode ("G92 X0 Y0");
    } else if (alt == 5) {
      // limits
      gcode ("$20=1");
      gcode ("$21=1");
      gcode ("$22=1");
    } else if (alt == 6) {
      // nolimits
      gcode ("$20=0");
      gcode ("$21=0");
      gcode ("$22=0");
    } else if (alt == 7) {
      // drill <S> <NUM>
  static char tmp1[MAX_LINE];
  float drill_depth;

      *(last_decimal_ptr = last_decimal_endp = tmp1) = '\0';
      execute (A[ap + 1], depth + 1);
      strcpy (tmp1, last_num);
      drill_depth = (float) atof (tmp1);
      wprintw (output, "\ndrill: %fmm", drill_depth);
      wrefresh (output);
      wrefresh (input);
      cnc_hammer_jig_peck_drill (drill_depth);
    } else if (alt == 8) {
      // test <SNUM> ',' <SNUM>
      {
  char num1[MAX_LINE], num2[MAX_LINE];

	execute (A[ap], depth + 1);
	strcpy (num1, last_num);
	execute (A[ap + 1], depth + 1);
	strcpy (num2, last_num);
	wprintw (output, "\ntest: %f %f", (float) atof (num1),
		 (float) atof (num2));
	wrefresh (output);
	wrefresh (input);
      }
    } else if (alt == 9) {
	execute (A[ap], depth + 1);
    }
    break;

  case P_PITREX:
//\\#
//\\P<PITREX> = 'pitrex'  ' '  'mailer' | 'pitrex' <CART>;
    if (alt == 0) {
      // cut 3 rectangles of coroplast (covering platen) to
      // ship pitrex cartridge in (two halves)
      cnc_pitrex_mailer();
    } else if (alt == 1) {
      // cut hole in standard vectrex case for pitrex
      cnc_pitrex_cartridge();
    }
    break;

  case P_CART:
//\\#
//\\P<CART> = ' '  'cart' | ' '  'hole' | ;
    break;

//\\#
//\\ E
//\\ # 'E' is end of grammar.  Everything after this is ignored.
  default:
    wprintw (debugline,
	     "\n*** Internal error at line %d.  ap=%d  phrase=%d\n", __LINE__,
	     ap, phrase);
    wrefresh (debugline);
    exit (2);
  }

#ifdef DEBUG_RUNTIME
  if (debug_runtime) {
    wprintw (output, "\n%s<< %d: execute(%s)", indents (depth), saved_ap,
	     (A[saved_ap] >= 512 ? phrasename[A[saved_ap] - 512] : "???"));
    wrefresh (output);
    wrefresh (input);
  }
#endif

  return 0;
}

void append_error (char *line, int gcode_errnum)
{
  switch (gcode_errnum) {
  case 1:
    strcat (line,
	    " G-code words consist of a letter and a value. Letter was not found.");
    break;
  case 2:
    strcat (line,
	    " Numeric value format is not valid or missing an expected value.");
    break;
  case 3:
    strcat (line,
	    " Grbl ‘$’ system command was not recognized or supported.");
    break;
  case 4:
    strcat (line, " Negative value received for an expected positive value.");
    break;
  case 5:
    strcat (line, " Homing cycle is not enabled via settings.");
    break;
  case 6:
    strcat (line, " Minimum step pulse time must be greater than 3usec.");
    break;
  case 7:
    strcat (line,
	    " EEPROM read failed. Reset and restored to default values.");
    break;
  case 8:
    strcat (line,
	    " Grbl ‘$’ command cannot be used unless Grbl is IDLE. Ensures smooth operation during a job.");
    break;
  case 9:
    strcat (line, " G-code locked out during alarm or jog state");
    break;
  case 10:
    strcat (line,
	    " Soft limits cannot be enabled without homing also enabled.");
    break;
  case 11:
    strcat (line,
	    " Max characters per line exceeded. Line was not processed and executed.");
    break;
  case 12:
    strcat (line,
	    " [Compile Option] Grbl ‘$’ setting value exceeds the maximum step rate supported.");
    break;
  case 13:
    strcat (line,
	    " Safety door detected as opened and door state initiated.");
    break;
  case 14:
    strcat (line,
	    " [Grbl-Mega Only] Build info or startup line exceeded EEPROM line length limit.");
    break;
  case 15:
    strcat (line, " Jog target exceeds machine travel. Command ignored.");
    break;
  case 16:
    strcat (line,
	    " Jog command with no ‘=’ or contains prohibited g-code.");
    break;
  case 20:
    strcat (line, " Unsupported or invalid g-code command found in block.");
    break;
  case 21:
    strcat (line,
	    " More than one g-code command from same modal group found in block.");
    break;
  case 22:
    strcat (line, " Feed rate has not yet been set or is undefined.");
    break;
  case 23:
    strcat (line, " G-code command in block requires an integer value.");
    break;
  case 24:
    strcat (line,
	    " Two G-code commands that both require the use of the XYZ axis words were detected in the block.");
    break;
  case 25:
    strcat (line, " A G-code word was repeated in the block.");
    break;
  case 26:
    strcat (line,
	    " A G-code command implicitly or explicitly requires XYZ axis words in the block, but none were detected.");
    break;
  case 27:
    strcat (line,
	    " N line number value is not within the valid range of 1 – 9,999,999.");
    break;
  case 28:
    strcat (line,
	    " A G-code command was sent, but is missing some required P or L value words in the line.");
    break;
  case 29:
    strcat (line,
	    " Grbl supports six work coordinate systems G54-G59. G59.1, G59.2, and G59.3 are not supported.");
    break;
  case 30:
    strcat (line,
	    " The G53 G-code command requires either a G0 seek or G1 feed motion mode to be active. A different motion was active.");
    break;
  case 31:
    strcat (line,
	    " There are unused axis words in the block and G80 motion mode cancel is active.");
    break;
  case 32:
    strcat (line,
	    " A G2 or G3 arc was commanded but there are no XYZ axis words in the selected plane to trace the arc.");
    break;
  case 33:
    strcat (line,
	    " The motion command has an invalid target. G2, G3, and G38.2 generates this error, if the arc is impossible to generate or if the probe target is the current position.");
    break;
  case 34:
    strcat (line,
	    " A G2 or G3 arc, traced with the radius definition, had a mathematical error when computing the arc geometry. Try either breaking up the arc into semi-circles or quadrants, or redefine them with the arc offset definition.");
    break;
  case 35:
    strcat (line,
	    " A G2 or G3 arc, traced with the offset definition, is missing the IJK offset word in the selected plane to trace the arc.");
    break;
  case 36:
    strcat (line,
	    " There are unused, leftover G-code words that aren’t used by any command in the block.");
    break;
  case 37:
    strcat (line,
	    " The G43.1 dynamic tool length offset command cannot apply an offset to an axis other than its configured axis. The Grbl default axis is the Z-axis.");
    break;
  case 38:
    strcat (line, " An invalid tool number sent to the parser");
    break;
  default:
    break;
  }
  wprintw (debugline, "\n%s", line);
  wrefresh (debugline);
  wrefresh (input);
  /* 
     ALARM:1: strcat(line, " Hard limit triggered. Machine position is likely 
     lost due to sudden and immediate halt. Re-homing is highly
     recommended."); ALARM:2: strcat(line, " G-code motion target exceeds
     machine travel. Machine position safely retained. Alarm may be
     unlocked."); ALARM:3: strcat(line, " Reset while in motion. Grbl cannot
     guarantee position. Lost steps are likely. Re-homing is highly
     recommended."); ALARM:4: strcat(line, " Probe fail. The probe is not in
     the expected initial state before starting probe cycle, where G38.2 and
     G38.3 is not triggered and G38.4 and G38.5 is triggered."); ALARM:5:
     strcat(line, " Probe fail. Probe did not contact the workpiece within
     the programmed travel for G38.2 and G38.4."); ALARM:6: strcat(line, "
     Homing fail. Reset during active homing cycle."); ALARM:7: strcat(line,
     " Homing fail. Safety door was opened during active homing cycle.");
     ALARM:8: strcat(line, " Homing fail. Cycle failed to clear limit switch
     when pulling off. Try increasing pull-off setting or check wiring.");
     ALARM:9: strcat(line, " Homing fail. Could not find limit switch within
     search distance. Defined as 1.5 * max_travel on search and 5 * pulloff
     on locate phases.");

     Hold:0: strcat(line, " Hold complete. Ready to resume."); Hold:1:
     strcat(line, " Hold in-progress. Reset will throw an alarm.");

     Door:0: strcat(line, " Door closed. Ready to resume."); Door:1:
     strcat(line, " Machine stopped. Door still ajar. Can’t resume until
     closed."); Door:2: strcat(line, " Door opened. Hold (or parking retract) 
     in-progress. Reset will throw an alarm."); Door:3: strcat(line, " Door
     closed and resuming. Restoring from park, if applicable. Reset will
     throw an alarm."); */
}

int gcode (char *format, ...)
{				// FALSE on error
  char commands[MAX_LINE], line[MAX_LINE];
  va_list argList;
  char *s;
  int i;
  size_t numRead;
  char c;

  va_start (argList, format);
  vsnprintf (commands, MAX_LINE, format, argList);
  va_end (argList);

  wprintw (output, "\n# %s", commands);
  wrefresh (output);
  wrefresh (input);

  strcat (commands, "\n");
  if (write (Data_To_CNC_Fd, commands, strlen (commands)) !=
      strlen (commands))
    fatal ("partial/failed write (To_CNC)");

  // Now we read everything returned up to the 'ok' or 'error' response.
  for (;;) {
    // readline();
    s = line;
    i = 1;
    for (;;) {
#ifdef ZERO_EVERY_TIME
      FD_ZERO (&inFds);
      FD_SET (STDIN_FILENO, &inFds);
      FD_SET (Data_From_CNC_Fd, &inFds);
#endif
      if (select (Data_From_CNC_Fd + 1, &inFds, NULL, NULL, NULL) == -1) {
	if (errno != EINTR)
	  errExit ("select");	// EINTR happens on window resize
      }

      do {
      } while (!FD_ISSET (Data_From_CNC_Fd, &inFds));	/* CNC --> screen */
      numRead = read (Data_From_CNC_Fd, &c, 1);
      if (numRead <= 0)
	break;
      if (c == '\r')
	continue;
      if (c == '\n')
	break;
      *s++ = c;
      i++;
      if (i == MAX_LINE)
	break;
    }
    *s = '\0';
    if (strncmp (line, "error:", 6) == 0) {
      gcode_errnum = atoi (line + 6);
      append_error (line, gcode_errnum);
    }
    wprintw (output, "\n%s", line);
    wrefresh (output);
    wrefresh (input);
    if (strcmp (line, "ok") == 0)
      break;

    if ((strcmp (line, "ok") == 0)
	|| (strncasecmp (line, "error:", 6) == 0)
	|| (strncasecmp (line, "ALARM:", 6) == 0)
	|| (strncasecmp (line, "hold:", 5) == 0)
	|| (strncasecmp (line, "door:", 5) == 0)
	|| (strcmp (line, "[MSG:Reset to continue]") == 0)
     ) {
      return (strcmp (line, "ok") == 0);
    }
  }
  return TRUE;
}

int gcode_sendfile (char *fname)
{
  char codeline[MAX_LINE], *code_endp;
  FILE *gc = fopen (fname, "r");

  if (gc == NULL) {
    // bad! Print diag.
    wprintw (output, "\nCannot open file '%s' - %s", fname, strerror (errno));
    wrefresh (debugline);
    wrefresh (output);
    wrefresh (input);
    return FALSE;
  }
  code_endp = codeline;
  for (;;) {
  int c;

    c = fgetc (gc);
    if (c == '\r')
      continue;
    if (c == EOF)
      break;
    if (c == '\n') {
      *code_endp = '\0';
      if (!gcode ("%s", codeline)) {
	fclose (gc);
	return FALSE;
      }
      code_endp = codeline;
    } else {
      *code_endp++ = c;
    }
  }
  fclose (gc);
  return TRUE;
}

int gcode_gotoxy (float x, float y)
{
  return gcode ("G0 X%f Y%f", x, y);
}

int gcode_cuttoxy (float x, float y)
{
  return gcode ("G1 X%f Y%f", x, y);
}

int gcode_gotoz (float z)
{
  return gcode ("G0 Z%f", z);
}

int gcode_cuttoz (float z)
{
  return gcode ("G1 Z%f", z);
}

int gcode_spindle_on (void)
{
  if (current_spindle_speed > 10000) {
    // gcode("$30=20000"); // doesn't work unless GRBL is idle.
    gcode ("S4000");
    gcode ("M3");
    gcode ("G4 P.25");
    gcode ("S6000");
    gcode ("M3");
    gcode ("G4 P.25");
    gcode ("S8000");
    gcode ("M3");
    gcode ("G4 P.25");
    gcode ("S10000");
    gcode ("M3");
    gcode ("G4 P.25");
  }
  gcode ("S%d", current_spindle_speed);
  return gcode ("M3");
}

int gcode_spindle_off (void)
{
  return gcode ("M5");
}

void laser_mode(void) {
  // The hookup: https://www.facebook.com/photo.php?fbid=10224223205907555&set=p.10224223205907555&type=3&theater
  // wait until '?' status shows 'idle'
  // probably will be already, at start of job
  gcode("$30=1000"); // 1000 is full laser power
  gcode("$31=0");    // min laser power 0
  gcode("$32=1");    // laser-mode enabled
}

void cnc_mode(void) {
  // wait until '?' status shows 'idle'
  // unlikely to be so at end of job
  gcode("G4 P0.7");   // 0.7s pause - G4 P200 (Dwell for a moment - [P<time in ms>] [S<time in sec>]) <-- not true.  P in seconds
  gcode("$32=0");     // laser mode disabled

  gcode("$30=20000"); // max spindle speed is 20000
  gcode("$31=0");     // min spindle speed is 0
}

void laser_test(void) {
  gcode("G94"); // perhaps redundant - speed in units per time step, not pulses per distance step, maybe
  gcode("G17"); // also likely redundant... - XY plane
  gcode("G21"); // use mm

  gcode("G91"); // Relative coords

  gcode("M3");  // PWM mode is 'Constant Power Laser On' vs M4 - M4 works really well at preventing overburns,
                // but with slow accelerating rigs, you may get unburnt areas
  gcode("F300");// burn movement speed
  gcode("S50");// 5% power
  
  // blip the laser at low power just as a confidence test for now
  gcode("G1 Y10"); // 1mm ticks
  gcode("G0 Y-10"); // 1mm ticks
  // G4 P200 (Dwell for a moment - [P<time in ms>] [S<time in sec>])
    
  gcode("G90");      // Absolute co-ords
  // gcode("M2"); // terminate and reset defaults
}

void laser_focus(void) {
  int i;
  // https://www.facebook.com/groups/SainSmart.GenmitsuCNC/permalink/2573475146296637/
  gcode("G94"); // perhaps redundant - speed in units per time step, not pulses per distance step, maybe
  gcode("G17"); // also likely redundant... - XY plane
  gcode("G21"); // use mm

  gcode("G90"); gcode("G0 X0 Y0 "); // absolute move to 0,0 origin to draw ruler
  gcode("G91"); // Relative coords

  gcode("M3");  // PWM mode is 'Constant Power Laser On' vs M4 - M4 works really well at preventing overburns,
                // but with slow accelerating rigs, you may get unburnt areas
                // M3 is really 'laser on while moving with G1'

  gcode("F300");// burn movement speed
  gcode("S350");// 35% power for drawing ticks
  
  gcode("(Draw Reference Marks)");

  for (i = 0; i < 5; i++) {
    gcode("G1 Y1"); // 1mm ticks
    gcode("G0 X2 Y-1"); // 2mm apart
  }
  gcode("G1 Y3"); // big tick in the middle
  gcode("G0 X2 Y-3");
  for (i = 0; i < 5; i++) {
    gcode("G1 Y1"); // 1mm ticks
    gcode("G0 X2 Y-1"); // 2mm apart
  }

  gcode("G90"); gcode("G0 X0 Y4"); gcode("G91"); // absolute move to reposition for focus test

  gcode("G0 Z-5");  // 5mm below start pos, 5 ticks left of center
  gcode("S350");    // 35% power for drawing focus test bars
  for (i = 0; i < 10; i++) {
    gcode("G1 Y10");
    gcode("G0 X2 Y-10 Z1"); // 10 different heights, 1mm up for each line, lines 2mm apart
  }
  gcode("G1 Y10");
  gcode("G0 Z-5");   // drop back 5mm to original Z, corresp to center tick

  gcode("M5");       // laser off

  gcode("G90");      // Absolute co-ords
  gcode("G0 X0 Y0"); // return to origin
  // gcode("M2"); // terminate and reset defaults
  gcode("(END OF PROGRAM)");
}

void laser_strength(void) {
  int i, burn;
  // https://www.facebook.com/groups/SainSmart.GenmitsuCNC/permalink/2573475146296637/
  gcode("G94"); // perhaps redundant - speed in units per time step, not pulses per distance step, maybe
  gcode("G17"); // also likely redundant... - XY plane
  gcode("G21"); // use mm

  gcode("G90"); gcode("G0 X-100 Y0"); // absolute move to 0,0 origin to draw ruler
  gcode("G91"); // Relative coords

  // try M4 instead of M3 - see if itburns cardboard rather than just scoring it...
  gcode("M4");  // PWM mode is 'Constant Power Laser On' vs M4 - M4 works really well at preventing overburns,
                // but with slow accelerating rigs, you may get unburnt areas
                // M3 is really 'laser on while moving with G1'

  /*
      cardboard shipping box (monster sodas):
      S900 or less did not break surface at F300
      for 3 levels of grey use S400 S650 S900 at F300
      S1000
        F100 - doesn't cut through
          90 - can be pushed through, no charring on back
          70 - cut out, including paper label on back, light charring on back
          60 - pushed out, including paper label on back, charring on back
          50 - fell out, including paper label on back, heavy charring on back

      Still to test - coroplast
   */  
  for (burn = 100; burn >= 50; burn -= 10) {
    gcode("F%0d", burn);// burn movement speed
    gcode("S1000"); // anything less doesn't cut cardboard
    // still to try... lower depth and cut again...

    // burn a 1cm square at increasing strengths
    gcode("G1 Y10"); // burn
    gcode("G1 X10"); // burn
    gcode("G1 Y-10"); // burn
    gcode("G1 X-10"); // burn
    
    gcode("G0 X20"); // move
  }

  gcode("M5");       // laser off
  gcode("F500");
  gcode("G90");      // Absolute co-ords
  gcode("G0 X0 Y0"); // return to origin
}

void cnc_rectangle (float from_x, float from_y,
		    float to_x, float to_y,
		    float step_x, float step_y, int want_dogbones)
{				// dogbones currently ignored
  // starts at from_x,from_y - don't care (for now) where it finishes
  float x, y;

  if (to_x - from_x > to_y - from_y) {	// fewer cuts assuming step_x==step_y
    y = from_y;
    for (;;) {
      gcode_cuttoxy (to_x, y);	// across
      if (y == to_y)
	break;
      y += step_y;
      if (y > to_y) {
	y = to_y;
      }
      gcode_cuttoxy (to_x, y);	// up
      gcode_cuttoxy (from_x, y);	// back
      if (y == to_y)
	break;
      y += step_y;
      if (y > to_y) {
	y = to_y;
      }
      gcode_cuttoxy (from_x, y);	// up
    }
  } else {
    // fill in after testing
    x = from_x;
    for (;;) {
      gcode_cuttoxy (x, to_y);	// across
      if (x == to_x)
	break;
      x += step_x;
      if (x > to_x) {
	x = to_x;
      }
      gcode_cuttoxy (x, to_y);	// up
      gcode_cuttoxy (x, from_y);	// back
      if (x == to_x)
	break;
      x += step_x;
      if (x > to_x) {
	x = to_x;
      }
      gcode_cuttoxy (x, from_y);	// up
    }
  }
}

void cnc_pocket (float from_x, float from_y, float from_z,
		 float to_x, float to_y, float to_z,
		 float step_x, float step_y, float step_z,
		 float current_tool_width, int want_dogbones)
{				// TO DO: add dogbones boolean
  // TO DO: step_z should be small.  Do a sanity check on it.
  float z;

  if (current_tool_width == 0.0) {
    // TO DO: ask user for tool width
    current_tool_width = DEFAULT_TOOL_WIDTH;	// largest of the sainsmart
						// end mills
  }
  // force ordering so that inset for toolwidth comes out OK!
  if (from_x > to_x) {		// from_x has to be left of to_x
  float tmp = to_x;

    to_x = from_x;
    from_x = tmp;
  }
  if (from_y > to_y) {		// from_y has to be nearer than to_y
  float tmp = to_y;

    to_y = from_y;
    from_y = tmp;
  }
  if (from_z < to_z) {		// from_z has to be above to_z
  float tmp = to_z;

    to_z = from_z;
    from_z = tmp;
  }
  wprintw (output,
	   "\n( Cutting a pocket from (%f,%f,%f) down to (%f,%f,%f) in steps of (%f,%f,%f)... ) with a %fmm tool\n",
	   from_x, from_y, from_z, to_x, to_y, to_z, step_x, step_y, step_z,
	   current_tool_width);
  wrefresh (output);
  wrefresh (input);
  gcode_gotoxy (from_x + current_tool_width / 2,
		from_y + current_tool_width / 2);
  gcode_spindle_on ();
  z = from_z;			// higher than to_z
  for (;;) {			// z loop
    if (z < to_z)
      break;
    gcode_cuttoz (z);
    cnc_rectangle (from_x + current_tool_width / 2,
		   from_y + current_tool_width / 2,
		   to_x - current_tool_width / 2,
		   to_y - current_tool_width / 2, step_x, step_y,
		   want_dogbones);
    gcode_gotoz (z + 1.0);	// raise a little before move
    gcode_gotoxy (from_x + current_tool_width / 2, from_y + current_tool_width / 2);	// move 
											// back 
											// to 
											// origin
    z -= step_z;		// steps are currently unsigned quantities
  }
  gcode_gotoz (from_z);
  gcode_spindle_off ();
  gcode_gotoxy ((from_x + to_x) / 2, (from_y + to_y) / 2);
}

static float tiny = 0.001;
int equals (float a, float b)
{				// because floating point equality
				// comparisons are affected by accuracy and
				// rounding
  return ((a > (b - tiny)) && (a < (b + tiny)));
}

int notequals (float a, float b)
{
  return !equals (a, b);
}

// http://www.helmancnc.com/how-to-mill-full-circle-cnc-program-example-code/
// i,j specify center of circle - you don't need to move there too!

int gcode_circle_rel (float radius)
{				// at current center with current tool width. 
				// Compensate elsewhere!
  // during testing, NO DEPTH GIVEN ASSUMES RELATIVE MODE
  if (radius < 0.0)
    return 0;
  gcode ("G0 X0 Y%f (center to top radius)", radius);
  gcode ("G2 X0 Y0 I0 J-%f (draw circle)", radius);
  return gcode ("G0 X0 Y-%f (top radius to center)", radius);
}

int gcode_circle_abs (float x, float y, float radius, float move_height, float cut_height)
{
  if (radius < 0.0)  return 0;
  // in abs mode on entry
  gcode ("G90 (absolute moves)");
  // raise to move height
  gcode_gotoz (move_height);
  // move to abs coords of center of circle
  gcode_gotoxy (x,y);
  // move to cut height
  gcode_gotoz (cut_height);
  // switch to rel mode
  gcode ("G91 (relative moves)");
  // cut circle
  gcode ("G0 X0 Y%f (center to top radius)", radius);
  gcode ("G2 X0 Y0 I0 J-%f (draw circle)", radius);
  // return to center
  gcode ("G0 X0 Y-%f (top radius to center)", radius);
  // switch to abs mode
  gcode ("G90 (absolute moves)");
  // raise to move height
  return gcode_gotoz (move_height);
}

int gcode_disc (float max_radius)
{				// at current center with current tool width. 
				// Compensate elsewhere!
  float radius = current_tool_width / 2,	// ASSUMES RELATIVE MODE
   step = 0.6;

  do {
    gcode_circle_rel (radius);
    radius += step;
  } while (radius < max_radius);
  return gcode_circle_rel (max_radius);
}

void cnc_cylinder (float radius, float depth)
{
  float current_depth = 0.0, z_step = 0.5;
  char y;

  if (current_tool_width == 0.0) {
    // TO DO: ask user for tool width
    current_tool_width = DEFAULT_TOOL_WIDTH;	// largest of the sainsmart
						// end mills
  }

  wprintw (output, "\nCutting a cylinder at the current tool position\n");
  wprintw (output,
	   "\nPress 'y' when you are ready to cut, or 'n' to cancel.");
  wrefresh (output);
  wrefresh (input);

  for (;;) {			// loop over reading single characters,
				// looking for 'y' or 'n' -
    // any other character stays in this loop - we don't want an accidental
    // lean on the keyboard to cause ANY machine movement while changing the
    // tool!

#ifdef ZERO_EVERY_TIME
    FD_ZERO (&inFds);
    FD_SET (STDIN_FILENO, &inFds);
    FD_SET (Data_From_CNC_Fd, &inFds);
#endif
    
    if (select (Data_From_CNC_Fd + 1, &inFds, NULL, NULL, NULL) == -1) {
      if (errno != EINTR)
	errExit ("select");	// EINTR happens on window resize
    }

    do {
    } while (!FD_ISSET (STDIN_FILENO, &inFds));

    y = 'n';
    (void) read (STDIN_FILENO, &y, 1);
    if (y == 'y') {
      gcode_spindle_on ();
      gcode ("G91 (relative moves)");
      current_depth = 0.0;
      for (;;) {
	current_depth += z_step;
	if (current_depth > depth)
	  break;
	gcode ("G1 Z-%f", z_step);
	gcode_disc (radius - current_tool_width / 2);
      }
      gcode ("G1 Z-%f", depth - (current_depth - z_step));
      gcode_disc (radius - current_tool_width / 2);
      gcode_spindle_off ();
      gcode ("G1 Z%f", depth);	// return to starting point
      gcode ("G90 (absolute moves)");
    } else if (y == 'n') {
      wprintw (output, "\nCancelled.\n\nok"); wrefresh (output); wrefresh (input);
      return;
    }
  }
  /*
filled circle discussion (cylinder):

Bertalan Fábián It may look somewhat simpler with relative coordinates, just repeat with incrementing I:
G90 (absolute coordinates)
...
G00 X10.0 Y10 (absolute coordinates of the center of the hole)
G00 Z0.0 (absolute coordinate of the top of the stock)
G90 (switch to relative coordinates)
G01 Z-0.5 (cut 0.5mm deep at once, relative coordinate!)
G01 X-1.0 (go left 1mm)
G02 X0.0 Y0.0 I1.0 J0.0 (arc with center (1.0, 0.0) from current position, full circle as we return to the same position)
G01 X-1.0 (another mm to left, now we are (-2.0,0.0) from the center)
G02 X0.0 Y10.0 I2.0 J0.0 (arc with center (2.0, 0.0) from current position, full circle as we return to the same position)
G01 X-1.0 (another mm to left, now we are (-3.0,0.0) from the center)
G02 X0.0 Y0.0 I3.0 J0.0 (arc with center (3.0, 0.0) from current position, full circle as we return to the same position)
G01 X-1.0 (another mm to left, now we are (-4.0,0.0) from the center)
G02 X0.0 Y0.0 I4.0 J0.0 (arc with center (4.0, 0.0) from current position, full circle as we return to the same position)
=> now you have a circle with radius of (4mm+radius of the bit)
repeat until you reach the desired radius
Then sum the amounts you stepped left to go back to the center:
G01 X4.0
repeat the from G01 Z-0.5 until reaching the desired depth
G90 (back to absolute coordinates)
G00 Z5.0 (lift to safe position and then continue with whatever)
   */
}

void cnc_hammer_jig_peck_drill (float dz)
{
  // in inches, delta movements
  gcode_cuttoz (dz * 1 / 5);
  gcode ("G4 P.1");
  gcode_cuttoz (-dz * 1 / 5);
  gcode ("G4 P.1");
  gcode_cuttoz (dz * 2 / 5);
  gcode ("G4 P.1");
  gcode_cuttoz (-dz * 2 / 5);
  gcode ("G4 P.1");
  gcode_cuttoz (dz * 3 / 5);
  gcode ("G4 P.1");
  gcode_cuttoz (-dz * 3 / 5);
  gcode ("G4 P.1");
  gcode_cuttoz (dz * 4 / 5);
  gcode ("G4 P.1");
  gcode_cuttoz (-dz * 4 / 5);
  gcode ("G4 P.1");
  gcode_cuttoz (dz);
  gcode ("G4 P.1");
  gcode_cuttoz (-dz);
}

void cnc_hammer_jig (void)
{
  int y;

  wprintw (output, "\nCutting jig for GPIO hammer header\n");
  wrefresh (output);
  wrefresh (input);
  if (Default_Units == undefined) {
    // don't
  }
  wprintw (output, "\nChange the tool for the appropriate PCB drill.");
  wprintw (output,
	   "\nThe drill tool should already be placed exactly where you want the");
  wprintw (output,
	   "\nlower-left hole of the :::::::::::::::::::: to start, then drop the");
  wprintw (output,
	   "\ndrill down so it is touching the surface and lock the collet.");
  wprintw (output,
	   "\nPress 'y' when you are ready to drill, or 'n' to cancel.");
  wrefresh (output);
  wrefresh (input);

  for (;;) {			// loop over reading single characters,
				// looking for 'y' or 'n' -
    // any other character stays in this loop - we don't want an accidental
    // lean on the keyboard to cause ANY machine movement while changing the
    // tool!

#ifdef ZERO_EVERY_TIME
    FD_ZERO (&inFds);
    FD_SET (STDIN_FILENO, &inFds);
    FD_SET (Data_From_CNC_Fd, &inFds);
#endif
    
    if (select (Data_From_CNC_Fd + 1, &inFds, NULL, NULL, NULL) == -1) {
      if (errno != EINTR)
	errExit ("select");	// EINTR happens on window resize
    }

    do {
    } while (!FD_ISSET (STDIN_FILENO, &inFds));

    y = 'n';
    (void) read (STDIN_FILENO, &y, 1);
    if (y == 'y') {
  int x;

      gcode ("G20");		// inches
      gcode ("G91 (Relative moves)");
      gcode_spindle_on ();
      for (x = 0; x < 20; x++) {
	cnc_hammer_jig_peck_drill (-0.3);	// leaves back at 0.
	gcode_gotoz (0.05);	// raise above zero
	gcode_gotoxy (0.1, 0.0);	// move to next pin
	gcode_gotoz (-0.05);	// lower to zero again
      }
#ifdef NEVER
      gcode_gotoz (0.1);	// raise above zero
      gcode_gotoxy (-2.0, 0.1);	// start of second row
      gcode_gotoz (-0.1);	// back to 0 level
      for (x = 0; x < 20; x++) {
	cnc_hammer_jig_peck_drill (-0.3);	// leaves back at 0.
	gcode_gotoz (0.1);	// raise above zero
	gcode_gotoxy (0.1, 0.0);	// move to next pin
	gcode_gotoz (-0.1);	// lower to zero again
      }
      gcode_gotoz (0.1);	// raise above zero
      gcode_gotoxy (-2.0, -0.1);	// back to starting x,y position
      gcode_gotoz (-0.1);	// back to z0
#else
      gcode_spindle_off ();
      gcode_gotoz (0.1);	// raise above zero
      gcode_gotoxy (-2.0, 0.0);	// start of second row
      gcode_gotoz (-0.1);	// back to 0 level
#endif
      gcode ("G21");		// back to mm
      gcode ("G90 (Absolute moves)");
    } else if (y == 'n') {
      wprintw (output, "\nCancelled.\n\nok"); wrefresh (output); wrefresh (input);
      return;
    }
  }
}

void cnc_pitrex_mailer(void)
{
  //     top/lhs    mid     bot/rhs
  //    +--------------------------+
  //    |        |        |        |
  //    | +----+ | +----+ |  o     |
  //    |  |   | |  |   | | |  o   |   bot shell
  //    |  |   | |  |   | | |  o   |
  //    | +----+ | +----+ |  o     |
  //    |        |        |        |
  //    | +__--+ | +_---+ | +----+ |
  //    |  |   | |  |   | |  |   | |   top shell
  //    |  |   | |  |   | |  |   | |
  //    | +~~--+ | +~---+ | +----+ |
  //    |        |        |        |
  //    +--------------------------+
  //
  int y;


  // TO DO: save a copy of this, redo tomorrow as laser, use script to generate ifdefs...
  
  gcode_spindle_off ();
  current_spindle_speed = 20000;
  current_feedrate = 1000;
  gcode ("F%d", current_feedrate);

  wprintw (output, "\nCut three layers of packing material to ship pitrex cases\n");
  wrefresh (output);
  wrefresh (input);
  if (Default_Units == undefined) {
    // don't
  }
  wprintw (output, "\nPlease center the cutting tool in the middle of the plate");
  wprintw (output, "\ntouching the top coroplast - this will be (0,0,0).");
  wprintw (output,
	   "\nPress 'y' when you are ready to cut, or 'n' to cancel. ('a' for air cut)");
  wrefresh (output);
  wrefresh (input);

  for (;;) {			// loop over reading single characters,
				// looking for 'y' or 'n' -
    // any other character stays in this loop - we don't want an accidental
    // lean on the keyboard to cause ANY machine movement while changing the
    // tool!

#ifdef ZERO_EVERY_TIME
    FD_ZERO (&inFds);
    FD_SET (STDIN_FILENO, &inFds);
    FD_SET (Data_From_CNC_Fd, &inFds);
#endif
    
    if (select (Data_From_CNC_Fd + 1, &inFds, NULL, NULL, NULL) == -1) {
      if (errno != EINTR)
	errExit ("select");	// EINTR happens on window resize
    }

    do {
    } while (!FD_ISSET (STDIN_FILENO, &inFds));

    y = 'n';
    (void) read (STDIN_FILENO, &y, 1);
    if ((y == 'y') || (y == 'a')) {
      int loops;
      float depth;
      const float move_depth = 10.0;
      const float NECK_LONG = 27.0;
      const float NECK_SHORT = 3.0;
      const float HALFGAP = 4.5; // vertical seperation
      const float SPACING = 52.0;  // horizontal separation
      const float SHELL_WIDTH = 73.0;  // increased from 71 left-to-right of shell
      const float SHELL_DEPTH = 71.0;  // increased from 69 front-to-back of shell
      const float SHORT_TO_BAR = 13.5;  // from front of shell to crossbar
      const float LONG_TO_BAR = 57.5;  // from back of shell to crossbar
      const float tabgap = 6.0; // leave to cut with scissors so does not drop out
      float cut_depth = -6.5; // -4 to cut if flat. no more than 8! Wide margin for flexing
      if (y == 'a') cut_depth = 3.0; // can tweak this down to -0.5 for marking cut?

      // set to 0.0 if no retaining tabs wanted
      gcode ("G21");		// mm
      gcode ("G90 (absolute moves)");
      gcode ("G10 L2 P1 X0 Y0"); // reset x,y // MUST START OUT AT CENTER!
      gcode ("G92 X0 Y0");       // same!

      gcode_gotoz (move_depth);	// safe retract
      if (y == 'y') gcode_spindle_on (); // no spindle for air cuts!

      // center layer, upper area (lower half of shell)

      // bar:
      gcode_gotoxy (-SHELL_DEPTH/2.0+SHORT_TO_BAR,  0+HALFGAP+tabgap); // lower
      gcode_gotoz (cut_depth);	// cut depth
      gcode_gotoxy (-SHELL_DEPTH/2.0+SHORT_TO_BAR, SHELL_WIDTH+HALFGAP-tabgap); // cut bar, lower to upper
      gcode_gotoz (move_depth);	// safe retract

      gcode_gotoxy (-SHELL_DEPTH/2.0, SHELL_WIDTH+HALFGAP); // move to top-left
      gcode_gotoz (cut_depth);	// cut depth
      gcode_gotoxy (+SHELL_DEPTH/2.0, SHELL_WIDTH+HALFGAP); // cut to top-right
      gcode_gotoxy (+SHELL_DEPTH/2.0,  0+HALFGAP); // cut to lower-right
      gcode_gotoxy (-SHELL_DEPTH/2.0,  0+HALFGAP); // cut to lower-left
      // should drop out now unless we leave a tab gap
      gcode_gotoz (move_depth);	// safe retract

      // center layer, lower area (upper half of shell)
      // bar
      gcode_gotoxy (-SHELL_DEPTH/2.0+SHORT_TO_BAR,   0-HALFGAP-tabgap); // move
      gcode_gotoz (cut_depth);	// cut depth
      gcode_gotoxy (-SHELL_DEPTH/2.0+SHORT_TO_BAR,   -SHELL_WIDTH-HALFGAP+tabgap); // cut bar, upper to lower
      gcode_gotoz (move_depth);	// safe retract

      // TO DO: narrow neck on insertion part
      gcode_gotoxy (-SHELL_DEPTH/2.0+NECK_LONG, -SHELL_WIDTH-HALFGAP); // on edge
      gcode_gotoz (cut_depth);	// cut depth
      gcode_gotoxy (-SHELL_DEPTH/2.0+NECK_LONG, -SHELL_WIDTH-HALFGAP+NECK_SHORT); // up
      gcode_gotoxy (-SHELL_DEPTH/2.0, -SHELL_WIDTH-HALFGAP+NECK_SHORT); // across
      
      gcode_gotoxy (-SHELL_DEPTH/2.0, -SHELL_WIDTH-HALFGAP); // back down to lower left, normal starting point
      gcode_gotoxy (+SHELL_DEPTH/2.0, -SHELL_WIDTH-HALFGAP); // cut to lower right
      gcode_gotoxy (+SHELL_DEPTH/2.0,   0-HALFGAP); // cut to upper right
      gcode_gotoxy (-SHELL_DEPTH/2.0,   0-HALFGAP); // cut to upper left
      // would drop out except for tab gap

      // fix last part of neck
      gcode_gotoxy (-SHELL_DEPTH/2.0,   0-HALFGAP-NECK_SHORT); // down
      gcode_gotoxy (-SHELL_DEPTH/2.0+NECK_LONG,   0-HALFGAP-NECK_SHORT); // right
      gcode_gotoxy (-SHELL_DEPTH/2.0+NECK_LONG,   0-HALFGAP); // back up - neck is now cut out
      gcode_gotoz (move_depth);	// safe retract


      // RHS (lowest layer) upper area   (lower half of shell)

      // for the two lugs on crossbar:
      gcode_gotoxy (SPACING-0+SHORT_TO_BAR,  SHELL_WIDTH+HALFGAP);
      gcode_gotoz (cut_depth);	       // cut depth
      gcode_gotoxy (SPACING-0+SHORT_TO_BAR,  0+HALFGAP);    // cut
      gcode_gotoz (move_depth);	// safe retract

      // now 5 holes for supports (bottom half of shell)
      gcode_circle_abs (SPACING-0+SHORT_TO_BAR+38.0, (SHELL_WIDTH/2.0)+HALFGAP+12.0, 2.0, move_depth, cut_depth);
      gcode_circle_abs (SPACING-0+SHORT_TO_BAR+38.0, (SHELL_WIDTH/2.0)+HALFGAP-12.0, 2.0, move_depth, cut_depth);

      gcode_circle_abs (SPACING-0+SHORT_TO_BAR+8.0, (SHELL_WIDTH/2.0)+HALFGAP+20.0, 2.0, move_depth, cut_depth);
      gcode_circle_abs (SPACING-0+SHORT_TO_BAR+8.0, (SHELL_WIDTH/2.0)+HALFGAP, 3.0, move_depth, cut_depth);
      gcode_circle_abs (SPACING-0+SHORT_TO_BAR+8.0, (SHELL_WIDTH/2.0)+HALFGAP-20.0, 2.0, move_depth, cut_depth);

      
      // RHS (lowest layer), lower area (top half of shell)

      // bar:
      gcode_gotoz (move_depth);	// safe retract
      gcode_gotoxy (SPACING-0+SHORT_TO_BAR, 0-HALFGAP-tabgap);
      gcode_gotoz (cut_depth);	       // cut depth
      gcode_gotoxy (SPACING-0+SHORT_TO_BAR, -SHELL_WIDTH-HALFGAP+tabgap); // cut bar, top to low
      gcode_gotoz (move_depth);	// safe retract

      gcode_gotoxy (SPACING-0, -SHELL_WIDTH-HALFGAP); // move to lower left corner
      gcode_gotoz (cut_depth);	       // cut depth
      gcode_gotoxy (SPACING+SHELL_DEPTH, -SHELL_WIDTH-HALFGAP); // cut to lower right
      gcode_gotoxy (SPACING+SHELL_DEPTH,   0-HALFGAP); // cut to upper right
      gcode_gotoxy (SPACING-0,   0-HALFGAP);    // cut to upper left
      // would drop out except for tab gap
      gcode_gotoz (move_depth);	// safe retract

      
      // LHS (top layer) upper area (lower half of shell)
      // bar:
      gcode_gotoxy (-SPACING-SHELL_DEPTH+SHORT_TO_BAR,  0+HALFGAP); // move
      gcode_gotoz (cut_depth);	// cut depth
      gcode_gotoxy (-SPACING-SHELL_DEPTH+SHORT_TO_BAR,  SHELL_WIDTH+HALFGAP); // move
      gcode_gotoz (move_depth);	// safe retract

      gcode_gotoxy (-SPACING-SHELL_DEPTH, SHELL_WIDTH+HALFGAP); // start at top-left
      gcode_gotoz (cut_depth);	// cut depth
      gcode_gotoxy (-SPACING+0, SHELL_WIDTH+HALFGAP);    // cut to top-right
      gcode_gotoxy (-SPACING+0,  0+HALFGAP);    // cut to lower-right
      gcode_gotoxy (-SPACING-SHELL_DEPTH,  0+HALFGAP); // cut to lower-left
      // would drop out except for tab gap
      gcode_gotoz (move_depth);	// safe retract

      // lhs (top layer) lower area (upper half of shell)
      // bar
      gcode_gotoxy (-SPACING-SHELL_DEPTH+SHORT_TO_BAR,   0-HALFGAP-tabgap); // move
      gcode_gotoz (cut_depth);	        // cut depth
      gcode_gotoxy (-SPACING-SHELL_DEPTH+SHORT_TO_BAR, -SHELL_WIDTH-HALFGAP+tabgap); // cut bar top to bot
      gcode_gotoz (move_depth);	// safe retract

      // TO DO: narrow neck on insertion part
      gcode_gotoxy (-SPACING-SHELL_DEPTH+NECK_LONG, -SHELL_WIDTH-HALFGAP);            // move
      gcode_gotoz (cut_depth);	        // cut depth
      gcode_gotoxy (-SPACING-SHELL_DEPTH+NECK_LONG, -SHELL_WIDTH-HALFGAP+NECK_SHORT); // down
      gcode_gotoxy (-SPACING-SHELL_DEPTH,           -SHELL_WIDTH-HALFGAP+NECK_SHORT); // left
      gcode_gotoxy (-SPACING-SHELL_DEPTH,           -SHELL_WIDTH-HALFGAP);            // up, to start at lower left - normal starting point
      gcode_gotoxy (-SPACING+0, -SHELL_WIDTH-HALFGAP);    // cut to lower right
      gcode_gotoxy (-SPACING+0,   0-HALFGAP);    // cut to upper right
      gcode_gotoxy (-SPACING-SHELL_DEPTH,   0-HALFGAP); // cut to upper left
      // would drop out here except for tab gap
      // fix other neck:
      gcode_gotoxy (-SPACING-SHELL_DEPTH,   0-HALFGAP-NECK_SHORT); // down
      gcode_gotoxy (-SPACING-SHELL_DEPTH+NECK_LONG,   0-HALFGAP-NECK_SHORT); // back
      gcode_gotoxy (-SPACING-SHELL_DEPTH+NECK_LONG,   0-HALFGAP); // up
      gcode_gotoz (move_depth);	// safe retract

      
      gcode_spindle_off();
      gcode_gotoxy (0,0);  // park

      gcode ("?");
      wprintw (output, "\nReturning to command mode.\n\nok"); wrefresh (output); wrefresh (input);
    } else if (y == 'n') {
      wprintw (output, "\nCancelled.\n\nok"); wrefresh (output); wrefresh (input);
      return;
    }
  }
  
}

void cnc_pitrex_cartridge (void)
{
  int y;

  gcode_spindle_off ();
  current_spindle_speed = 20000;
  current_feedrate = 1000;
  gcode ("F%d", current_feedrate);

  wprintw (output, "\nCut a hole in a standard Vectrex cart for the Pi Zero WH\n");
  wrefresh (output);
  wrefresh (input);
  if (Default_Units == undefined) {
    // don't
  }
  wprintw (output, "\nBy this point you should have carefully moved the cutting tool");
  wprintw (output,
	   "\nso that it is exactly touching the point of the 'insert' arrow, and");
  wprintw (output,
	   "\nhave quit and restarted this program (possibly also the proxy) so that");
  wprintw (output,
	   "\nthe point is true 0,0,0");
  wprintw (output,
	   "\nPress 'y' when you are ready to cut, or 'n' to cancel.");
  wrefresh (output);
  wrefresh (input);

  for (;;) {			// loop over reading single characters,
				// looking for 'y' or 'n' -
    // any other character stays in this loop - we don't want an accidental
    // lean on the keyboard to cause ANY machine movement while changing the
    // tool!

#ifdef ZERO_EVERY_TIME
    FD_ZERO (&inFds);
    FD_SET (STDIN_FILENO, &inFds);
    FD_SET (Data_From_CNC_Fd, &inFds);
#endif
    
    if (select (Data_From_CNC_Fd + 1, &inFds, NULL, NULL, NULL) == -1) {
      if (errno != EINTR)
	errExit ("select");	// EINTR happens on window resize
    }

    do {
    } while (!FD_ISSET (STDIN_FILENO, &inFds));

    y = 'n';
    (void) read (STDIN_FILENO, &y, 1);
    if (y == 'y') {
      int loops;
      float depth;

      gcode ("G21");		// mm
      gcode ("G90 (absolute moves)");
      gcode_gotoz (10.0);	// raise above zero
      gcode_gotoxy (-26.300,60.500); // air cut to confirm, better be ready to hit ESTOP :-)
      gcode_gotoz (2.0);	// raise above zero
      gcode_gotoxy (27.700,60.500);
      gcode_gotoxy (27.700,54.000);
      gcode_gotoxy (-26.300,54.000);
      gcode_gotoxy (-26.300,60.500); // back to start, ready to cut...
      gcode_spindle_on ();
      depth = 0.0;
      for (loops = 0; loops < 40; loops++) {
	gcode_gotoz(depth);
        gcode_gotoxy (27.700,60.500);
        gcode_gotoxy (27.700,54.000);
        gcode_gotoxy (-26.300,54.000);
        gcode_gotoxy (-26.300,60.500); // back to start, ready to cut...
	depth -= 0.1; // 4mm total
      }
      gcode_gotoz (2.0);	// raise above zero
      gcode_spindle_off ();
      // retract
      gcode_gotoz (10.0);	// raise above zero
      gcode_gotoxy (0.0,70.500);
      gcode ("?");
      wprintw (output, "\nReturning to command mode.\n\nok"); wrefresh (output); wrefresh (input);
    } else if (y == 'n') {
      wprintw (output, "\nCancelled.\n\nok"); wrefresh (output); wrefresh (input);
      return;
    }
  }
}

int gcode_jog (char *direction, char *quantity, char *units)
{

  // We'll use G1 to allow feedrate control, just in case this is
  // used for cutting by hand...

  char empty[MAX_LINE] = { '\0' };
  if (Default_Units == undefined) {
    if (*units == '\0') {
      wprintw (output, "\nDefault units not set?");
      wrefresh (debugline);
      wrefresh (output);
      wrefresh (input);
      return FALSE;
    }
    wprintw (output, "\nSetting default units to %s", units);
    wrefresh (debugline);
    wrefresh (output);
    wrefresh (input);
    if (strcmp (units, "mm") == 0)
      Default_Units = mm;
    else if (strcmp (units, "in") == 0)
      Default_Units = in;
  } else {
    if (*units == '\0')
      units = (Default_Units == mm ? "mm" : "in");
  }
  wprintw (debugline, "\ndirection \"%s\"  quantity \"%s\"  units \"%s\"",
	   direction, quantity, units);
  wrefresh (debugline); wrefresh (input);

  // override default units
  if (strcmp (units, (Default_Units == mm ? "mm" : "in")) != 0) {
    gcode (strcmp (units, "in") == 0 ? "G20 (inches)" : "G21 (mm)");
  }

  gcode ("G91 (Relative moves)");

  if (strcmp (direction, "left") == 0) {
    gcode ("G1 X-%s", quantity);
  } else if (strcmp (direction, "right") == 0) {
    gcode ("G1 X%s", quantity);
  } else if (strcmp (direction, "nearer") == 0) {
    gcode ("G1 Y-%s", quantity);
  } else if (strcmp (direction, "farther") == 0) {
    gcode ("G1 Y%s", quantity);
  } else if (strcmp (direction, "up") == 0) {
    gcode ("G1 Z%s", quantity);
  } else if (strcmp (direction, "down") == 0) {
    gcode ("G1 Z-%s", quantity);
  }

  gcode ("G90 (Absolute moves)");

  // if units were overriden, restore the default here
  if (strcmp (units, (Default_Units == mm ? "mm" : "in")) != 0) {
    gcode (Default_Units == in ? "G20 (inches)" : "G21 (mm)");
  }
  wrefresh (debugline);
  wrefresh (output);
  wrefresh (input);
  return TRUE;			// for now
}

int gcode_default_units (char *units)
{
  if (strcmp (units, "mm") == 0) {
    Default_Units = mm;
    gcode ("G21 (mm)");
  } else if (strcmp (units, "in") == 0) {
    Default_Units = in;
    gcode ("G20 (inches)");
  } else {
    Default_Units = undefined;
    gcode ("G21 (mm)");
    // Only mm or in supported. but we'll use mm to keep unexpected moves to
    // a minimum
  }
  return TRUE;			// for now
}

int gcode_probe (void)
{
  char y;

  // G10 L2 P1 X0 Y0 Z0 (Redefine WPos zero position)
  wprintw (output,
	   "\nMake sure the probe is below the tool and the clip is attached.");
  wprintw (output,
	   "\nPress 'y' when you are ready to probe. Any other input cancels.");
  wrefresh (output);
  wrefresh (input);

#ifdef ZERO_EVERY_TIME
  FD_ZERO (&inFds);
  FD_SET (STDIN_FILENO, &inFds);
  FD_SET (Data_From_CNC_Fd, &inFds);
#endif
  
  if (select (Data_From_CNC_Fd + 1, &inFds, NULL, NULL, NULL) == -1) {
    if (errno != EINTR)
      errExit ("select");	// EINTR happens on window resize
  }

  do {
  } while (!FD_ISSET (STDIN_FILENO, &inFds));

  y = 'n';
  (void) read (STDIN_FILENO, &y, 1);
  if (y == 'y') {
    gcode ("S20");		// 20 mm/mi TO DO: if 'in' selected + restore
    gcode ("G38.2 F10 Z-10");	// probe Z, set z 0
    gcode ("G92 Z19.7");	// set x y 0
  } else {
    wprintw (output, "\nCancelled.\n\nok"); wrefresh (output); wrefresh (input);
  }

  wrefresh (output);
  wrefresh (input);
  return TRUE;			// for now
}

int gcode_display_info (void)
{

  // After homing, x,y,z = -499,-399,-1 i.e. backed off 1mm from end stops at 
  // -500, -400, 0
  // z is fully retracted.

  // $10=1 report MPos
  // $10=0 report WPos
  // gcode("$10=0");
  // spindle speed on my machine.  Probably 10000 on yours.
  // gcode("$30=20000");
  // set maximum travel
  // gcode("$130=300");
  // gcode("$131=180");

  if (Default_Units == undefined) {
    wprintw (output, "\nNo default unit set - will not cut!");
    wrefresh (output);
    wrefresh (input);
  } else {
    wprintw (output, "\nDefault units set to %s",
	     (Default_Units == mm ? "mm" : "in"));
    wrefresh (output);
    wrefresh (input);
  }
  return TRUE;			// for now
}

int main (int argc, char **argv)
{
  int serial_port;

  // This is the curses version...
  // Input is always on the bottom line, while any asynchronous
  // data from the CNC scrolls immediately above it.

  int i = 2, height, width, ch, new_width, new_height;
  char *keyboard_line, *received_line, *debug_line, cnc_buf[MAX_LINE],
   keyboard_raw_input_buf[MAX_LINE], keyboard_line_buf[MAX_LINE],
   received_line_buf[MAX_LINE], debug_buf[MAX_LINE];
  struct winsize ws;
  size_t numRead;
  char *s;

  // Get clean version of executable name.  Should work on most existing
  // systems (2006)
  progname = argv[0];
  if ((s = strrchr (progname, '/')) != NULL)
    progname = s + 1;		// Unix
  if ((s = strrchr (progname, '\\')) != NULL)
    progname = s + 1;		// M$
  if ((s = strrchr (progname, ']')) != NULL)
    progname = s + 1;		// Dec
  if ((s = strrchr (progname, ';')) != NULL)
    *s = '\0';			// Version no's
  if (((s = strrchr (progname, '.')) != NULL)
      && (strcasecmp (s, ".exe") == 0))
    *s = '\0';
  if (((s = strrchr (progname, '.')) != NULL)
      && (strcasecmp (s, ".com") == 0))
    *s = '\0';

  serial_port = 0;
  while ((argc > 1) && (*argv[1] == '-')) {
    if ((argc > 1) && (strcmp (argv[1], "-1") == 0)) {
      argv++;
      argc--;
      serial_port = 1;
    }
    if ((argc > 1) && (strcmp (argv[1], "-d") == 0)) {
      argv++;
      argc--;
#ifdef DEBUG_PARSER		// Tables included in header file?
      debug_parser = TRUE;
#endif
      debug_runtime = TRUE;
    }
    if ((argc > 1) && (strcmp (argv[1], "-dp") == 0)) {
      argv++;
      argc--;
#ifdef DEBUG_PARSER		// Tables included in header file?
      debug_parser = TRUE;
#endif
    }
    if ((argc > 1) && (strcmp (argv[1], "-dr") == 0)) {
      argv++;
      argc--;
      debug_runtime = TRUE;
    }
    if ((argc > 1) && (*argv[1] == '-')
	&& (strcmp (argv[1], "-d") != 0)
	&& (strcmp (argv[1], "-dp") != 0)
	&& (strcmp (argv[1], "-dr") != 0)) {
      fprintf (stderr, "syntax: %s [-d[pr]]\n", progname);
      exit (1);
    }
  }

#ifdef NEVER
  if (argc == 2) {
    rc = interpret (argv[1]);
    if (rc < 0) {
      fprintf (stderr, "Can't\n");
    } else if (rc > 0) {
      fprintf (stderr, "Won't\n");
    } else
      fprintf (stderr, "ok\n");
  } else {
    fprintf (stderr, "syntax: %s [-d[pr]] 'command line'\n", progname);
    exit (1);
  }
#endif

  // system("stty -F /dev/ttyUSB0 speed 115200 -brkint -icrnl -imaxbel -opost 
  // -onlcr -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke");
  if (serial_port == 0) {
    system
     ("stty -F /dev/ttyUSB0 speed 115200 -brkint -icrnl -imaxbel -opost -onlcr -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke > /dev/null");
  } else {
    system
     ("stty -F /dev/ttyUSB1 speed 115200 -brkint -icrnl -imaxbel -opost -onlcr -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke > /dev/null");
  }
#ifdef NEVER
  Data_From_CNC_Fd =
   open ((serial_port == 0 ? "/dev/ttyUSB0" : "/dev/ttyUSB1"), O_RDONLY);
  if (Data_From_CNC_Fd < 0) {
    errExit ("open");
  }
#else
  Data_From_CNC_Fd =
#endif
   Data_To_CNC_Fd =
   open ((serial_port == 0 ? "/dev/ttyUSB0" : "/dev/ttyUSB1"),
	 O_RDWR /* O_WRONLY */ );
  if (Data_From_CNC_Fd < 0) {
    errExit ("open");
  }

  tcsendbreak (Data_To_CNC_Fd, 0);
  {
  char tmp = 'X' & 31;

    write (Data_To_CNC_Fd, &tmp, 1);
  }

  if (tcgetattr (STDIN_FILENO, &ttyOrig) == -1)
    errExit ("tcgetattr");
  if (ioctl (STDIN_FILENO, TIOCGWINSZ, &ws) < 0)
    errExit ("ioctl-TIOCGWINSZ");

  /* relay data between terminal and CNC */

  /* Place terminal in raw mode so that we can pass all terminal input to the 
     pseudoterminal master untouched - important for ^X (though we may use
     cooked mode later) */

  /* Place terminal keyboard in raw mode (noncanonical mode with all input
     and output processing disabled). Save previous terminal settings. */

  {
  struct termios t;

    if (tcgetattr (STDIN_FILENO, &t) == -1)	/* return -1 */
      ;

    ttyOrig = t;

    t.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO);
    /* Noncanonical mode, disable signals, extended input processing, and
       echoing */

    t.c_iflag &=
     ~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR | INPCK | ISTRIP | IXON |
       PARMRK);
    /* Disable special handling of CR, NL, and BREAK. No 8th-bit stripping or 
       parity error handling. Disable START/STOP output flow control. */

    t.c_oflag &= ~OPOST;	/* Disable all output processing */

    t.c_cc[VMIN] = 1;		/* Character-at-a-time input */
    t.c_cc[VTIME] = 0;		/* with blocking */

    if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &t) == -1)	/* return -1 */
      ;

  }

  if (atexit (ttyReset) != 0)
    errExit ("atexit");

  /* Loop, monitoring the terminal and serial port for input. If the terminal
     is ready for input, then read some bytes and write them to the CNC. If
     the CNC is replying with status information, then read some bytes and
     write them to the terminal. */

  initscr ();
  getmaxyx (stdscr, height, width);
  debugline = newwin (4, width - 1, 0, 0);
  // one blank line as a separator
  output = newwin (height - 6, width - 1, 5, 0);
  input = newwin (1, width - 1, height - 1, 0);
  scrollok (debugline, TRUE);
  scrollok (output, TRUE);
  scrollok (input, TRUE);
  // keypad (stdscr, TRUE);

  noecho ();
  raw ();
  // cbreak (); // disable line-buffering
  // timeout (1); // wait 1 milliseconds for input

  wprintw (debugline,
	   "\nAsynchronous and debugging messages will appear here.");
  wrefresh (debugline);
  for (i = 0; i < height; i++)
    wprintw (output, "\n");
  wrefresh (output);
  wrefresh (input);		// to move the cursor

  *(keyboard_line = keyboard_line_buf) = '\0';
  *(received_line = received_line_buf) = '\0';
  *(debug_line = debug_buf) = '\0';

  gcode_display_info ();

  for (;;) {
    getmaxyx (stdscr, new_height, new_width);
    if (new_width != width || new_height != height) {
      // this isn't perfect detection of a window size change but it will do.
      height = new_height;
      width = new_width;
      delwin (output);
      delwin (input);

      output = newwin (height - 1, width - 1, 0, 0);
      input = newwin (1, width - 1, height - 1, 0);

      scrollok (debugline, TRUE);
      scrollok (output, TRUE);
      scrollok (input, TRUE);
      clear ();
      for (i = 0; i < height; i++)
	wprintw (output, "\n");
      refresh ();
      wrefresh (output);
      wrefresh (input);		// to move the cursor

      wprintw (input, "\n%s", keyboard_line_buf);
      wrefresh (input);		// restore input line
    }

    // only ever do this once?
    FD_ZERO (&inFds);
    FD_SET (STDIN_FILENO, &inFds);
    FD_SET (Data_From_CNC_Fd, &inFds);

    if (select (Data_From_CNC_Fd + 1, &inFds, NULL, NULL, NULL) == -1) {
      if (errno != EINTR)
	errExit ("select");	// EINTR happens on window resize
    }
    // this is where we receive the results of executing a gcode command:
    if (FD_ISSET (Data_From_CNC_Fd, &inFds)) {	/* CNC --> screen */
      numRead = read (Data_From_CNC_Fd, cnc_buf, BUF_SIZE);
      if (numRead > 0)		// exit(EXIT_SUCCESS);
	for (i = 0; i < numRead; i++) {
	  ch = cnc_buf[i];
	  if (ch == '\r')
	    continue;
	  if (ch == '\n') {
	    if ((*received_line_buf == '<')
		|| (strncmp (received_line_buf, "[GC:", 4) == 0)) {
	      if (status_requested) {
		wprintw (output, "\n%s", received_line_buf);
		wrefresh (output);
		status_requested = FALSE;
	      } else {
		wprintw (debugline, "\n%s", received_line_buf);
		wrefresh (debugline);
	      }
	      wrefresh (input);
	    } else if ((!expecting_ok)
		       && (strcmp (received_line_buf, "ok") == 0)) {
	      wprintw (debugline, "\n%s", received_line_buf);
	      wrefresh (debugline);
// don't forget to cancel 'expecting_ok' if we received either 'ok' or 'error' ...
	    } else {
	      if (strncasecmp (received_line_buf, "error:", 6) == 0) {
		// also need hold: and door:
		gcode_errnum = atoi (received_line_buf + 6);
		append_error (received_line_buf, gcode_errnum);
	      }
	      wprintw (output, "\n%s", received_line_buf);
	      wrefresh (output);
	    }
	    if (expecting_ok) {
	      if ((strcmp (received_line_buf, "ok") == 0)
		  || (strncasecmp (received_line_buf, "error:", 6) == 0)
		  || (strncasecmp (received_line_buf, "ALARM:", 6) == 0)
		  || (strncasecmp (received_line_buf, "hold:", 5) == 0)
		  || (strncasecmp (received_line_buf, "door:", 5) == 0)
	       ) {
		expecting_ok = FALSE;
	      }
	    }
	    *(received_line = received_line_buf) = '\0';
	    continue;
	  }
	  /* if (write (STDOUT_FILENO, cnc_buf, numRead) != numRead) fatal
	     ("partial/failed write (STDOUT_FILENO)"); */
	  *received_line++ = ch;
	  *received_line = '\0';
	}
    }
    wprintw (input, "\n%s", keyboard_line_buf);	// optimise this later by
						// only printing each
						// character as it arrives.
    wrefresh (input);

    if (FD_ISSET (STDIN_FILENO, &inFds)) {	/* keyboard --> CNC */
  int escapeFlag = 0;

      numRead = read (STDIN_FILENO, keyboard_raw_input_buf, BUF_SIZE);
      if (numRead <= 0)
	exit (EXIT_SUCCESS);
      i = -1;
      for (;;) {
	i = i + 1;
	if (i >= numRead)
	  break;
	ch = keyboard_raw_input_buf[i];
	if (escapeFlag == 2) {	// has to come first as any of the special
				// characters might be a legit part of a
				// cursor sequence.
	  escapeFlag = 0;
	  switch (ch) {
	  case 'A':		// up
	    wprintw (output, "\nfarther %d", (int) xy_jog_default);	// defaults 
									// not 
									// really 
									// implemented 
									// yet
	    gcode_jog ("farther", "1", "");
	    break;
	  case 'B':		// down
	    wprintw (output, "\nnearer %d", (int) xy_jog_default);
	    gcode_jog ("nearer", "1", "");
	    break;
	  case 'C':		// right
	    wprintw (output, "\nright %d", (int) xy_jog_default);
	    gcode_jog ("right", "1", "");
	    break;
	  case 'D':		// left
	    wprintw (output, "\nleft %d", (int) xy_jog_default);
	    gcode_jog ("left", "1", "");
	    break;
	  default:
	    wprintw (output, "\nunknown key sequence ^[[%c");
	    break;
	  }
	  wrefresh (output);
	  wrefresh (input);
	  continue;
	} else if ((ch == ('[' & 31)) && (escapeFlag == 0)) {	// tiny state 
								// machine
								// for cursor 
								// key
								// sequence
	  escapeFlag = 1;
	  continue;		// ideally would also include a timeout in
				// case user pressed ESC key...
	} else if ((ch == '[') && (escapeFlag == 1)) {
	  escapeFlag = 2;
	  continue;
	} else if ((keyboard_line == keyboard_line_buf) && (ch == ('X' & 31))) {	// These 
											// tests 
											// for 
											// immediate 
											// characters 
											// are 
											// restricted 
											// to 
											// start 
											// of 
											// line 
											// only 
											// 
	  if (write (Data_To_CNC_Fd, &ch, 1) != 1)
	    fatal ("partial/failed write (To_CNC)");
	  *(keyboard_line = keyboard_line_buf) = '\0';
	  wprintw (output, "\n^X (reset GRBL)");
	  wrefresh (output);
	  wrefresh (input);
	} else if ((keyboard_line == keyboard_line_buf) && (ch == '~')) {
	  if (write (Data_To_CNC_Fd, &ch, 1) != 1)
	    fatal ("partial/failed write (To_CNC)");
	  *(keyboard_line = keyboard_line_buf) = '\0';
	  wprintw (output, "\n~ (cycle start)");
	  wrefresh (output);
	  wrefresh (input);
	} else if ((keyboard_line == keyboard_line_buf) && (ch == '!')) {
	  if (write (Data_To_CNC_Fd, &ch, 1) != 1)
	    fatal ("partial/failed write (To_CNC)");
	  *(keyboard_line = keyboard_line_buf) = '\0';
	  wprintw (output, "\n! (feed hold)");
	  wrefresh (output);
	  wrefresh (input);
	} else if ((keyboard_line == keyboard_line_buf) && (ch == '?')) {
	  status_requested = TRUE;
	  if (write (Data_To_CNC_Fd, &ch, 1) != 1)
	    fatal ("partial/failed write (To_CNC)");
	  *(keyboard_line = keyboard_line_buf) = '\0';
	  wprintw (output, "\n? (current status)");
	  wrefresh (output);
	  wrefresh (input);
	} else if ((keyboard_line == keyboard_line_buf) && (ch == '>')) {
	  // extension to request MPos
	  // $10=1 report MPos
	  // $10=0 report WPos
	  status_requested = TRUE;
	  gcode ("$10=1");
	  wrefresh (output);
	  wrefresh (input);
	  ch = '?';
	  if (write (Data_To_CNC_Fd, &ch, 1) != 1)
	    fatal ("partial/failed write (To_CNC)");
	  *(keyboard_line = keyboard_line_buf) = '\0';
	  wprintw (output, "\n? (current status)");
	  wrefresh (output);
	  wrefresh (input);
	  gcode ("$10=0");
	  wrefresh (output);
	  wrefresh (input);
	} else if ((keyboard_line == keyboard_line_buf) && (ch == ' ')) {	// non-standard 
										// command 
										// added 
										// - 
										// space 
										// bar 
										// is 
										// easier 
										// to 
										// hit 
										// in 
										// an 
										// emergency!

	  ch = 'X' & 31;	// (in addition to a real e-stop button. Just 
				// in case.)
	  // or should I be sending '!' ?

	  if (write (Data_To_CNC_Fd, &ch, 1) != 1)
	    fatal ("partial/failed write (To_CNC)");
	  *(keyboard_line = keyboard_line_buf) = '\0';
	  wprintw (output, "\n' ' (PAUSED!)");	// do more than 'feed hold'.
						// Treat like an extra
						// e-stop...
	  wrefresh (output);
	  wrefresh (input);
	} else if (ch == ('U' & 31)) {	// cancel line.
	  *(keyboard_line = keyboard_line_buf) = '\0';
	  wprintw (input, "\n");
	  wrefresh (input);
	} else if (ch == ('C' & 31)) {
	  exit (0);
	} else if (ch == ('D' & 31)) {
	  exit (0);
	} else if ((ch == '\r') || (ch == '\n')) {	// \r from keyboard,
							// \n from a file...
	  *keyboard_line = '\0';	// (should already be done)
	  // move completed line from input window to output window as a log
	  // of the echo.
	  wprintw (output, "\n%s", keyboard_line_buf);
	  wrefresh (output);
	  wprintw (input, "\n");
	  wrefresh (input);
	  if (local_command (keyboard_line_buf)) {
	    // Check to see if we recognise this line as a special command
	    // before defaulting it to a GCODE command.
	    *(keyboard_line = keyboard_line_buf) = '\0';
	  } else {
	    *keyboard_line++ = '\n';
	    *keyboard_line = '\0';
	    expecting_ok = TRUE;
	    // regular gcode commands are sent here.  Really need to
	    // restructure this whole thing!
	    if (write
		(Data_To_CNC_Fd, keyboard_line_buf,
		 strlen (keyboard_line_buf)) != strlen (keyboard_line_buf))
	      fatal ("partial/failed write (To_CNC)");
	    *(keyboard_line = keyboard_line_buf) = '\0';
	  }
	} else if ((ch == ('H' & 31)) || (ch == 127)) {	// erase
	  if (keyboard_line > keyboard_line_buf) {
	    *--keyboard_line = '\0';
	    *keyboard_line++ = ' ';
	    *keyboard_line = '\0';
	    wprintw (input, "%s", keyboard_line_buf);
	    wprintw (input, "\n");
	    wrefresh (input);
	    *--keyboard_line = '\0';
	    wprintw (input, "%s", keyboard_line_buf);
	    wrefresh (input);
	  }
	} else if ((keyboard_line - keyboard_line_buf) <
		   ((sizeof (keyboard_line_buf)) - 1)) {
	  // regular buffered characters come through here
	  *keyboard_line++ = ch;
	  *keyboard_line = 0;
	  wprintw (input, "\n%s", keyboard_line_buf);	// we don't really
							// need to redraw the 
							// whole line, but it 
							// doesn't hurt.
	  wrefresh (input);	// Old one scrolls off. Easiest way to handle 
				// it.  Single-line window.
	} else {
	  // buffer full perhaps?, do bell()?
	}
      }
    }
  }
  ttyReset ();
  return (EXIT_FAILURE);

}