diff --git a/.gitignore b/.gitignore index 9a65324..8a15123 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ # Generated by Nix /.pre-commit-config.yaml -/result # Meson /build diff --git a/.woodpecker/check.yml b/.woodpecker/check.yml deleted file mode 100644 index 0901624..0000000 --- a/.woodpecker/check.yml +++ /dev/null @@ -1,31 +0,0 @@ -labels: - type: exec - -steps: -- name: flake check - image: bash - commands: - - nix flake check - -- name: package check - image: bash - commands: - - nix build - -- name: notify - image: bash - secrets: - - source: matrix_roomid - target: room - - source: matrix_username - target: user - - source: matrix_password - target: pass - - source: matrix_homeserver - target: address - commands: - - nix run github:ambroisie/matrix-notifier - when: - status: - - failure - - success diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 7d72bf3..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -cmake_minimum_required(VERSION 3.10) -project(abacus VERSION 0.0.0 LANGUAGES CXX) -enable_testing() - -add_library(common_options INTERFACE) -target_compile_features(common_options INTERFACE - cxx_std_20 -) -target_compile_options(common_options INTERFACE - -Wall - -Wextra -) -target_include_directories(common_options INTERFACE - src -) - -add_subdirectory(src) -add_subdirectory(tests) diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 8aa2645..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) [year] [fullname] - -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/flake.nix b/flake.nix index a697e6b..f5b2bcf 100644 --- a/flake.nix +++ b/flake.nix @@ -29,40 +29,9 @@ }; outputs = { self, futils, nixpkgs, pre-commit-hooks }: - { - overlays.default = final: prev: { - abacus = final.stdenv.mkDerivation { - pname = "abacus"; - version = "0.0.0"; - - src = self; - - nativeBuildInputs = with final; [ - bison - cmake - flex - ninja - pkg-config - ]; - - checkInputs = with final; [ - gtest - ]; - - doCheck = true; - - meta = with final.lib; { - description = "A simple calculator using big numbers"; - homepage = "https://gitea.belanyi.fr/ambroisie/abacus"; - license = licenses.mit; - maintainers = [ ambroisie ]; - platforms = platforms.unix; - }; - }; - }; - } // futils.lib.eachDefaultSystem (system: + futils.lib.eachDefaultSystem (system: let - pkgs = import nixpkgs { inherit system; overlays = [ self.overlays.default ]; }; + pkgs = import nixpkgs { inherit system; }; in { checks = { @@ -87,17 +56,44 @@ }; }; - devShells.default = pkgs.mkShell { - inputsFrom = with self.packages.${system}; [ - abacus - ]; + defaultPackage = self.packages.${system}.abacus; + + devShell = pkgs.mkShell { + inherit (self.defaultPackage.${system}) + buildInputs + nativeBuildInputs + ; inherit (self.checks.${system}.pre-commit) shellHook; }; packages = futils.lib.flattenTree { - inherit (pkgs) abacus; - default = pkgs.abacus; + abacus = pkgs.stdenv.mkDerivation { + pname = "abacus"; + version = "0.0.0"; + + src = self; + + nativeBuildInputs = with pkgs; [ + bison + flex + meson + ninja + pkg-config + ]; + + buildInputs = with pkgs; [ + gtest + ]; + + meta = with pkgs.lib; { + description = "A simple calculator using big numbers"; + homepage = "https://gitea.belanyi.fr/ambroisie/abacus"; + license = licenses.mit; + maintainers = [ ambroisie ]; + platforms = platforms.unix; + }; + }; }; }); } diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..4910f6a --- /dev/null +++ b/meson.build @@ -0,0 +1,13 @@ +project( + 'abacus', + 'cpp', + version: '0.0.0', + license: 'MIT', + default_options: [ + 'warning_level=3', + 'cpp_std=c++17', + ], +) + +subdir('src') +subdir('tests') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt deleted file mode 100644 index 531ca47..0000000 --- a/src/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -add_executable(abacus abacus.cc) -target_link_libraries(abacus PRIVATE common_options) - -add_subdirectory(bignum) -add_subdirectory(parse) - -target_link_libraries(abacus PRIVATE - bignum - parse -) - -install(TARGETS abacus) diff --git a/src/abacus.cc b/src/abacus.cc index 45d2394..3e735a0 100644 --- a/src/abacus.cc +++ b/src/abacus.cc @@ -1,4 +1,4 @@ -#include "parse/parser-driver.hh" +#include "parser-driver.hh" // FIXME: I would like `parse/parser-driver.hh` path instead... int main() { abacus::parse::ParserDriver driver{}; diff --git a/src/bignum/CMakeLists.txt b/src/bignum/CMakeLists.txt deleted file mode 100644 index 6b64eb6..0000000 --- a/src/bignum/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -add_library(bignum STATIC - bignum.cc - bignum.hh -) -target_link_libraries(bignum PRIVATE common_options) diff --git a/src/bignum/bignum.cc b/src/bignum/bignum.cc index f38159f..c3588a9 100644 --- a/src/bignum/bignum.cc +++ b/src/bignum/bignum.cc @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -25,46 +24,12 @@ bool do_less_than(digits_type const& lhs, digits_type const& rhs) { rhs.rend()); } -void trim_leading_zeros(digits_type& num) { +void do_trim_leading_zeros(digits_type& num) { auto const it = std::find_if(num.rbegin(), num.rend(), [](auto v) { return v != 0; }); num.erase(it.base(), num.end()); } -std::ostream& do_dump(digits_type const& num, std::ostream& out) { - std::copy(num.rbegin(), num.rend(), std::ostream_iterator(out)); - return out; -} - -// More optimised than full-on div_mod -digits_type do_halve(digits_type num) { - assert(num.size() != 0); - - int carry = 0; - for (auto i = num.rbegin(); i != num.rend(); ++i) { - auto const was_odd = (*i % 2) == 1; - *i /= 2; - *i += carry; - if (was_odd) { - carry = BASE / 2; - } else { - carry = 0; - } - } - - trim_leading_zeros(num); - - return num; -} - -bool is_odd(digits_type const& num) { - if (num.size() == 0) { - return false; - } - - return (num.front() % 2) == 1; -} - digits_type do_addition(digits_type const& lhs, digits_type const& rhs) { int carry = 0; digits_type res; @@ -83,17 +48,18 @@ digits_type do_addition(digits_type const& lhs, digits_type const& rhs) { ++it2; } - auto leftover = [=]() { - if (it1 != end1) { - return std::span(it1, end1); - } - return std::span(it2, end2); - }(); + auto it = it1; + auto end = end1; + if (it1 == end1) { + it = it2; + end = end2; + } - for (auto value : leftover) { - int addition = value + carry; + while (it != end) { + int addition = *it + carry; carry = addition / BASE; res.push_back(addition % BASE); + ++it; } if (carry != 0) { @@ -107,7 +73,7 @@ digits_type do_substraction(digits_type const& lhs, digits_type const& rhs) { assert(!do_less_than(lhs, rhs)); digits_type complement; - auto const take_complement = [](auto num) { return BASE - 1 - num; }; + auto const take_complement = [](auto num) { return 9 - num; }; std::transform(lhs.begin(), lhs.end(), std::back_inserter(complement), take_complement); @@ -116,7 +82,7 @@ digits_type do_substraction(digits_type const& lhs, digits_type const& rhs) { std::transform(complement.begin(), complement.end(), complement.begin(), take_complement); - trim_leading_zeros(complement); + do_trim_leading_zeros(complement); return complement; } @@ -144,33 +110,59 @@ std::pair do_div_mod(digits_type const& lhs, throw std::invalid_argument("attempt to divide by zero"); } - digits_type multiple = rhs; - digits_type rank; - rank.push_back(1); - - while (!do_less_than(lhs, multiple)) { - multiple = do_addition(multiple, multiple); - rank = do_addition(rank, rank); - } - digits_type quotient; digits_type remainder = lhs; while (!do_less_than(remainder, rhs)) { - while (do_less_than(remainder, multiple)) { - multiple = do_halve(multiple); - rank = do_halve(rank); + // TODO: use `do_halve` to back down after calculate highest multiple + digits_type multiple = rhs; + digits_type rank; + rank.push_back(1); + + digits_type prev_multiple = multiple; + digits_type prev_rank = rank; + + while (!do_less_than(remainder, multiple)) { + prev_multiple = multiple; + prev_rank = rank; + multiple = do_addition(multiple, multiple); + rank = do_addition(rank, rank); } - assert(!do_less_than(multiple, rhs)); - - quotient = do_addition(quotient, rank); - remainder = do_substraction(remainder, multiple); + quotient = do_addition(quotient, prev_rank); + remainder = do_substraction(remainder, prev_multiple); } return std::make_pair(quotient, remainder); } +// More optimised than full-on div_mod +digits_type do_halve(digits_type num) { + assert(num.size() != 0); + + int carry = 0; + for (auto i = num.rbegin(); i != num.rend(); ++i) { + auto const was_odd = (*i % 2) == 1; + *i /= 2; + *i += carry; + if (was_odd) { + carry = BASE / 2; + } + } + + do_trim_leading_zeros(num); + + return num; +} + +bool is_odd(digits_type const& num) { + if (num.size() == 0) { + return false; + } + + return (num.front() % 2) == 1; +} + digits_type do_pow(digits_type lhs, digits_type rhs) { assert(rhs.size() != 0); @@ -234,8 +226,10 @@ std::ostream& BigNum::dump(std::ostream& out) const { if (is_negative()) { out << '-'; } + std::copy(digits_.rbegin(), digits_.rend(), + std::ostream_iterator(out)); - return do_dump(digits_, out); + return out; } std::istream& BigNum::read(std::istream& in) { @@ -363,15 +357,11 @@ bool BigNum::less_than(BigNum const& rhs) const { return sign_ < rhs.sign_; } - if (is_positive()) { - return do_less_than(digits_, rhs.digits_); - } else { - return do_less_than(rhs.digits_, digits_); - } + return do_less_than(digits_, rhs.digits_); } void BigNum::canonicalize() { - trim_leading_zeros(digits_); + do_trim_leading_zeros(digits_); if (digits_.size() == 0) { sign_ = 0; @@ -385,15 +375,15 @@ bool BigNum::is_canonicalized() const { return sign_ == 0; } - // `back` is valid since there is at least one element - auto const has_leading_zero = digits_.back() == 0; - if (has_leading_zero) { + auto const leading_zeros = std::find_if(digits_.rbegin(), digits_.rend(), + [](auto v) { return v != 0; }); + if (leading_zeros != digits_.rbegin()) { return false; } - auto const has_overflow = std::any_of(digits_.begin(), digits_.end(), - [](auto v) { return v >= BASE; }); - if (has_overflow) { + auto const overflow = std::find_if(digits_.begin(), digits_.end(), + [](auto v) { return v >= BASE; }); + if (overflow != digits_.end()) { return false; } @@ -423,8 +413,8 @@ std::pair div_mod(BigNum const& lhs, BigNum const& rhs) { return std::make_pair(BigNum(), BigNum()); } - auto quotient = BigNum(0); - auto remainder = BigNum(0); + BigNum quotient; + BigNum remainder; std::tie(quotient.digits_, remainder.digits_) = do_div_mod(lhs.digits_, rhs.digits_); @@ -449,7 +439,7 @@ BigNum pow(BigNum const& lhs, BigNum const& rhs) { return BigNum(); } - auto res = BigNum(0); + BigNum res; res.digits_ = do_pow(lhs.digits_, rhs.digits_); res.sign_ = is_odd(rhs.digits_) ? lhs.sign_ : 1; @@ -468,7 +458,7 @@ BigNum sqrt(BigNum const& num) { "attempt to take the square root of a negative number"); } - auto res = BigNum(0); + BigNum res; res.digits_ = do_sqrt(num.digits_); res.sign_ = 1; @@ -478,46 +468,4 @@ BigNum sqrt(BigNum const& num) { return res; } -BigNum log2(BigNum const& num) { - assert(num.is_canonicalized()); - - if (num.is_zero()) { - throw std::invalid_argument("attempt to take the log2 of zero"); - } else if (num.is_negative()) { - throw std::invalid_argument( - "attempt to take the log2 of a negative number"); - } - - auto tmp = num; - auto res = BigNum(0); - auto one = BigNum(1); - - while (tmp > one) { - tmp.digits_ = do_halve(tmp.digits_); - res += one; - } - - assert(res.is_canonicalized()); - - return res; -} - -BigNum log10(BigNum const& num) { - assert(num.is_canonicalized()); - assert(BASE == 10); - - if (num.is_zero()) { - throw std::invalid_argument("attempt to take the log10 of zero"); - } else if (num.is_negative()) { - throw std::invalid_argument( - "attempt to take the log10 of a negative number"); - } - - auto res = BigNum(num.digits_.size() - 1); - - assert(res.is_canonicalized()); - - return res; -} - } // namespace abacus::bignum diff --git a/src/bignum/bignum.hh b/src/bignum/bignum.hh index 6a7a009..1880af0 100644 --- a/src/bignum/bignum.hh +++ b/src/bignum/bignum.hh @@ -91,10 +91,6 @@ public: friend BigNum sqrt(BigNum const& num); - friend BigNum log2(BigNum const& num); - - friend BigNum log10(BigNum const& num); - friend bool operator==(BigNum const& lhs, BigNum const& rhs) { return lhs.equal(rhs); } @@ -119,10 +115,6 @@ public: return !(lhs < rhs); } - explicit operator bool() { - return !is_zero(); - } - bool is_zero() const; bool is_positive() const; bool is_negative() const; diff --git a/src/bignum/meson.build b/src/bignum/meson.build new file mode 100644 index 0000000..7346d21 --- /dev/null +++ b/src/bignum/meson.build @@ -0,0 +1,16 @@ +bignum_sources = files( + 'bignum.cc', + 'bignum.hh', +) + +bignum_inc = include_directories('.') + +bignum_lib = library( + 'bignum', + sources: bignum_sources, +) + +bignum = declare_dependency( + link_with: bignum_lib, + include_directories: bignum_inc, +) diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 0000000..182500e --- /dev/null +++ b/src/meson.build @@ -0,0 +1,15 @@ +abacus_sources = files( + 'abacus.cc', +) + +subdir('bignum') +subdir('parse') + +abacus = executable( + 'abacus', + sources: abacus_sources, + dependencies: [ + bignum, + parse, + ], +) diff --git a/src/parse/CMakeLists.txt b/src/parse/CMakeLists.txt deleted file mode 100644 index 22a706c..0000000 --- a/src/parse/CMakeLists.txt +++ /dev/null @@ -1,23 +0,0 @@ -find_package(BISON REQUIRED) -find_package(FLEX REQUIRED) - -bison_target(parser_sources parser.yy ${CMAKE_CURRENT_BINARY_DIR}/parser.cc DEFINES_FILE ${CMAKE_CURRENT_BINARY_DIR}/parser.hh) -flex_target(scanner_sources scanner.ll ${CMAKE_CURRENT_BINARY_DIR}/scanner.cc) -add_flex_bison_dependency(scanner_sources parser_sources) - -add_library(parse STATIC - parser-driver.cc - parser-driver.hh - ${BISON_parser_sources_OUTPUTS} - ${FLEX_scanner_sources_OUTPUTS} -) -target_link_libraries(parse PRIVATE common_options) - -target_link_libraries(parse PRIVATE - bignum -) - -target_include_directories(parse PUBLIC - $ - $ -) diff --git a/src/parse/meson.build b/src/parse/meson.build new file mode 100644 index 0000000..254d868 --- /dev/null +++ b/src/parse/meson.build @@ -0,0 +1,48 @@ +flex_binary = find_program('flex', required: true) +lexer_sources = custom_target( + 'lexer_sources', + input: 'scanner.ll', + output: 'scanner.cc', + command: [ + flex_binary, + '-o', + '@OUTPUT@', + '@INPUT@', + ], +) + +bison_binary = find_program('bison', required: true) +parser_sources = custom_target( + 'parser_sources', + input: 'parser.yy', + output: [ + 'parser.cc', + 'parser.hh', + ], + command: [ + bison_binary, + '@INPUT@', + '--output=@OUTPUT0@', + '--defines=@OUTPUT1@', + '--graph', + # FIXME: html output in bison 3.7.90 + ], +) + +parse_inc = include_directories('.') + +parse_lib = library( + 'parser', + 'parser-driver.cc', + 'parser-driver.hh', + lexer_sources, + parser_sources, + dependencies: [ + bignum, + ], +) + +parse = declare_dependency( + link_with: parse_lib, + include_directories: parse_inc, +) diff --git a/src/parse/parser-driver.hh b/src/parse/parser-driver.hh index ad88efd..d5ba7ea 100644 --- a/src/parse/parser-driver.hh +++ b/src/parse/parser-driver.hh @@ -4,7 +4,7 @@ #include "parser.hh" -#include "bignum/bignum.hh" +#include "bignum.hh" // FIXME: I would like `bignum/bignum.hh` path instead... namespace abacus::parse { diff --git a/src/parse/parser.yy b/src/parse/parser.yy index 07e3e3a..d8fbc87 100644 --- a/src/parse/parser.yy +++ b/src/parse/parser.yy @@ -36,7 +36,7 @@ namespace abacus::parse { class ParserDriver; } // namespace abacus::parse -#include "bignum/bignum.hh" +#include "bignum.hh" // FIXME: I would like `bignum/bignum.hh` path instead... } %code provides { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt deleted file mode 100644 index 269aea0..0000000 --- a/tests/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -add_subdirectory(unit) diff --git a/tests/meson.build b/tests/meson.build new file mode 100644 index 0000000..082b746 --- /dev/null +++ b/tests/meson.build @@ -0,0 +1 @@ +subdir('unit') diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt deleted file mode 100644 index bdf51ae..0000000 --- a/tests/unit/CMakeLists.txt +++ /dev/null @@ -1,16 +0,0 @@ -find_package(GTest) - -if (${GTest_FOUND}) -include(GoogleTest) - -add_executable(bignum_test bignum.cc) -target_link_libraries(bignum_test PRIVATE common_options) - -target_link_libraries(bignum_test PRIVATE - bignum - GTest::gtest - GTest::gtest_main -) - -gtest_discover_tests(bignum_test) -endif (${GTest_FOUND}) diff --git a/tests/unit/bignum.cc b/tests/unit/bignum.cc index 1126a17..8040ed0 100644 --- a/tests/unit/bignum.cc +++ b/tests/unit/bignum.cc @@ -2,7 +2,7 @@ #include -#include "bignum/bignum.hh" +#include "bignum.hh" using namespace abacus::bignum; @@ -68,34 +68,6 @@ TEST(BigNum, comparisons_digits) { EXPECT_GT(ten, nine); } -TEST(BigNum, comparisons_negative) { - auto const zero = BigNum(0); - auto const minus_one = BigNum(-1); - auto const minus_two = BigNum(-2); - - EXPECT_LT(minus_one, zero); - EXPECT_LE(minus_one, zero); - EXPECT_LT(minus_two, minus_one); - EXPECT_LE(minus_two, minus_one); - EXPECT_LE(minus_one, minus_one); - EXPECT_GE(minus_two, minus_two); - - EXPECT_GT(zero, minus_one); - EXPECT_GE(zero, minus_one); - EXPECT_GT(minus_one, minus_two); - EXPECT_GE(minus_one, minus_two); - EXPECT_GE(minus_one, minus_one); - EXPECT_GE(minus_two, minus_two); -} - -TEST(BigNum, comparisons_digits_negative) { - auto const minus_nine = BigNum(-9); - auto const minus_ten = BigNum(-10); - - EXPECT_LT(minus_ten, minus_nine); - EXPECT_GT(minus_nine, minus_ten); -} - TEST(BigNum, unary) { auto const zero = BigNum(0); auto const one = BigNum(1); @@ -404,44 +376,3 @@ TEST(BigNum, sqrt) { EXPECT_EQ(sqrt(ninety_nine), nine); EXPECT_EQ(sqrt(hundred), ten); } - -TEST(BigNum, log2) { - auto const zero = BigNum(0); - auto const one = BigNum(1); - auto const two = BigNum(2); - auto const three = BigNum(3); - auto const four = BigNum(4); - auto const five = BigNum(5); - auto const seven = BigNum(7); - auto const eight = BigNum(8); - auto const nine = BigNum(9); - - EXPECT_EQ(log2(one), zero); - EXPECT_EQ(log2(two), one); - EXPECT_EQ(log2(three), one); - EXPECT_EQ(log2(four), two); - EXPECT_EQ(log2(five), two); - EXPECT_EQ(log2(seven), two); - EXPECT_EQ(log2(eight), three); - EXPECT_EQ(log2(nine), three); -} - -TEST(BigNum, log10) { - auto const zero = BigNum(0); - auto const one = BigNum(1); - auto const two = BigNum(2); - auto const nine = BigNum(9); - auto const ten = BigNum(10); - auto const eleven = BigNum(11); - auto const ninety_nine = BigNum(99); - auto const hundred = BigNum(100); - auto const hundred_one = BigNum(101); - - EXPECT_EQ(log10(one), zero); - EXPECT_EQ(log10(nine), zero); - EXPECT_EQ(log10(ten), one); - EXPECT_EQ(log10(eleven), one); - EXPECT_EQ(log10(ninety_nine), one); - EXPECT_EQ(log10(hundred), two); - EXPECT_EQ(log10(hundred_one), two); -} diff --git a/tests/unit/meson.build b/tests/unit/meson.build new file mode 100644 index 0000000..db8eddd --- /dev/null +++ b/tests/unit/meson.build @@ -0,0 +1,26 @@ +gtest = dependency( + 'gtest', + main: true, + required: false, +) + +if gtest.found() + unit_test_sources = files( + 'bignum.cc', + ) + + unit_tests = executable( + 'unit_tests', + sources: unit_test_sources, + dependencies: [ + bignum, + gtest, + ], + ) + + test( + 'unit tests', + unit_tests, + protocol: 'gtest', + ) +endif