I have been playing with a C++ parser library, boost::spirit, at work. To 
try and learn now it works, I wrote a parser for the stdmenus.ui 
configuration file.

The code consists of 5 'grammars': Menubar, MenubarItem, Menu, MenuItem and 
UI which latter couples it all together. It's all very elegant once you've 
got the grip of it and is both powerful and rigorous.

I thought I'd post the code for your perusal, given that nobody seems 
particularly enamoured of lyxlex and this approach would give us both lexer 
and parser together. See attached.

Compile with something like (your boost tree needs to have the spirit 
library, so the one that comes with LyX is no good):

g++ -I$BOOST_PATH -g -O2 -W -Wall -o read_ui_file read_ui_file.C

Run as
$ ./read_ui_file $LYX_DIR/lib/ui/stdmenus.ui

Just a bit of fun for you all ;-)
Angus

ps The downside is that it is expensive to compile because spirit uses 
expression templates internally. On my 2.7GHz machine here at home it takes 
almost exactly 1 minute to compile and link, producing a 12MB executable 
when compiled "-g -O2". Nonetheless, I think that most of that is debug 
cruft as evidenced by its 'size' stats:
$ size read_ui_file
   text    data     bss     dec     hex filename
 176758   15413     604  192775   2f107 read_ui_file


/**
 * \file read_ui_file.C
 *
 * \author Angus Leeming
 */

#include <boost/spirit/core.hpp>
#include <boost/spirit/utility/confix.hpp>
#include <boost/spirit/utility/escape_char.hpp>
#include <boost/spirit/symbols/symbols.hpp>
#include <boost/spirit/dynamic/if.hpp>
#include <boost/spirit/iterator/file_iterator.hpp>

#include <cassert>
#include <iostream>
#include <string>

namespace spirit = boost::spirit;


// These four structs contain the contents of the Menus and Menubars
struct MenuItem {
	enum Tag {
		branches,
		documents,
		exportformats,
		floatinsert,
		floatlistinsert,
		importformats,
		item,
		lastfiles,
		optitem,
		optsubmenu,
		pasterecent,
		separator,
		submenu,
		toc,
		updateformats,
		viewformats,
		none
	};

	Tag id;
	std::string entry;
	std::string shortcut;
	std::string lfun;

	MenuItem() : id(none) {}
};


struct MenubarItem {
	std::string menuname;
	std::string entry;
	std::string shortcut;
};


struct Menu {
	std::string name;
	std::vector<MenuItem> items;
	void add(MenuItem const & mi) { items.push_back(mi); }
};


struct Menubar {
	std::string name;
	std::vector<MenubarItem> menus;
	void add(MenubarItem const & mbi) { menus.push_back(mbi); }
};


namespace {

// 'Actor's used by the parser to insert data.
template <typename MenuT>
struct add_menu_action {
	add_menu_action(std::vector<MenuT> & menus_, MenuT & menu_)
		: menus(menus_), menu(menu_) {}

	template <typename IteratorT>
	void operator()(IteratorT const & first, IteratorT const & last) const
	{
		menus.push_back(menu);
		menu = MenuT();
	}

private:
	std::vector<MenuT> & menus;
	MenuT & menu;
};


template <typename MenuT, typename ItemT>
struct add_item_action {
	add_item_action(std::vector<MenuT> & m_, ItemT & mi_)
		: menus(m_), item(mi_) {}

	template <typename IteratorT>
	void operator()(IteratorT const & first, IteratorT const & last) const
	{
		assert(!menus.empty());
		menus.back().add(item);
		item = ItemT();
	}

private:
	std::vector<MenuT> & menus;
	ItemT & item;
};


struct Actions {
	Actions()
	: add_menubar(menubars, placeholder_menubar),
	  add_menu(menus, placeholder_menu),
	  add_menu_to_menubar(menubars, placeholder_menubaritem),
	  add_item_to_menu(menus, placeholder_menuitem)
	{}

	std::vector<Menubar> menubars;
	std::vector<Menu>    menus;

