Home · Docs · Primer · Examples · Prefixing · Syntax · Rules · Parser · Tree · Actions · Debug
Support

HAPY ACTIONS

This page documents Hapy Actions API. Actions augment the grammar with semantic checks and on-the-fly interpretation code.

Table of Contents

Introduction

Action is a piece of code that can be attached to a grammar rule. The Hapy parser executes the attached Action right after the rule matches. Note that a single rule may match many times during the parsing process and a local match may be rejected by further rule matching and backtracking. The action is executed every time the corresponding rule matches.

An action is passed the parsing subtree formed by the match, the result status code, and other housekeeping information. An action may:

Under the Hapy hood, an Action is a wrapper around ActionBase, a simple C++ class with a function operator that takes a pointer to Action::Params as a parameter (see Action.h). The operator calls a pure virtual act() method. Defining your own actions by overriding the act() method is efficient but awkward. The sections below describe two alternative ways to define actions. Examples source code is also available in Hapy distribution as docs/actions.cc.

Attaching Action to a Rule

This section explains how to attach an existing action to a grammar rule. For information on how to create an action, see sections below.

To enable an action, you must attach it to one of the parsing rules. There are two ways to do that. First, an action can be attached using a subscript operator[] of the Rule class:

	Rule instruction, name, expression, ...;
	instruction = name[&grokName] >> expression[&grokExpression] >> ';';
	name = ...
	expression = ...
	

This method keeps semantic actions and syntax rules together, for a compact grammar declaration. The subscript operator[] method does not modify its rule. Thus, the first method can be applied to predefined constant rules such as empty_r and to temporary rules:

	call = empty_r[&prepCall] >> (name >> parameters)[&grokCall];
	

The alternative is to use an action method of the Rule class:

	Rule instruction, name, expression, ...;
	instruction = name >> expression >> ';';
	name = ...
	expression = ...

	name.action(&grokName);
	expression.action(&grokExpression);
	

This second method allows to isolate semantic actions and syntax rules, for a clean, readable grammar. Naturally, the action method modifies its rule. Thus, the second method makes it easy to attach the same action to all occurrences of the rule in the grammar:

	instruction = name >> expression >> ';';
	call = name >> parameters;
	...
	name.action(&grokName); // will be called for each name above
	

The best method to use depends on your grammar, actions, and taste. Both methods can be used inside the same grammar.

Stand-alone Function Action

Simple actions can be defined as stand-alone C++ functions. An action function takes a pointer to Action::Params as a parameter and returns nothing. The following is an example of an action that turns on debugging every time a particular rule matches the input.

	#include <Hapy/Actions.h> /* required header if you do actions */
	...
	using namespace Hapy;

	static
	void TurnDebuggingOn(Action::Params *) {
		Rule::Debug(true);
	}
	...

	Rule particular;
	particular = ...;
	particular.action(&TurnDebuggingOn);
	

To pass additional parameters to your action or change its profile, use standard STL (e.g., bind2nd) or similar custom wrappers. For example, lets add a simple "reason" parameter to the above TurnDebuggingOn() function and call the resulting function ExplainDebuggingOn()

	#include <Hapy/Actions.h>
	#include <functional>     /* to get access to bind() */
	...

	static
	void ExplainDebuggingOn(Action::Params *, const char *reason) {
		clog << "turning debugging on because " << reason << endl;
		Rule::Debug(true);
	}
	...

	Rule r1, r2;
	...
	r1.action(bind2nd(ptr_fun(&ExplainDebuggingOn), "rule r1 matched!"));
	r2.action(bind2nd(ptr_fun(&ExplainDebuggingOn), "coredump is near"));
	

Here is a more realistic example. The following piece of code adds a Pree tree (corresponding to the matched portion of the input) to an STL list. The pointer to the list is supplied as an additional action parameter:

	#include <Hapy/Actions.h>
	#include <list>
	...

	typedef std::list<Pree> Matches;

	static
	void AddMatch(Action::Params *p, Matches *matches) {
		matches->push_back(p->pree);
	}
	...

	Matches matches;
	Rule particular;
	particular = ...;
	particular.action(bind2nd(ptr_fun(&AddMatch), &matches));
	

Class Method Action

Non-trivial actions are usually implemented as class methods. A class method can be converted to an Action using the mem_action() function. The function is declared in Hapy/MemAction.h header file (which is included by Hapy/Actions.h).

The following is an example of a class implementing two actions common to configuration file interpretation. The Interpreter::handleAssignment() method records a value of a user-defined variable. The Interpreter::handleOption() method processes the configuration option. Each method is associated with the corresponding grammar rule via the Rule::action() call.

	#include <Hapy/FunAction.h>
	using namespace Hapy;

	class Interpreter {
		public:
			void handleAssignment(Action::Params *params);
			void handleOption(Action::Params *params);
			...
	};
	...

	Interpreter interpreter;
	Rule grammar, statement, assignment, option, ...;
	...
	grammar = *(statement >> ";");
	statement = assignment | option;
	assignment = name >> "=" >> value;
	option = name >> *value;
	...
	assignment.action(mem_action(&interpreter, &Interpreter::handleAssignment));
	option.action(mem_action(&interpreter, &Interpreter::handleOption));
	

The mem_action() function creates an Action object that keeps the pointer to the Interpreter object and the pointer to one of the Interpreter's methods. The action method is called every time the corresponding rule matches.

Preemptive Action

Hapy actions are always executed after the rule matches. Sometimes, it may be useful to execute an action before the rule is tried. This is possible by attaching the action to a zero-width matching rule:

	Rule realCore;  // the actual rule we are interested in
	realCore = ...;

	Rule core; // use "core" instead of the "realCore" rule
	core = empty_r[&PreemptiveAction] >> realCore; 
	

When the core rule is used instead of the realCore rule, the PreemptiveAction() action is executed before the realCore rule is matched.

If the above tricks end up being used a lot, we can add a Rule::pre_action() method.


SourceForge Home