Regular expressions

I am looking for a regular expression parser. I found the Nick Gammon 0.1.0 version, but that is too limited for the problem I am working on (no '|' metasymbol, no *,?,+ quantifiers on parenthesized groups). Can anyone advise?

For which arduino?

https://docs.micropython.org/en/latest/library/re.html

Arduino Uno

i'm curious "what" you are trying to do and if a using regular expressions is necessary. Other approaches may be simpler

look at Nick's original posting where he explained

A UNO does not have a lot of memory...

I am using an Arduino in combination with a Raspberry: the Raspberry for the brains and the Arduino for the muscles (stepper motor, servo, LED’s etc.). Using the serial protocol (which I have in place) I want to be able to send commands from the Raspberry such as:

rotate( servo1, 90, true)
expand(actuator, 0)
dispay( “this is a string”)
setdelay( motor1, 0.005)
stop()

I send these over the line as strings.

So at the Arduino side I need a piece of code that gives me the constituent parts in an array (arg[0] = name, arg[1…] = arguments)

A standard regex that does the trick is:

^(?<command>[a-zA-Z]\w*)\(\s*(?<head>[a-zA-Z]\w*|\d+(\.\d+)?|false|true|("[^"]*")*)(\s*\,\s* (?<tail>([a-zA-Z]\w*|\d+(\.\d+)?|false|true|("[^"]*"))*))*\s*\)$

(If you want you can check this with the unsurpassed
Expresso Regular Expression Tool)

What I need is an equivalent for the Arduino (Uno).

see this post Need more clarity with character arrays - #5 by J-M-L

my StreamCommandParser might be helpful.

i typically test code in simulation. the projects may have human interfaces: pots, encoders, buttons. i test them with command files that affect the variables these interfaces would normally affect

here's an command file

# throttle up and run

    setup
    state 3
    dbgWiFi  0
    debug    2

# --------------------------------------------------------------------
# train with 0 cars
    loco  I-10sa

    cars   10
    air    90
    mph    10
    loop   10

    encB    8   SVC
    loop   30

    encB    0   REL
    loop   10

    encB    6   LAP
    loop   10

    encB    0   REL
    loop   30

and here's code to process it. It first tokenizes the command, capturing each field in two arrays, one with just the string, the 2nd with value.

the first token, the cmd, determined what to expect and what function in the code to invoke. This code is very fluid, whatever i need

i don't see a need for parenthesis or quotes. multiple commands can be separated with semicolons and processing each one at a time

// 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>

#include "brakes.h"
#include "eng.h"
#include "physics.h"
#include "vars.h"

extern void setup (void);
extern void loop  (void);
extern unsigned int debug;

unsigned long _msec;

char        *progname;

// --------------------------------------------------------------------
#define MAX_TOKS   10
char * toks [MAX_TOKS];
int    vals [MAX_TOKS];

int
getToks(
    char *s )
{
    int  i = 0;

 // printf ("%s: %s\n", __func__, s);

    for (toks [i] = strtok (s, " "); toks [i]; )  {
        vals [i] = atoi (toks [i]);
        toks [++i] = strtok (NULL, " ");
    }

    return i;
}

// --------------------------------------------------------------------
extern void buttonFuncs (int);

extern float brkLnFil;
extern float brkLnVol;
extern float brkLnPsi;

extern float fps;

#undef DbgBr_Wifi
#ifdef DbgBr_Wifi
#endif
extern int   dbgBr;
extern int   dbgWifi;

