//============================================================================// // // // artichoke programming language // // // // Copyright (C) 2025 Erick Saul Guzman Ramos, whoami.artichoke.dev // // // // // // This program is free software: you can redistribute it and/or modify // // it under the terms of the GNU Affero General Public License as published // // by the Free Software Foundation, either version 3 of the License, or // // (at your option) any later version. // // // // This program 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 Affero General Public License for more details. // // // // You should have received a copy of the GNU Affero General Public License // // along with this program. If not, see . // // // //============================================================================// #include #include #include #include #include #include namespace lang = arti::lang; template static void CommonLiteralsSuccess( lang::TokenV kind, const std::array &lexemes ) { const std::string source = SourceFromTokens(lexemes); std::size_t it = 0; lang::Tokenizer tkz{ source }; for (auto token : tkz.range()) { REQUIRE(token.has_value()); REQUIRE(token->value == kind); REQUIRE(token->strValue == lexemes.at(it++)); } REQUIRE(it == lexemes.size()); REQUIRE(tkz.peek().has_value()); REQUIRE(tkz.peek()->value == lang::TokenV::tkEOF); } TEST_CASE("Strings_Simple", "[strings][valid]") { constexpr std::array lexemes = { R"("a")", R"("hello")", R"("with spaces")", R"("12345")", R"Q("!@#$%^&*()")Q" }; CommonLiteralsSuccess(lang::TokenV::tkString, lexemes); } TEST_CASE("Strings_Escapes", "[strings][valid][escapes]") { // Validate common escape sequences remain part of lexeme text. constexpr std::array lexemes = { R"("quote: \"")", R"("backslash: \\")", R"("newline: \n")", R"("tab: \t")", R"("mix: \"\\\n\t")" }; CommonLiteralsSuccess(lang::TokenV::tkString, lexemes); } TEST_CASE("Strings_OperatorsAdjacency", "[strings][operators]") { // "foo"+"bar" -> tkString, opPlus, tkString const std::string source = R"("foo"+"bar")"; lang::Tokenizer tkz{ source }; auto t1 = tkz.peek(); REQUIRE(t1.has_value()); REQUIRE(t1->value == lang::TokenV::tkString); REQUIRE(t1->strValue == R"("foo")"); REQUIRE(tkz.consume().has_value()); auto t2 = tkz.peek(); REQUIRE(t2.has_value()); REQUIRE(t2->value == lang::TokenV::opPlus); REQUIRE(tkz.consume().has_value()); auto t3 = tkz.peek(); REQUIRE(t3.has_value()); REQUIRE(t3->value == lang::TokenV::tkString); REQUIRE(t3->strValue == R"("bar")"); REQUIRE(tkz.consume().has_value()); auto eof = tkz.peek(); REQUIRE(eof.has_value()); REQUIRE(eof->value == lang::TokenV::tkEOF); } TEST_CASE("Strings_Unterminated_Error", "[strings][error]") { // Missing closing quote should yield an error. const std::string source = "\"unterminated"; lang::Tokenizer tkz{ source }; auto errTok = tkz.peek(); REQUIRE_FALSE(errTok.has_value()); const auto &err = errTok.error(); REQUIRE( err.message.find("Invalid literal") != std::string::npos ); } TEST_CASE("Chars_Simple", "[chars][valid]") { constexpr std::array lexemes = { R"('a')", R"('Z')", R"('0')", R"('_')", R"('$')" }; CommonLiteralsSuccess(lang::TokenV::tkCharacter, lexemes); } TEST_CASE("Chars_Escapes", "[chars][valid][escapes]") { constexpr std::array lexemes = { R"('\n')", R"('\t')", R"('\\')", R"('\'')" }; CommonLiteralsSuccess(lang::TokenV::tkCharacter, lexemes); } TEST_CASE("Chars_Invalid_Empty", "[chars][error]") { const std::string source = "''"; lang::Tokenizer tkz{ source }; auto errTok = tkz.peek(); REQUIRE_FALSE(errTok.has_value()); const auto &err = errTok.error(); REQUIRE( err.message.find("Invalid literal") != std::string::npos ); } TEST_CASE("Chars_Invalid_Multiple", "[chars][error]") { const std::string source = "'ab'"; lang::Tokenizer tkz{ source }; auto errTok = tkz.peek(); REQUIRE_FALSE(errTok.has_value()); const auto &err = errTok.error(); REQUIRE( err.message.find("Invalid literal") != std::string::npos ); } TEST_CASE("Chars_Unterminated", "[chars][error]") { const std::string source = "'a"; lang::Tokenizer tkz{ source }; auto errTok = tkz.peek(); REQUIRE_FALSE(errTok.has_value()); const auto &err = errTok.error(); REQUIRE( err.message.find("Invalid literal") != std::string::npos ); } TEST_CASE("Chars_InvalidEscape", "[chars][error][.escapes]") { const std::string source = "'\\x'"; lang::Tokenizer tkz{ source }; auto errTok = tkz.peek(); REQUIRE_FALSE(errTok.has_value()); const auto &err = errTok.error(); REQUIRE( err.message.find("Invalid literal") != std::string::npos ); }