From 300c112ab45f930067bc414e816af55f263479f0 Mon Sep 17 00:00:00 2001 From: Karn Kaul Date: Thu, 5 Oct 2023 18:05:42 -0700 Subject: [PATCH] Hotifx: cleanup. Add LICENSE and README. Bug and typo fixes. Remove visitor. --- CMakeLists.txt | 2 +- LICENSE | 21 +++++++++++++ README.md | 48 ++++++++++++++++++++++++++++++ examples/quickstart/quickstart.cpp | 4 +-- kalcy/include/kalcy/env.hpp | 2 +- kalcy/include/kalcy/error.hpp | 4 +-- kalcy/include/kalcy/parser.hpp | 17 +++++------ kalcy/include/kalcy/scanner.hpp | 2 +- kalcy/include/kalcy/visitor.hpp | 14 --------- kalcy/src/error.cpp | 2 +- kalcy/src/eval.cpp | 5 ++-- kalcy/src/parser.cpp | 14 ++++++--- tests/tests/test_eval.cpp | 2 +- tests/tests/test_parser.cpp | 2 +- 14 files changed, 98 insertions(+), 41 deletions(-) create mode 100644 LICENSE create mode 100644 README.md delete mode 100644 kalcy/include/kalcy/visitor.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b08b2d4..7a4286a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.24) -project(kalcy VERSION 0.1) +project(kalcy VERSION 0.2) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8a54915 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Karn Kaul and contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..045ad42 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +## Arithmetic expression evaluator + +`kalcy` is a simple expression parsing library built using C++20. In addition to numeric literals and operators, it supports an environment with customizable functions and constants. + +**Sample output** + +``` +./kalcy-quickstart +usage: ./kalcy-quickstart [-v] "" + +./kalcy-quickstart 42 +42 + +./kalcy-quickstart -v "1 + 2 * 3 ^ 2" +19 +expression : 1 + 2 * 3 ^ 2 +AST : (1 + (2 * (3 ^ 2))) + +./kalcy-quickstart -v "-2.5 * sqrt(pi)" +-4.43113 +expression : -2.5 * sqrt(pi) +AST : (-2.5 * sqrt(pi)) +``` + +### Requirements + +- CMake 3.24+ +- C++20 compiler and standard library (+ runtime). + +### CMake + +Acquire the source and add it to your project's build tree via CMake `FetchContent` or `git clone` / `git submodules` + `add_subdirectory`. + +Then link to `kalcy`: + +```cmake +target_link_libraries(your-target PRIVATE kalcy::kalcy) +``` + +All required headers are included in: + +```cpp +#include +``` + +### Quickstart + +Check out [examples/quickstart](examples/quickstart/quickstart.cpp). diff --git a/examples/quickstart/quickstart.cpp b/examples/quickstart/quickstart.cpp index d217a0e..a1ba0ad 100644 --- a/examples/quickstart/quickstart.cpp +++ b/examples/quickstart/quickstart.cpp @@ -9,7 +9,7 @@ namespace { auto run(std::string_view const text, bool verbose) -> bool { try { // parse text into an expression - auto expr = kalcy::Parser{text}.parse(); + auto expr = kalcy::Parser{}.parse(text); assert(expr != nullptr); // evaluate parsed expression // a custom Env can be used and passed, if desired @@ -33,7 +33,7 @@ auto main(int argc, char** argv) -> int { bool verbose{}; // check if verbose - if (args.front() == std::string_view{"-v"}) { + if (!args.empty() && args.front() == std::string_view{"-v"}) { verbose = true; // advance args by 1 args = args.subspan(1); diff --git a/kalcy/include/kalcy/env.hpp b/kalcy/include/kalcy/env.hpp index ac32fed..49c8ebc 100644 --- a/kalcy/include/kalcy/env.hpp +++ b/kalcy/include/kalcy/env.hpp @@ -46,7 +46,7 @@ class Env { /// \returns Return value of invoked function. /// \param name Name of function. /// \param args Arguments to function. - /// \throws UndefinedSymbol if name is not defined as a function. + /// \throws UndefinedSymbol, ArgsMismatch. /// [[nodiscard]] auto invoke(Token name, std::span args) const noexcept(false) -> double; diff --git a/kalcy/include/kalcy/error.hpp b/kalcy/include/kalcy/error.hpp index 9e7dfe4..e6775e7 100644 --- a/kalcy/include/kalcy/error.hpp +++ b/kalcy/include/kalcy/error.hpp @@ -47,7 +47,7 @@ struct ArgsMismatch : Error { explicit ArgsMismatch(Token token, std::size_t argument_count); }; -struct InvalidOperaor : Error { - explicit InvalidOperaor(Token token); +struct InvalidOperator : Error { + explicit InvalidOperator(Token token); }; } // namespace kalcy diff --git a/kalcy/include/kalcy/parser.hpp b/kalcy/include/kalcy/parser.hpp index b59c913..f929d75 100644 --- a/kalcy/include/kalcy/parser.hpp +++ b/kalcy/include/kalcy/parser.hpp @@ -9,21 +9,18 @@ namespace kalcy { class Parser { public: /// - /// \brief Construct a parser for passed text. + /// \brief Parse tokens into an expression. /// \param text text to parse. + /// \returns Parsed expression (or null if no tokens). + /// \throws ParseError. /// /// Caller must ensure the source of text outlives all expressions parsed from it. /// - explicit Parser(std::string_view text); - - /// - /// \brief Parse tokens into an expression. - /// \returns Parsed expression (or null if no tokens). - /// \throws Error on parse error. - /// - auto parse() noexcept(false) -> UExpr; + auto parse(std::string_view text) noexcept(false) -> UExpr; private: + void reset(std::string_view text); + [[nodiscard]] auto expression() -> UExpr; [[nodiscard]] auto sum() -> UExpr; [[nodiscard]] auto product() -> UExpr; @@ -37,7 +34,7 @@ class Parser { auto consume(Token::Type type) noexcept(false) -> void; void advance(); - Scanner m_scanner; + Scanner m_scanner{}; Token m_previous{}; Token m_current{}; diff --git a/kalcy/include/kalcy/scanner.hpp b/kalcy/include/kalcy/scanner.hpp index af766c3..8a87e8b 100644 --- a/kalcy/include/kalcy/scanner.hpp +++ b/kalcy/include/kalcy/scanner.hpp @@ -13,7 +13,7 @@ class Scanner { /// /// Caller must ensure source of text outlives all tokens scanned from it. /// - explicit Scanner(std::string_view text) : m_text(text) {} + explicit Scanner(std::string_view text = {}) : m_text(text) {} /// /// \brief Check if no more tokens remain. diff --git a/kalcy/include/kalcy/visitor.hpp b/kalcy/include/kalcy/visitor.hpp deleted file mode 100644 index d693031..0000000 --- a/kalcy/include/kalcy/visitor.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -namespace kalcy { -/// -/// \brief Overload wrapper for multiple lambdas. -/// -template -struct Visitor : Types... { - using Types::operator()...; -}; - -template -Visitor(Types...) -> Visitor; -} // namespace kalcy diff --git a/kalcy/src/error.cpp b/kalcy/src/error.cpp index 41df862..3b63826 100644 --- a/kalcy/src/error.cpp +++ b/kalcy/src/error.cpp @@ -21,5 +21,5 @@ UndefinedSymbol::UndefinedSymbol(Token const token) : Error(token, std::format(" ArgsMismatch::ArgsMismatch(Token const token, std::size_t argument_count) : Error(token, std::format("{} does not take {} argument(s)", token.lexeme, argument_count)), argument_count(argument_count) {} -InvalidOperaor::InvalidOperaor(Token const token) : Error(token, std::format("invalid operator: '{}'", token.lexeme)) {} +InvalidOperator::InvalidOperator(Token const token) : Error(token, std::format("invalid operator: '{}'", token.lexeme)) {} } // namespace kalcy diff --git a/kalcy/src/eval.cpp b/kalcy/src/eval.cpp index 277dcdd..6e50c6d 100644 --- a/kalcy/src/eval.cpp +++ b/kalcy/src/eval.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -20,7 +19,7 @@ struct Evaluator { } auto operator()(expr::Unary const& unary) const -> double { - if (unary.op.type != Token::Type::eMinus) { throw InvalidOperaor{unary.op}; } + if (unary.op.type != Token::Type::eMinus) { throw InvalidOperator{unary.op}; } return -Evaluator{env}.evaluate(*unary.right); } @@ -37,7 +36,7 @@ struct Evaluator { default: break; } - throw InvalidOperaor{binary.op}; + throw InvalidOperator{binary.op}; } auto operator()(expr::Call const& call) const -> double { diff --git a/kalcy/src/parser.cpp b/kalcy/src/parser.cpp index 3740365..1d4d649 100644 --- a/kalcy/src/parser.cpp +++ b/kalcy/src/parser.cpp @@ -4,9 +4,8 @@ #include namespace kalcy { -Parser::Parser(std::string_view text) : m_scanner(text), m_current(m_scanner.next()), m_next(m_scanner.next()) {} - -auto Parser::parse() noexcept(false) -> UExpr { +auto Parser::parse(std::string_view text) noexcept(false) -> UExpr { + reset(text); if (!m_current) { return {}; } auto ret = expression(); if (m_current || m_next) { @@ -16,6 +15,13 @@ auto Parser::parse() noexcept(false) -> UExpr { return ret; } +void Parser::reset(std::string_view const text) { + m_scanner = Scanner{text}; + m_current = m_scanner.next(); + m_next = m_scanner.next(); + m_previous = {}; +} + auto Parser::expression() -> UExpr { return sum(); } auto Parser::sum() -> UExpr { @@ -23,7 +29,7 @@ auto Parser::sum() -> UExpr { while (match(Token::Type::ePlus, Token::Type::eMinus)) { auto const token = m_previous; - auto rhs = expression(); + auto rhs = product(); ret = std::make_unique(expr::Binary{.left = std::move(ret), .op = token, .right = std::move(rhs)}); } diff --git a/tests/tests/test_eval.cpp b/tests/tests/test_eval.cpp index 08527ea..8ea9cc7 100644 --- a/tests/tests/test_eval.cpp +++ b/tests/tests/test_eval.cpp @@ -7,7 +7,7 @@ namespace { using namespace kalcy; -auto make_expr(std::string_view text) -> UExpr { return Parser{text}.parse(); } +auto make_expr(std::string_view text) -> UExpr { return Parser{}.parse(text); } ADD_TEST(Evaluate) { auto const env = Env{}; diff --git a/tests/tests/test_parser.cpp b/tests/tests/test_parser.cpp index 108d4ae..c6a62b6 100644 --- a/tests/tests/test_parser.cpp +++ b/tests/tests/test_parser.cpp @@ -6,7 +6,7 @@ namespace { using namespace kalcy; -auto parse(std::string_view const text) -> UExpr { return Parser{text}.parse(); } +auto parse(std::string_view const text) -> UExpr { return Parser{}.parse(text); } ADD_TEST(ParseEmptyString) { EXPECT(parse("") == nullptr); }