| Home | JetPAG - Jet Parser Auto-Generator | ||
| Articles | |||
The meta-language used by JetPAG for building recognizer sets is very flexible. Despite this flexibility and simplicity of syntax, numerous injections can be done for tweaking the grammars and fitting generated files in special situations, gaining more control over the generated code. In this article I explain how to use features provided by JetPAG for such purposes. First things first, let's have a simple anatomy class of how JetPAG generates files. Let's start with a simple grammar: grammar G: scanner L: INT: '0'-'9'+ ;Figure 1: a simple grammar file These are the important parts from the generated header file for the scanner L:
include "jpfiles/lib/scanner.hpp"
#include "G_ttypes.hpp"
namespace G
{
// Lexical analyzer
class L
: public jetpag::basic_scanner<char>
{
public:
explicit L(Stream &istm_);
virtual StreamToken nextToken();
private:
void match__INT(jetpag::tokenInfo &);
// Create a default char stream manager
CharStreamManager* createDefaultSM(std::istream&);
}; // L
} // G
Figure 2: generated header file for a simple scanner
First things you note are the #include directives. Recognizers generated by JetPAG require two headers: an internal header defining the base type, and the generated header file containing definitions for token types. All recognizers generated from a grammar file are member types in a namespace that has the grammar's name. If you want to nest this namespace in more namespaces, use the multi-value grammar option namespaces as follows:
grammar G< namespaces=Outer Inner >:
namespace Outer
{
namespace Inner
{
namespace G
{
// ...
} // G
} // Inner
} // Outer
Figure 3: automatically nesting recognizers in foreign namespaces
This option is useful if the generated recognizers are to be sub-tools in larger projects. Going back to Figure 2. Scanners derive from the base type jetpag::basic_scanner<_charT>, where _charT is the character type for used input streams. Both scanners and parsers must use have the character type for working together. The type of _charT can be changed with the grammar option char_type. JetPAG enables exttending the inheritance chain of generated recognizer using the inherits modifier, which takes a list of the base types (Note: the internal base type always comes and initializs first in the chain). Let's modify the original grammar file to see how can we tweak the inheritance chain: grammar G< char_type="wchar_t" >: scanner L inherits {% public MyBaseType %}, {% protected MyBaseType2 %}:
class L
: public jetpag::basic_scanner<wchar_t>
, public MyBaseType
, protected MyBaseType2
{
Figure 4: tweaking the inheritance chain with special options and modifiers
Inheritance chain is clearly a great facility. But this facility might become actually problematic if the base types require non-default initialization (internal base types are so). JetPAG enables defining custom constructors for generated recognizers with a full customization of argument lists, initializer lists and free actions. If any custom constructors are defined they replace the default one. Arguments and initializers are generated following the internal ones. In this illustration I use the #type_sub directive which inserts code before the constructors as private to show how to use custom constructors: scanner L inherits {% public BaseRequiringInit %}: #type_sub $$ int _membi; #ctor( int a, int b = 0 ) : BaseRequiringInit(a), _membi(b) { }
// in L.hpp
class L
: public jetpag::basic_scanner
, public public BaseRequiringInit
{
private:
int _membi;
public:
explicit L(Stream &istm_, int, int=(0));
// in L.cpp
L::
L(Stream &istm_, int a, int b)
: Scanner(istm_), BaseRequiringInit(a), _membi(b)
{
}
Figure 5: a custom constructor
As shown, custom arguments and initializers come after the internal ones needed by the recognizer. It is worth saying that arguments, initializers and actions are all optiona, so a custom constructor like this is 100% legal and generates a custom constructor identical that generated by default:
#ctor ()
{
}
Figure 6: a very simple custom constructor
Speaking about members and #type_sub, we should talk about destructors. Many times the recognizer uses member pointers and other objects which require management of allocated memory, and freeing memory and other similar jobs are usualy done in the destructor. JetPAG enables customizing a single destructor for the recognizer. Defining a custom destructor is similar to defining a custom constructor, except that only actions are allowed and only one custom destructor is allowed as well. This example demonstrates how to define a custom destructor: grammar G: scanner L: #dtor ( ) { $$ // OK ... }
// in L.hpp
class L
: public jetpag::basic_scanner<char>
{
public:
explicit L(Stream &istm_);
~L();
// in L.cpp
L::
~L()
{
// OK ...
}
Figure 7: a custom destructor
Now it is the time to show all of the direcitves available for embedding source code in generated files. Directives are all optional, must must follow this order and must come before grammar rules:
#head_top_over
#head_top_below
#type_over
#type_sub
#ctor
#dtor
#type_below
#head_bottom
#impl_top_over
#impl_top_below
#impl_ns
#impl_bottom
// Header file
head_top_over
#include "jpfiles/lib/scanner.hpp"
#include "G_ttypes.hpp"
head_top_below
namespace G
{
type_over
// Lexical analyzer
class L
: public jetpag::basic_scanner<char>
{
type_sub
// Members ...
}; // L
type_below
} // G
head_bottom
// Implementation file
impl_top_over
#include "L.hpp"
impl_top_below
namespace G
{
impl_ns
// Everything ...
} // G
impl_bottom
Figure 8: directives for embedding source code in generated files
|
|||