Ms Windows Resource Lint Header
      Part of MsWindowsResourceLint. Save this as rcLint.h:
        //  this program parses RC files (as VC++ outputs them)
        //  and reports on CUA style issues
      
      
        #pragma warning(disable: 4786)
        #include 
        #include 
      
        using std::cout;
        using std::endl;
        using std::ostream;
        using std::string;
        typedef std::map< string, string >  strings_t;
        typedef std::vector         spec_t;
        typedef string::size_type           size_type;
       
      
        class
        Source
        {
        public:
        Source(string const & rc = ""):
        m_rc(rc),
        m_bot(0),
        m_eot(0)
        {}
      
      
        void           setResource(string const & rc)  {  m_rc = rc;  }
        size_type      getBOT()  {  return m_bot;  }
        string const & getPriorToken()    {  return m_priorToken;    }
        string const & getCurrentToken()  {  return m_currentToken;  }
      
      
        string const &
        pullNextToken()
        {
        m_priorToken = m_currentToken;
        extractNextToken();
        return m_currentToken;
        }
      
      
        size_type
        getLineNumber(size_type at)
        {
        size_type lineNumber = 1;
      
      
        for(size_type idx(0);  idx < at;  ++idx)
        if ('\n' == m_rc[idx])
        ++lineNumber;
      
      
        return lineNumber;
        }
      
      
        string
        getLine(size_type at)
        {
        size_type bol = m_rc.rfind('\n', at);
        if (string::npos == bol)  bol = 0;  else ++bol;
        size_type eol = m_rc.find('\n', at);
        if (string::npos == eol)  eol = m_rc.length();  else ++eol;
        return m_rc.substr(bol, eol - bol);
        }
      
      
        private:
      
      
        string const &
        extractNextToken()
        {
        char static const delims[] = " \t\n,";
      
      
        m_bot = m_rc.find_first_not_of(delims, m_eot);
      
      
        if (string::npos == m_bot)
        m_currentToken = "";
        else if (m_rc[m_bot] == '"')
        m_currentToken = parseString();
        else if (m_rc.substr(m_bot, 2) == "//")
        {
        if (skipUntil("\n"))
        return extractNextToken();
        }
        else if (m_rc.substr(m_bot, 2) == "/*")
        {
        if (skipUntil("*/"))
        return extractNextToken();
        }
        /*    else if (m_rc.substr(m_bot, 1) == "#")
        {
        string line = getLine(m_bot);
        size_type at(0);
        while(isspace(line[at]) && at < line.size())  ++at;
      
      
        if ('#' == line[at])  
        {
        m_eot = m_bot + 1;
        if (skipUntil("\n"))
        return extractNextToken();
        }
        }*/
        else
        {
        m_eot = m_rc.find_first_of(" \n,/", m_bot);
        m_currentToken = m_rc.substr(m_bot, m_eot - m_bot);
        }
      
      
        if ('#' == m_currentToken[0])
        {
        //        assert(m_rc.substr(m_bot, 1) == "#");
        string line = getLine(m_bot);
        size_type at(0);
        while(isspace(line[at]) && at < line.size())  ++at;
      
      
        if ('#' == line[at])  
        {
        --m_eot;
        if (skipUntil("\n"))
        return extractNextToken();
        }
        }
      
      
        return m_currentToken;
        }
      
      
        bool
        skipUntil(char const * delimiter)
        {
        m_eot = m_rc.find(delimiter, m_eot + 1);
      
      
        if (string::npos == m_eot)
        {
        m_currentToken = "";
        return false;
        }
        m_eot += strlen(delimiter);
        return true;
        }
      
      
        char
        parseStringChar()
        {
        if (m_rc[m_eot] == '\\')
        {
        m_eot += 1;
        char escapee(m_rc[m_eot++]);
      
      
        switch (escapee)
        {
        case 'n' :  return '\n';
        case 'r' :  return '\r';
        case 't' :  return '\t';
        case '0' :  return '\0';
        case '\\':  return '\\';
        case 'a' :  return '\a';
        default  :  //  TODO  \x, \v \b, \f
        if (isdigit(escapee))
        {
        string slug = m_rc.substr(m_eot - 1, 3);
        return char(strtol(slug.c_str(), NULL, 8));
        }  
        else
        //assert(false);
        return escapee;
        }        
        }
        else if (m_rc[m_eot] == '"' && m_rc[m_eot+1] == '"')
        m_eot++;
      
      
        return m_rc[m_eot++];
        }
      
      
        string
        parseString()
        {
        m_eot = m_bot + 1;
        string z;
      
      
        while ( m_eot < m_rc.length() && 
        ( m_rc[m_eot] != '"' || 
        m_rc[m_eot + 1] == '"' ) )
        z += parseStringChar();
      
      
        if (m_eot < m_rc.length())
        m_eot += 1;
      
      
        return z;
        }
      
      
        string    m_rc;
        size_type m_bot;
        size_type m_eot;
        string    m_priorToken;
        string    m_currentToken;
        };
      
      
        class Resource;
      
      
        class
        ResourceHandle
        {
        public:  //  don't read this it's just a sharing smart pointer...
        ResourceHandle(Resource *p = NULL):
        m_pInt(new int(p != NULL)), m_p(p) {}
        Resource *get()  {  return m_p;  }
        Resource *operator->()  {  return m_p;  }
        ResourceHandle(ResourceHandle const & rh):
        m_pInt(rh.m_pInt), m_p(rh.m_p) {  ++(*m_pInt);  }
        ~ResourceHandle();
        private:
        Resource * m_p;
        int      * m_pInt;
        };
      
      
        class
        ComplaintDepartment
        {
        public:
        virtual string      getLine(size_type at) = 0;
        virtual size_type   getLineNumber(size_type at) = 0;
        virtual void        complain(string const & description, size_type bot, string evidence = "") = 0;
        virtual string      getString(string const & id) = 0;
        virtual Resource  * getAccelerators(string const & menuID) = 0;
        };  //  DependencyInversionPrinciple
      
      
        class
        Resource
        {
        public:
        void        setBeginningOfText(size_type bot)  {  m_bot = bot;  }
        string      getID()  {  return getSpec(0);  }
        virtual Resource  * clone()  {  return new Resource(*this);  }
        virtual Resource  * get(string /*id*/) {  return NULL;  }
        virtual Resource  * get(int /*idx*/) {  return NULL;  }
        virtual Resource  * get_(int /*idx*/) {  return NULL;  }
        virtual void        parse(Source & /*aSource*/) { }
        virtual string      getLabel()  {  return getSpec(getLabelIndex());  }
        virtual void        LintOne(ComplaintDepartment & ) {}
        virtual void        LintAll(ComplaintDepartment & ) {}
        size_type   getBeginningOfText()  {  return m_bot;  }
        int         getSpecCount() const {  return m_spec.size();  }
        string const & getSpec(int x) const {  return m_spec[x];  }
        void        addSpec(string const & token)  {  m_spec.push_back(token);  }  
        virtual bool        weBePromptLabel()  {  return false;  }
      
      
        char
        getLabelHotkey()
        {
        string label = getLabel();
        size_type at = label.find('&');
      
      
        if ( string::npos != at && 
        (at + 2) < label.length() && 
        '&' != label[at + 1] )
        return label[at + 1];
      
      
        return '\0';
        }
      
      
        virtual void
        printTree(ostream & out, int depth = 1)
        {
        out << std::setw(depth) << " " << getID() << endl;
        }
      
      
        void
        LintLabelPrompt(ComplaintDepartment & aCD)  //  TODO  oaoo?
        {
        char hotKey = this->getLabelHotkey();
        if (!hotKey)
        aCD.complain("Missing & in Menu Label", m_bot);  //  TODO  make m_bot private
        }
      
      
        private:
        virtual int         getLabelIndex()  {  return 1;  }
        spec_t     m_spec;
        size_type  m_bot;    
        };
      
      
        class  //  TODO  or Accelerator
        Control:  public Resource
        {
        public:
      
      
        bool weBePromptLabel()  {  return "LTEXT" == getSpec(1) && 
        ':' == *getLabel().rbegin();  }
      
      
        private:
        virtual int         getLabelIndex()  {  return 2;  }
        Resource     * clone()  {  return new Control(*this);  }
      
      
        bool weBeButton()  {  return  "CONTROL" == getSpec(1) && 
        "Button" == getSpec(4);  }
      
      
        bool weBePushButton()  {  return ( "PUSHBUTTON" == getSpec(1) || 
        "DEFPUSHBUTTON" == getSpec(1) ) && 
        "IDOK" != getSpec(3)   && 
        "IDCANCEL" != getSpec(3);  }
      
      
        virtual void
        LintOne(ComplaintDepartment & aCD)
        {
      
      
        //  TODO  put quotes back on strings
      
      
        if ( weBeButton() ||
        weBePromptLabel() ||
        weBePushButton() )
        {
        //        cout << getLabel() << endl;
        if (!getLabelHotkey())
        aCD.complain("Button missing hotkey", getBeginningOfText());
      
      
        }
        }
      
      
        };
      
      
        string
        toString(int i)
        {
        std::stringstream z;  z << i;
        return z.str();
        }
      
      
        class
        ResourceCollection:  public Resource
        {
        public:
      
      
        void
        add(Resource & nu)  
        {
        m_Resources[nu.getID()] = nu.clone();
        }
      
      
        void
        printTree(ostream & out, int depth = 1)
        {
        Resource::printTree(out, depth);
      
      
        for( Resources_t::iterator it = m_Resources.begin();
        it != m_Resources.end();
        ++it )
        it->second->printTree(out, depth + 5);
        }  //  TODO  this merged with LintAll() == Visitor
      
      
        int getCount() {  return m_Resources.size();  }
      
      
        Resource * 
        get(string id)
        {
        Resources_t::iterator it = m_Resources.find(id);
        if (it == m_Resources.end())  return NULL;
        return it->second.operator->();
        }
      
      
        Resource  *
        get(int idx)
        {
        Resources_t::iterator it = m_Resources.begin();
        while (it != m_Resources.end() && idx--)  ++it;
        return it != m_Resources.end()?  it->second.operator->():  NULL;
        }
      
      
        Resource  *
        get_(int idx)
        {
        return get(toString(idx));/*
        Resources_t::iterator it = m_Resources.begin();
        while (it != m_Resources.end() && idx--)  ++it;
        return it != m_Resources.end()?  it->second.operator->():  NULL;*/
        }
      
      
        virtual string parseName(Source & /*aSource*/) {  return "";  }
      
      
        void
        parseResource(ResourceCollection & aSink, Source & aSource)
        {    
        setBeginningOfText(aSource.getBOT());
        addSpec(parseName(aSource));
        string begin;
      
      
        do  {
        begin = aSource.pullNextToken();
        if (begin == "")  return;
        } while("BEGIN" != begin);
      
      
        aSource.pullNextToken();
      
      
        for(;;)
        {
        string token = aSource.getCurrentToken();
        if ("" == token || "END" == token)  break;
        parse(aSource);
        }
        aSink.add(*this);
        }
      
      
        void
        LintAll(ComplaintDepartment & aCD)
        {
        for( Resources_t::iterator it = m_Resources.begin();
        it != m_Resources.end();
        ++it )
        {
        it->second->LintOne(aCD);
        it->second->LintAll(aCD);
        }
        }
      
      
        void
        LintDupedLabelHotKeys(ComplaintDepartment & aCD, string type)
        {
        Resource * pControl = NULL;
      
      
        for ( int idx(0); pControl = get(idx);  ++idx )
        if (char hotKey = pControl->getLabelHotkey())
        {
        string evidence;
        Resource * pControl2 = NULL;
      
      
        for ( int idx2(idx + 1); pControl2 = get(idx2);  ++idx2 )  //  TODO OAOO
        {
        char hotKey2 = pControl2->getLabelHotkey();
      
      
        if (hotKey == hotKey2)
        evidence += aCD.getLine(pControl2->getBeginningOfText());
        }
        if ("" != evidence)
        aCD.complain( "Duplicated " + type + " hotkey",
        pControl->getBeginningOfText(), evidence );
        }
        }
      
      
        virtual bool isFirstTokenOfNextItem(string const & token) {  return false;  }
      
      
        void
        initResource(Resource & aResource, Source & aSource)
        {
        aResource.addSpec(toString(getCount()));
        aResource.addSpec(aSource.getCurrentToken());
        aResource.setBeginningOfText(aSource.getBOT());
        string token = aSource.pullNextToken();
      
      
        while (token != "END")
        {
        if ( isFirstTokenOfNextItem(token) )
        break;
      
      
        aResource.addSpec(token);
        token = aSource.pullNextToken();
        }
        add(aResource);
        }
      
      
        private:
        typedef std::map< string, ResourceHandle >
        Resources_t;
        Resources_t m_Resources;
        };
      
      
        ResourceHandle::~ResourceHandle()  //  permits derived Resource objects to occupy std::map<>
        {
        --(*m_pInt);
        if(!m_pInt)  {  delete m_p;  delete m_pInt;  }
        }
      
      
        class
        MenuHierarchy:  public ResourceCollection
        {
        public:
        void setMenuID(string const & nu)  {  m_MenuID = nu;  }
        string const & getMenuID()  {  return m_MenuID;  }
        virtual void addMenuItem(Source & aSource);
        void parse(Source & aSource);
      
      
        private:
        string m_MenuID;
        };
      
      
        class  //  also string in STRINGTABLE
        MenuItem:  public MenuHierarchy
        {
        public:
      
      
        private:
        Resource * clone()  {  return new MenuItem(*this);  }
      
      
        void
        complainAbout(ComplaintDepartment & aCD, char const * description, Resource & aResource)
        {
        aCD.complain
        ( 
        description,
        getBeginningOfText(),
        aCD.getLine(aResource.getBeginningOfText())
        );
        }
      
      
        void
        LintAccelerator(ComplaintDepartment & aCD, Resource & aControl)
        {
        if (getID() == aControl.getSpec(2))  //  TODO  make getLabel get the label
        {
        size_type at = getLabel().find('\t');
      
      
        if (string::npos == at)
        complainAbout(aCD, "Missing accelerator prompt in Menu Label", aControl);
        else
        {
        string slice = getLabel().substr(at);
        string keyCap = slice.substr(slice.length() - 1, 1);
      
      
        if (keyCap != aControl.getSpec(1))
        complainAbout(aCD, "Wrong accelerator prompt in Menu Label", aControl);
        }
        }
        }
      
      
        void
        LintAccelerators(ComplaintDepartment & aCD, Resource & anAccelerator)
        {
        for(int idx(0);;++idx)
        {
        Resource * pControl = anAccelerator.get(idx);
        if (!pControl)  break;
        LintAccelerator(aCD, *pControl);
        }
        }
      
      
        void
        LintOne(ComplaintDepartment & aCD)
        {
        LintLabelPrompt(aCD);
      
      
        string help = aCD.getString(getID());
      
      
        if ("" == help)
        aCD.complain("Missing StringTable help for MenuItem", this->getBeginningOfText());
      
      
        Resource  * p = aCD.getAccelerators(getMenuID());
        if (p)  LintAccelerators(aCD, *p);
        }
      
      
        };
      
      
        class
        Popup:  public MenuHierarchy
        {
        public:
      
      
        Resource *clone()  {  return new Popup(*this);  }
      
      
        string
        parseName(Source & aSource)
        {
        return aSource.pullNextToken();
        }
      
      
        private:
      
      
        void
        LintOne(ComplaintDepartment & aCD)
        {
        LintLabelPrompt(aCD);
        LintDupedLabelHotKeys(aCD, "MenuItem");
        }
      
      
        private:
        virtual int         getLabelIndex()  {  return 0;  }
      
      
        };
      
      
        void
        MenuHierarchy::addMenuItem(Source & aSource)
        {
        string label = aSource.pullNextToken();
        assert(label.size());
        if ("SEPARATOR" == label)  
        {
        aSource.pullNextToken();
        return;
        }
        size_type bot = aSource.getBOT();  //  TODO  getBeginningOfText()
        string id = aSource.pullNextToken();
        MenuItem anItem;    
        anItem.setBeginningOfText(bot);
        anItem.addSpec(id);
        anItem.addSpec(label);
        anItem.setMenuID(getMenuID());
        string token;
      
      
        for (;;)
        {
        token = aSource.pullNextToken();
      
      
        if ( "END" == token || "POPUP" == token || "MENUITEM" == token)
        break;
      
      
        anItem.addSpec(token);
        }
        add(anItem);
        }
      
      
        void
        MenuHierarchy::parse(Source & aSource)
        {
        string token = aSource.getCurrentToken();
      
      
        if ("MENUITEM" == token)
        addMenuItem(aSource);
        else if ("POPUP" == token)
        {
        Popup aPopup;
        aPopup.setMenuID(getMenuID());
        aPopup.parseResource(*this, aSource);
        token = aSource.pullNextToken();
        }
        else
        assert(false);  //  syntax error in your resource code! Look at token and m_bot
        }
      
      
        class
        Menu:  public MenuHierarchy
        {
        public:
      
      
        Resource *clone()  {  return new Menu(*this);  }
      
      
        string
        parseName(Source & aSource)
        {
        string token = aSource.getPriorToken();  //  TODO  commonalize this
        setMenuID(token);
        return token;
        }
      
      
        };
      
      
        class
        StringTable: public ResourceCollection
        {
        public:
        StringTable(int idx):  m_idx(toString(idx))  {}
      
      
        private:
        string
        parseName(Source & aSource)
        {
        return m_idx;
        }
      
      
        void
        parse(Source & aSource)
        {
        string token = aSource.getCurrentToken();
        Resource anEntry;
        anEntry.setBeginningOfText(aSource.getBOT());
        anEntry.addSpec(token);
        anEntry.addSpec(aSource.pullNextToken());
        add(anEntry);
        aSource.pullNextToken();
        }
      
      
        Resource *clone()  {  return new StringTable(*this);  }
        string m_idx;
        };
      
      
        class
        Accelerators: public ResourceCollection
        {
        Resource *clone()  {  return new Accelerators(*this);  }
      
      
        string
        parseName(Source & aSource)
        {
        return aSource.getPriorToken() + " ACCELERATORS";
        }
      
      
        bool
        isFirstTokenOfNextItem(string const & token)
        {
        return (1 == token.length() && "|" != token) || 
        "VK_" == token.substr(0, 3);
        }
      
      
        void
        parse(Source & aSource)
        {
        Control aCtrl;
        initResource(aCtrl, aSource);
        }
      
      
        };
      
      
        class
        Dialog: public ResourceCollection
        {
        public:
      
      
        Resource *clone()  {  return new Dialog(*this);  }
      
      
        string
        parseName(Source & aSource)  //  get prior name class?? TODO
        {
        return aSource.getPriorToken();
        }
      
      
        void
        LintPromptLabel(ComplaintDepartment & aCD, Resource & aResource, int idx)
        {
      
      
        if (Resource * pNext = get(toString(idx + 1)))
        {
        bool found (false);
      
      
        for(int x(0);  x < pNext->getSpecCount();  ++x)
        {
        if ("WS_TABSTOP" == pNext->getSpec(x))
        {
        found = true;
        break;
        }
        }
        if (!found)
        aCD.complain( "Prompt before control without tabstop",
        aResource.getBeginningOfText() );
        }
        else
        {
        aCD.complain( "Prompt at end of control list",
        aResource.getBeginningOfText() );
        }
      
      
        }
      
      
        void
        LintOne(ComplaintDepartment & aCD)
        {
        LintDupedLabelHotKeys(aCD, "Control");
      
      
        Resource * pResource = NULL;
      
      
        for ( int idx(0); pResource = get(toString(idx));  ++idx )
        if (pResource->weBePromptLabel())
        LintPromptLabel(aCD, *pResource, idx);
        }
      
      
        bool
        isFirstTokenOfNextItem(string const & token)
        {
        return token == "LTEXT"       || token == "RTEXT"        || token == "DEFPUSHBUTTON"   || token == "PUSHBUTTON" || 
        token == "AUTO3STATE"  || token == "AUTOCHECKBOX" || token == "AUTORADIOBUTTON" || token == "CHECKBOX"   || 
        token == "COMBOBOX"    || token == "CONTROL"      || token == "CTEXT"           || token == "EDITTEXT"   || 
        token == "GROUPBOX"    || token == "ICON"         || token == "LISTBOX"         || token == "PUSHBOX"    || 
        token == "RADIOBUTTON" || token == "SCROLLBAR"    || token == "STATE3";
        }
      
      
        void
        parse(Source & aSource)
        {
        Control aCtrl;
        initResource(aCtrl, aSource);
        }
      
      
        };
      
      
        class
        ResourceFile :  public ResourceCollection, public ComplaintDepartment
        {
        public:
      
      
        Resource *clone() {  return NULL;  }  //  the buck stops here
        ResourceFile():  m_pComplaints(NULL)  {}
      
      
        virtual size_type getLineNumber(size_type at) {  
        return m_Source.getLineNumber(at);  }  
      
      
        virtual string getLine(size_type at) {  
        return m_Source.getLine(at);  }  
      
      
        string
        getString(string const & id)
        {
        int idx(0);
      
      
        for(;;)
        {
        Resource * p = get(toString(idx++));
        if (!p)  return "";
        p = p->get(id);
        if (p)  return p->getLabel();
        }
        }
      
      
        void
        callLint(ostream & complaints)
        {
        m_pComplaints = &complaints;
        LintAll(*this);
        m_pComplaints = NULL;
        }
      
      
        Resource *
        getAccelerators(string const & menuID)
        {
        return get(menuID + " ACCELERATORS");
        }
      
      
        void
        complain(string const & description, size_type bot, string evidence = "")
        {
        size_type lineNumber(getLineNumber(bot));
      
      
        if ("" != evidence)  *m_pComplaints << evidence;
      
      
        }
      
      
        void
        parseFile( string const & rc,
        string const & fileName = "" )
        {
        m_Source.setResource(rc);
        setBeginningOfText(m_Source.getBOT());
        addSpec(fileName);
        int idx(0);
      
      
        for(;;)
        {
        string token = m_Source.pullNextToken();
        if ("" == token)  return;
      
      
        if ("MENU" == token)
        {
        Menu().parseResource(*this, m_Source);
        }
        else if ("DIALOG" == token || "DIALOGEX" == token)
        {
        Dialog().parseResource(*this, m_Source);
        }
        else if ("ACCELERATORS" == token)
        {
        Accelerators().parseResource(*this, m_Source);
        }
        else if ("STRINGTABLE" == token)
        {
        StringTable(idx++).parseResource(*this, m_Source);
        }
        else
        {
        //            std::cerr << "Unrecognized: " << token << endl;
        }
        }
        }
      
      
        //  TODO  everyone deals with END the same way. 
      
      
        private:
        Source      m_Source;
        ostream   * m_pComplaints;
        };