void
application (char *filename)  {
    FILE  *fp;
    char   s[BUFSIZ];
    int    args [9];

    printf ("%s: %s %s\n", __func__, progname, filename);

    if ( (fp = fopen (filename, "rb")) == NULL)  {
        perror ("app - fopen input");
        exit (1);
    }

    while (fgets (s, BUFSIZ, fp) != NULL)  {
        int len = strlen(s);
        if (1 == len || '#' == s[0])  {
            printf ("%s", s);
            continue;
        }

        s [len-1] = '\0';
        printf (" %s: %-12s\n", __func__, s);

        int nToks = getToks (s);
    //  printf (" - [%d] %s\n", nToks, toks [0]);

        // --------------------------------------
        // two word commands
        if (2 <= nToks)  {
            if (! strcmp (toks [0], "loop"))  {
                for (int n = 0; n < vals [1]; n++)  {
                    millisTic ();
                    loop ();
                }
            }

            else if (! strcmp (toks [0], "debug"))
                debug = vals [1];

            else if (! strcmp (toks [0], "air"))
                airBrkFill (vals [1]);

            else if (! strcmp (toks [0], "but"))
                buttonFuncs (vals [1]);

#define DbgBr_Wifi
#ifdef DbgBr_Wifi
# ifndef Rino
            else if (! strcmp (toks [0], "dbgBr"))
                dbgBr = vals [1];
# endif

            else if (! strcmp (toks [0], "dbgWiFi"))
                dbgWifi = vals [1];
#endif

            else if (! strcmp (toks [0], "cars"))
                cars = vals [1];

            else if (! strcmp (toks [0], "brAir"))  {
                for (int i = 0; i <=  BRK_A_LAST; i++)  {
                    printf ("%s: encBpos %d %s\n", __func__, i, airBrkStr [i]);
                    if (! strcmp (airBrkStr [i], toks [1])) {
                        encBpos = i;
                        break;
                    }
                }
            }

            else if (! strcmp (toks [0], "brInd"))  {
                for (int i = 0; i <=  BRK_I_LAST; i++)  {
                    if (! strcmp (indBrkStr [i], toks [1])) {
                        encApos = i;
                        break;
                    }
                 // printf ("%s: encBpos %d\n", __func__, i);
                }
            }

            else if (! strcmp (toks [0], "encA"))
                encApos = vals [1];

            else if (! strcmp (toks [0], "encB"))
                encBpos = vals [1];

            else if (! strcmp (toks [0], "fps"))
                fps = atof (toks [1]);

            else if (! strcmp (toks [0], "gr"))
                grX10 = vals [1];

            else if (! strcmp (toks [0], "loco"))  {
                pEng = & engs [engGet (toks [1])];
                printf ("     %s: loco %s\n", pEng->name);
            }

            else if (! strcmp (toks [0], "mph"))
                phySetMph (vals [1]);

            else if (! strcmp (toks [0], "rev"))
                reverser = atoi (toks [1]);

            else if (! strcmp (toks [0], "state"))
                state = vals [1];

            else if (! strcmp (toks [0], "thr"))
                throttle = atoi (toks [1]);

            else  {
                printf (" %s: Error - unknown %s\n", __func__, toks [0]);
                exit (1);
            }
        }

        // --------------------------------------
        // single word commands
        if (! strcmp (toks [0], "setup"))
            setup ();

        if (! strcmp (toks [0], "quit"))
            exit (0);
    }
}

// --------------------------------------------------------------------
void help (void)  {
    printf (" Usage: %s \n", progname);
}

// --------------------------------------------------------------------
int main (int argc, char **argv)  {
    int   c;

    progname = *argv;

    while ((c = getopt(argc, argv, "D:o")) != -1)  {
        switch (c)  {
        case 'D':
            debug = atoi (optarg);
            break;

        case 'o':
            break;

        default:
            printf ("Error: unknown option '%c'\n", optopt);
                        help();
            break;
        };

    }

    if (optind == argc)
        help();
    else
        for ( ; optind < argc; optind++)
            application (argv[optind]);

    return 0;
}

Thanks.

Yes, I agree I can easily do without the ‘syntactic sugar’, and your approach will certainly work for me as well. But I was only wondering if a regex library existed with which I could implement my approach with a few lines of code.

Thank you, I will look into this!

A Regex library is pretty extreme overkill to parse such trivial syntax. It can be done with a FAR smaller footprint, in terms of lines of code, memory, and CPU cycles, without Regex.

I guess you're right. I just recently made my first steps into the Arduino world, coming from PCs with abundant memory and cycles.

i'm curious how you can simplify this to just a few lines using regex? what would they be?

In C#, I would use one or more of the methods below.

using System.Text.RegularExpressions;

public static Regex regex = new Regex(
      "^(?<command>[a-zA-Z]\\w*)\\(\\s*(?<head>[a-zA-Z]\\w*|\\d+(\\."+
      "\\d+)?|false|true|(\"[^\"]*\")*)(\\s*\\,\\s* (?<tail>([a-zA-"+
      "Z]\\w*|\\d+(\\.\\d+)?|false|true|(\"[^\"]*\"))*))*\\s*\\)$"
    );

// Split the InputText wherever the regex matches
string[] results = regex.Split(InputText);

// Capture the first Match, if any, in the InputText
Match m = regex.Match(InputText);

// Capture all Matches in the InputText
MatchCollection ms = regex.Matches(InputText);

// Test to see if there is a match in the InputText
bool IsMatch = regex.IsMatch(InputText);

// Get the names of all the named and numbered capture groups
string[] GroupNames = regex.GetGroupNames();

// Get the numbers of all the named and numbered capture groups
int[] GroupNumbers = regex.GetGroupNumbers();

Maybe have a look at the examples in Robin's updated serial input basics tutorial; it has some examples for parsing (of simple strings).

No, how are you going to put the value 22 from the string
"motor1 22"
into the variable motor1

After the/each comma, at least one space is required? In that case \s+ would be -- well "better" may be too strong....

(I used regex101.com BTW)

This seems like a basic lexer, as you would use to parse a programming language. Each token is one of

  • word that starts with a letter
    • includes true and false
  • number with optional decimal place
  • string with double-quotes -- no embedded double-quote allowed
  • symbols you care about: ( , )

Should be able to find examples out there to adapt, or recall from memory.

Hello kenb4,

The spaces in the regex are insignificant. A 'real' space would be indicated by '\ '. There is a regex option IgnorePatternWhitespace to set this.

An embedded " is denoted by "" (e,g, "abc""def"). This is handled correctly by the part of the pattern: (\"[^\"]*\")+, which actually defines a concatenation of strings without embedded quotes. (notice that I changed the erroneous final * in the original pattern into a +).

I also agree that the "true" and "false" are unnecessary since the first alternative will also recognize them.

Regards, John