You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

359 lines
9.2 KiB

#include "fml.h"
#include <boost/algorithm/string.hpp>
#include <boost/tokenizer.hpp>
namespace FML {
///////////////////////////////////////////////////////////////////////////////
// Utilities
bool fml_convert(const std::string& input, bool& b)
{
std::string names[5][2] = { { "1", "0" },
{ "y", "n" },
{ "yes", "no" },
{ "true", "false" },
{ "on", "off" } };
std::string processedInput = input;
boost::trim(processedInput);
boost::to_lower(processedInput);
for(int i=0;i<5;i++) {
if(names[i][0] == processedInput) {
b = true;
return true;
}
if(names[i][1] == processedInput) {
b = false;
return true;
}
}
return false;
}
bool fml_convert(const std::string& input, std::string& output) {
output = input;
return true;
}
///////////////////////////////////////////////////////////////////////////////
// Node
Node::~Node()
{
for(NodeList::iterator it = m_children.begin(); it != m_children.end(); ++it)
delete (*it);
}
Node* Node::at(const std::string& childTag) const
{
for(NodeList::const_iterator it = m_children.begin(); it != m_children.end(); ++it) {
if((*it)->tag() == childTag)
return (*it);
}
return NULL;
}
Node* Node::at(int pos) const
{
if(pos < 0 || pos >= size())
return NULL;
return m_children[pos];
}
std::string Node::value(const std::string& def) const
{
if(!m_value.empty())
return m_value;
return def;
}
std::string Node::valueAt(const std::string childTag, const std::string& def) const
{
for(NodeList::const_iterator it = m_children.begin(); it != m_children.end(); ++it) {
if((*it)->tag() == childTag)
return (*it)->value();
}
return def;
}
void Node::addNode(Node *node)
{
if(node->hasTag() && node->hasValue()) {
// remove nodes wit the same tag
for(NodeList::iterator it = m_children.begin(); it != m_children.end(); ++it) {
if((*it)->tag() == node->tag()) {
delete (*it);
m_children.erase(it);
break;
}
}
}
m_children.push_back(node);
node->setParent(this);
}
std::string Node::generateErrorMessage(const std::string& message) const
{
std::stringstream ss;
ss << "FML error ";
if(m_parser && !m_parser->getWhat().empty())
ss << "in " << m_parser->getWhat();
if(m_line > 0)
ss << "at line " << m_line;
ss << ": " << message;
return ss.str();
}
///////////////////////////////////////////////////////////////////////////////
// Parser
Parser::~Parser()
{
if(m_rootNode)
delete m_rootNode;
}
bool Parser::load(std::istream& in)
{
// initialize root node
if(m_rootNode)
delete m_rootNode;
m_rootNode = new Node();
m_rootNode->setTag("root");
m_currentParent = m_rootNode;
m_currentDepth = 0;
m_currentLine = 0;
m_multilineMode = DONT_MULTILINE;
m_multilineData.clear();
while(in.good() && !in.eof()) {
m_currentLine++;
std::string line;
std::getline(in, line);
parseLine(line);
if(hasError())
return false;
}
// stop multilining if enabled
if(isMultilining())
stopMultilining();
return !hasError();
}
void Parser::parseLine(std::string& line)
{
// process multiline data first
if(isMultilining() && parseMultiline(line))
return;
// calculate depth
std::size_t numSpaces = line.find_first_not_of(' ');
// trim left whitespaces
boost::trim_left(line);
// skip comment lines
if(line[0] == '#')
return;
// skip empty lines
if(line.empty())
return;
int depth = 0;
if(numSpaces != std::string::npos) {
depth = numSpaces / 2;
// check for syntax error
if(numSpaces % 2 != 0) {
setErrorMessage("file must be idented every 2 whitespaces", m_currentLine);
return;
}
}
// a depth above
if(depth == m_currentDepth+1) {
// change current parent to the previous added node
m_currentParent = m_previousNode;
// a depth below, change parent to previus parent and add new node inside previuos parent
} else if(depth < m_currentDepth) {
// change current parent to the the new depth parent
for(int i=0;i<m_currentDepth-depth;++i)
m_currentParent = m_currentParent->parent();
// else if nots the same depth it's a syntax error
} else if(depth != m_currentDepth) {
setErrorMessage("invalid indentation level", m_currentLine);
return;
}
// update current depth
m_currentDepth = depth;
// add node
Node *node = parseNode(line);
m_currentParent->addNode(node);
m_previousNode = node;
}
Node *Parser::parseNode(std::string& line)
{
// determine node tag and value
std::string tag;
std::string value;
// its a node that has tag and possible a value
std::size_t dotsPos = line.find_first_of(':');
if(dotsPos != std::string::npos) {
tag = line.substr(0, dotsPos);
value = line.substr(dotsPos+1);
}
// its a node that has a value but no tag
else if(line[0] == '-') {
value = line.substr(1);
}
// its a node that has only a tag
else {
tag = line;
}
// trim the tag and value
boost::trim(tag);
boost::trim(value);
// create the node
Node *node = new Node(this);
node->setLine(m_currentLine);
node->setTag(tag);
// process node value
if(!value.empty()) {
// multiline text scalar
if(value[0] == '|') {
startMultilining(value);
}
// sequence
else if(value[0] == '[') {
if(boost::ends_with(value, "]")) {
value.erase(value.length()-1, 1);
value.erase(0, 1);
boost::trim(value);
boost::tokenizer<boost::escaped_list_separator<char> > tokens(value);
for(boost::tokenizer<boost::escaped_list_separator<char> >::iterator it = tokens.begin(); it != tokens.end(); ++it) {
value = *it;
boost::trim(value);
Node *child = new Node(this);
child->setLine(m_currentLine);
child->setValue(value);
node->addNode(child);
}
} else
setErrorMessage("missing ']' in sequence", m_currentLine);
}
// text scalar
else {
node->setValue(parseTextScalar(value));
}
}
return node;
}
std::string Parser::parseTextScalar(std::string value)
{
if(value[0] == '"' && value[value.length()-1] == '"') {
value = value.substr(1, value.length()-2);
// escape characters
boost::replace_all(value, "\\\\", "\\");
boost::replace_all(value, "\\\"", "\"");
boost::replace_all(value, "\\n", "\n");
}
return value;
}
void Parser::startMultilining(const std::string& param)
{
m_multilineMode = MULTILINE_DONT_FOLD;
m_currentDepth++;
if(param.length() == 2) {
switch(param[1]) {
case '-':
m_multilineMode = MULTILINE_FOLD_BLOCK;
break;
case '+':
m_multilineMode = MULTILINE_FOLD_FLOW;
break;
default:
setErrorMessage("invalid multiline identifier", m_currentLine);
break;
}
}
}
void Parser::stopMultilining()
{
// remove all new lines at the end
if(m_multilineMode == MULTILINE_DONT_FOLD || m_multilineMode == MULTILINE_FOLD_BLOCK) {
while(true) {
int lastPos = m_multilineData.length()-1;
if(m_multilineData[lastPos] != '\n')
break;
m_multilineData.erase(lastPos, 1);
}
}
if(m_multilineMode == MULTILINE_FOLD_BLOCK)
m_multilineData.append("\n");
m_previousNode->setValue(m_multilineData);
m_multilineMode = DONT_MULTILINE;
m_currentDepth--;
m_multilineData.clear();
}
bool Parser::parseMultiline(std::string line)
{
// calculate depth
std::size_t numSpaces = line.find_first_not_of(' ');
// depth above or equal current depth, add the text to the multiline
if(numSpaces != std::string::npos && (int)numSpaces >= m_currentDepth*2) {
m_multilineData += parseTextScalar(line.substr(m_currentDepth*2)) + "\n";
return true;
// depth below the current depth, check if it is a node
} else if(numSpaces == std::string::npos || (int)numSpaces < m_currentDepth*2) {
// if it has contents, its a node, then we must end multiline
if(line.find_first_not_of(' ') != std::string::npos) {
stopMultilining();
// the line still have a node on it
}
// no contents, just an extra line
else {
m_multilineData += "\n";
return true;
}
}
return false;
}
void Parser::setErrorMessage(const std::string& message, int line)
{
std::stringstream ss;
ss << "FML syntax error in ";
if(!m_what.empty())
ss << m_what << " ";
if(line > 0)
ss << "at line " << line;
ss << ": " << message;
m_error = ss.str();
}
} // namespace FML {