/* Author: Juan Rada-Vilela, Ph.D. Copyright (C) 2010-2014 FuzzyLite Limited All rights reserved This file is part of fuzzylite. fuzzylite is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. fuzzylite 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with fuzzylite. If not, see . fuzzyliteâ„¢ is a trademark of FuzzyLite Limited. */ #include "fl/imex/FclImporter.h" #include "fl/Headers.h" #include #include namespace fl { FclImporter::FclImporter() : Importer() { } FclImporter::~FclImporter() { } std::string FclImporter::name() const { return "FclImporter"; } Engine* FclImporter::fromString(const std::string& fcl) const { FL_unique_ptr engine(new Engine); std::map tags; tags["VAR_INPUT"] = "END_VAR"; tags["VAR_OUTPUT"] = "END_VAR"; tags["FUZZIFY"] = "END_FUZZIFY"; tags["DEFUZZIFY"] = "END_DEFUZZIFY"; tags["RULEBLOCK"] = "END_RULEBLOCK"; std::map::const_iterator tagFinder; std::string currentTag = "", closingTag = ""; std::ostringstream block; std::istringstream fclReader(fcl); std::string line; int lineNumber = 0; while (std::getline(fclReader, line)) { ++lineNumber; std::vector comments; comments = Op::split(line, "//"); if (comments.size() > 1) { line = comments.front(); } comments = Op::split(line, "#"); if (comments.size() > 1) { line = comments.front(); } line = Op::trim(line); if (line.empty() or line.at(0) == '%' or line.at(0) == '#' or (line.substr(0, 2) == "//")) { continue; } line = fl::Op::findReplace(line, ";", ""); std::istringstream tokenizer(line); std::string firstToken; tokenizer >> firstToken; if (firstToken == "FUNCTION_BLOCK") { if (tokenizer.rdbuf()->in_avail() > 0) { std::ostringstream name; std::string token; tokenizer >> token; name << token; while (tokenizer >> token) { name << " " << token; } engine->setName(name.str()); } continue; } if (firstToken == "END_FUNCTION_BLOCK") { break; } if (currentTag.empty()) { tagFinder = tags.find(firstToken); if (tagFinder == tags.end()) { std::ostringstream ex; ex << "[syntax error] unknown block definition <" << firstToken << "> " << " in line " << lineNumber << ": " << line; throw fl::Exception(ex.str(), FL_AT); } currentTag = tagFinder->first; closingTag = tagFinder->second; block.str(""); block.clear(); block << line << "\n"; continue; } if (not currentTag.empty()) { if (firstToken == closingTag) { processBlock(currentTag, block.str(), engine.get()); currentTag = ""; closingTag = ""; } else if (tags.find(firstToken) != tags.end()) { //if opening new block without closing the previous one std::ostringstream ex; ex << "[syntax error] expected <" << closingTag << "> before <" << firstToken << "> in line: " << line; throw fl::Exception(ex.str(), FL_AT); } else { block << line << "\n"; } continue; } } if (not currentTag.empty()) { std::ostringstream ex; ex << "[syntax error] "; if (block.rdbuf()->in_avail() > 0) { ex << "expected <" << closingTag << "> for block:\n" << block.str(); } else { ex << "expected <" << closingTag << ">, but not found"; } throw fl::Exception(ex.str(), FL_AT); } return engine.release(); } void FclImporter::processBlock(const std::string& tag, const std::string& block, Engine* engine) const { if (tag == "VAR_INPUT" or tag == "VAR_OUTPUT") { processVar(tag, block, engine); } else if (tag == "FUZZIFY") { processFuzzify(block, engine); } else if (tag == "DEFUZZIFY") { processDefuzzify(block, engine); } else if (tag == "RULEBLOCK") { processRuleBlock(block, engine); } else { std::ostringstream ex; ex << "[syntax error] unexpected tag <" << tag << "> for block:\n" << block; throw fl::Exception(ex.str(), FL_AT); } } void FclImporter::processVar(const std::string& tag, const std::string& block, Engine* engine)const { std::istringstream blockReader(block); std::string line; std::getline(blockReader, line); //discard first line as it is VAR_INPUT while (std::getline(blockReader, line)) { std::vector token = Op::split(line, ":"); if (token.size() != 2) { std::ostringstream ex; ex << "[syntax error] expected property of type (key : value) in line: " << line; throw fl::Exception(ex.str(), FL_AT); } std::string name = fl::Op::validName(token.at(0)); if (tag == "VAR_INPUT") engine->addInputVariable(new InputVariable(name)); else if (tag == "VAR_OUTPUT") engine->addOutputVariable(new OutputVariable(name)); else { std::ostringstream ex; ex << "[syntax error] unexpected tag <" << tag << "> in line: " << line; throw fl::Exception(ex.str(), FL_AT); } } } void FclImporter::processFuzzify(const std::string& block, Engine* engine)const { std::istringstream blockReader(block); std::string line; std::getline(blockReader, line); std::string name; std::size_t index = line.find_first_of(' '); if (index != std::string::npos) { name = fl::Op::validName(line.substr(index + 1)); } else { std::ostringstream ex; ex << "[syntax error] expected name of input variable in line: " << line; throw fl::Exception(ex.str(), FL_AT); } if (not engine->hasInputVariable(name)) { std::ostringstream ex; ex << "[syntax error] engine does not contain " "input variable <" << name << "> from line: " << line; throw fl::Exception(ex.str(), FL_AT); } InputVariable* inputVariable = engine->getInputVariable(name); while (std::getline(blockReader, line)) { std::istringstream ss(line); std::string firstToken; ss >> firstToken; try { if (firstToken == "RANGE") { std::pair minmax = parseRange(line); inputVariable->setMinimum(minmax.first); inputVariable->setMaximum(minmax.second); } else if (firstToken == "ENABLED") { inputVariable->setEnabled(parseEnabled(line)); } else if (firstToken == "TERM") { inputVariable->addTerm(parseTerm(line, engine)); } else throw fl::Exception("[syntax error] unexpected token " "<" + firstToken + ">" + line, FL_AT); } catch (fl::Exception& ex) { ex.append("At line: <" + line + ">"); throw; } } } void FclImporter::processDefuzzify(const std::string& block, Engine* engine) const { std::istringstream blockReader(block); std::string line; std::getline(blockReader, line); std::string name; std::size_t index = line.find_first_of(' '); if (index != std::string::npos) { name = fl::Op::validName(line.substr(index + 1)); } else { std::ostringstream ex; ex << "[syntax error] expected an output variable name in line: " << line; throw fl::Exception(ex.str(), FL_AT); } if (not engine->hasOutputVariable(name)) { std::ostringstream ex; ex << "[syntax error] output variable <" << name << "> not registered in engine. " << "Line: " << line; throw fl::Exception(ex.str(), FL_AT); } OutputVariable* outputVariable = engine->getOutputVariable(name); while (std::getline(blockReader, line)) { line = fl::Op::trim(line); std::istringstream tokenizer(line); std::string firstToken; tokenizer >> firstToken; if (firstToken == "TERM") { outputVariable->addTerm(parseTerm(line, engine)); } else if (firstToken == "METHOD") { outputVariable->setDefuzzifier(parseDefuzzifier(line)); } else if (firstToken == "ACCU") { outputVariable->fuzzyOutput()->setAccumulation(parseSNorm(line)); } else if (firstToken == "DEFAULT") { std::pair defaultAndLock = parseDefaultValue(line); outputVariable->setDefaultValue(defaultAndLock.first); outputVariable->setLockPreviousOutputValue(defaultAndLock.second or outputVariable->isLockedPreviousOutputValue()); } else if (firstToken == "RANGE") { std::pair minmax = parseRange(line); outputVariable->setMinimum(minmax.first); outputVariable->setMaximum(minmax.second); } else if (firstToken == "LOCK") { std::pair output_range = parseLocks(line); outputVariable->setLockPreviousOutputValue(output_range.first); outputVariable->setLockOutputValueInRange(output_range.second); } else if (firstToken == "ENABLED") { outputVariable->setEnabled(parseEnabled(line)); } else { std::ostringstream ex; ex << "[syntax error] unexpected token <" << firstToken << "> in line: " << line; throw fl::Exception(ex.str(), FL_AT); } } } void FclImporter::processRuleBlock(const std::string& block, Engine* engine)const { std::istringstream blockReader(block); std::string line; std::string name; std::getline(blockReader, line); std::size_t index = line.find_last_of(' '); if (index != std::string::npos) name = line.substr(index); RuleBlock * ruleblock = new RuleBlock(name); engine->addRuleBlock(ruleblock); while (std::getline(blockReader, line)) { std::string firstToken = line.substr(0, line.find_first_of(' ')); if (firstToken == "AND") { ruleblock->setConjunction(parseTNorm(line)); } else if (firstToken == "OR") { ruleblock->setDisjunction(parseSNorm(line)); } else if (firstToken == "ACT") { ruleblock->setActivation(parseTNorm(line)); } else if (firstToken == "ENABLED") { ruleblock->setEnabled(parseEnabled(line)); } else if (firstToken == "RULE") { std::size_t ruleStart = line.find_first_of(':'); if (ruleStart == std::string::npos) ruleStart = 4; // "RULE".size() std::string ruleText = line.substr(ruleStart + 1); ruleText = fl::Op::trim(ruleText); Rule* rule = new Rule(ruleText); try { rule->load(engine); } catch (...) { //ignore } ruleblock->addRule(rule); } else { std::ostringstream ex; ex << "[syntax error] keyword <" << firstToken << "> not recognized in line: " << line; throw fl::Exception(ex.str(), FL_AT); } } } TNorm* FclImporter::parseTNorm(const std::string& line) const { std::vector token = Op::split(line, ":"); if (token.size() != 2) { std::ostringstream ex; ex << "[syntax error] expected property of type (key : value) in line: " << line; throw fl::Exception(ex.str(), FL_AT); } std::string name = Op::trim(token.at(1)); std::string className = name; if (name == "NONE") className = ""; else if (name == "MIN") className = Minimum().className(); else if (name == "PROD") className = AlgebraicProduct().className(); else if (name == "BDIF") className = BoundedDifference().className(); else if (name == "DPROD") className = DrasticProduct().className(); else if (name == "EPROD") className = EinsteinProduct().className(); else if (name == "HPROD") className = HamacherProduct().className(); else if (name == "NMIN") className = NilpotentMinimum().className(); return FactoryManager::instance()->tnorm()->constructObject(className); } SNorm* FclImporter::parseSNorm(const std::string& line) const { std::vector token = Op::split(line, ":"); if (token.size() != 2) { std::ostringstream ex; ex << "[syntax error] expected property of type (key : value) in line: " << line; throw fl::Exception(ex.str(), FL_AT); } std::string name = Op::trim(token.at(1)); std::string className = name; if (name == "NONE") className = ""; else if (name == "MAX") className = Maximum().className(); else if (name == "ASUM") className = AlgebraicSum().className(); else if (name == "BSUM") className = BoundedSum().className(); else if (name == "NSUM") className = NormalizedSum().className(); else if (name == "DSUM") className = DrasticSum().className(); else if (name == "ESUM") className = EinsteinSum().className(); else if (name == "HSUM") className = HamacherSum().className(); else if (name == "NMAX") className = NilpotentMaximum().className(); return FactoryManager::instance()->snorm()->constructObject(className); } Term* FclImporter::parseTerm(const std::string& line, const Engine* engine) const { std::ostringstream spacer; for (std::size_t i = 0; i < line.size(); ++i) { if (line.at(i) == '(' or line.at(i) == ')' or line.at(i) == ',') { spacer << " " << line.at(i) << " "; } else if (line.at(i) == ':') { spacer << " :"; } else if (line.at(i) == '=') { spacer << "= "; } else spacer << line.at(i); } std::string spacedLine = spacer.str(); enum FSM { S_KWTERM, S_NAME, S_ASSIGN, S_TERMCLASS, S_PARAMETERS }; FSM state = S_KWTERM; std::istringstream tokenizer(spacedLine); std::string token; std::string name, termClass; std::vector parameters; while (tokenizer >> token) { if (state == S_KWTERM and token == "TERM") { state = S_NAME; continue; } if (state == S_NAME) { name = token; state = S_ASSIGN; continue; } if (state == S_ASSIGN and token == ":=") { state = S_TERMCLASS; continue; } if (state == S_TERMCLASS) { if (fl::Op::isNumeric(token)) { termClass = Constant().className(); parameters.push_back(token); } else if (token == "(") { termClass = Discrete().className(); } else { termClass = token; } state = S_PARAMETERS; continue; } if (state == S_PARAMETERS) { if (termClass != Function().className() and (token == "(" or token == ")" or token == ",")) { continue; } if (token == ";") break; parameters.push_back(fl::Op::trim(token)); } } if (state <= S_TERMCLASS) throw fl::Exception("[syntax error] malformed term in line: " + line, FL_AT); FL_unique_ptr term; term.reset(FactoryManager::instance()->term()->constructObject(termClass)); Term::updateReference(term.get(), engine); term->setName(fl::Op::validName(name)); std::string separator; if (not dynamic_cast (term.get())) { separator = " "; } term->configure(Op::join(parameters, separator)); //remove spaces for text of function return term.release(); } Defuzzifier* FclImporter::parseDefuzzifier(const std::string& line) const { std::vector token = Op::split(line, ":"); if (token.size() != 2) { std::ostringstream ex; ex << "[syntax error] expected property of type (key : value) in " << "line: " << line; throw fl::Exception(ex.str(), FL_AT); } std::string name = fl::Op::trim(token.at(1)); std::string className = name; if (name == "NONE") className = ""; else if (name == "COG") className = Centroid().className(); else if (name == "COA") className = Bisector().className(); else if (name == "LM") className = SmallestOfMaximum().className(); else if (name == "RM") className = LargestOfMaximum().className(); else if (name == "MM") className = MeanOfMaximum().className(); else if (name == "COGS") className = WeightedAverage().className(); else if (name == "COGSS") className = WeightedSum().className(); return FactoryManager::instance()->defuzzifier()->constructObject(className); } std::pair FclImporter::parseDefaultValue(const std::string& line) const { std::vector token = Op::split(line, ":="); if (token.size() != 2) { std::ostringstream ex; ex << "[syntax error] expected property of type (key := value) in line: " << line; throw fl::Exception(ex.str(), FL_AT); } std::vector values = Op::split(token.at(1), "|"); std::string defaultValue = values.front(); std::string nc; if (values.size() == 2) nc = values.back(); defaultValue = fl::Op::trim(defaultValue); nc = fl::Op::trim(nc); scalar value; try { value = fl::Op::toScalar(defaultValue); } catch (...) { std::ostringstream ex; ex << "[syntax error] expected numeric value, " << "but found <" << defaultValue << "> in line: " << line; throw fl::Exception(ex.str(), FL_AT); } bool lockPreviousOutput = (nc == "NC"); if (not (lockPreviousOutput or nc.empty())) { throw fl::Exception("[syntax error] expected keyword , " "but found <" + nc + "> in line: " + line, FL_AT); } return std::pair(value, lockPreviousOutput); } std::pair FclImporter::parseRange(const std::string& line) const { std::vector token = Op::split(line, ":="); if (token.size() != 2) { std::ostringstream ex; ex << "[syntax error] expected property of type (key := value) in line: " << line; throw fl::Exception(ex.str(), FL_AT); } std::string rangeToken = token.at(1); std::ostringstream range; for (std::size_t i = 0; i < rangeToken.size(); ++i) { char character = rangeToken.at(i); if (character == '(' or character == ')' or character == ' ' or character == ';') continue; range << character; } token = Op::split(range.str(), ".."); if (token.size() != 2) { std::ostringstream ex; ex << "[syntax error] expected property of type 'start .. end', " << "but found <" << range.str() << "> in line: " << line; throw fl::Exception(ex.str(), FL_AT); } scalar minimum, maximum; int index; try { minimum = Op::toScalar(token.at(index = 0)); maximum = Op::toScalar(token.at(index = 1)); } catch (std::exception& ex) { (void) ex; std::ostringstream ss; ss << "[syntax error] expected numeric value, but found <" << token.at(index) << "> in " << "line: " << line; throw fl::Exception(ss.str(), FL_AT); } return std::pair(minimum, maximum); } std::pair FclImporter::parseLocks(const std::string& line) const { std::size_t index = line.find_first_of(":"); if (index == std::string::npos) { throw fl::Exception("[syntax error] expected property of type " "'key : value' in line: " + line, FL_AT); } bool output, range; std::string value = line.substr(index + 1); std::vector flags = fl::Op::split(value, "|"); if (flags.size() == 1) { std::string flag = fl::Op::trim(flags.front()); output = (flag == "PREVIOUS"); range = (flag == "RANGE"); if (not (output or range)) { throw fl::Exception("[syntax error] expected locking flags " ", but found <" + flag + "> in line: " + line, FL_AT); } } else if (flags.size() == 2) { std::string flagA = fl::Op::trim(flags.front()); std::string flagB = fl::Op::trim(flags.back()); output = (flagA == "PREVIOUS" or flagB == "PREVIOUS"); range = (flagA == "RANGE" or flagB == "RANGE"); if (not (output and range)) { throw fl::Exception("[syntax error] expected locking flags " ", but found " "<" + flags.front() + "|" + flags.back() + "> in line: " + line, FL_AT); } } else { throw fl::Exception("[syntax error] expected locking flags " ", but found " "<" + value + "> in line: " + line, FL_AT); } return std::pair(output, range); } bool FclImporter::parseEnabled(const std::string& line) const { std::vector tokens = Op::split(line, ":"); if (tokens.size() != 2) { std::ostringstream ex; ex << "[syntax error] expected property of type (key : value) in " << "line: " << line; throw fl::Exception(ex.str(), FL_AT); } std::string boolean = fl::Op::trim(tokens.at(1)); if (boolean == "TRUE") return true; if (boolean == "FALSE") return false; throw fl::Exception("[syntax error] expected boolean , but found <" + line + ">", FL_AT); } FclImporter* FclImporter::clone() const { return new FclImporter(*this); } }