diff --git a/meson.build b/meson.build index d9fe0ac..b81436b 100644 --- a/meson.build +++ b/meson.build @@ -11,6 +11,11 @@ project( fmt_dep = dependency('fmt') sdl2_dep = dependency('sdl2') +add_project_arguments( + '-Wno-gnu-statement-expression-from-macro-expansion', + language: 'cpp', +) + common_sources = [] subdir('utils') subdir('scriptlang') @@ -18,7 +23,6 @@ subdir('markup') browser_sources = common_sources subdir('browser') -browser_sources executable( 'web-browser', diff --git a/scriptlang/error.hpp b/scriptlang/error.hpp new file mode 100644 index 0000000..7e84f29 --- /dev/null +++ b/scriptlang/error.hpp @@ -0,0 +1,17 @@ +#pragma once + +namespace scriptlang { + +enum class Errors { + NotImplemented, + LexerNoTokenYet, + LexerStringNotTerminated, + LexerUnexpectedCharacer, + LexerMultilineCommentNotTerminated, + NoLexerOutput, + ParserExhausted, + ParserMalformedStringLiteral, + ParserStructNotTerminated, +}; + +} diff --git a/scriptlang/lexer.cpp b/scriptlang/lexer.cpp index 6f77eca..94e7928 100644 --- a/scriptlang/lexer.cpp +++ b/scriptlang/lexer.cpp @@ -1,13 +1,14 @@ #include "lexer.hpp" +#include "error.hpp" #include #include namespace scriptlang { -auto constexpr Lexer::next() noexcept -> Result +auto Lexer::make_token() noexcept -> Result { if (done()) - return token(Tokens::Eof, index); + return token(Tokens::Eof, index, current_location()); if (std::isdigit(current()) != 0) return make_number(); if (std::isalpha(current()) != 0 or current() == '_') @@ -17,32 +18,34 @@ auto constexpr Lexer::next() noexcept -> Result return make_static(); } -auto constexpr Lexer::make_number() noexcept -> Result +auto Lexer::make_number() noexcept -> Result { auto begin = index; + auto span_from = current_location(); while (!done() and std::isdigit(current()) != 0) step(); if (current() == '.') { step(); while (!done() and std::isdigit(current()) != 0) step(); - return token(Tokens::Float, begin); + return token(Tokens::Float, begin, span_from); } - return token(Tokens::Int, begin); + return token(Tokens::Int, begin, span_from); } -auto constexpr Lexer::make_id() noexcept -> Result +auto Lexer::make_id() noexcept -> Result { auto begin = index; + auto span_from = current_location(); while (!done() and (std::isalpha(current()) != 0 or std::isdigit(current()) != 0 or current() == '_')) step(); - return token(id_or_keyword_type(text.substr(begin, index - begin)), begin); + return token(id_or_keyword_type(text.substr(begin, index - begin)), begin, + span_from); } -auto constexpr Lexer::id_or_keyword_type(std::string_view substring) noexcept - -> Tokens +auto Lexer::id_or_keyword_type(std::string_view substring) noexcept -> Tokens { if (substring.compare("if") == 0) return Tokens::If; @@ -75,9 +78,10 @@ auto constexpr Lexer::id_or_keyword_type(std::string_view substring) noexcept return Tokens::Id; } -auto constexpr Lexer::make_string() noexcept -> Result +auto Lexer::make_string() noexcept -> Result { auto begin = index; + auto span_from = current_location(); step(); auto escaped = false; while (!done() and (current() != '"' or escaped)) { @@ -85,22 +89,23 @@ auto constexpr Lexer::make_string() noexcept -> Result step(); } if (current() != '"') - return {}; + return Errors::LexerStringNotTerminated; step(); - return token(Tokens::String, begin); + return token(Tokens::String, begin, span_from); } -auto constexpr Lexer::make_static() noexcept -> Result +auto Lexer::make_static() noexcept -> Result { auto begin = index; + auto span_from = current_location(); auto type = static_token_type(); if (!type) - return {}; - return token(*type, begin); + return type.transform(); + return token(*type, begin, span_from); } // NOLINTNEXTLINE(readability-function-cognitive-complexity) -auto constexpr Lexer::static_token_type() noexcept -> Result +auto Lexer::static_token_type() noexcept -> Result { using TT = Tokens; auto stepped = [&](Tokens v) { @@ -202,10 +207,10 @@ auto constexpr Lexer::static_token_type() noexcept -> Result return stepped(TT::GreaterEqual); return TT::Greater; } - return {}; + return Errors::LexerUnexpectedCharacer; } -auto constexpr Lexer::skip_multiline_comment() noexcept -> Result +auto Lexer::skip_multiline_comment() noexcept -> Result { step(); auto last = current(); @@ -213,12 +218,12 @@ auto constexpr Lexer::skip_multiline_comment() noexcept -> Result while (!done() and last != '*' and current() != '/') step(); if (last != '*' or current() != '/') - return {}; + return Errors::LexerMultilineCommentNotTerminated; step(); return Tokens::MultilineComment; } -auto constexpr Lexer::skip_singleline_comment() noexcept -> Result +auto Lexer::skip_singleline_comment() noexcept -> Result { step(); while (!done() and current() != '\n') diff --git a/scriptlang/lexer.hpp b/scriptlang/lexer.hpp index 71c134a..68d59ad 100644 --- a/scriptlang/lexer.hpp +++ b/scriptlang/lexer.hpp @@ -1,3 +1,4 @@ +#include "error.hpp" #include "utils/all.hpp" #include #include @@ -80,10 +81,14 @@ struct Location { int line, column; }; +struct Span { + Location from, to; +}; + struct Token { Tokens type; size_t index, length; - Location location; + Span span; }; class Lexer { @@ -91,29 +96,39 @@ public: Lexer(std::string_view text) : text { text } { } - auto constexpr next() noexcept -> Result; - auto peek() noexcept -> Result + auto next() noexcept -> Result { return make_token(); } + auto peek() noexcept -> Result { if (last_token) - return Result::create_ok(*last_token); - return {}; + return Result::create_ok(*last_token); + return Errors::LexerNoTokenYet; } private: - auto constexpr make_number() noexcept -> Result; - auto constexpr make_id() noexcept -> Result; - auto constexpr id_or_keyword_type(std::string_view substring) noexcept - -> Tokens; - auto constexpr make_string() noexcept -> Result; - auto constexpr make_static() noexcept -> Result; - auto constexpr static_token_type() noexcept -> Result; - auto constexpr skip_multiline_comment() noexcept -> Result; - auto constexpr skip_singleline_comment() noexcept -> Result; + auto make_token() noexcept -> Result; + auto make_number() noexcept -> Result; + auto make_id() noexcept -> Result; + auto id_or_keyword_type(std::string_view substring) noexcept -> Tokens; + auto make_string() noexcept -> Result; + auto make_static() noexcept -> Result; + auto static_token_type() noexcept -> Result; + auto skip_multiline_comment() noexcept -> Result; + auto skip_singleline_comment() noexcept -> Result; - [[nodiscard]] auto constexpr inline token( - Tokens type, size_t begin) noexcept -> Token + [[nodiscard]] auto constexpr inline current_location() const noexcept + -> Location { - auto token = Token { type, begin, index - begin, { line, column } }; + return { line, column }; + } + [[nodiscard]] auto constexpr inline token( + Tokens type, size_t begin, Location span_from) noexcept -> Token + { + auto token = Token { + type, + begin, + index - begin, + { span_from, { line, column } }, + }; last_token = token; return token; } diff --git a/scriptlang/meson.build b/scriptlang/meson.build index 79dd2fe..ffe15db 100644 --- a/scriptlang/meson.build +++ b/scriptlang/meson.build @@ -1,5 +1,5 @@ common_sources += files( - 'parser.cpp', 'lexer.cpp', + 'parser.cpp', ) diff --git a/scriptlang/parser.cpp b/scriptlang/parser.cpp index f3481b0..f98e09a 100644 --- a/scriptlang/parser.cpp +++ b/scriptlang/parser.cpp @@ -1,14 +1,110 @@ #include "parser.hpp" +#include "error.hpp" +#include +#include namespace scriptlang { -auto Parser::parse_value() noexcept -> Result, void> +auto parse_expression(bool strictly_values) noexcept + -> Result, Errors> { - switch (lexer.peek()->type) { + if (strictly_values) + return parse_expression(true); + return Errors::NotImplemented; +} + +auto Parser::parse_struct(bool strictly_values) noexcept + -> Result, Errors> +{ + auto values = std::map> {}; + auto first_brace = TRY(lexer.peek()); + if (TRY(lexer.peek()).type == Tokens::LBrace) { + TRY(lexer.next()); + auto last_brace = TRY(lexer.peek()); + if (last_brace.type != Tokens::RBrace) + return Errors::ParserStructNotTerminated; + return { + std::make_unique( + token_span(first_brace, last_brace), std::move(values)), + }; + } + return parse_atom(); +} + +auto Parser::parse_atom() noexcept + -> Result, Errors> +{ + auto token = TRY(lexer.peek()); + switch (token.type) { case Tokens::Id: + return { + std::make_unique(token_span(token, token), + token_text(lexer.peek()->index, lexer.peek()->length)), + }; + case Tokens::Int: + return { + std::make_unique(token_span(token, token), + std::atol( + token_text(lexer.peek()->index, lexer.peek()->length) + .c_str())), + }; + case Tokens::Float: + return { + std::make_unique(token_span(token, token), + std::atof( + token_text(lexer.peek()->index, lexer.peek()->length) + .c_str())), + }; + case Tokens::False: + return { + std::make_unique(token_span(token, token), false), + }; + case Tokens::True: + return { + std::make_unique(token_span(token, token), true), + }; + case Tokens::String: + return { + std::make_unique(token_span(token, token), + *parse_string_value( + token_text(lexer.peek()->index, lexer.peek()->length))), + }; default: - return {}; + return Errors::ParserExhausted; } } +[[nodiscard]] auto Parser::parse_string_value(std::string_view literal) noexcept + -> Result +{ + if (literal.size() < 2) + return Errors::ParserMalformedStringLiteral; + auto value = std::string {}; + auto escaped = false; + for (const auto c : literal.substr(1, literal.size() - 2)) { + if (escaped) { + value.push_back([&] { + switch (c) { + case 'n': + return '\n'; + case 'r': + return '\r'; + case 't': + return '\t'; + case 'v': + return '\n'; + default: + return c; + } + }()); + escaped = false; + } else if (c == '\\') { + escaped = true; + } else { + value.push_back(c); + } + } + return value; +} + } diff --git a/scriptlang/parser.hpp b/scriptlang/parser.hpp index 5d0c47b..7f32d7b 100644 --- a/scriptlang/parser.hpp +++ b/scriptlang/parser.hpp @@ -1,5 +1,6 @@ #pragma once +#include "error.hpp" #include "lexer.hpp" #include "utils/all.hpp" #include "utils/result.hpp" @@ -12,10 +13,6 @@ namespace scriptlang { -struct Span { - Location from, to; -}; - enum class Expressions { Binary, Negate, @@ -25,6 +22,7 @@ enum class Expressions { Call, Operator, + Struct, Id, Int, Float, @@ -47,66 +45,101 @@ public: private: }; -class Id final : public Expression { +class Struct final : public Expression { public: - Id(std::string value) - : m_value { std::move(value) } + Struct(Span span, std::map> values) + : m_span { span } + , m_values { std::move(values) } { } [[nodiscard]] auto expression_type() const noexcept -> Expressions override { return Expressions::Id; } + [[nodiscard]] auto span() const noexcept -> Span override { return m_span; } + [[nodiscard]] auto values() const noexcept -> auto& { return m_values; } + +private: + Span m_span; + std::map> m_values; +}; + +class Id final : public Expression { +public: + Id(Span span, std::string value) + : m_span { span } + , m_value { std::move(value) } + { } + [[nodiscard]] auto expression_type() const noexcept -> Expressions override + { + return Expressions::Id; + } + [[nodiscard]] auto span() const noexcept -> Span override { return m_span; } [[nodiscard]] auto value() const noexcept { return m_value; } private: + Span m_span; std::string m_value; }; + class Int final : public Expression { public: - Int(int64_t value) - : m_value { value } + Int(Span span, int64_t value) + : m_span { span } + , m_value { value } { } [[nodiscard]] auto expression_type() const noexcept -> Expressions override { return Expressions::Int; } + [[nodiscard]] auto span() const noexcept -> Span override { return m_span; } [[nodiscard]] auto value() const noexcept { return m_value; } private: + Span m_span; int64_t m_value; }; + class Float final : public Expression { public: - Float(double value) - : m_value { value } + Float(Span span, double value) + : m_span { span } + , m_value { value } { } [[nodiscard]] auto expression_type() const noexcept -> Expressions override { return Expressions::Float; } [[nodiscard]] auto value() const noexcept { return m_value; } + [[nodiscard]] auto span() const noexcept -> Span override { return m_span; } private: + Span m_span; double m_value; }; + class Bool final : public Expression { public: - Bool(bool value) - : m_value { value } + Bool(Span span, bool value) + : m_span { span } + , m_value { value } { } [[nodiscard]] auto expression_type() const noexcept -> Expressions override { return Expressions::Bool; } [[nodiscard]] auto value() const noexcept { return m_value; } + [[nodiscard]] auto span() const noexcept -> Span override { return m_span; } private: + Span m_span; bool m_value; }; + class String final : public Expression { public: - String(std::string value) - : m_value { std::move(value) } + String(Span span, std::string value) + : m_span { span } + , m_value { std::move(value) } { } [[nodiscard]] auto expression_type() const noexcept -> Expressions override { @@ -116,8 +149,10 @@ public: { return m_value; } + [[nodiscard]] auto span() const noexcept -> Span override { return m_span; } private: + Span m_span; std::string m_value; }; @@ -127,11 +162,25 @@ public: : text { text } , lexer(text) { } - auto parse_expression() noexcept - -> Result, void>; - auto parse_value() noexcept -> Result, void>; + auto parse_expression(bool strictly_values) noexcept + -> Result, Errors>; + auto parse_struct(bool strictly_values) noexcept + -> Result, Errors>; + auto parse_atom() noexcept -> Result, Errors>; private: + [[nodiscard]] static auto parse_string_value( + std::string_view literal) noexcept -> Result; + [[nodiscard]] auto token_text(size_t index, size_t length) const noexcept + -> std::string + { + return std::string { text.substr(index, length) }; + } + [[nodiscard]] static auto token_span(Token from, Token to) noexcept -> Span + { + return { from.span.from, to.span.to }; + } + std::string_view text; Lexer lexer; }; diff --git a/utils/result.hpp b/utils/result.hpp index d34ee47..fdee2f9 100644 --- a/utils/result.hpp +++ b/utils/result.hpp @@ -157,6 +157,15 @@ public: return unwrap(); } + // Transforms `Result` into `Result`. + // Requries result to be an error. + template + [[nodiscard]] constexpr auto transform() const noexcept + -> Result + { + return Result::create_error(unwrap_error()); + } + [[nodiscard]] constexpr auto map(auto func) noexcept { using NewValue = decltype(func(unwrap())); @@ -643,3 +652,12 @@ private: }; } + +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define TRY(expr) \ + ({ \ + auto result = (expr); \ + if (result.is_error()) \ + return { std::move(result.unwrap_error()) }; \ + std::move(result.unwrap()); \ + })