Compare commits

..

No commits in common. "main" and "1dbed201e631d39216e5bbc2ad818558add89575" have entirely different histories.

22 changed files with 227 additions and 369 deletions

1
.gitignore vendored
View file

@ -1,6 +1,5 @@
# Generated by Nix
/.pre-commit-config.yaml
/result
# Meson
/build

View file

@ -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

View file

@ -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)

21
LICENSE
View file

@ -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.

View file

@ -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;
};
};
};
});
}

13
meson.build Normal file
View file

@ -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')

View file

@ -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)

View file

@ -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{};

View file

@ -1,5 +0,0 @@
add_library(bignum STATIC
bignum.cc
bignum.hh
)
target_link_libraries(bignum PRIVATE common_options)

View file

@ -3,7 +3,6 @@
#include <algorithm>
#include <iostream>
#include <iterator>
#include <span>
#include <cassert>
#include <cmath>
@ -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<int>(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);
auto it = it1;
auto end = end1;
if (it1 == end1) {
it = it2;
end = end2;
}
return std::span(it2, 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<digits_type, digits_type> 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<int>(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_);
}
}
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 const overflow = std::find_if(digits_.begin(), digits_.end(),
[](auto v) { return v >= BASE; });
if (has_overflow) {
if (overflow != digits_.end()) {
return false;
}
@ -423,8 +413,8 @@ std::pair<BigNum, BigNum> 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

View file

@ -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;

16
src/bignum/meson.build Normal file
View file

@ -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,
)

15
src/meson.build Normal file
View file

@ -0,0 +1,15 @@
abacus_sources = files(
'abacus.cc',
)
subdir('bignum')
subdir('parse')
abacus = executable(
'abacus',
sources: abacus_sources,
dependencies: [
bignum,
parse,
],
)

View file

@ -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
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
)

48
src/parse/meson.build Normal file
View file

@ -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,
)

View file

@ -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 {

View file

@ -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 {

View file

@ -1 +0,0 @@
add_subdirectory(unit)

1
tests/meson.build Normal file
View file

@ -0,0 +1 @@
subdir('unit')

View file

@ -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})

View file

@ -2,7 +2,7 @@
#include <gtest/gtest.h>
#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);
}

26
tests/unit/meson.build Normal file
View file

@ -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