diff --git a/src/backend/legacy-parser.h b/src/backend/legacy-parser.h new file mode 100644 index 00000000..df67d63d --- /dev/null +++ b/src/backend/legacy-parser.h @@ -0,0 +1,445 @@ +/* BOSS + + A "one-click" program for users that quickly optimises and avoids + detrimental conflicts in their TES IV: Oblivion, Nehrim - At Fate's Edge, + TES V: Skyrim, Fallout 3 and Fallout: New Vegas mod load orders. + + Copyright (C) 2009-2012 BOSS Development Team. + + This file is part of BOSS. + + BOSS is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation, either version 3 of + the License, or (at your option) any later version. + + BOSS is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BOSS. If not, see + . + + $Revision: 2188 $, $Date: 2011-01-20 10:05:16 +0000 (Thu, 20 Jan 2011) $ +*/ + +#ifndef __BOSS_GRAMMAR_H__ +#define __BOSS_GRAMMAR_H__ + +#ifndef BOOST_SPIRIT_UNICODE +#define BOOST_SPIRIT_UNICODE +#endif + +#include "Parsing/Grammar.h" +#include "Common/Globals.h" +#include "Common/Error.h" +#include "Support/Helpers.h" +#include "Support/Logger.h" +#include "Output/Output.h" +#include "Common/Classes.h" +#include "Common/Game.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +////////////////////////////// +// Modlist data conversions +////////////////////////////// + +BOOST_FUSION_ADAPT_STRUCT( + boss::MasterlistVar, + (std::string, conditions) + (std::string, data) +) + +BOOST_FUSION_ADAPT_STRUCT( + boss::Message, + (std::string, conditions) + (uint32_t, key) + (std::string, data) +) + +BOOST_FUSION_ADAPT_STRUCT( + boss::Item, + (std::string, conditions) + (uint32_t, type) + (std::string, data) + (std::vector, messages) +) + + +namespace boss { + namespace fs = boost::filesystem; + namespace qi = boost::spirit::qi; + namespace unicode = boost::spirit::unicode; + namespace phoenix = boost::phoenix; + + using namespace qi::labels; + using boost::algorithm::to_lower_copy; + + using qi::skip; + using qi::eol; + using qi::eoi; + using qi::lexeme; + using qi::on_error; + using qi::fail; + using qi::lit; + using qi::omit; + using qi::eps; + using qi::hex; + using qi::bool_; + using qi::uint_; + + using unicode::char_; + using unicode::no_case; + using unicode::space; + using unicode::xdigit; + + using namespace std; + + using qi::grammar; + using boost::spirit::info; + + //typedef boost::u8_to_u32_iterator iterator_type; + //typedef iterator_type grammarIter; + typedef string::const_iterator grammarIter; + + /////////////////////////////// + // Keyword structures + /////////////////////////////// + + struct masterlistMsgKey_ : qi::symbols { + masterlistMsgKey_::masterlistMsgKey_() { + add //New Message keywords. + ("say",SAY) + ("tag",TAG) + ("req",REQ) + ("inc", INC) + ("dirty",DIRTY) + ("warn",WARN) + ("error",ERR) + ; + } + }; + + struct typeKey_ : qi::symbols { + typeKey_::typeKey_() { + add //Group keywords. + ("begingroup:", BEGINGROUP) //Needs the colon there unfortunately. + ("endgroup:", ENDGROUP) //Needs the colon there unfortunately. + ("endgroup", ENDGROUP) + ("mod:", MOD) //Needs the colon there unfortunately. + ("regex:", REGEX) + ; + } + }; + + + /////////////////////////////// + //Skipper Grammar + /////////////////////////////// + + //Skipper for userlist, modlist and ini parsers. + class Skipper : public grammar { + public: + Skipper::Skipper() : Skipper::base_type(start, "skipper grammar") { + + start = + spc + | UTF8 + | CComment + | CPlusPlusComment + | iniComment + | eof; + + spc = space - eol; + + UTF8 = char_("\xef") >> char_("\xbb") >> char_("\xbf"); //UTF8 BOM + + CComment = "/*" >> *(char_ - "*/") >> "*/"; + + iniComment = lit("#") >> *(char_ - eol); + + CPlusPlusComment = !(lit("http:") | lit("https:") | lit("file:")) >> "//" >> *(char_ - eol); + + eof = *(spc | CComment | CPlusPlusComment | eol) >> eoi; + } + + void Skipper::SkipIniComments(const bool b) { + if (b) + iniComment = (lit("#") | lit(";")) >> *(char_ - eol); + else + iniComment = CComment; + } + private: + qi::rule start, spc, eof, CComment, CPlusPlusComment, lineComment, iniComment, UTF8; + }; + + /////////////////////////////// + //Modlist/Masterlist Grammar + /////////////////////////////// + + //Modlist/Masterlist grammar. + class modlist_grammar : public grammar(), Skipper> { + public: + modlist_grammar::modlist_grammar() + : modlist_grammar::base_type(modList, "modlist grammar"), + errorBuffer(NULL) { + masterlistMsgKey_ masterlistMsgKey; + typeKey_ typeKey; + const vector noMessages; //An empty set of messages. + + modList = + *eol + > + ( + listVar [phoenix::bind(&modlist_grammar::StoreVar, this, _1)] + | globalMessage [phoenix::bind(&modlist_grammar::StoreGlobalMessage, this, _1)] + | listItem [phoenix::bind(&modlist_grammar::StoreItem, this, _val, _1)] + ) % +eol; + + listVar %= + conditionals + >> no_case[lit("set")] + >> ( + ':' + > charString + ); + + globalMessage = + conditionals + >> no_case[lit("global")] + >> ( + messageKeyword + >> ':' + >> charString + ); + + listItem %= + conditionals + > ItemType + > itemName + > itemMessages; + + ItemType %= + no_case[typeKey] + | eps [_val = MOD]; + + itemName = + charString [phoenix::bind(&modlist_grammar::ToName, this, _val, _1)] + | eps [phoenix::bind(&modlist_grammar::ToName, this, _val, "")]; + + itemMessages %= + ( + +eol + >> itemMessage % +eol + ) | eps [_1 = noMessages]; + + itemMessage %= + conditionals + >> messageKeyword + >> ':' + >> charString //The double >> matters. A single > doesn't work. + ; + + charString %= lexeme[+(char_ - eol)]; //String, with no skipper. + + messageKeyword %= no_case[masterlistMsgKey]; + + conditionals = + ( + conditional [_val = _1] + > *((andOr > conditional) [_val += _1 + _2]) + ) + | no_case[unicode::string("else")][_val = _1] + | eps [_val = ""]; + + andOr %= + unicode::string("&&") + | unicode::string("||"); + + conditional %= + ( + no_case[unicode::string("ifnot") + | unicode::string("if")] + ) + > functCondition; + + functCondition %= + ( + no_case[unicode::string("var")] > char_('(') > variable > char_(')') //Variable condition. + ) | ( + no_case[unicode::string("file")] > char_('(') > file > char_(')') //File condition. + ) | ( + no_case[unicode::string("checksum")] > char_('(') > file > char_(',') > checksum > char_(')') //Checksum condition. + ) | ( + no_case[unicode::string("version")] > char_('(') > file > char_(',') > version > char_(',') > comparator > char_(')') //Version condition. + ) | ( + no_case[unicode::string("regex")] > char_('(') > regex > char_(')') //Regex condition. + ) | ( + no_case[unicode::string("active")] > char_('(') > file > char_(')') //Active condition. + ) | ( + no_case[unicode::string("lang")] > char_('(') > language > char_(')') //Language condition. + ) + ; + + variable %= +(char_ - (')' | eol)); + + file %= lexeme[char_('"') > +(char_ - ('"' | eol)) > char_('"')]; + + checksum %= +xdigit; + + version %= file; + + comparator %= char_('=') | char_('>') | char_('<'); + + regex %= file; + + language %= file; + + + modList.name("modList"); + listVar.name("listVar"); + listItem.name("listItem"); + ItemType.name("ItemType"); + itemName.name("itemName"); + itemMessages.name("itemMessages"); + itemMessage.name("itemMessage"); + charString.name("charString"); + messageKeyword.name("messageKeyword"); + conditionals.name("conditional"); + andOr.name("andOr"); + conditional.name("conditional"); + functCondition.name("condition"); + shortCondition.name("condition"); + variable.name("variable"); + file.name("file"); + checksum.name("checksum"); + version.name("version"); + comparator.name("comparator"); + regex.name("regex file"); + file.name("language"); + + on_error(modList, phoenix::bind(&modlist_grammar::SyntaxError, this, _1, _2, _3, _4)); + on_error(listVar, phoenix::bind(&modlist_grammar::SyntaxError, this, _1, _2, _3, _4)); + on_error(listItem, phoenix::bind(&modlist_grammar::SyntaxError, this, _1, _2, _3, _4)); + on_error(ItemType, phoenix::bind(&modlist_grammar::SyntaxError, this, _1, _2, _3, _4)); + on_error(itemName, phoenix::bind(&modlist_grammar::SyntaxError, this, _1, _2, _3, _4)); + on_error(itemMessages, phoenix::bind(&modlist_grammar::SyntaxError, this, _1, _2, _3, _4)); + on_error(itemMessage, phoenix::bind(&modlist_grammar::SyntaxError, this, _1, _2, _3, _4)); + on_error(charString, phoenix::bind(&modlist_grammar::SyntaxError, this, _1, _2, _3, _4)); + on_error(messageKeyword, phoenix::bind(&modlist_grammar::SyntaxError, this, _1, _2, _3, _4)); + on_error(conditionals, phoenix::bind(&modlist_grammar::SyntaxError, this, _1, _2, _3, _4)); + on_error(andOr, phoenix::bind(&modlist_grammar::SyntaxError, this, _1, _2, _3, _4)); + on_error(conditional, phoenix::bind(&modlist_grammar::SyntaxError, this, _1, _2, _3, _4)); + on_error(functCondition, phoenix::bind(&modlist_grammar::SyntaxError, this, _1, _2, _3, _4)); + on_error(shortCondition, phoenix::bind(&modlist_grammar::SyntaxError, this, _1, _2, _3, _4)); + on_error(variable, phoenix::bind(&modlist_grammar::SyntaxError, this, _1, _2, _3, _4)); + on_error(file, phoenix::bind(&modlist_grammar::SyntaxError, this, _1, _2, _3, _4)); + on_error(checksum, phoenix::bind(&modlist_grammar::SyntaxError, this, _1, _2, _3, _4)); + on_error(version, phoenix::bind(&modlist_grammar::SyntaxError, this, _1, _2, _3, _4)); + on_error(comparator, phoenix::bind(&modlist_grammar::SyntaxError, this, _1, _2, _3, _4)); + on_error(regex, phoenix::bind(&modlist_grammar::SyntaxError, this, _1, _2, _3, _4)); + on_error(language, phoenix::bind(&modlist_grammar::SyntaxError, this, _1, _2, _3, _4)); + } + + void modlist_grammar::SetErrorBuffer(ParsingError * inErrorBuffer) { + errorBuffer = inErrorBuffer; + } + + void modlist_grammar::SetGlobalMessageBuffer(vector * inGlobalMessageBuffer) { + globalMessageBuffer = inGlobalMessageBuffer; + } + + void modlist_grammar::SetVarStore(vector * varStore) { + setVars = varStore; + } + + void modlist_grammar::SetCRCStore(boost::unordered_map * CRCStore) { + fileCRCs = CRCStore; + } + + void modlist_grammar::SetParentGame(const Game * game) { + parentGame = game; + } + private: + qi::rule(), Skipper> modList; + qi::rule listItem; + qi::rule ItemType; + qi::rule itemName; + qi::rule(), Skipper> itemMessages; + qi::rule itemMessage, globalMessage; + qi::rule listVar; + qi::rule charString, andOr, conditional, conditionals, functCondition, shortCondition, variable, file, checksum, version, comparator, regex, language; + qi::rule messageKeyword; + ParsingError * errorBuffer; + vector * globalMessageBuffer; + vector * setVars; //Vars set by masterlist. + boost::unordered_map * fileCRCs; //CRCs calculated. + const Game * parentGame; + vector openGroups; //Need to keep track of which groups are open to match up endings properly in MF1. + + //Parser error reporter. + void modlist_grammar::SyntaxError(grammarIter const& /*first*/, grammarIter const& last, grammarIter const& errorpos, boost::spirit::info const& what) { + if (errorBuffer == NULL || !errorBuffer->Empty()) + return; + + ostringstream out; + out << what; + string expect = out.str(); + + string context(errorpos, min(errorpos +50, last)); + boost::trim_left(context); + + ParsingError e(str(MasterlistParsingErrorHeader % expect), context, MasterlistParsingErrorFooter); + *errorBuffer = e; + LOG_ERROR(Outputter(PLAINTEXT, e).AsString().c_str()); + return; + } + + //Stores the given item and records any changes to open groups. + void modlist_grammar::StoreItem(vector& list, Item currentItem) { + if (currentItem.Type() == BEGINGROUP) + openGroups.push_back(currentItem.Name()); + else if (currentItem.Type() == ENDGROUP) + openGroups.pop_back(); + if (!currentItem.Name().empty()) //A blank line can be confused with a mod entry. + list.push_back(currentItem); + } + + //Stores the masterlist variable. + void modlist_grammar::StoreVar(const MasterlistVar var) { + setVars->push_back(var); + } + + //Stores the global message. + void modlist_grammar::StoreGlobalMessage(const Message message) { + globalMessageBuffer->push_back(message); + } + + //Turns a given string into a path. Can't be done directly because of the openGroups checks. + void modlist_grammar::ToName(string& p, string itemName) { + boost::algorithm::trim(itemName); + if (itemName.empty() && !openGroups.empty()) + p = openGroups.back(); + else + p = itemName; + } + }; +} +#endif