	Menu        placeholder_menu;
	Menubar     placeholder_menubar;
	MenuItem    placeholder_menuitem;
	MenubarItem placeholder_menubaritem;

	add_menu_action<Menubar> add_menubar;
	add_menu_action<Menu>    add_menu;

	add_item_action<Menubar, MenubarItem> add_menu_to_menubar;
	add_item_action<Menu, MenuItem>       add_item_to_menu;
};


// These are the 'grammars' that define the contents of the various components
// of the file and how they all fit together.
struct MenubarItemGrammar : public spirit::grammar<MenubarItemGrammar>
{
	Actions & actions;

	MenubarItemGrammar(Actions & a_) : actions(a_) {}

        template <typename ScannerT>
        struct definition {
		definition(MenubarItemGrammar const & self);

		typedef spirit::rule<ScannerT> rule_t;
		rule_t const & start() const { return rule; }

	private:
		spirit::subrule<0> expression;
		spirit::subrule<1> quoted_entry;
		spirit::subrule<2> menuname;
		spirit::subrule<3> entry;
		spirit::subrule<4> shortcut;
		spirit::subrule<5> quoted_menuname;
		spirit::subrule<6> unquoted_menuname;
		rule_t rule;
        };
};


struct MenubarGrammar : public spirit::grammar<MenubarGrammar>
{
	Actions & actions;

	MenubarGrammar(Actions & a_) : actions(a_) {}

        template <typename ScannerT>
        struct definition {
		definition(MenubarGrammar const & self);

		typedef spirit::rule<ScannerT> rule_t;
		rule_t const & start() const { return rule; }

	private:
		MenubarItemGrammar const menubaritem_p;
		rule_t rule;
        };
};


// MenuItems that have no other arguments
struct Zeroarg_menuid_p : spirit::symbols<MenuItem::Tag>
{
	Zeroarg_menuid_p() {
		add
			("branches",        MenuItem::branches)
			("documents",       MenuItem::documents)
			("exportformats",   MenuItem::exportformats)
			("floatinsert",     MenuItem::floatinsert)
			("floatlistinsert", MenuItem::floatlistinsert)
			("importformats",   MenuItem::importformats)
			("lastfiles",       MenuItem::lastfiles)
			("pasterecent",     MenuItem::pasterecent)
			("separator",       MenuItem::separator)
			("toc",             MenuItem::toc)
			("updateformats",   MenuItem::updateformats)
			("viewformats",     MenuItem::viewformats);
	}

};


// MenuItems that have two other arguments, "Entry|Shortcut" "lfun"
struct Twoarg_menuid_p : spirit::symbols<MenuItem::Tag>
{
	Twoarg_menuid_p() {
		add
			("item",       MenuItem::item)
			("optitem",    MenuItem::optitem)
			("optsubmenu", MenuItem::optsubmenu)
			("submenu",    MenuItem::submenu);
	}

};


struct MenuItemGrammar : public spirit::grammar<MenuItemGrammar>
{
	Actions & actions;

	MenuItemGrammar(Actions & a_) : actions(a_) {}

        template <typename ScannerT>
        struct definition {
		definition(MenuItemGrammar const & self);

		typedef spirit::rule<ScannerT> rule_t;
		rule_t const & start() const { return rule; }

	private:
		Zeroarg_menuid_p const zeroarg_menuid_p;
		Twoarg_menuid_p  const twoarg_menuid_p;

		spirit::subrule<0> expression;
		spirit::subrule<1> zeroarg_p;
		spirit::subrule<2> twoarg_p;
		spirit::subrule<3> menutitle;
		spirit::subrule<4> shortcut;
		spirit::subrule<5> quoted_entry;
		spirit::subrule<6> unquoted_lfun;
		spirit::subrule<7> quoted_lfun;
		spirit::subrule<8> lfun;
		rule_t rule;
        };
};


struct MenuGrammar : public spirit::grammar<MenuGrammar>
{
	Actions & actions;

	MenuGrammar(Actions & a_) : actions(a_) {}

