mirror of
https://github.com/yhirose/cpp-peglib.git
synced 2024-11-13 22:55:29 +00:00
922 lines
23 KiB
C++
922 lines
23 KiB
C++
|
#include "catch.hh"
|
|||
|
#include <peglib.h>
|
|||
|
|
|||
|
using namespace peg;
|
|||
|
|
|||
|
#if !defined(PEGLIB_NO_UNICODE_CHARS)
|
|||
|
TEST_CASE("Simple syntax test (with unicode)", "[general]")
|
|||
|
{
|
|||
|
parser parser(
|
|||
|
u8" ROOT ← _ "
|
|||
|
" _ <- ' ' "
|
|||
|
);
|
|||
|
|
|||
|
bool ret = parser;
|
|||
|
REQUIRE(ret == true);
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
TEST_CASE("Simple syntax test", "[general]")
|
|||
|
{
|
|||
|
parser parser(R"(
|
|||
|
ROOT <- _
|
|||
|
_ <- ' '
|
|||
|
)");
|
|||
|
|
|||
|
bool ret = parser;
|
|||
|
REQUIRE(ret == true);
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Empty syntax test", "[general]")
|
|||
|
{
|
|||
|
parser parser("");
|
|||
|
bool ret = parser;
|
|||
|
REQUIRE(ret == false);
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Backslash escape sequence test", "[general]")
|
|||
|
{
|
|||
|
parser parser(R"(
|
|||
|
ROOT <- _
|
|||
|
_ <- '\\'
|
|||
|
)");
|
|||
|
|
|||
|
bool ret = parser;
|
|||
|
REQUIRE(ret == true);
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Invalid escape sequence test", "[general]")
|
|||
|
{
|
|||
|
parser parser(R"(
|
|||
|
ROOT <- _
|
|||
|
_ <- '\'
|
|||
|
)");
|
|||
|
|
|||
|
bool ret = parser;
|
|||
|
REQUIRE(ret == false);
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Action taking non const Semantic Values parameter", "[general]")
|
|||
|
{
|
|||
|
parser parser(R"(
|
|||
|
ROOT <- TEXT
|
|||
|
TEXT <- [a-zA-Z]+
|
|||
|
)");
|
|||
|
|
|||
|
parser["ROOT"] = [&](SemanticValues& sv) {
|
|||
|
auto s = any_cast<std::string>(sv[0]);
|
|||
|
s[0] = 'H'; // mutate
|
|||
|
return std::string(std::move(s)); // move
|
|||
|
};
|
|||
|
|
|||
|
parser["TEXT"] = [&](SemanticValues& sv) {
|
|||
|
return sv.token();
|
|||
|
};
|
|||
|
|
|||
|
std::string val;
|
|||
|
auto ret = parser.parse("hello", val);
|
|||
|
REQUIRE(ret == true);
|
|||
|
REQUIRE(val == "Hello");
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("String capture test", "[general]")
|
|||
|
{
|
|||
|
parser parser(R"(
|
|||
|
ROOT <- _ ('[' TAG_NAME ']' _)*
|
|||
|
TAG_NAME <- (!']' .)+
|
|||
|
_ <- [ \t]*
|
|||
|
)");
|
|||
|
|
|||
|
std::vector<std::string> tags;
|
|||
|
|
|||
|
parser["TAG_NAME"] = [&](const SemanticValues& sv) {
|
|||
|
tags.push_back(sv.str());
|
|||
|
};
|
|||
|
|
|||
|
auto ret = parser.parse(" [tag1] [tag:2] [tag-3] ");
|
|||
|
|
|||
|
REQUIRE(ret == true);
|
|||
|
REQUIRE(tags.size() == 3);
|
|||
|
REQUIRE(tags[0] == "tag1");
|
|||
|
REQUIRE(tags[1] == "tag:2");
|
|||
|
REQUIRE(tags[2] == "tag-3");
|
|||
|
}
|
|||
|
|
|||
|
using namespace peg;
|
|||
|
|
|||
|
TEST_CASE("String capture test2", "[general]")
|
|||
|
{
|
|||
|
std::vector<std::string> tags;
|
|||
|
|
|||
|
Definition ROOT, TAG, TAG_NAME, WS;
|
|||
|
ROOT <= seq(WS, zom(TAG));
|
|||
|
TAG <= seq(chr('['), TAG_NAME, chr(']'), WS);
|
|||
|
TAG_NAME <= oom(seq(npd(chr(']')), dot())), [&](const SemanticValues& sv) { tags.push_back(sv.str()); };
|
|||
|
WS <= zom(cls(" \t"));
|
|||
|
|
|||
|
auto r = ROOT.parse(" [tag1] [tag:2] [tag-3] ");
|
|||
|
|
|||
|
REQUIRE(r.ret == true);
|
|||
|
REQUIRE(tags.size() == 3);
|
|||
|
REQUIRE(tags[0] == "tag1");
|
|||
|
REQUIRE(tags[1] == "tag:2");
|
|||
|
REQUIRE(tags[2] == "tag-3");
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("String capture test3", "[general]")
|
|||
|
{
|
|||
|
parser pg(R"(
|
|||
|
ROOT <- _ TOKEN*
|
|||
|
TOKEN <- '[' < (!']' .)+ > ']' _
|
|||
|
_ <- [ \t\r\n]*
|
|||
|
)");
|
|||
|
|
|||
|
|
|||
|
std::vector<std::string> tags;
|
|||
|
|
|||
|
pg["TOKEN"] = [&](const SemanticValues& sv) {
|
|||
|
tags.push_back(sv.token());
|
|||
|
};
|
|||
|
|
|||
|
auto ret = pg.parse(" [tag1] [tag:2] [tag-3] ");
|
|||
|
|
|||
|
REQUIRE(ret == true);
|
|||
|
REQUIRE(tags.size() == 3);
|
|||
|
REQUIRE(tags[0] == "tag1");
|
|||
|
REQUIRE(tags[1] == "tag:2");
|
|||
|
REQUIRE(tags[2] == "tag-3");
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Cyclic grammer test", "[general]")
|
|||
|
{
|
|||
|
Definition PARENT;
|
|||
|
Definition CHILD;
|
|||
|
|
|||
|
PARENT <= seq(CHILD);
|
|||
|
CHILD <= seq(PARENT);
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Visit test", "[general]")
|
|||
|
{
|
|||
|
Definition ROOT, TAG, TAG_NAME, WS;
|
|||
|
|
|||
|
ROOT <= seq(WS, zom(TAG));
|
|||
|
TAG <= seq(chr('['), TAG_NAME, chr(']'), WS);
|
|||
|
TAG_NAME <= oom(seq(npd(chr(']')), dot()));
|
|||
|
WS <= zom(cls(" \t"));
|
|||
|
|
|||
|
AssignIDToDefinition defIds;
|
|||
|
ROOT.accept(defIds);
|
|||
|
|
|||
|
REQUIRE(defIds.ids.size() == 4);
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Token check test", "[general]")
|
|||
|
{
|
|||
|
parser parser(R"(
|
|||
|
EXPRESSION <- _ TERM (TERM_OPERATOR TERM)*
|
|||
|
TERM <- FACTOR (FACTOR_OPERATOR FACTOR)*
|
|||
|
FACTOR <- NUMBER / '(' _ EXPRESSION ')' _
|
|||
|
TERM_OPERATOR <- < [-+] > _
|
|||
|
FACTOR_OPERATOR <- < [/*] > _
|
|||
|
NUMBER <- < [0-9]+ > _
|
|||
|
_ <- [ \t\r\n]*
|
|||
|
)");
|
|||
|
|
|||
|
REQUIRE(parser["EXPRESSION"].is_token() == false);
|
|||
|
REQUIRE(parser["FACTOR"].is_token() == false);
|
|||
|
REQUIRE(parser["FACTOR_OPERATOR"].is_token() == true);
|
|||
|
REQUIRE(parser["NUMBER"].is_token() == true);
|
|||
|
REQUIRE(parser["_"].is_token() == true);
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Lambda action test", "[general]")
|
|||
|
{
|
|||
|
parser parser(R"(
|
|||
|
START <- (CHAR)*
|
|||
|
CHAR <- .
|
|||
|
)");
|
|||
|
|
|||
|
std::string ss;
|
|||
|
parser["CHAR"] = [&](const SemanticValues& sv) {
|
|||
|
ss += *sv.c_str();
|
|||
|
};
|
|||
|
|
|||
|
bool ret = parser.parse("hello");
|
|||
|
REQUIRE(ret == true);
|
|||
|
REQUIRE(ss == "hello");
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("enter/leave handlers test", "[general]")
|
|||
|
{
|
|||
|
parser parser(R"(
|
|||
|
START <- LTOKEN '=' RTOKEN
|
|||
|
LTOKEN <- TOKEN
|
|||
|
RTOKEN <- TOKEN
|
|||
|
TOKEN <- [A-Za-z]+
|
|||
|
)");
|
|||
|
|
|||
|
parser["LTOKEN"].enter = [&](const char*, size_t, any& dt) {
|
|||
|
auto& require_upper_case = *any_cast<bool*>(dt);
|
|||
|
require_upper_case = false;
|
|||
|
};
|
|||
|
parser["LTOKEN"].leave = [&](const char*, size_t, size_t, any&, any& dt) {
|
|||
|
auto& require_upper_case = *any_cast<bool*>(dt);
|
|||
|
require_upper_case = true;
|
|||
|
};
|
|||
|
|
|||
|
auto message = "should be upper case string...";
|
|||
|
|
|||
|
parser["TOKEN"] = [&](const SemanticValues& sv, any& dt) {
|
|||
|
auto& require_upper_case = *any_cast<bool*>(dt);
|
|||
|
if (require_upper_case) {
|
|||
|
const auto& s = sv.str();
|
|||
|
if (!std::all_of(s.begin(), s.end(), ::isupper)) {
|
|||
|
throw parse_error(message);
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
bool require_upper_case = false;
|
|||
|
any dt = &require_upper_case;
|
|||
|
REQUIRE(parser.parse("hello=world", dt) == false);
|
|||
|
REQUIRE(parser.parse("HELLO=world", dt) == false);
|
|||
|
REQUIRE(parser.parse("hello=WORLD", dt) == true);
|
|||
|
REQUIRE(parser.parse("HELLO=WORLD", dt) == true);
|
|||
|
|
|||
|
parser.log = [&](size_t ln, size_t col, const std::string& msg) {
|
|||
|
REQUIRE(ln == 1);
|
|||
|
REQUIRE(col == 7);
|
|||
|
REQUIRE(msg == message);
|
|||
|
};
|
|||
|
parser.parse("hello=world", dt);
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("WHITESPACE test", "[general]")
|
|||
|
{
|
|||
|
parser parser(R"(
|
|||
|
# Rules
|
|||
|
ROOT <- ITEM (',' ITEM)*
|
|||
|
ITEM <- WORD / PHRASE
|
|||
|
|
|||
|
# Tokens
|
|||
|
WORD <- < [a-zA-Z0-9_]+ >
|
|||
|
PHRASE <- < '"' (!'"' .)* '"' >
|
|||
|
|
|||
|
%whitespace <- [ \t\r\n]*
|
|||
|
)");
|
|||
|
|
|||
|
auto ret = parser.parse(R"( one, "two, three", four )");
|
|||
|
|
|||
|
REQUIRE(ret == true);
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("WHITESPACE test2", "[general]")
|
|||
|
{
|
|||
|
parser parser(R"(
|
|||
|
# Rules
|
|||
|
ROOT <- ITEM (',' ITEM)*
|
|||
|
ITEM <- '[' < [a-zA-Z0-9_]+ > ']'
|
|||
|
|
|||
|
%whitespace <- (SPACE / TAB)*
|
|||
|
SPACE <- ' '
|
|||
|
TAB <- '\t'
|
|||
|
)");
|
|||
|
|
|||
|
std::vector<std::string> items;
|
|||
|
parser["ITEM"] = [&](const SemanticValues& sv) {
|
|||
|
items.push_back(sv.token());
|
|||
|
};
|
|||
|
|
|||
|
auto ret = parser.parse(R"([one], [two] ,[three] )");
|
|||
|
|
|||
|
REQUIRE(ret == true);
|
|||
|
REQUIRE(items.size() == 3);
|
|||
|
REQUIRE(items[0] == "one");
|
|||
|
REQUIRE(items[1] == "two");
|
|||
|
REQUIRE(items[2] == "three");
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("WHITESPACE test3", "[general]") {
|
|||
|
parser parser(R"(
|
|||
|
StrQuot <- < '"' < (StrEscape / StrChars)* > '"' >
|
|||
|
StrEscape <- '\\' any
|
|||
|
StrChars <- (!'"' !'\\' any)+
|
|||
|
any <- .
|
|||
|
%whitespace <- [ \t]*
|
|||
|
)");
|
|||
|
|
|||
|
parser["StrQuot"] = [](const SemanticValues& sv) {
|
|||
|
REQUIRE(sv.token() == R"( aaa \" bbb )");
|
|||
|
};
|
|||
|
|
|||
|
auto ret = parser.parse(R"( " aaa \" bbb " )");
|
|||
|
REQUIRE(ret == true);
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("WHITESPACE test4", "[general]") {
|
|||
|
parser parser(R"(
|
|||
|
ROOT <- HELLO OPE WORLD
|
|||
|
HELLO <- 'hello'
|
|||
|
OPE <- < [-+] >
|
|||
|
WORLD <- 'world' / 'WORLD'
|
|||
|
%whitespace <- [ \t\r\n]*
|
|||
|
)");
|
|||
|
|
|||
|
parser["HELLO"] = [](const SemanticValues& sv) {
|
|||
|
REQUIRE(sv.token() == "hello");
|
|||
|
};
|
|||
|
|
|||
|
parser["OPE"] = [](const SemanticValues& sv) {
|
|||
|
REQUIRE(sv.token() == "+");
|
|||
|
};
|
|||
|
|
|||
|
parser["WORLD"] = [](const SemanticValues& sv) {
|
|||
|
REQUIRE(sv.token() == "world");
|
|||
|
};
|
|||
|
|
|||
|
auto ret = parser.parse(" hello + world ");
|
|||
|
REQUIRE(ret == true);
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Word expression test", "[general]") {
|
|||
|
parser parser(R"(
|
|||
|
ROOT <- 'hello' ','? 'world'
|
|||
|
%whitespace <- [ \t\r\n]*
|
|||
|
%word <- [a-z]+
|
|||
|
)");
|
|||
|
|
|||
|
REQUIRE(parser.parse("helloworld") == false);
|
|||
|
REQUIRE(parser.parse("hello world") == true);
|
|||
|
REQUIRE(parser.parse("hello,world") == true);
|
|||
|
REQUIRE(parser.parse("hello, world") == true);
|
|||
|
REQUIRE(parser.parse("hello , world") == true);
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Skip token test", "[general]")
|
|||
|
{
|
|||
|
parser parser(
|
|||
|
" ROOT <- _ ITEM (',' _ ITEM _)* "
|
|||
|
" ITEM <- ([a-z0-9])+ "
|
|||
|
" ~_ <- [ \t]* "
|
|||
|
);
|
|||
|
|
|||
|
parser["ROOT"] = [&](const SemanticValues& sv) {
|
|||
|
REQUIRE(sv.size() == 2);
|
|||
|
};
|
|||
|
|
|||
|
auto ret = parser.parse(" item1, item2 ");
|
|||
|
|
|||
|
REQUIRE(ret == true);
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Skip token test2", "[general]")
|
|||
|
{
|
|||
|
parser parser(R"(
|
|||
|
ROOT <- ITEM (',' ITEM)*
|
|||
|
ITEM <- < ([a-z0-9])+ >
|
|||
|
%whitespace <- [ \t]*
|
|||
|
)");
|
|||
|
|
|||
|
parser["ROOT"] = [&](const SemanticValues& sv) {
|
|||
|
REQUIRE(sv.size() == 2);
|
|||
|
};
|
|||
|
|
|||
|
auto ret = parser.parse(" item1, item2 ");
|
|||
|
|
|||
|
REQUIRE(ret == true);
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Backtracking test", "[general]")
|
|||
|
{
|
|||
|
parser parser(R"(
|
|||
|
START <- PAT1 / PAT2
|
|||
|
PAT1 <- HELLO ' One'
|
|||
|
PAT2 <- HELLO ' Two'
|
|||
|
HELLO <- 'Hello'
|
|||
|
)");
|
|||
|
|
|||
|
size_t count = 0;
|
|||
|
parser["HELLO"] = [&](const SemanticValues& /*sv*/) {
|
|||
|
count++;
|
|||
|
};
|
|||
|
|
|||
|
parser.enable_packrat_parsing();
|
|||
|
|
|||
|
bool ret = parser.parse("Hello Two");
|
|||
|
REQUIRE(ret == true);
|
|||
|
REQUIRE(count == 1); // Skip second time
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Backtracking with AST", "[general]")
|
|||
|
{
|
|||
|
parser parser(R"(
|
|||
|
S <- A? B (A B)* A
|
|||
|
A <- 'a'
|
|||
|
B <- 'b'
|
|||
|
)");
|
|||
|
|
|||
|
parser.enable_ast();
|
|||
|
std::shared_ptr<Ast> ast;
|
|||
|
bool ret = parser.parse("ba", ast);
|
|||
|
REQUIRE(ret == true);
|
|||
|
REQUIRE(ast->nodes.size() == 2);
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Octal/Hex/Unicode value test", "[general]")
|
|||
|
{
|
|||
|
parser parser(
|
|||
|
R"( ROOT <- '\132\x7a\u30f3' )"
|
|||
|
);
|
|||
|
|
|||
|
auto ret = parser.parse("Zzン");
|
|||
|
|
|||
|
REQUIRE(ret == true);
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Ignore case test", "[general]") {
|
|||
|
parser parser(R"(
|
|||
|
ROOT <- HELLO WORLD
|
|||
|
HELLO <- 'hello'i
|
|||
|
WORLD <- 'world'i
|
|||
|
%whitespace <- [ \t\r\n]*
|
|||
|
)");
|
|||
|
|
|||
|
parser["HELLO"] = [](const SemanticValues& sv) {
|
|||
|
REQUIRE(sv.token() == "Hello");
|
|||
|
};
|
|||
|
|
|||
|
parser["WORLD"] = [](const SemanticValues& sv) {
|
|||
|
REQUIRE(sv.token() == "World");
|
|||
|
};
|
|||
|
|
|||
|
auto ret = parser.parse(" Hello World ");
|
|||
|
REQUIRE(ret == true);
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("mutable lambda test", "[general]")
|
|||
|
{
|
|||
|
std::vector<std::string> vec;
|
|||
|
|
|||
|
parser pg("ROOT <- 'mutable lambda test'");
|
|||
|
|
|||
|
// This test makes sure if the following code can be compiled.
|
|||
|
pg["TOKEN"] = [=](const SemanticValues& sv) mutable {
|
|||
|
vec.push_back(sv.str());
|
|||
|
};
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Simple calculator test", "[general]")
|
|||
|
{
|
|||
|
parser parser(R"(
|
|||
|
Additive <- Multitive '+' Additive / Multitive
|
|||
|
Multitive <- Primary '*' Multitive / Primary
|
|||
|
Primary <- '(' Additive ')' / Number
|
|||
|
Number <- [0-9]+
|
|||
|
)");
|
|||
|
|
|||
|
parser["Additive"] = [](const SemanticValues& sv) {
|
|||
|
switch (sv.choice()) {
|
|||
|
case 0:
|
|||
|
return any_cast<int>(sv[0]) + any_cast<int>(sv[1]);
|
|||
|
default:
|
|||
|
return any_cast<int>(sv[0]);
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
parser["Multitive"] = [](const SemanticValues& sv) {
|
|||
|
switch (sv.choice()) {
|
|||
|
case 0:
|
|||
|
return any_cast<int>(sv[0]) * any_cast<int>(sv[1]);
|
|||
|
default:
|
|||
|
return any_cast<int>(sv[0]);
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
parser["Number"] = [](const SemanticValues& sv) {
|
|||
|
return atoi(sv.c_str());
|
|||
|
};
|
|||
|
|
|||
|
int val;
|
|||
|
parser.parse("(1+2)*3", val);
|
|||
|
|
|||
|
REQUIRE(val == 9);
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Calculator test", "[general]")
|
|||
|
{
|
|||
|
// Construct grammer
|
|||
|
Definition EXPRESSION, TERM, FACTOR, TERM_OPERATOR, FACTOR_OPERATOR, NUMBER;
|
|||
|
|
|||
|
EXPRESSION <= seq(TERM, zom(seq(TERM_OPERATOR, TERM)));
|
|||
|
TERM <= seq(FACTOR, zom(seq(FACTOR_OPERATOR, FACTOR)));
|
|||
|
FACTOR <= cho(NUMBER, seq(chr('('), EXPRESSION, chr(')')));
|
|||
|
TERM_OPERATOR <= cls("+-");
|
|||
|
FACTOR_OPERATOR <= cls("*/");
|
|||
|
NUMBER <= oom(cls("0-9"));
|
|||
|
|
|||
|
// Setup actions
|
|||
|
auto reduce = [](const SemanticValues& sv) -> long {
|
|||
|
long ret = any_cast<long>(sv[0]);
|
|||
|
for (auto i = 1u; i < sv.size(); i += 2) {
|
|||
|
auto num = any_cast<long>(sv[i + 1]);
|
|||
|
switch (any_cast<char>(sv[i])) {
|
|||
|
case '+': ret += num; break;
|
|||
|
case '-': ret -= num; break;
|
|||
|
case '*': ret *= num; break;
|
|||
|
case '/': ret /= num; break;
|
|||
|
}
|
|||
|
}
|
|||
|
return ret;
|
|||
|
};
|
|||
|
|
|||
|
EXPRESSION = reduce;
|
|||
|
TERM = reduce;
|
|||
|
TERM_OPERATOR = [](const SemanticValues& sv) { return *sv.c_str(); };
|
|||
|
FACTOR_OPERATOR = [](const SemanticValues& sv) { return *sv.c_str(); };
|
|||
|
NUMBER = [](const SemanticValues& sv) { return stol(sv.str(), nullptr, 10); };
|
|||
|
|
|||
|
// Parse
|
|||
|
long val;
|
|||
|
auto r = EXPRESSION.parse_and_get_value("1+2*3*(4-5+6)/7-8", val);
|
|||
|
|
|||
|
REQUIRE(r.ret == true);
|
|||
|
REQUIRE(val == -3);
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Calculator test2", "[general]")
|
|||
|
{
|
|||
|
// Parse syntax
|
|||
|
auto syntax = R"(
|
|||
|
# Grammar for Calculator...
|
|||
|
EXPRESSION <- TERM (TERM_OPERATOR TERM)*
|
|||
|
TERM <- FACTOR (FACTOR_OPERATOR FACTOR)*
|
|||
|
FACTOR <- NUMBER / '(' EXPRESSION ')'
|
|||
|
TERM_OPERATOR <- [-+]
|
|||
|
FACTOR_OPERATOR <- [/*]
|
|||
|
NUMBER <- [0-9]+
|
|||
|
)";
|
|||
|
|
|||
|
std::string start;
|
|||
|
auto grammar = ParserGenerator::parse(syntax, strlen(syntax), start, nullptr);
|
|||
|
auto& g = *grammar;
|
|||
|
|
|||
|
// Setup actions
|
|||
|
auto reduce = [](const SemanticValues& sv) -> long {
|
|||
|
long ret = any_cast<long>(sv[0]);
|
|||
|
for (auto i = 1u; i < sv.size(); i += 2) {
|
|||
|
auto num = any_cast<long>(sv[i + 1]);
|
|||
|
switch (any_cast<char>(sv[i])) {
|
|||
|
case '+': ret += num; break;
|
|||
|
case '-': ret -= num; break;
|
|||
|
case '*': ret *= num; break;
|
|||
|
case '/': ret /= num; break;
|
|||
|
}
|
|||
|
}
|
|||
|
return ret;
|
|||
|
};
|
|||
|
|
|||
|
g["EXPRESSION"] = reduce;
|
|||
|
g["TERM"] = reduce;
|
|||
|
g["TERM_OPERATOR"] = [](const SemanticValues& sv) { return *sv.c_str(); };
|
|||
|
g["FACTOR_OPERATOR"] = [](const SemanticValues& sv) { return *sv.c_str(); };
|
|||
|
g["NUMBER"] = [](const SemanticValues& sv) { return stol(sv.str(), nullptr, 10); };
|
|||
|
|
|||
|
// Parse
|
|||
|
long val;
|
|||
|
auto r = g[start].parse_and_get_value("1+2*3*(4-5+6)/7-8", val);
|
|||
|
|
|||
|
REQUIRE(r.ret == true);
|
|||
|
REQUIRE(val == -3);
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Calculator test3", "[general]")
|
|||
|
{
|
|||
|
// Parse syntax
|
|||
|
parser parser(R"(
|
|||
|
# Grammar for Calculator...
|
|||
|
EXPRESSION <- TERM (TERM_OPERATOR TERM)*
|
|||
|
TERM <- FACTOR (FACTOR_OPERATOR FACTOR)*
|
|||
|
FACTOR <- NUMBER / '(' EXPRESSION ')'
|
|||
|
TERM_OPERATOR <- [-+]
|
|||
|
FACTOR_OPERATOR <- [/*]
|
|||
|
NUMBER <- [0-9]+
|
|||
|
)");
|
|||
|
|
|||
|
auto reduce = [](const SemanticValues& sv) -> long {
|
|||
|
long ret = any_cast<long>(sv[0]);
|
|||
|
for (auto i = 1u; i < sv.size(); i += 2) {
|
|||
|
auto num = any_cast<long>(sv[i + 1]);
|
|||
|
switch (any_cast<char>(sv[i])) {
|
|||
|
case '+': ret += num; break;
|
|||
|
case '-': ret -= num; break;
|
|||
|
case '*': ret *= num; break;
|
|||
|
case '/': ret /= num; break;
|
|||
|
}
|
|||
|
}
|
|||
|
return ret;
|
|||
|
};
|
|||
|
|
|||
|
// Setup actions
|
|||
|
parser["EXPRESSION"] = reduce;
|
|||
|
parser["TERM"] = reduce;
|
|||
|
parser["TERM_OPERATOR"] = [](const SemanticValues& sv) { return static_cast<char>(*sv.c_str()); };
|
|||
|
parser["FACTOR_OPERATOR"] = [](const SemanticValues& sv) { return static_cast<char>(*sv.c_str()); };
|
|||
|
parser["NUMBER"] = [](const SemanticValues& sv) { return stol(sv.str(), nullptr, 10); };
|
|||
|
|
|||
|
// Parse
|
|||
|
long val;
|
|||
|
auto ret = parser.parse("1+2*3*(4-5+6)/7-8", val);
|
|||
|
|
|||
|
REQUIRE(ret == true);
|
|||
|
REQUIRE(val == -3);
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Calculator test with AST", "[general]")
|
|||
|
{
|
|||
|
parser parser(R"(
|
|||
|
EXPRESSION <- _ TERM (TERM_OPERATOR TERM)*
|
|||
|
TERM <- FACTOR (FACTOR_OPERATOR FACTOR)*
|
|||
|
FACTOR <- NUMBER / '(' _ EXPRESSION ')' _
|
|||
|
TERM_OPERATOR <- < [-+] > _
|
|||
|
FACTOR_OPERATOR <- < [/*] > _
|
|||
|
NUMBER <- < [0-9]+ > _
|
|||
|
~_ <- [ \t\r\n]*
|
|||
|
)");
|
|||
|
|
|||
|
parser.enable_ast();
|
|||
|
|
|||
|
std::function<long (const Ast&)> eval = [&](const Ast& ast) {
|
|||
|
if (ast.name == "NUMBER") {
|
|||
|
return stol(ast.token);
|
|||
|
} else {
|
|||
|
const auto& nodes = ast.nodes;
|
|||
|
auto result = eval(*nodes[0]);
|
|||
|
for (auto i = 1u; i < nodes.size(); i += 2) {
|
|||
|
auto num = eval(*nodes[i + 1]);
|
|||
|
auto ope = nodes[i]->token[0];
|
|||
|
switch (ope) {
|
|||
|
case '+': result += num; break;
|
|||
|
case '-': result -= num; break;
|
|||
|
case '*': result *= num; break;
|
|||
|
case '/': result /= num; break;
|
|||
|
}
|
|||
|
}
|
|||
|
return result;
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
std::shared_ptr<Ast> ast;
|
|||
|
auto ret = parser.parse("1+2*3*(4-5+6)/7-8", ast);
|
|||
|
ast = AstOptimizer(true).optimize(ast);
|
|||
|
auto val = eval(*ast);
|
|||
|
|
|||
|
REQUIRE(ret == true);
|
|||
|
REQUIRE(val == -3);
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Ignore semantic value test", "[general]")
|
|||
|
{
|
|||
|
parser parser(R"(
|
|||
|
START <- ~HELLO WORLD
|
|||
|
HELLO <- 'Hello' _
|
|||
|
WORLD <- 'World' _
|
|||
|
_ <- [ \t\r\n]*
|
|||
|
)");
|
|||
|
|
|||
|
parser.enable_ast();
|
|||
|
|
|||
|
std::shared_ptr<Ast> ast;
|
|||
|
auto ret = parser.parse("Hello World", ast);
|
|||
|
|
|||
|
REQUIRE(ret == true);
|
|||
|
REQUIRE(ast->nodes.size() == 1);
|
|||
|
REQUIRE(ast->nodes[0]->name == "WORLD");
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Ignore semantic value of 'or' predicate test", "[general]")
|
|||
|
{
|
|||
|
parser parser(R"(
|
|||
|
START <- _ !DUMMY HELLO_WORLD '.'
|
|||
|
HELLO_WORLD <- HELLO 'World' _
|
|||
|
HELLO <- 'Hello' _
|
|||
|
DUMMY <- 'dummy' _
|
|||
|
~_ <- [ \t\r\n]*
|
|||
|
)");
|
|||
|
|
|||
|
parser.enable_ast();
|
|||
|
|
|||
|
std::shared_ptr<Ast> ast;
|
|||
|
auto ret = parser.parse("Hello World.", ast);
|
|||
|
|
|||
|
REQUIRE(ret == true);
|
|||
|
REQUIRE(ast->nodes.size() == 1);
|
|||
|
REQUIRE(ast->nodes[0]->name == "HELLO_WORLD");
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Ignore semantic value of 'and' predicate test", "[general]")
|
|||
|
{
|
|||
|
parser parser(R"(
|
|||
|
START <- _ &HELLO HELLO_WORLD '.'
|
|||
|
HELLO_WORLD <- HELLO 'World' _
|
|||
|
HELLO <- 'Hello' _
|
|||
|
~_ <- [ \t\r\n]*
|
|||
|
)");
|
|||
|
|
|||
|
parser.enable_ast();
|
|||
|
|
|||
|
std::shared_ptr<Ast> ast;
|
|||
|
auto ret = parser.parse("Hello World.", ast);
|
|||
|
|
|||
|
REQUIRE(ret == true);
|
|||
|
REQUIRE(ast->nodes.size() == 1);
|
|||
|
REQUIRE(ast->nodes[0]->name == "HELLO_WORLD");
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Literal token on AST test1", "[general]")
|
|||
|
{
|
|||
|
parser parser(R"(
|
|||
|
STRING_LITERAL <- '"' (('\\"' / '\\t' / '\\n') / (!["] .))* '"'
|
|||
|
)");
|
|||
|
parser.enable_ast();
|
|||
|
|
|||
|
std::shared_ptr<Ast> ast;
|
|||
|
auto ret = parser.parse(R"("a\tb")", ast);
|
|||
|
|
|||
|
REQUIRE(ret == true);
|
|||
|
REQUIRE(ast->is_token == true);
|
|||
|
REQUIRE(ast->token == R"("a\tb")");
|
|||
|
REQUIRE(ast->nodes.empty());
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Literal token on AST test2", "[general]")
|
|||
|
{
|
|||
|
parser parser(R"(
|
|||
|
STRING_LITERAL <- '"' (ESC / CHAR)* '"'
|
|||
|
ESC <- ('\\"' / '\\t' / '\\n')
|
|||
|
CHAR <- (!["] .)
|
|||
|
)");
|
|||
|
parser.enable_ast();
|
|||
|
|
|||
|
std::shared_ptr<Ast> ast;
|
|||
|
auto ret = parser.parse(R"("a\tb")", ast);
|
|||
|
|
|||
|
REQUIRE(ret == true);
|
|||
|
REQUIRE(ast->is_token == false);
|
|||
|
REQUIRE(ast->token.empty());
|
|||
|
REQUIRE(ast->nodes.size() == 3);
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Literal token on AST test3", "[general]")
|
|||
|
{
|
|||
|
parser parser(R"(
|
|||
|
STRING_LITERAL <- < '"' (ESC / CHAR)* '"' >
|
|||
|
ESC <- ('\\"' / '\\t' / '\\n')
|
|||
|
CHAR <- (!["] .)
|
|||
|
)");
|
|||
|
parser.enable_ast();
|
|||
|
|
|||
|
std::shared_ptr<Ast> ast;
|
|||
|
auto ret = parser.parse(R"("a\tb")", ast);
|
|||
|
|
|||
|
REQUIRE(ret == true);
|
|||
|
REQUIRE(ast->is_token == true);
|
|||
|
REQUIRE(ast->token == R"("a\tb")");
|
|||
|
REQUIRE(ast->nodes.empty());
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Missing missing definitions test", "[general]")
|
|||
|
{
|
|||
|
parser parser(R"(
|
|||
|
A <- B C
|
|||
|
)");
|
|||
|
|
|||
|
REQUIRE(!parser);
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Definition duplicates test", "[general]")
|
|||
|
{
|
|||
|
parser parser(R"(
|
|||
|
A <- ''
|
|||
|
A <- ''
|
|||
|
)");
|
|||
|
|
|||
|
REQUIRE(!parser);
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Semantic values test", "[general]")
|
|||
|
{
|
|||
|
parser parser(R"(
|
|||
|
term <- ( a b c x )? a b c
|
|||
|
a <- 'a'
|
|||
|
b <- 'b'
|
|||
|
c <- 'c'
|
|||
|
x <- 'x'
|
|||
|
)");
|
|||
|
|
|||
|
for (const auto& rule: parser.get_rule_names()){
|
|||
|
parser[rule.c_str()] = [rule](const SemanticValues& sv, any&) {
|
|||
|
if (rule == "term") {
|
|||
|
REQUIRE(any_cast<std::string>(sv[0]) == "a at 0");
|
|||
|
REQUIRE(any_cast<std::string>(sv[1]) == "b at 1");
|
|||
|
REQUIRE(any_cast<std::string>(sv[2]) == "c at 2");
|
|||
|
return std::string();
|
|||
|
} else {
|
|||
|
return rule + " at " + std::to_string(sv.c_str() - sv.ss);
|
|||
|
}
|
|||
|
};
|
|||
|
}
|
|||
|
|
|||
|
REQUIRE(parser.parse("abc"));
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Ordered choice count", "[general]")
|
|||
|
{
|
|||
|
parser parser(R"(
|
|||
|
S <- 'a' / 'b'
|
|||
|
)");
|
|||
|
|
|||
|
parser["S"] = [](const SemanticValues& sv) {
|
|||
|
REQUIRE(sv.choice() == 1);
|
|||
|
REQUIRE(sv.choice_count() == 2);
|
|||
|
};
|
|||
|
|
|||
|
parser.parse("b");
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Ordered choice count 2", "[general]")
|
|||
|
{
|
|||
|
parser parser(R"(
|
|||
|
S <- ('a' / 'b')*
|
|||
|
)");
|
|||
|
|
|||
|
parser["S"] = [](const SemanticValues& sv) {
|
|||
|
REQUIRE(sv.choice() == 0);
|
|||
|
REQUIRE(sv.choice_count() == 0);
|
|||
|
};
|
|||
|
|
|||
|
parser.parse("b");
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Semantic value tag", "[general]")
|
|||
|
{
|
|||
|
parser parser(R"(
|
|||
|
S <- A? B* C?
|
|||
|
A <- 'a'
|
|||
|
B <- 'b'
|
|||
|
C <- 'c'
|
|||
|
)");
|
|||
|
|
|||
|
{
|
|||
|
using namespace udl;
|
|||
|
parser["S"] = [](const SemanticValues& sv) {
|
|||
|
REQUIRE(sv.size() == 1);
|
|||
|
REQUIRE(sv.tags.size() == 1);
|
|||
|
REQUIRE(sv.tags[0] == "C"_);
|
|||
|
};
|
|||
|
auto ret = parser.parse("c");
|
|||
|
REQUIRE(ret == true);
|
|||
|
}
|
|||
|
|
|||
|
{
|
|||
|
using namespace udl;
|
|||
|
parser["S"] = [](const SemanticValues& sv) {
|
|||
|
REQUIRE(sv.size() == 2);
|
|||
|
REQUIRE(sv.tags.size() == 2);
|
|||
|
REQUIRE(sv.tags[0] == "B"_);
|
|||
|
REQUIRE(sv.tags[1] == "B"_);
|
|||
|
};
|
|||
|
auto ret = parser.parse("bb");
|
|||
|
REQUIRE(ret == true);
|
|||
|
}
|
|||
|
|
|||
|
{
|
|||
|
using namespace udl;
|
|||
|
parser["S"] = [](const SemanticValues& sv) {
|
|||
|
REQUIRE(sv.size() == 2);
|
|||
|
REQUIRE(sv.tags.size() == 2);
|
|||
|
REQUIRE(sv.tags[0] == "A"_);
|
|||
|
REQUIRE(sv.tags[1] == "C"_);
|
|||
|
};
|
|||
|
auto ret = parser.parse("ac");
|
|||
|
REQUIRE(ret == true);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
TEST_CASE("Negated Class test", "[general]")
|
|||
|
{
|
|||
|
parser parser(R"(
|
|||
|
ROOT <- [^a-z_]+
|
|||
|
)");
|
|||
|
|
|||
|
bool ret = parser;
|
|||
|
REQUIRE(ret == true);
|
|||
|
|
|||
|
REQUIRE(parser.parse("ABC123"));
|
|||
|
REQUIRE_FALSE(parser.parse("ABcZ"));
|
|||
|
REQUIRE_FALSE(parser.parse("ABCZ_"));
|
|||
|
REQUIRE_FALSE(parser.parse(""));
|
|||
|
}
|
|||
|
|
|||
|
// vim: et ts=4 sw=4 cin cino={1s ff=unix
|