Commit fae7c4b8 authored by Paul Asmuth's avatar Paul Asmuth
Browse files

implement element specification parsing

parent 61c2ec36
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -15,8 +15,8 @@
namespace signaltk {

struct PropertyList {
  std::list<std::pair<std::string, std::string>> properties;
  std::list<std::pair<std::string, std::unique_ptr<PropertyList>>> children;
  std::vector<std::pair<std::string, std::string>> properties;
  std::vector<std::pair<std::string, std::unique_ptr<PropertyList>>> children;
};


+119 −50
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@
 * <http://www.gnu.org/licenses/>.
 */
#include <regex>
#include <iostream>
#include "element_spec_parser.h"

namespace signaltk {
@@ -22,58 +23,89 @@ SpecParser::SpecParser(
    has_error_(false) {}

ReturnCode SpecParser::parse(PropertyList* plist) {
  if (!parseDefinitions(plist)) {
    return ReturnCode::error(
        "EPARSE",
        StringUtil::format(
            "<$0:$1> $2",
            error_lineno_,
            error_colno_,
            error_msg_));
  }

  return ReturnCode::success();
}

bool SpecParser::parseDefinitions(PropertyList* plist) {
  TokenType ttype;
  std::string tbuf;

  /* a file consists of a list of top-level definitions */
  while (getToken(&ttype, &tbuf)) {
    if (!parsePropertyOrList(plist)) {
      return false;
    }
  }

  return true;
}

bool SpecParser::parsePropertyOrList(PropertyList* plist) {
  std::string pname;
  if (!expectAndConsumeString(&pname)) {
    return false;
  }

  TokenType ttype;
  std::string tbuf;
  if (!getToken(&ttype, &tbuf)) {
    setError("unexpected end of file; expected COLON or LCBRACE");
    return false;
  }

  switch (ttype) {
      case T_ENDLINE:
        consumeToken();
        continue;
      case T_STRING:
        pname = tbuf;
        consumeToken();
        break;
    case T_COLON:
      return
          consumeToken() &&
          parseProperty(pname, plist) &&
          expectAndConsumeToken(T_SEMICOLON);

    case T_LCBRACE:
      return
          consumeToken() &&
          parsePropertyList(pname, plist) &&
          expectAndConsumeToken(T_RCBRACE);

    default:
      setError(
          StringUtil::format(
                "unexpected token '$0'; expected STRING or ENDLINE",
              "unexpected token '$0'; expected COLON OR LCBRACE",
              printToken(ttype, tbuf)));
        break;
      return false;
  }
}

    if (has_error_) {
      break;
bool SpecParser::parseProperty(const std::string& pname, PropertyList* plist) {
  std::string pvalue;
  if (!expectAndConsumeString(&pvalue)) {
    return false;
  }

    if (!getToken(&ttype, &tbuf)) {
      setError("unexpected end of file; expected COLON or LCBRACE");
      break;
  plist->properties.emplace_back(pname, pvalue);
  return true;
}

    switch (ttype) {
      case T_LCBRACE:
        consumeToken();
        break;
      case T_COLON:
        consumeToken();
        break;
bool SpecParser::parsePropertyList(const std::string& pname, PropertyList* plist) {
  auto pvalue = std::make_unique<PropertyList>();

  TokenType ttype;
  std::string tbuf;
  while (getToken(&ttype, &tbuf) && ttype != T_RCBRACE) {
    if (!parsePropertyOrList(pvalue.get())) {
      return false;
    }
  }

  if (has_error_) {
    return ReturnCode::error(
        "EPARSE",
        StringUtil::format(
            "<$0:$1> $2",
            error_lineno_,
            error_colno_,
            error_msg_));
  } else {
    return ReturnCode::success();
  }
  plist->children.emplace_back(pname, std::move(pvalue));
  return true;
}

bool SpecParser::getToken(
@@ -105,6 +137,7 @@ bool SpecParser::getToken(
  /* skip whitespace */
  while (input_cur_ < input_end_) {
    if (*input_cur_ == ' ' ||
        *input_cur_ == '\n' ||
        *input_cur_ == '\t' ||
        *input_cur_ == '\r') {
      ++input_cur_;
@@ -139,12 +172,6 @@ bool SpecParser::getToken(
  /* single character tokens */
  switch (*input_cur_) {

    case '\n': {
      token_type_ = T_ENDLINE;
      input_cur_++;
      goto return_token;
    }

    case ':': {
      token_type_ = T_COLON;
      input_cur_++;
@@ -251,9 +278,52 @@ return_token:
  return true;
}

void SpecParser::consumeToken() {
bool SpecParser::consumeToken() {
  has_token_ = false;
  token_buf_.clear();
  return true;
}

bool SpecParser::expectAndConsumeToken(TokenType desired_type) {
  TokenType actual_type;
  const char* tbuf = nullptr;
  size_t tbuf_len = 0;

  if (!getToken(&actual_type, &tbuf, &tbuf_len)) {
    return false;
  }

  if (actual_type != desired_type) {
    setError(
        StringUtil::format(
            "unexpected token; expected: $0, got: $1",
            printToken(desired_type),
            printToken(actual_type, tbuf, tbuf_len)));

    return false;
  }

  consumeToken();
  return true;
}

bool SpecParser::expectAndConsumeString(std::string* buf) {
  TokenType ttype;
  if (!getToken(&ttype, buf)) {
    return false;
  }

  if (ttype != T_STRING) {
    setError(
        StringUtil::format(
            "unexpected token; expected: STRING, got: $0",
            printToken(ttype, *buf)));

    return false;
  }

  consumeToken();
  return true;
}

std::string SpecParser::printToken(TokenType type) {
@@ -273,7 +343,6 @@ std::string SpecParser::printToken(
  std::string out;
  switch (type) {
    case T_STRING: out = "STRING"; break;
    case T_ENDLINE: out = "ENDLINE"; break;
    case T_COLON: out = "COLON"; break;
    case T_SEMICOLON: out = "SEMICOLON"; break;
    case T_LCBRACE: out = "LCBRACE"; break;
+9 −1
Original line number Diff line number Diff line
@@ -40,15 +40,23 @@ public:

  bool hasToken() const;

  void consumeToken();
  bool consumeToken();

protected:

  bool parseDefinitions(PropertyList* plist);
  bool parsePropertyOrList(PropertyList* plist);
  bool parseProperty(const std::string& pname, PropertyList* plist);
  bool parsePropertyList(const std::string& pname, PropertyList* plist);

  bool getToken(
      TokenType* type,
      const char** buf,
      size_t* buf_len) const;

  bool expectAndConsumeToken(TokenType type);
  bool expectAndConsumeString(std::string* buf);

  std::string printToken(TokenType type);

  std::string printToken(
+69 −37
Original line number Diff line number Diff line
@@ -38,11 +38,6 @@ TEST_CASE(SpecParserTest, TestTokenize, [] () {
  EXPECT(tbuf == "");
  parser.consumeToken();

  EXPECT(parser.getToken(&ttype, &tbuf) == true);
  EXPECT(ttype == SpecParser::T_ENDLINE);
  EXPECT(tbuf == "");
  parser.consumeToken();

  EXPECT(parser.getToken(&ttype, &tbuf) == true);
  EXPECT(ttype == SpecParser::T_STRING);
  EXPECT(tbuf == "prop");
@@ -63,11 +58,6 @@ TEST_CASE(SpecParserTest, TestTokenize, [] () {
  EXPECT(tbuf == "");
  parser.consumeToken();

  EXPECT(parser.getToken(&ttype, &tbuf) == true);
  EXPECT(ttype == SpecParser::T_ENDLINE);
  EXPECT(tbuf == "");
  parser.consumeToken();

  EXPECT(parser.getToken(&ttype, &tbuf) == true);
  EXPECT(ttype == SpecParser::T_RCBRACE);
  EXPECT(tbuf == "");
@@ -97,11 +87,6 @@ TEST_CASE(SpecParserTest, TestTokenizeWithQuoting, [] () {
  EXPECT(tbuf == "");
  parser.consumeToken();

  EXPECT(parser.getToken(&ttype, &tbuf) == true);
  EXPECT(ttype == SpecParser::T_ENDLINE);
  EXPECT(tbuf == "");
  parser.consumeToken();

  EXPECT(parser.getToken(&ttype, &tbuf) == true);
  EXPECT(ttype == SpecParser::T_STRING);
  EXPECT(tbuf == "prop1");
@@ -122,11 +107,6 @@ TEST_CASE(SpecParserTest, TestTokenizeWithQuoting, [] () {
  EXPECT(tbuf == "");
  parser.consumeToken();

  EXPECT(parser.getToken(&ttype, &tbuf) == true);
  EXPECT(ttype == SpecParser::T_ENDLINE);
  EXPECT(tbuf == "");
  parser.consumeToken();

  EXPECT(parser.getToken(&ttype, &tbuf) == true);
  EXPECT(ttype == SpecParser::T_STRING);
  EXPECT(tbuf == "prop2");
@@ -147,11 +127,6 @@ TEST_CASE(SpecParserTest, TestTokenizeWithQuoting, [] () {
  EXPECT(tbuf == "");
  parser.consumeToken();

  EXPECT(parser.getToken(&ttype, &tbuf) == true);
  EXPECT(ttype == SpecParser::T_ENDLINE);
  EXPECT(tbuf == "");
  parser.consumeToken();

  EXPECT(parser.getToken(&ttype, &tbuf) == true);
  EXPECT(ttype == SpecParser::T_RCBRACE);
  EXPECT(tbuf == "");
@@ -181,11 +156,6 @@ TEST_CASE(SpecParserTest, TestTokenizeWithComments, [] () {
  EXPECT(tbuf == "");
  parser.consumeToken();

  EXPECT(parser.getToken(&ttype, &tbuf) == true);
  EXPECT(ttype == SpecParser::T_ENDLINE);
  EXPECT(tbuf == "");
  parser.consumeToken();

  EXPECT(parser.getToken(&ttype, &tbuf) == true);
  EXPECT(ttype == SpecParser::T_STRING);
  EXPECT(tbuf == "prop");
@@ -206,11 +176,6 @@ TEST_CASE(SpecParserTest, TestTokenizeWithComments, [] () {
  EXPECT(tbuf == "");
  parser.consumeToken();

  EXPECT(parser.getToken(&ttype, &tbuf) == true);
  EXPECT(ttype == SpecParser::T_ENDLINE);
  EXPECT(tbuf == "");
  parser.consumeToken();

  EXPECT(parser.getToken(&ttype, &tbuf) == true);
  EXPECT(ttype == SpecParser::T_RCBRACE);
  EXPECT(tbuf == "");
@@ -221,15 +186,82 @@ TEST_CASE(SpecParserTest, TestTokenizeWithComments, [] () {

TEST_CASE(SpecParserTest, TestParseSimple, [] () {
  std::string confstr =
      R"(element {
      R"(
        prop0: 1337;
        prop1: "hello";
        prop2: 'world';
      })";
      )";

  SpecParser parser(confstr.data(), confstr.size());
  PropertyList plist;
  CHECK_RC(parser.parse(&plist));

  EXPECT_EQ(plist.properties.size(), 3);
  EXPECT_EQ(plist.properties[0].first, "prop0");
  EXPECT_EQ(plist.properties[0].second, "1337");
  EXPECT_EQ(plist.properties[1].first, "prop1");
  EXPECT_EQ(plist.properties[1].second, "hello");
  EXPECT_EQ(plist.properties[2].first, "prop2");
  EXPECT_EQ(plist.properties[2].second, "world");
});

TEST_CASE(SpecParserTest, TestParseElement, [] () {
  std::string confstr =
      R"(
        blah: xxx;

        elem {
          prop0: 1337;
          prop1: "hello";
          prop2: 'world';
        }
      )";

  SpecParser parser(confstr.data(), confstr.size());
  PropertyList plist;
  CHECK_RC(parser.parse(&plist));

  EXPECT_EQ(plist.properties.size(), 1);
  EXPECT_EQ(plist.properties[0].first, "blah");
  EXPECT_EQ(plist.properties[0].second, "xxx");

  EXPECT_EQ(plist.children.size(), 1);
  EXPECT_EQ(plist.children[0].first, "elem");
  auto elem = plist.children[0].second.get();

  EXPECT_EQ(elem->properties.size(), 3);
  EXPECT_EQ(elem->properties[0].first, "prop0");
  EXPECT_EQ(elem->properties[0].second, "1337");
  EXPECT_EQ(elem->properties[1].first, "prop1");
  EXPECT_EQ(elem->properties[1].second, "hello");
  EXPECT_EQ(elem->properties[2].first, "prop2");
  EXPECT_EQ(elem->properties[2].second, "world");
});

TEST_CASE(SpecParserTest, TestParseNested, [] () {
  std::string confstr =
      R"(
        outer  {
          inner {
            prop: 1337;
          }
        }
      )";

  SpecParser parser(confstr.data(), confstr.size());
  PropertyList plist;
  CHECK_RC(parser.parse(&plist));

  EXPECT_EQ(plist.children.size(), 1);
  EXPECT_EQ(plist.children[0].first, "outer");
  auto outer = plist.children[0].second.get();

  EXPECT_EQ(outer->children.size(), 1);
  EXPECT_EQ(outer->children[0].first, "inner");
  auto inner = outer->children[0].second.get();

  EXPECT_EQ(inner->properties.size(), 1);
  EXPECT_EQ(inner->properties[0].first, "prop");
  EXPECT_EQ(inner->properties[0].second, "1337");
});