View Single Post
  #1   Spotlight this post!  
Unread 12-04-2012, 14:18
connor.worley's Avatar
connor.worley connor.worley is offline
Registered User
FRC #0973 (Greybots)
Team Role: Mentor
 
Join Date: Mar 2011
Rookie Year: 2010
Location: Berkeley/San Diego
Posts: 601
connor.worley has a reputation beyond reputeconnor.worley has a reputation beyond reputeconnor.worley has a reputation beyond reputeconnor.worley has a reputation beyond reputeconnor.worley has a reputation beyond reputeconnor.worley has a reputation beyond reputeconnor.worley has a reputation beyond reputeconnor.worley has a reputation beyond reputeconnor.worley has a reputation beyond reputeconnor.worley has a reputation beyond reputeconnor.worley has a reputation beyond repute
Parsing file-based autonomous modes with lex and yacc

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.
__________________
Team 973 (2016-???)
Team 5499 (2015-2016)
Team 254 (2014-2015)

Team 1538 (2011-2014)
2014 Driver (25W 17L 1T)
日本語でOK

Last edited by connor.worley : 12-04-2012 at 14:23.
Reply With Quote