        template <typename ScannerT>
        struct definition {
		definition(MenuGrammar const & self);

		typedef spirit::rule<ScannerT> rule_t;
		rule_t const & start() const { return rule; }

	private:
		MenuItemGrammar const menuitem_p;
		spirit::subrule<0> expression;
		spirit::subrule<1> menuname;
		spirit::subrule<2> quoted_menuname;
		spirit::subrule<3> unquoted_menuname;
		rule_t rule;
        };
};


struct UIGrammar : public spirit::grammar<UIGrammar>
{
	Actions & actions;

	UIGrammar(Actions & a_) : actions(a_) {}

        template <typename ScannerT>
        struct definition {
		definition(UIGrammar const & self);

		typedef spirit::rule<ScannerT> rule_t;
		rule_t const & start() const { return rule; }

	private:
		MenubarGrammar const menubar_p;
		MenuGrammar    const menu_p;
		rule_t rule;
        };
};


template <typename ScannerT>
MenubarItemGrammar::
definition<ScannerT>::definition(MenubarItemGrammar const & self)
{
	MenubarItem & mbi = self.actions.placeholder_menubaritem;

	using spirit::alpha_p;
	using spirit::as_lower_d;
	using spirit::c_escape_ch_p;
	using spirit::ch_p;
	using spirit::if_p;
	using spirit::assign;

	rule = (
		expression =
		as_lower_d["submenu"] >> quoted_entry >> menuname,

		quoted_entry =
		spirit::confix_p(ch_p('\"'),
				 entry >> !shortcut,
				 ch_p('\"')),

		menuname = quoted_menuname | unquoted_menuname,

		entry =
		(*(c_escape_ch_p - ch_p('|')))[assign(mbi.entry)],

		shortcut =
		ch_p('|') >> (+alpha_p)[assign(mbi.shortcut)],

		quoted_menuname = spirit::confix_p(ch_p('\"'),
						   unquoted_menuname,
						   ch_p('\"')),

		unquoted_menuname =
		(*(alpha_p | ch_p('-')))[assign(mbi.menuname)]
	);
}


template <typename ScannerT>
MenubarGrammar::
definition<ScannerT>::definition(MenubarGrammar const & self)
	: menubaritem_p(self.actions)
{
	Actions & actions = self.actions;

	using spirit::as_lower_d;

	rule = as_lower_d["menubar"][actions.add_menubar]
		>> +menubaritem_p[actions.add_menu_to_menubar]
		>> as_lower_d["end"];
}


template <typename ScannerT>
MenuItemGrammar::
definition<ScannerT>::definition(MenuItemGrammar const & self)
{
	MenuItem & menuitem = self.actions.placeholder_menuitem;

	using spirit::alnum_p;
	using spirit::as_lower_d;
	using spirit::c_escape_ch_p;
	using spirit::ch_p;
	using spirit::eol_p;
	using spirit::if_p;
	using spirit::assign;

	rule = (
		expression =
		if_p(twoarg_p)[quoted_entry >> lfun].else_p[zeroarg_p],

		zeroarg_p =
		as_lower_d[zeroarg_menuid_p][assign(menuitem.id)],

		twoarg_p =
		as_lower_d[twoarg_menuid_p][assign(menuitem.id)],

		quoted_entry =
		spirit::confix_p(ch_p('\"'),
				 menutitle >> !shortcut,
				 ch_p('\"')),

		lfun = quoted_lfun | unquoted_lfun,

		menutitle =
		(*(c_escape_ch_p - ch_p('|') - ch_p('\"')))
		[assign(menuitem.entry)],

		shortcut =
		ch_p('|') >> alnum_p[assign(menuitem.shortcut)],

		quoted_lfun = spirit::confix_p(ch_p('\"'),
					       unquoted_lfun,
					       ch_p('\"')),

		unquoted_lfun =
		(*as_lower_d[c_escape_ch_p - ch_p('\"') - eol_p])
		[assign(menuitem.lfun)]
	);
}


template <typename ScannerT>
MenuGrammar::
definition<ScannerT>::definition(MenuGrammar const & self)
	: menuitem_p(self.actions)
{
	Actions & actions  = self.actions;
	std::string & name = actions.placeholder_menu.name;

	using spirit::alpha_p;
	using spirit::as_lower_d;
	using spirit::ch_p;
	using spirit::assign;

	rule = (
		expression =
		as_lower_d["menu"]
		>> menuname[actions.add_menu]
		>> +(menuitem_p[actions.add_item_to_menu])
		>> as_lower_d["end"],

		menuname = quoted_menuname | unquoted_menuname,

		quoted_menuname = spirit::confix_p(ch_p('\"'),
						   unquoted_menuname,
						   ch_p('\"')),

		unquoted_menuname =
		as_lower_d[*(alpha_p | ch_p('_'))][assign(name)]
	);
}


template <typename ScannerT>
UIGrammar::
definition<ScannerT>::definition(UIGrammar const & self)
	: menubar_p(self.actions), menu_p(self.actions)
{
	using spirit::as_lower_d;
	rule = as_lower_d["menuset"] >> menubar_p >> +menu_p >> as_lower_d["end"];
}


void print(std::ostream & os, std::vector<Menubar> const & menubars)
{
	std::vector<Menubar>::const_iterator mb_it  = menubars.begin();
	std::vector<Menubar>::const_iterator mb_end = menubars.end();
	for (; mb_it != mb_end; ++mb_it) {
		os << "Menubar " << mb_it->name << '\n';

		std::vector<MenubarItem> const & menus = mb_it->menus;
		std::vector<MenubarItem>::const_iterator mbi_it  = menus.begin();
		std::vector<MenubarItem>::const_iterator mbi_end = menus.end();
		for (; mbi_it != mbi_end; ++mbi_it) {
			os << "\tName \"" <<  mbi_it->menuname << '\"';
			if (!mbi_it->entry.empty())
				os << " entry \"" << mbi_it->entry << '\"';
			if (!mbi_it->shortcut.empty())
				os << " shortcut \"" << mbi_it->shortcut << '\"';
			os << '\n';
		}
	}
}


void print(std::ostream & os, std::vector<Menu> const & menus)
{
	std::vector<Menu>::const_iterator m_it  = menus.begin();
	std::vector<Menu>::const_iterator m_end = menus.end();
	for (; m_it != m_end; ++m_it) {
		os << "\nMenu " << m_it->name << '\n';

		std::vector<MenuItem> const & items = m_it->items;
		std::vector<MenuItem>::const_iterator mi_it  = items.begin();
		std::vector<MenuItem>::const_iterator mi_end = items.end();
		for (; mi_it != mi_end; ++mi_it) {
			os << "\tID " << mi_it->id;
			if (!mi_it->entry.empty())
				os << " entry \"" << mi_it->entry << '\"';
			if (!mi_it->shortcut.empty())
				os << " shortcut \"" << mi_it->shortcut << '\"';
			if (!mi_it->lfun.empty())
				os << " lfun \"" << mi_it->lfun << '\"';
			os << '\n';
		}
	}
}

} // namespace anon


int main(int argc, char * argv[])
{
	if (argc != 2) {
		std::cerr << "Usage:\n" << argv[0] << " <input_file>\n";
		return 1;
	}

	typedef char                           char_t;
	typedef spirit::file_iterator<char_t>  iterator_t;
	typedef spirit::scanner<iterator_t>    scanner_t;
	typedef spirit::parse_info<iterator_t> info_t;

	iterator_t first(argv[1]);
	if (!first) {
		std::cout << "Unable to open file '" << argv[1] << "'!\n";
		return 1;
	}
	iterator_t last = first.make_end();

	using spirit::space_p;
	using spirit::comment_p;

	Actions actions;
	UIGrammar grammar(actions);

	info_t const info =
		spirit::parse(first, last, grammar, space_p | comment_p('#'));

	print(std::cout, actions.menubars);
	print(std::cout, actions.menus);

	return 0;
}

Reply via email to