From c0aecff8b3a8c5a5617fbf771d3375927ae769a0 Mon Sep 17 00:00:00 2001 From: bog Date: Sat, 14 Oct 2023 21:20:39 +0200 Subject: [PATCH] :sparkles: array index selector. --- doc/grammar.bnf | 5 +- src/Interpreter.cpp | 20 ++++++++ src/Lexer.cpp | 3 ++ src/Node.hpp | 3 +- src/Parser.cpp | 39 ++++++++++++++- src/Parser.hpp | 1 + tests/Interpreter.cpp | 109 ++++++++++++++++++++++++++++++++++++++++++ tests/Lexer.cpp | 4 +- tests/Parser.cpp | 57 ++++++++++++++++++++++ 9 files changed, 235 insertions(+), 6 deletions(-) diff --git a/doc/grammar.bnf b/doc/grammar.bnf index 20db27c..978a4bc 100644 --- a/doc/grammar.bnf +++ b/doc/grammar.bnf @@ -5,7 +5,8 @@ DEPS ::= LITERAL* BLOCK ::= obrace CMD_LST cbrace CMD_LST ::= (CMD (comma CMD)* comma?)? CMD ::= LITERAL* -LITERAL ::= ident | var | ARRAY +LITERAL ::= ident | var | ARRAY | INDEX -VAR_DECL ::= var assign (LITERAL | ARRAY) +VAR_DECL ::= var assign (LITERAL | ARRAY | INDEX) ARRAY ::= opar LITERAL* cpar +INDEX ::= (var|ARRAY) osquare ident csquare diff --git a/src/Interpreter.cpp b/src/Interpreter.cpp index 97de129..ff97963 100644 --- a/src/Interpreter.cpp +++ b/src/Interpreter.cpp @@ -162,6 +162,26 @@ namespace sn return values; } + else if (node->type() == NODE_INDEX) + { + auto var = *node->get(0); + auto val = *node->get(1); + + auto values = get_value(var); + ssize_t index = std::stoi(val->repr()); + + if (index < 0) + { + index = values.size() + index; + } + + if (index >= static_cast(values.size())) + { + throw run_error {"invalid index '" + std::to_string(index) + "'"}; + } + + return { values[index] }; + } throw run_error {"cannot find value for '" + SN_GET(NodeTypeStr, node->type(), "NODE_") diff --git a/src/Lexer.cpp b/src/Lexer.cpp index a98729c..a43d877 100644 --- a/src/Lexer.cpp +++ b/src/Lexer.cpp @@ -9,6 +9,9 @@ namespace sn m_separators.push_back('\n'); m_separators.push_back(' '); + add_text("[", NODE_OSQUARE); + add_text("]", NODE_CSQUARE); + add_text("(", NODE_OPAR); add_text(")", NODE_CPAR); add_text("=", NODE_ASSIGN); diff --git a/src/Node.hpp b/src/Node.hpp index a213775..0e68063 100644 --- a/src/Node.hpp +++ b/src/Node.hpp @@ -8,7 +8,8 @@ G(NODE_CBRACE), G(NODE_CMD), G(NODE_CMD_LST), G(NODE_COMMA), \ G(NODE_BLOCK), G(NODE_TARGET), G(NODE_DEPS), G(NODE_RULE), \ G(NODE_VAR), G(NODE_ASSIGN), G(NODE_VAR_DECL), G(NODE_OPAR), \ - G(NODE_CPAR), G(NODE_ARRAY) + G(NODE_CPAR), G(NODE_ARRAY), G(NODE_INDEX), G(NODE_OSQUARE), \ + G(NODE_CSQUARE) namespace sn { diff --git a/src/Parser.cpp b/src/Parser.cpp index 4f2983a..5d82570 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -180,6 +180,12 @@ namespace sn std::shared_ptr Parser::parse_literal() { + if (type_is(NODE_VAR) + && type_is(NODE_OSQUARE, 1)) + { + return parse_index(*consume(NODE_VAR)); + } + if (type_is(NODE_IDENT) || type_is(NODE_VAR)) { @@ -188,7 +194,14 @@ namespace sn if (type_is(NODE_OPAR)) { - return parse_array(); + auto array = parse_array(); + + if (type_is(NODE_OSQUARE)) + { + return parse_index(array); + } + + return array; } throw syntax_error {"unknown token '" @@ -206,7 +219,16 @@ namespace sn if (type_is(NODE_OPAR)) { - node->add_child(parse_array()); + auto array = parse_array(); + + if (type_is(NODE_OSQUARE)) + { + node->add_child(parse_index(array)); + } + else + { + node->add_child(array); + } } else { @@ -231,4 +253,17 @@ namespace sn return node; } + + std::shared_ptr Parser::parse_index(std::shared_ptr lhs) + { + auto node = std::make_shared(NODE_INDEX); + + node->add_child(lhs); + + consume(NODE_OSQUARE); + node->add_child(*consume(NODE_IDENT)); + consume(NODE_CSQUARE); + + return node; + } } diff --git a/src/Parser.hpp b/src/Parser.hpp index 3c4bf5b..c39ac9f 100644 --- a/src/Parser.hpp +++ b/src/Parser.hpp @@ -41,6 +41,7 @@ namespace sn std::shared_ptr parse_literal(); std::shared_ptr parse_var_decl(); std::shared_ptr parse_array(); + std::shared_ptr parse_index(std::shared_ptr lhs); }; } diff --git a/tests/Interpreter.cpp b/tests/Interpreter.cpp index bf556a2..b37c62b 100644 --- a/tests/Interpreter.cpp +++ b/tests/Interpreter.cpp @@ -84,6 +84,13 @@ public: != std::end(vec)); } + void test_not_contains(std::string const& str, std::vector vec) + { + INFO("'" << str << "' found"); + REQUIRE(std::find(std::begin(vec), std::end(vec), str) + == std::end(vec)); + } + protected: }; @@ -168,3 +175,105 @@ TEST_CASE_METHOD(InterpreterTest, "Interpreter_array_var") } } + +TEST_CASE_METHOD(InterpreterTest, "Interpreter_array_index") +{ + SECTION("get second: ok") + { + std::stringstream ss; + ss << "$X = (a b c)" << std::endl; + ss << "d -> $X[1] {" << std::endl; + ss << "executed" << std::endl; + ss << "}" << std::endl; + + auto res = TEST_RUN(ss.str(), std::filesystem::path("b")); + test_contains("executed", res); + } + + SECTION("get second: not ok") + { + std::stringstream ss; + ss << "$X = (a b c)" << std::endl; + ss << "d -> $X[1] {" << std::endl; + ss << "executed" << std::endl; + ss << "}" << std::endl; + + auto res = TEST_RUN(ss.str(), std::filesystem::path("a")); + test_not_contains("executed", res); + } + + SECTION("get last: ok") + { + std::stringstream ss; + ss << "$X = (a b c)" << std::endl; + ss << "d -> $X[-1] {" << std::endl; + ss << "executed" << std::endl; + ss << "}" << std::endl; + + auto res = TEST_RUN(ss.str(), std::filesystem::path("c")); + test_contains("executed", res); + } + + SECTION("get last: not ok") + { + std::stringstream ss; + ss << "$X = (a b c)" << std::endl; + ss << "d -> $X[-1] {" << std::endl; + ss << "executed" << std::endl; + ss << "}" << std::endl; + + auto res = TEST_RUN(ss.str(), std::filesystem::path("b")); + test_not_contains("executed", res); + } +} + +TEST_CASE_METHOD(InterpreterTest, "Interpreter_array_literal_index", "[.]") +{ + SECTION("get second: ok") + { + std::stringstream ss; + ss << "$X = (a b c)[1]" << std::endl; + ss << "d -> $X {" << std::endl; + ss << "executed" << std::endl; + ss << "}" << std::endl; + + auto res = TEST_RUN(ss.str(), std::filesystem::path("b")); + test_contains("executed", res); + } + + SECTION("get second: not ok") + { + std::stringstream ss; + ss << "$X = (a b c)[1]" << std::endl; + ss << "d -> $X {" << std::endl; + ss << "executed" << std::endl; + ss << "}" << std::endl; + + auto res = TEST_RUN(ss.str(), std::filesystem::path("a")); + test_not_contains("executed", res); + } + + SECTION("get last: ok") + { + std::stringstream ss; + ss << "$X = (a b c)[-1]" << std::endl; + ss << "d -> $X {" << std::endl; + ss << "executed" << std::endl; + ss << "}" << std::endl; + + auto res = TEST_RUN(ss.str(), std::filesystem::path("c")); + test_contains("executed", res); + } + + SECTION("get last: not ok") + { + std::stringstream ss; + ss << "$X = (a b c)[-1]" << std::endl; + ss << "d -> $X {" << std::endl; + ss << "executed" << std::endl; + ss << "}" << std::endl; + + auto res = TEST_RUN(ss.str(), std::filesystem::path("b")); + test_not_contains("executed", res); + } +} diff --git a/tests/Lexer.cpp b/tests/Lexer.cpp index 1f68c9f..43aa6bd 100644 --- a/tests/Lexer.cpp +++ b/tests/Lexer.cpp @@ -54,9 +54,11 @@ TEST_CASE_METHOD(LexerTest, "Lexer_vars") TEST_CASE_METHOD(LexerTest, "Lexer_arrays") { - m_lexer.scan(" () "); + m_lexer.scan(" ()[] "); test_next("OPAR"); test_next("CPAR"); + test_next("OSQUARE"); + test_next("CSQUARE"); test_end(); } diff --git a/tests/Parser.cpp b/tests/Parser.cpp index 9cf57f3..5238c65 100644 --- a/tests/Parser.cpp +++ b/tests/Parser.cpp @@ -130,3 +130,60 @@ TEST_CASE_METHOD(ParserTest, "Parser_array") ")))", ss.str()); } + +TEST_CASE_METHOD(ParserTest, "Parser_array_index") +{ + std::stringstream ss; + ss << "$BIM = hello.cpp" << std::endl; + ss << "$BAM = (world.hpp world.cpp)" << std::endl; + ss << " $BIM -> (hello.cpp world.cpp) {" << std::endl; + ss << " g++ $BIM[4] -o hello.elf, " << std::endl; + ss << " ls " << std::endl; + ss << " }" << std::endl; + + test_parse("DOC(" + "VAR_DECL(VAR[BIM],IDENT[hello.cpp])," + "VAR_DECL(VAR[BAM],ARRAY(IDENT[world.hpp],IDENT[world.cpp]))," + "RULE(" + "TARGET(" + "VAR[BIM]" + "),DEPS(" + "ARRAY(IDENT[hello.cpp],IDENT[world.cpp])" + "),BLOCK(" + "CMD(" + "IDENT[g++],INDEX(VAR[BIM],IDENT[4]),IDENT[-o],IDENT[hello.elf]" + "),CMD(" + "IDENT[ls]" + ")" + ")))", + ss.str()); +} + +TEST_CASE_METHOD(ParserTest, "Parser_array_index_literal") +{ + std::stringstream ss; + ss << "$BIM = hello.cpp" << std::endl; + ss << "$BAM = (world.hpp world.cpp)[14]" << std::endl; + ss << " $BIM -> (hello.cpp world.cpp) {" << std::endl; + ss << " g++ $BIM[4] -o hello.elf, " << std::endl; + ss << " ls " << std::endl; + ss << " }" << std::endl; + + test_parse("DOC(" + "VAR_DECL(VAR[BIM],IDENT[hello.cpp])," + "VAR_DECL(VAR[BAM]," + "INDEX(ARRAY(IDENT[world.hpp],IDENT[world.cpp]),IDENT[14]))," + "RULE(" + "TARGET(" + "VAR[BIM]" + "),DEPS(" + "ARRAY(IDENT[hello.cpp],IDENT[world.cpp])" + "),BLOCK(" + "CMD(" + "IDENT[g++],INDEX(VAR[BIM],IDENT[4]),IDENT[-o],IDENT[hello.elf]" + "),CMD(" + "IDENT[ls]" + ")" + ")))", + ss.str()); +}