├── .github ├── dependabot.yml └── workflows │ ├── ci-version.yml │ └── ci.yml ├── Cargo.toml ├── tests └── bc.rs ├── LICENSE ├── README.md ├── rustfmt.toml ├── .gitignore └── src └── lib.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bc" 3 | version = "0.1.16" 4 | authors = ["Magic Len "] 5 | edition = "2021" 6 | rust-version = "1.80" 7 | repository = "https://github.com/magiclen/bc" 8 | homepage = "https://magiclen.org/bc" 9 | keywords = ["bc", "timeout", "math", "parse", "equation"] 10 | categories = ["science"] 11 | description = "Use `bc` in the Rust Programming Language." 12 | license = "MIT" 13 | include = ["src/**/*", "Cargo.toml", "README.md", "LICENSE"] 14 | 15 | [dependencies] 16 | execute = "0.2" 17 | 18 | [dev-dependencies] 19 | assert-eq-float = "0.1" -------------------------------------------------------------------------------- /.github/workflows/ci-version.yml: -------------------------------------------------------------------------------- 1 | name: CI-version 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | 11 | jobs: 12 | tests: 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | os: 17 | - ubuntu-latest 18 | toolchain: 19 | - "1.80" 20 | - stable 21 | - nightly 22 | features: 23 | - 24 | name: Test ${{ matrix.toolchain }} on ${{ matrix.os }} (${{ matrix.features }}) 25 | runs-on: ${{ matrix.os }} 26 | steps: 27 | - uses: actions/checkout@v6 28 | - uses: actions-rust-lang/setup-rust-toolchain@v1 29 | with: 30 | toolchain: ${{ matrix.toolchain }} 31 | - run: cargo test --release ${{ matrix.features }} 32 | - run: cargo doc --release ${{ matrix.features }} -------------------------------------------------------------------------------- /tests/bc.rs: -------------------------------------------------------------------------------- 1 | use assert_eq_float::assert_eq_float; 2 | use bc::{bc, bc_timeout}; 3 | 4 | #[test] 5 | fn add() { 6 | let result = bc!("1+1").unwrap(); 7 | 8 | assert_eq!(2, result.parse::().unwrap()); 9 | } 10 | 11 | #[test] 12 | fn sub() { 13 | let result = bc!("2-1").unwrap(); 14 | 15 | assert_eq!(1, result.parse::().unwrap()); 16 | } 17 | 18 | #[test] 19 | fn multiply() { 20 | let result = bc!("2*3").unwrap(); 21 | 22 | assert_eq!(6, result.parse::().unwrap()); 23 | } 24 | 25 | #[test] 26 | fn divide() { 27 | let result = bc!("5/2").unwrap(); 28 | 29 | assert_eq_float!(2.5, result.parse::().unwrap()); 30 | } 31 | 32 | #[test] 33 | fn power() { 34 | let result = bc!("2^100").unwrap(); 35 | 36 | assert_eq!(1_267_650_600_228_229_401_496_703_205_376, result.parse::().unwrap()); 37 | } 38 | 39 | #[test] 40 | #[should_panic(expected = "called `Result::unwrap()` on an `Err` value: Timeout")] 41 | fn power_extreme() { 42 | bc_timeout!(1, "99999^99999").unwrap(); 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 magiclen.org (Ron Li) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | bc (An arbitrary precision calculator language) 2 | ==================== 3 | 4 | [![CI](https://github.com/magiclen/bc/actions/workflows/ci.yml/badge.svg)](https://github.com/magiclen/bc/actions/workflows/ci.yml) 5 | 6 | Use `bc` in the Rust Programming Language. 7 | 8 | ## Examples 9 | 10 | ```rust 11 | let result = bc::bc!("2 + 6"); 12 | 13 | assert_eq!("8", result.unwrap()); 14 | ``` 15 | 16 | ```rust 17 | let result = bc::bc!("2.5 + 6"); 18 | 19 | assert_eq!("8.5", result.unwrap()); 20 | ``` 21 | 22 | ```rust 23 | let result = bc::bc_timeout!("99^99"); 24 | 25 | assert_eq!("369729637649726772657187905628805440595668764281741102430259972423552570455277523421410650010128232727940978889548326540119429996769494359451621570193644014418071060667659301384999779999159200499899", result.unwrap()); 26 | ``` 27 | 28 | ```rust 29 | let result = bc::bc_timeout!(20, "99^99"); 30 | 31 | assert_eq!("369729637649726772657187905628805440595668764281741102430259972423552570455277523421410650010128232727940978889548326540119429996769494359451621570193644014418071060667659301384999779999159200499899", result.unwrap()); 32 | ``` 33 | 34 | ## Crates.io 35 | 36 | https://crates.io/crates/bc 37 | 38 | ## Documentation 39 | 40 | https://docs.rs/bc 41 | 42 | ## License 43 | 44 | [MIT](LICENSE) -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [ push, pull_request ] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | rustfmt: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v6 13 | - uses: actions-rust-lang/setup-rust-toolchain@v1 14 | with: 15 | toolchain: nightly 16 | components: rustfmt 17 | - uses: actions-rust-lang/rustfmt@v1 18 | 19 | clippy: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v6 23 | - uses: actions-rust-lang/setup-rust-toolchain@v1 24 | with: 25 | components: clippy 26 | - run: cargo clippy --all-targets --all-features -- -D warnings 27 | 28 | tests: 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | os: 33 | - ubuntu-latest 34 | toolchain: 35 | - "1.80" 36 | - stable 37 | - nightly 38 | features: 39 | - 40 | name: Test ${{ matrix.toolchain }} on ${{ matrix.os }} (${{ matrix.features }}) 41 | runs-on: ${{ matrix.os }} 42 | steps: 43 | - uses: actions/checkout@v6 44 | - uses: actions-rust-lang/setup-rust-toolchain@v1 45 | with: 46 | toolchain: ${{ matrix.toolchain }} 47 | - run: cargo test ${{ matrix.features }} 48 | - run: cargo doc ${{ matrix.features }} -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # array_width = 60 2 | # attr_fn_like_width = 70 3 | binop_separator = "Front" 4 | blank_lines_lower_bound = 0 5 | blank_lines_upper_bound = 1 6 | brace_style = "PreferSameLine" 7 | # chain_width = 60 8 | color = "Auto" 9 | # comment_width = 100 10 | condense_wildcard_suffixes = true 11 | control_brace_style = "AlwaysSameLine" 12 | empty_item_single_line = true 13 | enum_discrim_align_threshold = 80 14 | error_on_line_overflow = false 15 | error_on_unformatted = false 16 | # fn_call_width = 60 17 | fn_params_layout = "Tall" 18 | fn_single_line = false 19 | force_explicit_abi = true 20 | force_multiline_blocks = false 21 | format_code_in_doc_comments = true 22 | doc_comment_code_block_width = 80 23 | format_generated_files = true 24 | format_macro_matchers = true 25 | format_macro_bodies = true 26 | skip_macro_invocations = [] 27 | format_strings = true 28 | hard_tabs = false 29 | hex_literal_case = "Upper" 30 | imports_indent = "Block" 31 | imports_layout = "Mixed" 32 | indent_style = "Block" 33 | inline_attribute_width = 0 34 | match_arm_blocks = true 35 | match_arm_leading_pipes = "Never" 36 | match_block_trailing_comma = true 37 | max_width = 100 38 | merge_derives = true 39 | imports_granularity = "Crate" 40 | newline_style = "Unix" 41 | normalize_comments = false 42 | normalize_doc_attributes = true 43 | overflow_delimited_expr = true 44 | remove_nested_parens = true 45 | reorder_impl_items = true 46 | reorder_imports = true 47 | group_imports = "StdExternalCrate" 48 | reorder_modules = true 49 | short_array_element_width_threshold = 10 50 | # single_line_if_else_max_width = 50 51 | space_after_colon = true 52 | space_before_colon = false 53 | spaces_around_ranges = false 54 | struct_field_align_threshold = 80 55 | struct_lit_single_line = false 56 | # struct_lit_width = 18 57 | # struct_variant_width = 35 58 | tab_spaces = 4 59 | trailing_comma = "Vertical" 60 | trailing_semicolon = true 61 | type_punctuation_density = "Wide" 62 | use_field_init_shorthand = true 63 | use_small_heuristics = "Max" 64 | use_try_shorthand = true 65 | where_single_line = false 66 | wrap_comments = false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Intellij+all ### 2 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 3 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 4 | 5 | # User-specific stuff 6 | .idea/**/workspace.xml 7 | .idea/**/tasks.xml 8 | .idea/**/usage.statistics.xml 9 | .idea/**/dictionaries 10 | .idea/**/shelf 11 | 12 | # AWS User-specific 13 | .idea/**/aws.xml 14 | 15 | # Generated files 16 | .idea/**/contentModel.xml 17 | 18 | # Sensitive or high-churn files 19 | .idea/**/dataSources/ 20 | .idea/**/dataSources.ids 21 | .idea/**/dataSources.local.xml 22 | .idea/**/sqlDataSources.xml 23 | .idea/**/dynamic.xml 24 | .idea/**/uiDesigner.xml 25 | .idea/**/dbnavigator.xml 26 | 27 | # Gradle 28 | .idea/**/gradle.xml 29 | .idea/**/libraries 30 | 31 | # Gradle and Maven with auto-import 32 | # When using Gradle or Maven with auto-import, you should exclude module files, 33 | # since they will be recreated, and may cause churn. Uncomment if using 34 | # auto-import. 35 | # .idea/artifacts 36 | # .idea/compiler.xml 37 | # .idea/jarRepositories.xml 38 | # .idea/modules.xml 39 | # .idea/*.iml 40 | # .idea/modules 41 | # *.iml 42 | # *.ipr 43 | 44 | # CMake 45 | cmake-build-*/ 46 | 47 | # Mongo Explorer plugin 48 | .idea/**/mongoSettings.xml 49 | 50 | # File-based project format 51 | *.iws 52 | 53 | # IntelliJ 54 | out/ 55 | 56 | # mpeltonen/sbt-idea plugin 57 | .idea_modules/ 58 | 59 | # JIRA plugin 60 | atlassian-ide-plugin.xml 61 | 62 | # Cursive Clojure plugin 63 | .idea/replstate.xml 64 | 65 | # SonarLint plugin 66 | .idea/sonarlint/ 67 | 68 | # Crashlytics plugin (for Android Studio and IntelliJ) 69 | com_crashlytics_export_strings.xml 70 | crashlytics.properties 71 | crashlytics-build.properties 72 | fabric.properties 73 | 74 | # Editor-based Rest Client 75 | .idea/httpRequests 76 | 77 | # Android studio 3.1+ serialized cache file 78 | .idea/caches/build_file_checksums.ser 79 | 80 | ### Intellij+all Patch ### 81 | # Ignore everything but code style settings and run configurations 82 | # that are supposed to be shared within teams. 83 | 84 | .idea/* 85 | 86 | !.idea/codeStyles 87 | !.idea/runConfigurations 88 | 89 | ### Rust ### 90 | # Generated by Cargo 91 | # will have compiled files and executables 92 | debug/ 93 | target/ 94 | 95 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 96 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 97 | Cargo.lock 98 | 99 | # These are backup files generated by rustfmt 100 | **/*.rs.bk 101 | 102 | # MSVC Windows builds of rustc generate these, which store debugging information 103 | *.pdb 104 | 105 | ### Vim ### 106 | # Swap 107 | [._]*.s[a-v][a-z] 108 | !*.svg # comment out if you don't need vector files 109 | [._]*.sw[a-p] 110 | [._]s[a-rt-v][a-z] 111 | [._]ss[a-gi-z] 112 | [._]sw[a-p] 113 | 114 | # Session 115 | Session.vim 116 | Sessionx.vim 117 | 118 | # Temporary 119 | .netrwhist 120 | *~ 121 | # Auto-generated tag files 122 | tags 123 | # Persistent undo 124 | [._]*.un~ 125 | 126 | ### VisualStudioCode ### 127 | .vscode/* 128 | !.vscode/settings.json 129 | !.vscode/tasks.json 130 | !.vscode/launch.json 131 | !.vscode/extensions.json 132 | !.vscode/*.code-snippets 133 | 134 | # Local History for Visual Studio Code 135 | .history/ 136 | 137 | # Built Visual Studio Code Extensions 138 | *.vsix 139 | 140 | ### VisualStudioCode Patch ### 141 | # Ignore all local history of files 142 | .history 143 | .ionide -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | # bc (An arbitrary precision calculator language) 3 | 4 | Use `bc` in the Rust Programming Language. 5 | 6 | ## Examples 7 | 8 | ```rust 9 | let result = bc::bc!("2 + 6"); 10 | 11 | assert_eq!("8", result.unwrap()); 12 | ``` 13 | 14 | ```rust 15 | let result = bc::bc!("2.5 + 6"); 16 | 17 | assert_eq!("8.5", result.unwrap()); 18 | ``` 19 | 20 | ```rust 21 | let result = bc::bc_timeout!("99^99"); 22 | 23 | assert_eq!("369729637649726772657187905628805440595668764281741102430259972423552570455277523421410650010128232727940978889548326540119429996769494359451621570193644014418071060667659301384999779999159200499899", result.unwrap()); 24 | ``` 25 | 26 | ```rust 27 | let result = bc::bc_timeout!(20, "99^99"); 28 | 29 | assert_eq!("369729637649726772657187905628805440595668764281741102430259972423552570455277523421410650010128232727940978889548326540119429996769494359451621570193644014418071060667659301384999779999159200499899", result.unwrap()); 30 | ``` 31 | */ 32 | 33 | use std::{ 34 | error::Error, 35 | fmt::{Display, Error as FmtError, Formatter}, 36 | io, 37 | path::Path, 38 | process::Stdio, 39 | }; 40 | 41 | use execute::{command_args, Execute}; 42 | 43 | #[derive(Debug)] 44 | pub enum BCError { 45 | IOError(io::Error), 46 | NoResult, 47 | Timeout, 48 | /// Maybe it is a syntax error. 49 | Error(String), 50 | } 51 | 52 | impl From for BCError { 53 | #[inline] 54 | fn from(err: io::Error) -> Self { 55 | BCError::IOError(err) 56 | } 57 | } 58 | 59 | impl From for BCError { 60 | #[inline] 61 | fn from(err: String) -> Self { 62 | BCError::Error(err) 63 | } 64 | } 65 | 66 | impl Display for BCError { 67 | #[inline] 68 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { 69 | match self { 70 | BCError::IOError(err) => Display::fmt(err, f), 71 | BCError::NoResult => f.write_str("There is no result of BC."), 72 | BCError::Timeout => f.write_str("The BC calculation has timed out."), 73 | BCError::Error(text) => f.write_str(text.as_str()), 74 | } 75 | } 76 | } 77 | 78 | impl Error for BCError {} 79 | 80 | /// Call `bc`. 81 | pub fn bc, S: AsRef>(bc_path: P, statement: S) -> Result { 82 | let mut command = command_args!(bc_path.as_ref(), "-l", "-q"); 83 | 84 | command.stdout(Stdio::piped()); 85 | command.stderr(Stdio::piped()); 86 | 87 | let output = command.execute_input_output(format!("{}\n", statement.as_ref()).as_bytes())?; 88 | 89 | if output.stderr.is_empty() { 90 | if output.stdout.is_empty() { 91 | Err(BCError::NoResult) 92 | } else { 93 | Ok(handle_output(unsafe { String::from_utf8_unchecked(output.stdout) })) 94 | } 95 | } else { 96 | Err(BCError::Error(handle_output(unsafe { String::from_utf8_unchecked(output.stderr) }))) 97 | } 98 | } 99 | 100 | /// Call `bc` with `timeout`. 101 | pub fn bc_timeout, P: AsRef, S: AsRef>( 102 | timeout_path: PT, 103 | timeout_secs: u32, 104 | bc_path: P, 105 | statement: S, 106 | ) -> Result { 107 | let mut command = command_args!( 108 | timeout_path.as_ref(), 109 | format!("{}s", timeout_secs), 110 | bc_path.as_ref(), 111 | "-l", 112 | "-q" 113 | ); 114 | 115 | command.stdout(Stdio::piped()); 116 | command.stderr(Stdio::piped()); 117 | 118 | let output = command.execute_input_output(format!("{}\n", statement.as_ref()).as_bytes())?; 119 | 120 | if let Some(code) = output.status.code() { 121 | if code == 124 { 122 | return Err(BCError::Timeout); 123 | } 124 | } 125 | 126 | if output.stderr.is_empty() { 127 | if output.stdout.is_empty() { 128 | Err(BCError::NoResult) 129 | } else { 130 | Ok(handle_output(unsafe { String::from_utf8_unchecked(output.stdout) })) 131 | } 132 | } else { 133 | Err(BCError::Error(handle_output(unsafe { String::from_utf8_unchecked(output.stderr) }))) 134 | } 135 | } 136 | 137 | fn handle_output(mut output: String) -> String { 138 | let length = output.len(); 139 | 140 | unsafe { 141 | output.as_mut_vec().set_len(length - 1); 142 | } 143 | 144 | match output.find("\\\n") { 145 | Some(index) => { 146 | let mut s = String::from(&output[..index]); 147 | 148 | s.push_str(&output[(index + 2)..].replace("\\\n", "")); 149 | 150 | s 151 | }, 152 | None => output, 153 | } 154 | } 155 | 156 | /// Call `bc`. 157 | #[macro_export] 158 | macro_rules! bc { 159 | ($statement:expr) => { 160 | $crate::bc("bc", $statement) 161 | }; 162 | ($bc_path:expr, $statement:expr) => { 163 | $crate::bc($bc_path, $statement) 164 | }; 165 | } 166 | 167 | /// Call `bc` with `timeout`. 168 | #[macro_export] 169 | macro_rules! bc_timeout { 170 | ($statement:expr) => { 171 | $crate::bc_timeout("timeout", 15, "bc", $statement) 172 | }; 173 | ($timeout:expr, $statement:expr) => { 174 | $crate::bc_timeout("timeout", $timeout, "bc", $statement) 175 | }; 176 | ($timeout_path:expr, $timeout:expr, $bc_path:expr, $statement:expr) => { 177 | $crate::bc_timeout($timeout_path, $timeout, $bc_path, $statement) 178 | }; 179 | } 180 | --------------------------------------------------------------------------------