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; }