Note: the AutonController is meant for IterativeRobot-based robots, but can be modified fairly easily.
I've been playing around with reading autonomous modes from the filesystem on the cRIO and've found
lex and
yacc to be very useful and want to share them. I've set up configurations to read commands in the format of:
Code:
# comment
[ModeName]
COMMAND 0 0;
# command_name parameter timeout;
# examples
[Wait3sAndFire]
SHOOTER 3800 0;
NOTHING 0 3;
CHUTE 1 0;
[DoNothing]
NOTHING 0 15;
The first step is to tokenize the file using lex. This turns the plain text into meaningful symbols using regular expressions. For example [0-9\.]+ will be saved for yacc as a NUMBER. Comments and whitespace aren't tokenized, and ignored.
lexer.l
Code:
%{
#include <stdio.h>
#include "parser.h"
%}
%%
#.* /* comment */;
[0-9\.]+ yylval.number=atof(yytext); return NUMBER;
[a-zA-Z0-9_][a-zA-Z0-9_]* yylval.string=strdup(yytext); return WORD;
\[ return LBRACKET;
\] return RBRACKET;
; return SEMICOLON;
\n /* whitespace */;
[ \t]+ /* whitespace */;
%%
Moving on to yacc now. Yacc lets you define grammar, and corresponding code. It works top down and can use recursion (start: goes first and then commands: recurs). Since some of the data we give yacc is number data, and some is text data, we need a structure that can hold both. This is what the union in parser.y is for. We define and reference parts of the AutonController class in this file, you'll see the rest next.
parser.y
Code:
%{
#include <stdio.h>
#include <string.h>
#include "AutonController.h"
void yyerror(const char *str)
{
//printf("error: %s\n",str);
}
extern "C"
{
int yyparse(void);
int yylex(void);
int yywrap()
{
return 1;
}
}
extern FILE* yyin;
AutonController* controller = NULL;
int AutonController::parseCommands(FILE* file)
{
controller = this;
yyin = file;
return yyparse();
}
char* section = NULL;
%}
%token <number> NUMBER
%token <string> WORD
%token <string> LBRACKET
%token <string> RBRACKET
%token <string> SEMICOLON
%union
{
double number;
char* string;
}
%%
start:
section commands
;
commands:
|
commands command
;
command:
section
|
WORD NUMBER NUMBER SEMICOLON
{
controller->addCommand(section, $1, $2, $3);
}
;
section:
LBRACKET WORD RBRACKET
{
section = $2;
}
;
%%
Before going further, we can actually run lex and yacc now. Use these commands:
Code:
lex lexer.l
yacc -o parser.cc -d parser.y
Yacc passes the commands to AutonController, a class to store our auton data.
AutonController.h
Code:
#ifndef _AUTON_CONTROLLER_H
#define _AUTON_CONTROLLER_H
#include <map>
#include <queue>
#include <string>
#include <stdio.h>
#include "WPILib.h"
typedef struct AutonCommand
{
std::string type;
double parameter;
double timeout;
} AutonCommand;
class AutonController
{
public:
AutonController(const char* filename);
void reloadCommands();
void reset();
void handle();
void addCommand(char* scriptName, char* type, double param, double timeout);
void cycleMode();
char* getModeName();
private:
DriverStationLCD* lcd;
const char* scriptFilename;
std::map<char*, std::queue<AutonCommand> > commands;
std::map<char*, std::queue<AutonCommand> >::iterator selector;
Timer* timer;
int parseCommands(FILE* file);
};
#endif
AutonController stores commands in STL queues, and stores those queues in a map of Mode Name -> Command Queue. An iterator is used to select modes.
AutonController.cpp
Code:
#include "AutonController.h"
AutonController::AutonController(const char* filename)
{
lcd = DriverStationLCD::GetInstance();
timer = new Timer();
scriptFilename = filename;
reloadCommands();
}
void AutonController::reloadCommands()
{
commands.clear();
FILE* file = fopen(scriptFilename, "r");
if(!file)
{
printf("Auton script file \"%s\" does not exist. No commands loaded.\n", scriptFilename);
return;
}
if(parseCommands(file) != 0)
{
printf("Syntax error in %s!\n", scriptFilename);
commands.clear();
}
selector = commands.begin();
timer->Start();
fclose(file);
}
void AutonController::reset()
{
// Reset robot state here
timer->Reset();
}
void AutonController::handle()
{
AutonCommand curCmd = selector->second.front();
if(selector->second.empty());
return;
bool result = false;
printf("Current command: %s\n", curCmd.type.c_str());
if(curCmd.type == "NOTHING")
{
// waiting...
} else
{
printf("Unknown command \"%s\" in script %s (file %s)!\n", curCmd.type.c_str(), selector->first, scriptFilename);
result = true;
}
if(result || timer->Get() > curCmd.timeout)
{
selector->second.pop();
timer->Reset();
}
}
void AutonController::addCommand(char* scriptName, char* type, double param, double timeout)
{
printf("Adding %s (%f) to %s with timeout of %f\n", type, param, scriptName, timeout);
AutonCommand c;
c.type = type;
c.parameter = param;
c.timeout = timeout;
commands[scriptName].push(c);
}
void AutonController::cycleMode()
{
selector++;
if(selector == commands.end())
{
lcd->PrintfLine(DriverStationLCD::kUser_Line1, "Reloading auto script");
lcd->UpdateLCD();
reloadCommands();
}
}
char* AutonController::getModeName()
{
return selector->first;
}
Hope you find this useful. Improvements could be added, such as parsing expressions using yacc (ex. DRIVE 5*5 0; ). If you want to learn more about lex and yacc, I found
this tutorial to be very useful.