diff --git a/.clangd b/.clangd index 7c6ddb1..4808c4e 100644 --- a/.clangd +++ b/.clangd @@ -16,6 +16,8 @@ Diagnostics: - bugprone-easily-swappable-parameters - readability-convert-member-functions-to-static - bugprone-exception-escape + - modernize-use-nodiscard + - readability-else-after-return CheckOptions: UnusedIncludes: Strict diff --git a/.vscode/launch.json b/.vscode/launch.json index 7e126bd..299d0a4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,9 +8,9 @@ "type": "lldb", "request": "launch", "name": "Debug Browser", - "program": "${workspaceFolder}/builddir/web-browser", + "program": "${workspaceFolder}/builddir/browser", "args": [], - "cwd": "${workspaceFolder}" + "cwd": "${workspaceFolder}/builddir" } ] } \ No newline at end of file diff --git a/bong_grammar.txt b/bong_grammar.txt new file mode 100644 index 0000000..1061a13 --- /dev/null +++ b/bong_grammar.txt @@ -0,0 +1,114 @@ + +top_level -> + | element + | value + +element -> Name single_line_fields element_body:? + +element_body -> "{" element_fields "}" + +element_fields -> (_ element_field (__linebreak__ element_field):*):? __linebreak__ + +element_field -> + | element + | Id + | Class + | element_property + | value + +single_line_fields -> + (first_single_line_field + (__singleline__ single_line_field):*):? __singleline__ + +first_single_line_field -> + | Id + | Class + | __singleline__ element_property + | __singleline__ single_line_value + +single_line_field -> + | Id + | Class + | element_property + | single_line_value + +element_property -> Name _ ("=" | ":") _ value + +single_line_value -> + | array + | Int + | Float + | String + | bool + | Null + +value -> + | object + | array + | Int + | Float + | String + | bool + | Null + +object -> "{" object_properties "}" + +object_properties -> (_ object_property (_ "," _ object_property):* _ ",":?):? _ + +object_property -> (Name | String) _ ("=" | ":") _ value + +array -> "[" array_values "]" + +array_values -> (_ value (_ "," _ value):* _ ",":?):? _ + +bool -> True | False + +__singleline__ -> mandatory_same_line_whitespace +_singleline_ -> optional_same_line_whitespace +__linebreak__ -> mandatory_linebreak +__ -> mandatory_whitespace +_ -> optional_whitespace + +mandatory_same_line_whitespace -> single_line_whitespace:+ +optional_same_line_whitespace -> single_line_whitespace:* + +mandatory_linebreak -> + single_line_whitespace:* + line_breaker + whitespace_and_line_break:* + +single_line_whitespace -> SingleLineComment | SingleLineWhitespace + +line_breaker -> MultiLineComment | MultiLineWhitespace | ";" + +whitespace_and_line_break -> singular_whitespace | ";" + +optional_whitespace -> singular_whitespace:* + +mandatory_whitespace -> singular_whitespace:+ + +singular_whitespace -> + | SingleLineComment + | MultiLineWhitespace + | SingleLineComment + | MultiLineComment + +=== tokens === + +SingleLineWhitespace -> [ \t\r\f\v]+ +MultiLineWhitespace -> [ \t\n\r\f\v]+ +SingleLineComment -> \/\/[^\n]+ +MultiLineComment -> \/\*.*?\*\/ + +Name -> [a-zA-Z_][a-zA-Z0-9_]* +Id -> #[a-zA-Z0-9_]+ +Class \.[a-zA-Z0-9_]+ + +Int -> [0-9]+ +Float -> [0-9]+\.[0-9]* +String -> " +Null -> "null" +False -> "false" +True -> "true" diff --git a/examples/helloworld/main.bong b/examples/helloworld/main.bong new file mode 100644 index 0000000..6f25f0a --- /dev/null +++ b/examples/helloworld/main.bong @@ -0,0 +1,18 @@ +page { + text.title { + "hello world" + } + input#answer { + placeholder="answer" + } + // text { "hello world" } + /* + input#non_answer { + placeholder="non answer" + } + */ + button#submit { + "submit" + } + text { "hello world" }; text { "hello world" } +} diff --git a/src/bong.cpp b/src/bong.cpp index f06f3a1..dc5dce2 100644 --- a/src/bong.cpp +++ b/src/bong.cpp @@ -1,48 +1,376 @@ #include "bong.hpp" #include "utils.hpp" #include +#include +#include +#include +#include namespace bong { +auto token_type_to_string(Tokens type) noexcept -> std::string_view +{ + switch (type) { + case Tokens::Eof: + return "Eof"; + case Tokens::SingleLineWhitespace: + return "SingleLineWhitespace"; + case Tokens::MultiLineWhitespace: + return "MultiLineWhitespace"; + case Tokens::SingleLineComment: + return "SingleLineComment"; + case Tokens::MultiLineComment: + return "MultiLineComment"; + case Tokens::Name: + return "Name"; + case Tokens::Id: + return "Id"; + case Tokens::Class: + return "Class"; + case Tokens::Int: + return "Int"; + case Tokens::Float: + return "Float"; + case Tokens::String: + return "String"; + case Tokens::Null: + return "Null"; + case Tokens::False: + return "False"; + case Tokens::True: + return "True"; + case Tokens::LBrace: + return "LBrace"; + case Tokens::RBrace: + return "RBrace"; + case Tokens::LBracket: + return "LBracket"; + case Tokens::RBracket: + return "RBracket"; + case Tokens::Equal: + return "Equal"; + case Tokens::Colon: + return "Colon"; + case Tokens::SemiColon: + return "SemiColon"; + case Tokens::Comma: + return "Comma"; + } +} + +auto Token::value() const noexcept -> std::string_view +{ + return text.substr(location.index, length); +} + +auto char_to_escaped_string(char c) noexcept -> std::string +{ + switch (c) { + case '\n': + return "\\n"; + case '\t': + return "\\t"; + case '\r': + return "\\r"; + case '\f': + return "\\f"; + case '\v': + return "\\v"; + default: + return { c }; + } +} + +auto escape_string(std::string_view value) noexcept -> std::string +{ + auto result = std::string {}; + for (auto c : value) + result += char_to_escaped_string(c); + return result; +} + +auto Token::to_string() const noexcept -> std::string +{ + return fmt::format( + "Token {{ [{}:{}], {}:{}, \t{}, \033[01;32m\"{}\"\033[00m }}", + location.index, length, location.line, location.col, + token_type_to_string(type), escape_string(value())); +} + +auto Lexer::collect() noexcept -> Result, Error> +{ + auto tokens = std::vector {}; + while (true) { + auto token = peek(); + if (!token) + return token.transform>(); + else if (token->type == Tokens::Eof) + break; + else + tokens.push_back(*token); + next(); + } + return tokens; +} + auto Lexer::make_token() noexcept -> Result { + if (done()) + return Token { Tokens::Eof, location(), 0, text }; auto c = current(); - if (std::isspace(c)) + if (std::isspace(c) != 0) return make_whitespace(); - else if (std::isdigit(c)) + else if (std::isdigit(c) != 0) return make_number(); - else if (std::isalpha(c)) + else if (std::isalpha(c) != 0) return make_name(); else return make_static(); } -auto Lexer::make_name() noexcept -> Result +auto Lexer::make_whitespace() noexcept -> Result { - auto begin_index = index; - auto begin = location; - while (!done() - and (std ::isalpha(current()) or std::isdigit(current()) - or current() == '_' or current() == '-')) { + auto begin = location(); + while (!done() and std::isspace(current()) != 0 and current() != '\n') { step(); } - return Token { Tokens::Name, begin_index, index - begin_index, begin }; + if (!done() and current() == '\n') { + while (!done() and std::isspace(current()) != 0) { + step(); + } + return Token { + Tokens::MultiLineWhitespace, + begin, + length_from(begin), + text, + }; + } else { + return Token { + Tokens::SingleLineWhitespace, + begin, + length_from(begin), + text, + }; + } } -auto Lexer::make_number() noexcept -> Result; +auto substring_matches(std::string_view text, size_t index, + std::string_view literal) noexcept -> bool +{ + return literal.size() == 4 + and text.substr(index, literal.size()).compare(literal) == 0; +} -auto Lexer::make_static() noexcept -> Result; +auto Lexer::make_name() noexcept -> Result +{ + auto begin = location(); + while (!done() + and ((std::isalpha(current()) != 0) or (std::isdigit(current()) != 0) + or current() == '_')) { + step(); + } + auto type = [&] { + if (substring_matches(text, begin.index, "null")) + return Tokens::Null; + else if (substring_matches(text, begin.index, "false")) + return Tokens::False; + else if (substring_matches(text, begin.index, "true")) + return Tokens::True; + else + return Tokens::Name; + }(); + return Token { type, begin, length_from(begin), text }; +} -auto Lexer::make_whitespace() noexcept -> Result; +auto Lexer::make_number() noexcept -> Result +{ + auto begin = location(); + while (!done() and (std::isdigit(current()) != 0)) { + step(); + } + if (!done() and current() == '.') { + step(); + if (done() or std::isdigit(current()) == 0) { + return Error { "expected digits after '.'", location() }; + } + while (!done() and (std::isdigit(current()) != 0)) { + step(); + } + return Token { Tokens::Float, begin, length_from(begin), text }; + } else { + return Token { Tokens::Int, begin, length_from(begin), text }; + } +} -auto Lexer::make_singleline_comment() noexcept -> Result; +auto Lexer::make_static() noexcept -> Result +{ + switch (current()) { + case '/': + return make_comment(); + case '"': + return make_string(); + case '#': + return make_id(); + case '.': + return make_class(); + case '{': + return make_single_char_token(Tokens::LBrace); + case '}': + return make_single_char_token(Tokens::RBrace); + case '[': + return make_single_char_token(Tokens::LBracket); + case ']': + return make_single_char_token(Tokens::RBracket); + case '=': + return make_single_char_token(Tokens::Equal); + case ':': + return make_single_char_token(Tokens::Colon); + case ';': + return make_single_char_token(Tokens::SemiColon); + case ',': + return make_single_char_token(Tokens::Comma); + default: + return Error { fmt::format("unexpected character '{}'", current()), + location() }; + } +} -auto Lexer::make_multiline_comment() noexcept -> Result; +auto Lexer::make_comment() noexcept -> Result +{ + auto begin = location(); + step(); + if (current() == '/') + return make_single_line_comment(begin); + else if (current() == '*') + return make_multi_line_comment(begin); + else + return Error { + fmt::format("expected '/' or '*', got '{}'", current()), + location(), + }; +} -auto Lexer::make_string() noexcept -> Result; +auto Lexer::make_multi_line_comment(Location begin) noexcept + -> Result +{ + step(); + step(); + while (!done() and text.at(index - 1) != '*' and current() != '/') { + step(); + } + if (done()) { + return Error { "expected \"*/\", got EOF", location() }; + } + step(); + return Token { Tokens::MultiLineComment, begin, length_from(begin), text }; +} -auto Lexer::make_id() noexcept -> Result; +auto Lexer::make_single_line_comment(Location begin) noexcept + -> Result +{ + step(); + while (!done() and current() != '\n') { + step(); + } + return Token { Tokens::SingleLineComment, begin, length_from(begin), text }; +} -auto Lexer::make_class() noexcept -> Result; +auto Lexer::make_string() noexcept -> Result +{ + auto begin = location(); + step(); + auto escaped = false; + while (!done() and (escaped || current() != '"')) { + escaped = !escaped and current() == '\\'; + step(); + } + if (done()) { + return Error { + fmt::format("expected '\"', got Eof"), + location(), + }; + } else if (current() != '\"') { + return Error { + fmt::format("expected '\"', got '{}'", current()), + location(), + }; + } + step(); + return Token { Tokens::String, begin, length_from(begin), text }; +} + +auto Lexer::make_id() noexcept -> Result +{ + auto begin = location(); + step(); + while (!done() + and ((std::isalpha(current()) != 0) or (std::isdigit(current()) != 0) + or current() == '_')) { + step(); + } + return Token { Tokens::Id, begin, length_from(begin), text }; +} + +auto Lexer::make_class() noexcept -> Result +{ + auto begin = location(); + step(); + while (!done() + and ((std::isalpha(current()) != 0) or (std::isdigit(current()) != 0) + or current() == '_')) { + step(); + } + return Token { Tokens::Class, begin, length_from(begin), text }; +} +auto Lexer::make_single_char_token(Tokens type) noexcept -> Result +{ + auto begin = location(); + step(); + return Token { type, begin, length_from(begin), text }; +} + +auto Lexer::step() noexcept -> void +{ + index++; + col++; + if (!done() and current() == '\n') { + line++; + col = 1; + } +} + +auto Parser::parse_top_level() noexcept -> Result, Error> +{ + if (!lexer.peek()) + return { lexer.peek().unwrap_error() }; + else if (lexer.peek()->type == Tokens::Name) + return parse_element(); + else + return parse_value(); +} + +auto Parser::parse_element() noexcept -> Result, Error> +{ + auto name = *lexer.peek(); + + auto ids = Element::Ids {}; + auto classes = Element::Classes {}; + auto properties = Element::Properties {}; + auto values = Element::Values {}; + + if (!lexer.next()) + return { lexer.peek().unwrap_error() }; + return Result, Error>::create_ok( + std::make_unique( + Element { std::string { name.value() }, {}, {}, {}, {} })); +} + +auto Parser::parse_single_line_fields( + Element::Initializer& initializer) noexcept -> Result +{ + if (auto result = parse_first_single_line_field(initializer); !result) + return result; + return {}; +} } diff --git a/src/bong.hpp b/src/bong.hpp index 07efc1a..b9cd397 100644 --- a/src/bong.hpp +++ b/src/bong.hpp @@ -1,14 +1,20 @@ #pragma once +#include "src/result.hpp" #include "utils.hpp" +#include +#include #include #include +#include +#include namespace bong { enum class Tokens { Eof, - Whitespace, + SingleLineWhitespace, + MultiLineWhitespace, SingleLineComment, MultiLineComment, @@ -19,7 +25,9 @@ enum class Tokens { Int, Float, String, - Bool, + Null, + False, + True, LBrace, RBrace, @@ -32,61 +40,218 @@ enum class Tokens { Comma, }; +auto token_type_to_string(Tokens type) noexcept -> std::string_view; + struct Location { + size_t index; int line, col; }; struct Token { Tokens type; - size_t index, length; Location location; + size_t length; + std::string_view text; + + auto value() const noexcept -> std::string_view; + auto to_string() const noexcept -> std::string; }; class Lexer { public: struct Error { std::string message; - size_t index; Location location; }; - Lexer(std::string_view text) - : text { text } + inline Lexer(std::string_view text) + : current_token { make_token() } + , text { text } { - (void)next(); + next(); } - auto next() noexcept -> Result + inline auto next() noexcept -> Result { return current_token = make_token(); } - [[nodiscard]] auto peek() const noexcept -> Result + [[nodiscard]] inline auto peek() const noexcept -> Result { return current_token; } + [[nodiscard]] auto collect() noexcept -> Result, Error>; private: auto make_token() noexcept -> Result; + auto make_whitespace() noexcept -> Result; auto make_name() noexcept -> Result; auto make_number() noexcept -> Result; auto make_static() noexcept -> Result; - auto make_whitespace() noexcept -> Result; - auto make_singleline_comment() noexcept -> Result; - auto make_multiline_comment() noexcept -> Result; + auto make_comment() noexcept -> Result; + auto make_single_line_comment(Location begin) noexcept + -> Result; + auto make_multi_line_comment(Location begin) noexcept + -> Result; auto make_string() noexcept -> Result; auto make_id() noexcept -> Result; auto make_class() noexcept -> Result; + auto make_single_char_token(Tokens type) noexcept -> Result; - auto current() const noexcept -> char { return text.at(index); } - auto done() const noexcept -> bool { return index >= text.size(); } - auto step() noexcept -> void { index++; } + inline auto current() const noexcept -> char { return text.at(index); } + inline auto done() const noexcept -> bool { return index >= text.size(); } + auto step() noexcept -> void; - Result current_token { - Error { "next() not called first", index, location }, - }; + auto location() const noexcept -> Location { return { index, line, col }; } + inline auto length_from(Location begin) const noexcept -> size_t + { + return index - begin.index; + } + inline auto length_from(size_t begin_index) const noexcept -> size_t + { + return index - begin_index; + } + + Result current_token; std::string_view text; size_t index { 0 }; - Location location { 1, 1 }; + int line { 1 }, col { 1 }; +}; + +enum class Nodes { + Element, + Object, + Array, + Int, + Float, + Bool, + String, +}; + +struct Node { + Node() = default; + virtual ~Node() = default; + virtual auto type() const noexcept -> Nodes; +}; + +struct Element final : public Node { + using Ids = std::vector; + using Classes = std::vector; + using Properties = std::map>; + using Values = std::vector>; + struct Initializer { + Ids ids; + Classes classes; + Properties properties; + Values values; + }; + + Element(std::string name, std::vector ids, + std::vector classes, + std::map> properties, + std::vector> values) + : name { std::move(name) } + , ids { std::move(ids) } + , classes { std::move(classes) } + , properties { std::move(properties) } + , values { std::move(values) } + { } + + std::string name; + Ids ids; + Classes classes; + Properties properties; + Values values; + + auto type() const noexcept -> Nodes override { return Nodes::Element; } +}; + +struct Object final : public Node { + std::map> properties; + + auto type() const noexcept -> Nodes override { return Nodes::Object; } +}; + +struct Array final : public Node { + std::vector> values; + + auto type() const noexcept -> Nodes override { return Nodes::Array; } +}; + +struct Int final : public Node { + int64_t value; + + auto type() const noexcept -> Nodes override { return Nodes::Int; } +}; + +struct Float final : public Node { + double value; + + auto type() const noexcept -> Nodes override { return Nodes::Float; } +}; + +struct String final : public Node { + std::string value; + + auto type() const noexcept -> Nodes override { return Nodes::String; } +}; + +struct Bool final : public Node { + bool value; + + auto type() const noexcept -> Nodes override { return Nodes::Bool; } +}; + +class Parser { +public: + struct Error { + Error(Lexer::Error&& error) + : message { std::move(error.message) } + , location { error.location } + { } + + std::string message; + Location location; + }; + + Parser(Lexer lexer) + : lexer { std::move(lexer) } + { } + + auto parse_top_level() noexcept -> Result, Error>; + auto parse_element() noexcept -> Result, Error>; + auto parse_element_body(Element::Initializer& initializer) noexcept + -> Result; + auto parse_element_fields(Element::Initializer& initializer) noexcept + -> Result; + auto parse_element_field(Element::Initializer& initializer) noexcept + -> Result; + auto parse_single_line_fields(Element::Initializer& initializer) noexcept + -> Result; + auto parse_first_single_line_field( + Element::Initializer& initializer) noexcept -> Result; + auto parse_single_line_field(Element::Initializer& initializer) noexcept + -> Result; + auto parse_element_property() noexcept -> Result; + auto parse_single_line_value() noexcept -> Result; + auto parse_value() noexcept -> Result, Error>; + auto parse_object() noexcept -> Result; + auto parse_object_properties() noexcept -> Result; + auto parse_object_property() noexcept -> Result; + auto parse_array() noexcept -> Result; + auto parse_array_values() noexcept -> Result; + auto parse_bool() noexcept -> Result; + auto parse_mandatory_same_line_whitespace() noexcept -> Result; + auto parse_optional_same_line_whitespace() noexcept -> Result; + auto parse_mandatory_linebreak() noexcept -> Result; + auto parse_single_line_whitespace() noexcept -> Result; + auto parse_line_breaker() noexcept -> Result; + auto parse_whitespace_and_line_break() noexcept -> Result; + auto parse_optional_whitespace() noexcept -> Result; + auto parse_mandatory_whitespace() noexcept -> Result; + auto parse_singular_whitespace() noexcept -> Result; + +private: + Lexer lexer; }; } diff --git a/src/main.cpp b/src/main.cpp index 0486dce..78b3c76 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,10 +3,15 @@ #include "SDL_rect.h" #include "SDL_render.h" #include "SDL_video.h" +#include "src/bong.hpp" #include "utils.hpp" #include #include +#include +#include #include +#include +#include class GUI { public: @@ -66,19 +71,46 @@ private: SDL_Renderer* renderer; }; +auto read_file_into_string(const std::string& filename) +{ + auto file = std::ifstream { filename }; + auto contents = std::string( + std::istreambuf_iterator(file), std::istreambuf_iterator()); + return contents; +} +struct Data { + int a, b; +}; + +auto func() -> std::unique_ptr +{ + return std::make_unique(Data { 5, 3 }); +} auto main() -> int { - // test - fmt::print("browser: hello world!\n"); - auto gui = GUI::create().unwrap(); - while (true) { - bool should_exit = gui->should_exit(); - if (should_exit) - break; - gui->set_background_color(100, 180, 220); - SDL_Rect rect = { .x = 0, .y = 0, .w = 50, .h = 50 }; - gui->create_rect(rect, 255, 0, 0); - gui->update_gui(); + auto text = read_file_into_string("../examples/helloworld/main.bong"); + auto tokens = bong::Lexer { text }.collect(); + if (tokens) { + fmt::print("tokens:\n"); + for (const auto& token : *tokens) + fmt::print(" {}\n", token.to_string()); + } else { + fmt::print("lexer error: {}\n at {}:{}\n", + tokens.unwrap_error().message, tokens.unwrap_error().location.line, + tokens.unwrap_error().location.col); } + + // test + // fmt::print("browser: hello world!\n"); + // auto gui = GUI::create().unwrap(); + // while (true) { + // bool should_exit = gui->should_exit(); + // if (should_exit) + // break; + // gui->set_background_color(100, 180, 220); + // SDL_Rect rect = { .x = 0, .y = 0, .w = 50, .h = 50 }; + // gui->create_rect(rect, 255, 0, 0); + // gui->update_gui(); + // } } \ No newline at end of file diff --git a/src/result.hpp b/src/result.hpp index 36f40a9..04c1eba 100644 --- a/src/result.hpp +++ b/src/result.hpp @@ -32,7 +32,7 @@ template struct Extracter { using Error = void; }; -template class [[nodiscard]] Result { +template class Result { static_assert(std::is_object_v && std::is_destructible_v, "incompatible Value in Result"); static_assert(std::is_object_v && std::is_destructible_v, @@ -209,28 +209,28 @@ public: return is_ok() ? if_ok(unwrap()) : if_error(unwrap_error()); } - [[nodiscard]] constexpr auto flatten() noexcept requires - std::same_as::Error> + [[nodiscard]] constexpr auto flatten() noexcept + requires std::same_as::Error> { using InnerValue = typename Extracter::Value; return is_ok() ? unwrap() : Result(unwrap_error()); } - [[nodiscard]] constexpr auto flatten() const noexcept requires - std::same_as::Error> + [[nodiscard]] constexpr auto flatten() const noexcept + requires std::same_as::Error> { using InnerValue = typename Extracter::Value; return is_ok() ? unwrap() : Result(unwrap_error()); } - [[nodiscard]] constexpr auto flatten_error() noexcept requires - std::same_as::Value> + [[nodiscard]] constexpr auto flatten_error() noexcept + requires std::same_as::Value> { using InnerError = typename Extracter::Error; return is_error() ? unwrap_error() : Result(unwrap()); } - [[nodiscard]] constexpr auto flatten_error() const noexcept requires - std::same_as::Value> + [[nodiscard]] constexpr auto flatten_error() const noexcept + requires std::same_as::Value> { using InnerError = typename Extracter::Error; return is_error() ? unwrap_error() @@ -267,7 +267,7 @@ struct Extracter> { using Error = ErrorInferer; }; -template class [[nodiscard]] Result { +template class Result { static_assert(std::is_object_v && std::is_destructible_v, "incompatible Error in Result"); @@ -385,14 +385,14 @@ public: return is_ok() ? if_ok(unwrap()) : if_error(); } - [[nodiscard]] constexpr auto flatten() noexcept requires - std::same_as::Error> + [[nodiscard]] constexpr auto flatten() noexcept + requires std::same_as::Error> { using InnerValue = typename Extracter::Value; return is_ok() ? unwrap() : Result(); } - [[nodiscard]] constexpr auto flatten() const noexcept requires - std::same_as::Error> + [[nodiscard]] constexpr auto flatten() const noexcept + requires std::same_as::Error> { using InnerValue = typename Extracter::Value; return is_ok() ? unwrap() : Result(); @@ -422,7 +422,7 @@ private: std::optional maybe_value; }; -template class [[nodiscard]] Result { +template class Result { static_assert(std::is_object_v && std::is_destructible_v, "incompatible Error in Result"); @@ -528,14 +528,14 @@ public: return is_ok() ? if_ok() : if_error(unwrap_error()); } - [[nodiscard]] constexpr auto flatten_error() noexcept requires - std::same_as::Value> + [[nodiscard]] constexpr auto flatten_error() noexcept + requires std::same_as::Value> { using InnerError = typename Extracter::Error; return is_error() ? unwrap_error() : Result(); } - [[nodiscard]] constexpr auto flatten_error() const noexcept requires - std::same_as::Value> + [[nodiscard]] constexpr auto flatten_error() const noexcept + requires std::same_as::Value> { using InnerError = typename Extracter::Error; return is_error() ? unwrap_error() : Result(); @@ -565,7 +565,7 @@ private: std::optional maybe_error; }; -template <> class [[nodiscard]] Result { +template <> class Result { public: enum class States { Ok, Error }; diff --git a/src/utils.hpp b/src/utils.hpp index e3ab7a3..a4c0d17 100644 --- a/src/utils.hpp +++ b/src/utils.hpp @@ -1,3 +1,5 @@ +#pragma once + #include "result.hpp" namespace utils {