├── .github └── workflows │ ├── clippy.yml │ ├── release.yml │ └── rust.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── src ├── ast.rs ├── lib.rs ├── main.rs ├── parser.lalrpop └── types │ ├── f64.rs │ ├── i64.rs │ ├── mod.rs │ └── u64.rs └── tests ├── README.md ├── expression_mode.rs └── shell_mode.rs /.github/workflows/clippy.yml: -------------------------------------------------------------------------------- 1 | on: push 2 | name: Clippy check 3 | jobs: 4 | clippy_check: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v1 8 | - run: rustup component add clippy 9 | - uses: actions-rs/clippy-check@v1 10 | with: 11 | token: ${{ secrets.GITHUB_TOKEN }} 12 | args: --all-features 13 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | release: 3 | types: [created] 4 | 5 | name: Release 6 | 7 | defaults: 8 | run: 9 | shell: bash 10 | 11 | jobs: 12 | build-release-binaries: 13 | name: Build binaries for release 14 | continue-on-error: true 15 | 16 | strategy: 17 | matrix: 18 | os: [ubuntu-latest, windows-latest, macos-latest] 19 | 20 | runs-on: "${{ matrix.os }}" 21 | steps: 22 | - uses: actions/checkout@v2 23 | - uses: actions/cache@v2 24 | with: 25 | path: | 26 | ~/.cargo/registry 27 | ~/.cargo/git 28 | target 29 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 30 | - name: build 31 | run: RUSTFLAGS='-C link-arg=-s' cargo build --release 32 | - name: identify the artifact and name it per the os 33 | run: | 34 | set -e -x 35 | bin=target/release/calc 36 | archive="$(printf 'calc-%s.tar.bz2' "$(uname | tr '[:upper:]' '[:lower:]')")" 37 | if [ ! -f "$bin" ]; then 38 | bin="$bin.exe" 39 | fi 40 | if [ ! -f "$bin" ]; then 41 | echo "could not locate compiled executable" 42 | exit 1 43 | fi 44 | mv "$bin" . 45 | bin="$(basename "$bin")" 46 | file "$bin" 47 | 48 | if [[ "$archive" == *mingw* ]]; then 49 | # in this case, the file doesn't end with .exe, but we're still building for windows 50 | # let's fix that 51 | mv "$bin" "$bin.exe" 52 | bin="$bin.exe" 53 | archive=calc-win.tar.bz2 54 | fi 55 | 56 | tar -cvjf "$archive" "$bin" 57 | echo "artifact=$archive" >> "$GITHUB_ENV" 58 | 59 | ls -lh "$bin" "$archive" 60 | - uses: actions/upload-artifact@v2 61 | with: 62 | name: "${{ env.artifact }}" 63 | path: "${{ env.artifact }}" 64 | 65 | upload-release-binaries: 66 | name: Add binaries to release 67 | if: always() 68 | needs: build-release-binaries 69 | continue-on-error: true 70 | 71 | strategy: 72 | matrix: 73 | artifact: [calc-linux.tar.bz2, calc-darwin.tar.bz2, calc-win.tar.bz2] 74 | 75 | runs-on: ubuntu-latest 76 | 77 | steps: 78 | - uses: actions/download-artifact@v4.1.7 79 | with: 80 | name: "${{ matrix.artifact }}" 81 | - name: Add to release 82 | uses: skx/github-action-publish-binaries@master 83 | env: 84 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 85 | with: 86 | args: "${{ matrix.artifact }}" 87 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/cache@v2 20 | with: 21 | path: | 22 | ~/.cargo/registry 23 | ~/.cargo/git 24 | target 25 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 26 | - name: Check 27 | run: cargo check --verbose 28 | - name: Run tests 29 | run: cargo test --verbose 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .vscode/ 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [0.4.0] - 2022-08-07 4 | 5 | Fix #14: operations with the `@` operator were being applied twice. 6 | 7 | ## [0.3.0] - 2021-02-24 8 | 9 | Enable output formatting. 10 | 11 | ## [0.2.1] - 2021-02-12 12 | 13 | Update installation instructions, create changelog. 14 | 15 | ## [0.2.0] - 2021-02-12 16 | 17 | Take over the crate name from a squatter. Publish the MVP. 18 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anstream" 16 | version = "0.6.18" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 19 | dependencies = [ 20 | "anstyle", 21 | "anstyle-parse", 22 | "anstyle-query", 23 | "anstyle-wincon", 24 | "colorchoice", 25 | "is_terminal_polyfill", 26 | "utf8parse", 27 | ] 28 | 29 | [[package]] 30 | name = "anstyle" 31 | version = "1.0.10" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 34 | 35 | [[package]] 36 | name = "anstyle-parse" 37 | version = "0.2.6" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 40 | dependencies = [ 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-query" 46 | version = "1.1.2" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 49 | dependencies = [ 50 | "windows-sys 0.59.0", 51 | ] 52 | 53 | [[package]] 54 | name = "anstyle-wincon" 55 | version = "3.0.6" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" 58 | dependencies = [ 59 | "anstyle", 60 | "windows-sys 0.59.0", 61 | ] 62 | 63 | [[package]] 64 | name = "anyhow" 65 | version = "1.0.94" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" 68 | 69 | [[package]] 70 | name = "ascii-canvas" 71 | version = "3.0.0" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" 74 | dependencies = [ 75 | "term", 76 | ] 77 | 78 | [[package]] 79 | name = "autocfg" 80 | version = "1.4.0" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 83 | 84 | [[package]] 85 | name = "bit-set" 86 | version = "0.5.3" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" 89 | dependencies = [ 90 | "bit-vec", 91 | ] 92 | 93 | [[package]] 94 | name = "bit-vec" 95 | version = "0.6.3" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" 98 | 99 | [[package]] 100 | name = "bitflags" 101 | version = "1.3.2" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 104 | 105 | [[package]] 106 | name = "bitflags" 107 | version = "2.6.0" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 110 | 111 | [[package]] 112 | name = "calc" 113 | version = "0.4.1" 114 | dependencies = [ 115 | "anyhow", 116 | "clap", 117 | "lalrpop", 118 | "lalrpop-util", 119 | "lazy_static", 120 | "num-runtime-fmt", 121 | "regex", 122 | "rustyline", 123 | "thiserror", 124 | ] 125 | 126 | [[package]] 127 | name = "cfg-if" 128 | version = "1.0.0" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 131 | 132 | [[package]] 133 | name = "clap" 134 | version = "4.5.23" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" 137 | dependencies = [ 138 | "clap_builder", 139 | "clap_derive", 140 | ] 141 | 142 | [[package]] 143 | name = "clap_builder" 144 | version = "4.5.23" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" 147 | dependencies = [ 148 | "anstream", 149 | "anstyle", 150 | "clap_lex", 151 | "strsim", 152 | ] 153 | 154 | [[package]] 155 | name = "clap_derive" 156 | version = "4.5.18" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 159 | dependencies = [ 160 | "heck", 161 | "proc-macro2", 162 | "quote", 163 | "syn", 164 | ] 165 | 166 | [[package]] 167 | name = "clap_lex" 168 | version = "0.7.4" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 171 | 172 | [[package]] 173 | name = "clipboard-win" 174 | version = "4.5.0" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362" 177 | dependencies = [ 178 | "error-code", 179 | "str-buf", 180 | "winapi", 181 | ] 182 | 183 | [[package]] 184 | name = "colorchoice" 185 | version = "1.0.3" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 188 | 189 | [[package]] 190 | name = "crunchy" 191 | version = "0.2.2" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" 194 | 195 | [[package]] 196 | name = "dirs-next" 197 | version = "2.0.0" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" 200 | dependencies = [ 201 | "cfg-if", 202 | "dirs-sys-next", 203 | ] 204 | 205 | [[package]] 206 | name = "dirs-sys-next" 207 | version = "0.1.2" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" 210 | dependencies = [ 211 | "libc", 212 | "redox_users", 213 | "winapi", 214 | ] 215 | 216 | [[package]] 217 | name = "either" 218 | version = "1.13.0" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 221 | 222 | [[package]] 223 | name = "ena" 224 | version = "0.14.3" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" 227 | dependencies = [ 228 | "log", 229 | ] 230 | 231 | [[package]] 232 | name = "endian-type" 233 | version = "0.1.2" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" 236 | 237 | [[package]] 238 | name = "equivalent" 239 | version = "1.0.1" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 242 | 243 | [[package]] 244 | name = "errno" 245 | version = "0.3.10" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" 248 | dependencies = [ 249 | "libc", 250 | "windows-sys 0.59.0", 251 | ] 252 | 253 | [[package]] 254 | name = "error-code" 255 | version = "2.3.1" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" 258 | dependencies = [ 259 | "libc", 260 | "str-buf", 261 | ] 262 | 263 | [[package]] 264 | name = "fd-lock" 265 | version = "3.0.13" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5" 268 | dependencies = [ 269 | "cfg-if", 270 | "rustix", 271 | "windows-sys 0.48.0", 272 | ] 273 | 274 | [[package]] 275 | name = "fixedbitset" 276 | version = "0.4.2" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" 279 | 280 | [[package]] 281 | name = "getrandom" 282 | version = "0.2.15" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 285 | dependencies = [ 286 | "cfg-if", 287 | "libc", 288 | "wasi", 289 | ] 290 | 291 | [[package]] 292 | name = "hashbrown" 293 | version = "0.15.2" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 296 | 297 | [[package]] 298 | name = "heck" 299 | version = "0.5.0" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 302 | 303 | [[package]] 304 | name = "home" 305 | version = "0.5.9" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" 308 | dependencies = [ 309 | "windows-sys 0.52.0", 310 | ] 311 | 312 | [[package]] 313 | name = "indexmap" 314 | version = "2.7.0" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" 317 | dependencies = [ 318 | "equivalent", 319 | "hashbrown", 320 | ] 321 | 322 | [[package]] 323 | name = "is_terminal_polyfill" 324 | version = "1.70.1" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 327 | 328 | [[package]] 329 | name = "iterext" 330 | version = "0.1.0" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "e76a40b972b250590567018fd753f44adedc270994fb7be37e4d8c20764bab49" 333 | dependencies = [ 334 | "itertools 0.10.5", 335 | ] 336 | 337 | [[package]] 338 | name = "itertools" 339 | version = "0.10.5" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 342 | dependencies = [ 343 | "either", 344 | ] 345 | 346 | [[package]] 347 | name = "itertools" 348 | version = "0.11.0" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" 351 | dependencies = [ 352 | "either", 353 | ] 354 | 355 | [[package]] 356 | name = "lalrpop" 357 | version = "0.20.2" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" 360 | dependencies = [ 361 | "ascii-canvas", 362 | "bit-set", 363 | "ena", 364 | "itertools 0.11.0", 365 | "lalrpop-util", 366 | "petgraph", 367 | "pico-args", 368 | "regex", 369 | "regex-syntax", 370 | "string_cache", 371 | "term", 372 | "tiny-keccak", 373 | "unicode-xid", 374 | "walkdir", 375 | ] 376 | 377 | [[package]] 378 | name = "lalrpop-util" 379 | version = "0.20.2" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" 382 | dependencies = [ 383 | "regex-automata", 384 | ] 385 | 386 | [[package]] 387 | name = "lazy_static" 388 | version = "1.5.0" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 391 | 392 | [[package]] 393 | name = "libc" 394 | version = "0.2.168" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" 397 | 398 | [[package]] 399 | name = "libredox" 400 | version = "0.1.3" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 403 | dependencies = [ 404 | "bitflags 2.6.0", 405 | "libc", 406 | ] 407 | 408 | [[package]] 409 | name = "linux-raw-sys" 410 | version = "0.4.14" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 413 | 414 | [[package]] 415 | name = "lock_api" 416 | version = "0.4.12" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 419 | dependencies = [ 420 | "autocfg", 421 | "scopeguard", 422 | ] 423 | 424 | [[package]] 425 | name = "log" 426 | version = "0.4.22" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 429 | 430 | [[package]] 431 | name = "memchr" 432 | version = "2.7.4" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 435 | 436 | [[package]] 437 | name = "new_debug_unreachable" 438 | version = "1.0.6" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" 441 | 442 | [[package]] 443 | name = "nibble_vec" 444 | version = "0.1.0" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" 447 | dependencies = [ 448 | "smallvec", 449 | ] 450 | 451 | [[package]] 452 | name = "nix" 453 | version = "0.26.4" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" 456 | dependencies = [ 457 | "bitflags 1.3.2", 458 | "cfg-if", 459 | "libc", 460 | ] 461 | 462 | [[package]] 463 | name = "num-runtime-fmt" 464 | version = "0.1.2" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "68a3aa892c8728aa98afcb19dcfab4bab012ad88c2fade0717564db3f521344e" 467 | dependencies = [ 468 | "iterext", 469 | "lazy_static", 470 | "regex", 471 | "thiserror", 472 | ] 473 | 474 | [[package]] 475 | name = "once_cell" 476 | version = "1.20.2" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 479 | 480 | [[package]] 481 | name = "parking_lot" 482 | version = "0.12.3" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 485 | dependencies = [ 486 | "lock_api", 487 | "parking_lot_core", 488 | ] 489 | 490 | [[package]] 491 | name = "parking_lot_core" 492 | version = "0.9.10" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 495 | dependencies = [ 496 | "cfg-if", 497 | "libc", 498 | "redox_syscall", 499 | "smallvec", 500 | "windows-targets 0.52.6", 501 | ] 502 | 503 | [[package]] 504 | name = "petgraph" 505 | version = "0.6.5" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" 508 | dependencies = [ 509 | "fixedbitset", 510 | "indexmap", 511 | ] 512 | 513 | [[package]] 514 | name = "phf_shared" 515 | version = "0.10.0" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" 518 | dependencies = [ 519 | "siphasher", 520 | ] 521 | 522 | [[package]] 523 | name = "pico-args" 524 | version = "0.5.0" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" 527 | 528 | [[package]] 529 | name = "precomputed-hash" 530 | version = "0.1.1" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" 533 | 534 | [[package]] 535 | name = "proc-macro2" 536 | version = "1.0.92" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" 539 | dependencies = [ 540 | "unicode-ident", 541 | ] 542 | 543 | [[package]] 544 | name = "quote" 545 | version = "1.0.37" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 548 | dependencies = [ 549 | "proc-macro2", 550 | ] 551 | 552 | [[package]] 553 | name = "radix_trie" 554 | version = "0.2.1" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" 557 | dependencies = [ 558 | "endian-type", 559 | "nibble_vec", 560 | ] 561 | 562 | [[package]] 563 | name = "redox_syscall" 564 | version = "0.5.7" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" 567 | dependencies = [ 568 | "bitflags 2.6.0", 569 | ] 570 | 571 | [[package]] 572 | name = "redox_users" 573 | version = "0.4.6" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" 576 | dependencies = [ 577 | "getrandom", 578 | "libredox", 579 | "thiserror", 580 | ] 581 | 582 | [[package]] 583 | name = "regex" 584 | version = "1.11.1" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 587 | dependencies = [ 588 | "aho-corasick", 589 | "memchr", 590 | "regex-automata", 591 | "regex-syntax", 592 | ] 593 | 594 | [[package]] 595 | name = "regex-automata" 596 | version = "0.4.9" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 599 | dependencies = [ 600 | "aho-corasick", 601 | "memchr", 602 | "regex-syntax", 603 | ] 604 | 605 | [[package]] 606 | name = "regex-syntax" 607 | version = "0.8.5" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 610 | 611 | [[package]] 612 | name = "rustix" 613 | version = "0.38.42" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" 616 | dependencies = [ 617 | "bitflags 2.6.0", 618 | "errno", 619 | "libc", 620 | "linux-raw-sys", 621 | "windows-sys 0.59.0", 622 | ] 623 | 624 | [[package]] 625 | name = "rustversion" 626 | version = "1.0.18" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" 629 | 630 | [[package]] 631 | name = "rustyline" 632 | version = "12.0.0" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "994eca4bca05c87e86e15d90fc7a91d1be64b4482b38cb2d27474568fe7c9db9" 635 | dependencies = [ 636 | "bitflags 2.6.0", 637 | "cfg-if", 638 | "clipboard-win", 639 | "fd-lock", 640 | "home", 641 | "libc", 642 | "log", 643 | "memchr", 644 | "nix", 645 | "radix_trie", 646 | "scopeguard", 647 | "unicode-segmentation", 648 | "unicode-width", 649 | "utf8parse", 650 | "winapi", 651 | ] 652 | 653 | [[package]] 654 | name = "same-file" 655 | version = "1.0.6" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 658 | dependencies = [ 659 | "winapi-util", 660 | ] 661 | 662 | [[package]] 663 | name = "scopeguard" 664 | version = "1.2.0" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 667 | 668 | [[package]] 669 | name = "siphasher" 670 | version = "0.3.11" 671 | source = "registry+https://github.com/rust-lang/crates.io-index" 672 | checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" 673 | 674 | [[package]] 675 | name = "smallvec" 676 | version = "1.13.2" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 679 | 680 | [[package]] 681 | name = "str-buf" 682 | version = "1.0.6" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" 685 | 686 | [[package]] 687 | name = "string_cache" 688 | version = "0.8.7" 689 | source = "registry+https://github.com/rust-lang/crates.io-index" 690 | checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" 691 | dependencies = [ 692 | "new_debug_unreachable", 693 | "once_cell", 694 | "parking_lot", 695 | "phf_shared", 696 | "precomputed-hash", 697 | ] 698 | 699 | [[package]] 700 | name = "strsim" 701 | version = "0.11.1" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 704 | 705 | [[package]] 706 | name = "syn" 707 | version = "2.0.90" 708 | source = "registry+https://github.com/rust-lang/crates.io-index" 709 | checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" 710 | dependencies = [ 711 | "proc-macro2", 712 | "quote", 713 | "unicode-ident", 714 | ] 715 | 716 | [[package]] 717 | name = "term" 718 | version = "0.7.0" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" 721 | dependencies = [ 722 | "dirs-next", 723 | "rustversion", 724 | "winapi", 725 | ] 726 | 727 | [[package]] 728 | name = "thiserror" 729 | version = "1.0.69" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 732 | dependencies = [ 733 | "thiserror-impl", 734 | ] 735 | 736 | [[package]] 737 | name = "thiserror-impl" 738 | version = "1.0.69" 739 | source = "registry+https://github.com/rust-lang/crates.io-index" 740 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 741 | dependencies = [ 742 | "proc-macro2", 743 | "quote", 744 | "syn", 745 | ] 746 | 747 | [[package]] 748 | name = "tiny-keccak" 749 | version = "2.0.2" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" 752 | dependencies = [ 753 | "crunchy", 754 | ] 755 | 756 | [[package]] 757 | name = "unicode-ident" 758 | version = "1.0.14" 759 | source = "registry+https://github.com/rust-lang/crates.io-index" 760 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 761 | 762 | [[package]] 763 | name = "unicode-segmentation" 764 | version = "1.12.0" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 767 | 768 | [[package]] 769 | name = "unicode-width" 770 | version = "0.1.14" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 773 | 774 | [[package]] 775 | name = "unicode-xid" 776 | version = "0.2.6" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" 779 | 780 | [[package]] 781 | name = "utf8parse" 782 | version = "0.2.2" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 785 | 786 | [[package]] 787 | name = "walkdir" 788 | version = "2.5.0" 789 | source = "registry+https://github.com/rust-lang/crates.io-index" 790 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 791 | dependencies = [ 792 | "same-file", 793 | "winapi-util", 794 | ] 795 | 796 | [[package]] 797 | name = "wasi" 798 | version = "0.11.0+wasi-snapshot-preview1" 799 | source = "registry+https://github.com/rust-lang/crates.io-index" 800 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 801 | 802 | [[package]] 803 | name = "winapi" 804 | version = "0.3.9" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 807 | dependencies = [ 808 | "winapi-i686-pc-windows-gnu", 809 | "winapi-x86_64-pc-windows-gnu", 810 | ] 811 | 812 | [[package]] 813 | name = "winapi-i686-pc-windows-gnu" 814 | version = "0.4.0" 815 | source = "registry+https://github.com/rust-lang/crates.io-index" 816 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 817 | 818 | [[package]] 819 | name = "winapi-util" 820 | version = "0.1.9" 821 | source = "registry+https://github.com/rust-lang/crates.io-index" 822 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 823 | dependencies = [ 824 | "windows-sys 0.59.0", 825 | ] 826 | 827 | [[package]] 828 | name = "winapi-x86_64-pc-windows-gnu" 829 | version = "0.4.0" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 832 | 833 | [[package]] 834 | name = "windows-sys" 835 | version = "0.48.0" 836 | source = "registry+https://github.com/rust-lang/crates.io-index" 837 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 838 | dependencies = [ 839 | "windows-targets 0.48.5", 840 | ] 841 | 842 | [[package]] 843 | name = "windows-sys" 844 | version = "0.52.0" 845 | source = "registry+https://github.com/rust-lang/crates.io-index" 846 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 847 | dependencies = [ 848 | "windows-targets 0.52.6", 849 | ] 850 | 851 | [[package]] 852 | name = "windows-sys" 853 | version = "0.59.0" 854 | source = "registry+https://github.com/rust-lang/crates.io-index" 855 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 856 | dependencies = [ 857 | "windows-targets 0.52.6", 858 | ] 859 | 860 | [[package]] 861 | name = "windows-targets" 862 | version = "0.48.5" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 865 | dependencies = [ 866 | "windows_aarch64_gnullvm 0.48.5", 867 | "windows_aarch64_msvc 0.48.5", 868 | "windows_i686_gnu 0.48.5", 869 | "windows_i686_msvc 0.48.5", 870 | "windows_x86_64_gnu 0.48.5", 871 | "windows_x86_64_gnullvm 0.48.5", 872 | "windows_x86_64_msvc 0.48.5", 873 | ] 874 | 875 | [[package]] 876 | name = "windows-targets" 877 | version = "0.52.6" 878 | source = "registry+https://github.com/rust-lang/crates.io-index" 879 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 880 | dependencies = [ 881 | "windows_aarch64_gnullvm 0.52.6", 882 | "windows_aarch64_msvc 0.52.6", 883 | "windows_i686_gnu 0.52.6", 884 | "windows_i686_gnullvm", 885 | "windows_i686_msvc 0.52.6", 886 | "windows_x86_64_gnu 0.52.6", 887 | "windows_x86_64_gnullvm 0.52.6", 888 | "windows_x86_64_msvc 0.52.6", 889 | ] 890 | 891 | [[package]] 892 | name = "windows_aarch64_gnullvm" 893 | version = "0.48.5" 894 | source = "registry+https://github.com/rust-lang/crates.io-index" 895 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 896 | 897 | [[package]] 898 | name = "windows_aarch64_gnullvm" 899 | version = "0.52.6" 900 | source = "registry+https://github.com/rust-lang/crates.io-index" 901 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 902 | 903 | [[package]] 904 | name = "windows_aarch64_msvc" 905 | version = "0.48.5" 906 | source = "registry+https://github.com/rust-lang/crates.io-index" 907 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 908 | 909 | [[package]] 910 | name = "windows_aarch64_msvc" 911 | version = "0.52.6" 912 | source = "registry+https://github.com/rust-lang/crates.io-index" 913 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 914 | 915 | [[package]] 916 | name = "windows_i686_gnu" 917 | version = "0.48.5" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 920 | 921 | [[package]] 922 | name = "windows_i686_gnu" 923 | version = "0.52.6" 924 | source = "registry+https://github.com/rust-lang/crates.io-index" 925 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 926 | 927 | [[package]] 928 | name = "windows_i686_gnullvm" 929 | version = "0.52.6" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 932 | 933 | [[package]] 934 | name = "windows_i686_msvc" 935 | version = "0.48.5" 936 | source = "registry+https://github.com/rust-lang/crates.io-index" 937 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 938 | 939 | [[package]] 940 | name = "windows_i686_msvc" 941 | version = "0.52.6" 942 | source = "registry+https://github.com/rust-lang/crates.io-index" 943 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 944 | 945 | [[package]] 946 | name = "windows_x86_64_gnu" 947 | version = "0.48.5" 948 | source = "registry+https://github.com/rust-lang/crates.io-index" 949 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 950 | 951 | [[package]] 952 | name = "windows_x86_64_gnu" 953 | version = "0.52.6" 954 | source = "registry+https://github.com/rust-lang/crates.io-index" 955 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 956 | 957 | [[package]] 958 | name = "windows_x86_64_gnullvm" 959 | version = "0.48.5" 960 | source = "registry+https://github.com/rust-lang/crates.io-index" 961 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 962 | 963 | [[package]] 964 | name = "windows_x86_64_gnullvm" 965 | version = "0.52.6" 966 | source = "registry+https://github.com/rust-lang/crates.io-index" 967 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 968 | 969 | [[package]] 970 | name = "windows_x86_64_msvc" 971 | version = "0.48.5" 972 | source = "registry+https://github.com/rust-lang/crates.io-index" 973 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 974 | 975 | [[package]] 976 | name = "windows_x86_64_msvc" 977 | version = "0.52.6" 978 | source = "registry+https://github.com/rust-lang/crates.io-index" 979 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 980 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calc" 3 | version = "0.4.1" 4 | authors = ["Peter Goodspeed-Niklaus "] 5 | edition = "2021" 6 | description = "CLI calculator app" 7 | license = "GPL-3.0-only" 8 | repository = "https://github.com/coriolinus/calc" 9 | categories = [ 10 | "command-line-interface", 11 | "command-line-utilities", 12 | "mathematics", 13 | ] 14 | keywords = ["calculator", "cli"] 15 | 16 | [dependencies] 17 | anyhow = { version = "1.0.94", optional = true } 18 | clap = { version = "4.5.23", features = ["derive"], optional = true } 19 | lalrpop-util = { version = "0.20.2", features = ["lexer"] } 20 | lazy_static = "1.5.0" 21 | num-runtime-fmt = "0.1" 22 | regex = "1.11.1" 23 | rustyline = { version = "12.0.0", optional = true } 24 | thiserror = "1.0.69" 25 | 26 | [build-dependencies] 27 | lalrpop = "0.20.2" 28 | 29 | [features] 30 | default = ["cli"] 31 | cli = [ 32 | "anyhow", 33 | "clap", 34 | "rustyline", 35 | ] 36 | 37 | [[bin]] 38 | name = "calc" 39 | required-features = ["cli"] 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `calc` 2 | 3 | [![Build and Test Status](https://github.com/coriolinus/calc/workflows/Build%20and%20Test/badge.svg?branch=main)](https://github.com/coriolinus/calc/actions?query=branch%3Amain+workflow%3A%22Build+and+Test%22) 4 | 5 | Yet another CLI calculator. Inspired by the excellent . 6 | 7 | ## Installation 8 | 9 | With a Rust toolchain in place: 10 | 11 | ```sh 12 | cargo install --force calc 13 | ``` 14 | 15 | Alternately, you can download a precompiled executable of the most recent [release](https://github.com/coriolinus/calc/releases). 16 | 17 | ## Usage 18 | 19 | ### Expression Mode 20 | 21 | ```sh 22 | $ calc "1/(2+(3*(4-5)))" 23 | -1 24 | $ calc "round(12345 / 543)" 25 | 23 26 | ``` 27 | 28 | When non-flag arguments are present, `calc` interprets them as an expression and evaluates them immediately. 29 | 30 | ### Shell Mode 31 | 32 | ```sh 33 | $ calc 34 | [0]: 1 + 1 35 | 2 36 | [1]: 3*(5/(3-4)) 37 | -15 38 | [2]: 3*pi**2 39 | 29.608813203268074 40 | [3]: @+1 41 | 30.608813203268074 42 | [4]: @@@*2 43 | -30 44 | [5]: ln(-1) 45 | NaN 46 | ``` 47 | 48 | In the absence of non-flag arguments, `calc` launches a simple shell which just evaluates each line of input. 49 | 50 | ## Reference 51 | 52 | ### Data Types 53 | 54 | Every invocation of `calc` interprets all arguments as a single data type. By default, `calc` uses `f64`, but other data types 55 | can be chosen by command-line flag: 56 | 57 | - `f64` (default): signed 64-bit floating point operations 58 | - `u64`: unsigned 64-bit integer operations 59 | - `i64`: signed 64-bit integer operations 60 | 61 | Note that the data type chosen will restrict the available operators, functions, and constants. For example, trigonometric operations 62 | are not available on integers, and bit-shifting operations are not available on floats. 63 | 64 | ### Numeric Input Format 65 | 66 | Numbers may contain `_` characters at any point. Those symbols are ignored; they are for user convenience and readability only. 67 | 68 | `calc` can handle inputs in several numeric bases. 69 | 70 | - Un-annotated numbers are assumed to be base 10. Example: `123.45`. 71 | 72 | Note: this is the only format which is legal for non-integral numbers. 73 | 74 | - Numbers with a `0b` prefix are in base 2. Example: `0b0110_1010`. 75 | - Numbers with a `0o` prefix are in base 8. Example: `0o755`. 76 | 77 | Note: a leading `0` is not in itself an octal prefix. Example: `0755` equals `0d755`. 78 | 79 | - Numbers with a `0d` prefix are in base 10. Example: `1234_5678`. 80 | - Numbers with a `0x` prefix are in base 16. Example: `0xdead_beef`. 81 | 82 | It is legal to intermix inputs of varying bases. 83 | 84 | ### Numeric Output Format 85 | 86 | The output format of an expression can be specified by adding a `:` symbol followed by a format 87 | specifier to the expression. 88 | 89 | The format specifier can be anything recognized by the [`num-runtime-fmt` crate](https://docs.rs/num-runtime-fmt/0.1.1/num_runtime_fmt/#format-string-grammar). 90 | 91 | ```sh 92 | $ calc -u "0o644 | 0o111 :#o" 93 | 0o755 94 | $ calc -u '0o755 & !0o111 :04o' 95 | 0644 96 | ``` 97 | 98 | ``` 99 | [0]: 0xab :b 4 100 | 1010 1011 101 | [1]: @[0] >>> 4 :x_4 102 | b000_0000_0000_000a 103 | [2]: @ & 0xF :4b 104 | 1010 105 | ``` 106 | 107 | ``` 108 | $ calc pi / 3 :v#04.4 109 | 0d01.0471 110 | ``` 111 | 112 | ### Order of Operations 113 | 114 | The following order of operations is used to resolve expressions: 115 | 116 | - Parentheses (`(...)`) 117 | - Unary Prefix Operators (`-` `!`) 118 | - Shifts and Exponentiation (`<<` `>>` `<<<` `>>>` `**`) 119 | - Bitwise operations (`&` `|` `^`) 120 | - Multiplication and Division (`*` `/` `//` `%`) 121 | - Addition and Subtraction (`+` `-`) 122 | 123 | Operations at the same level of precedence are resolved from left to right. 124 | 125 | ### Unary Prefix Operators 126 | 127 | - `-`: Negation 128 | - `!`: Bitwise Not 129 | 130 | ### Infix Operators 131 | 132 | - `+`: Addition 133 | - `-`: Subtraction 134 | - `*`: Multiplication 135 | - `/`: Division 136 | - `//`: Truncating Division: divides, truncating all data after the decimal point. 137 | - `**`: Exponentiation 138 | - `%` : Arithmetic remainder 139 | - `<<`: Left Shift 140 | - `>>`: Right Shift 141 | - `<<<`: Wrapping Left Shift (Rotate Left) 142 | - `>>>`: Wrappping Right Shift (Rotate Right) 143 | - `&`: Bitwise And 144 | - `|`: Bitwise Or 145 | - `^`: Bitwise Xor 146 | 147 | ### Functions 148 | 149 | - `abs`: Absolute Value 150 | - `ceil`: Smallest integer greater than or equal to the input 151 | - `floor`: Greatest integer less than or equal to the input 152 | - `round`: Nearest integer to the input; halfway cases away from 0.0 153 | - `sin`: Sine 154 | - `cos`: Cosine 155 | - `tan`: Tangent 156 | - `sinh`: Hyperbolic Sine 157 | - `cosh`: Hyperbolic Cosine 158 | - `tanh`: Hyperbolic Tangent 159 | - `asin`: Arcine 160 | - `acos`: Arccosine 161 | - `atan`: Arctangent 162 | - `asinh`: Inverse Hyperbolic Sine 163 | - `acosh`: Inverse Hyperbolic Cosine 164 | - `atanh`: Inverse Hyperbolic Tangent 165 | - `rad`: Convert a number in degrees to radians 166 | - `dec`: Convert a number in radians to degrees 167 | - `sqrt`: Square Root 168 | - `cbrt`: Cube Root 169 | - `log`: Base-10 Logarithm 170 | - `lg`: Base-2 Logarithm 171 | - `ln`: Natural (Base-e) Logarithm 172 | - `exp`: `e**x` 173 | 174 | Trigonometric functions operate on radians. 175 | 176 | ### Constants 177 | 178 | - `e`: Euler's Number 179 | - `pi`: Archimedes' Constant 180 | - `π`: Archimedes' Constant 181 | 182 | ### History 183 | 184 | In shell mode, `calc` keeps the results of all expressions in memory until it is quit. 185 | 186 | The pseudovariable `@` always refers to the result of the previous expression. 187 | The pseudovariable `@@` always refers to the result of the expression before the previous. 188 | Any number of `@` symbols can be chained this way. 189 | 190 | Simply chaining `@` symbols can get cumbersome. The syntax `@{N}`, where `N` is an integer, 191 | refers to the `N`th previous result. `@{1}` always refers to the result of the previous expression; 192 | it is equivalent to `@`. `@{3}` refers to the result 3 expressions ago; it is equivalent to `@@@`. 193 | 194 | The pseuaovariable `@[0]` always refers to the result of the first expression in this shell session. 195 | Likewise, `@[1]` refers to the second, and so on. The shell interface indicates the current expression. 196 | 197 | ## Warnings 198 | 199 | ### No Implicit Multiplication 200 | 201 | Implicit multiplication is not supported. Use a multiplication operator such as `*`. 202 | 203 | ### Floating Point Errors 204 | 205 | Floating point operations can compound lossily, and `calc` makes no special efforts to guard against 206 | this kind of error. For example: 207 | 208 | ```sh 209 | $ calc 'sin(rad(45)) - (sqrt(2) / 2)' 210 | -0.00000000000000011102230246251565 211 | ``` 212 | 213 | ## Crate Structure 214 | 215 | This crate includes both library code and CLI code. The CLI code is all gated behind feature `cli`; the 216 | `cli` feature is in the default features. This means that the CLI is built by default. However, it is 217 | possible to use this crate as a library without building any of the CLI code by including in your 218 | `Cargo.toml`: 219 | 220 | ```toml 221 | [dependencies] 222 | calc = { version = "*", default-features = false } 223 | ``` 224 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | lalrpop::process_root().unwrap(); 3 | } 4 | -------------------------------------------------------------------------------- /src/ast.rs: -------------------------------------------------------------------------------- 1 | use lalrpop_util::lalrpop_mod; 2 | use num_runtime_fmt::NumFmt; 3 | 4 | use crate::{ 5 | types::{Calcable, CalcableError}, 6 | Context, 7 | }; 8 | 9 | // no point getting style warnings for generated code 10 | lalrpop_mod!(#[allow(clippy::all)] pub parser); 11 | 12 | /// Error encountered while parsing an expression 13 | #[derive(Debug, thiserror::Error)] 14 | pub enum ParseError { 15 | #[error("index must fit into usize")] 16 | Index(#[source] std::num::ParseIntError), 17 | #[error("failed to parse format string")] 18 | Format(#[from] num_runtime_fmt::parse::Error), 19 | } 20 | 21 | /// A prefix operator. 22 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 23 | pub enum PrefixOperator { 24 | Negation, 25 | Not, 26 | } 27 | 28 | impl PrefixOperator { 29 | fn evaluate(&self, operand: N) -> Result::Err> { 30 | match self { 31 | Self::Negation => operand.neg().ok_or_else(|| N::Err::unimplemented("-")), 32 | Self::Not => operand.not().ok_or_else(|| N::Err::unimplemented("!")), 33 | } 34 | } 35 | } 36 | 37 | /// An infix operator. 38 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 39 | pub enum InfixOperator { 40 | Add, 41 | Sub, 42 | Mul, 43 | Div, 44 | TruncDiv, 45 | Pow, 46 | Rem, 47 | Lshift, 48 | Rshift, 49 | RotateL, 50 | RotateR, 51 | BitAnd, 52 | BitOr, 53 | BitXor, 54 | } 55 | 56 | impl InfixOperator { 57 | fn evaluate(&self, left: N, right: N) -> Result::Err> { 58 | match self { 59 | Self::Add => ::add(left, right), 60 | Self::Sub => ::sub(left, right), 61 | Self::Mul => ::mul(left, right), 62 | Self::Div => ::div(left, right), 63 | Self::TruncDiv => left.trunc_div(right), 64 | Self::Pow => left.pow(right), 65 | Self::Rem => left.rem(right), 66 | Self::Lshift => left.shl(right), 67 | Self::Rshift => left.shr(right), 68 | Self::RotateL => left.rotate_left(right), 69 | Self::RotateR => left.rotate_right(right), 70 | Self::BitAnd => left 71 | .bit_and(right) 72 | .ok_or_else(|| N::Err::unimplemented("&")), 73 | Self::BitOr => left.bit_or(right).ok_or_else(|| N::Err::unimplemented("|")), 74 | Self::BitXor => left 75 | .bit_xor(right) 76 | .ok_or_else(|| N::Err::unimplemented("^")), 77 | } 78 | } 79 | } 80 | 81 | /// A function name. 82 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 83 | pub enum Function { 84 | Abs, 85 | Ceil, 86 | Floor, 87 | Round, 88 | Sin, 89 | Cos, 90 | Tan, 91 | Sinh, 92 | Cosh, 93 | Tanh, 94 | Asin, 95 | Acos, 96 | Atan, 97 | Asinh, 98 | Acosh, 99 | Atanh, 100 | Rad, 101 | Deg, 102 | Sqrt, 103 | Cbrt, 104 | Log, 105 | Lg, 106 | Ln, 107 | Exp, 108 | } 109 | 110 | impl Function { 111 | fn evaluate(&self, operand: N) -> Result::Err> { 112 | let (result, symbol) = match self { 113 | Self::Abs => (operand.abs(), "abs"), 114 | Self::Ceil => (operand.ceil(), "ceil"), 115 | Self::Floor => (operand.floor(), "floor"), 116 | Self::Round => (operand.round(), "round"), 117 | Self::Sin => (operand.sin(), "sin"), 118 | Self::Cos => (operand.cos(), "cos"), 119 | Self::Tan => (operand.tan(), "tan"), 120 | Self::Sinh => (operand.sinh(), "sinh"), 121 | Self::Cosh => (operand.cosh(), "cosh"), 122 | Self::Tanh => (operand.tanh(), "tanh"), 123 | Self::Asin => (operand.asin(), "asin"), 124 | Self::Acos => (operand.acos(), "acos"), 125 | Self::Atan => (operand.atan(), "atan"), 126 | Self::Asinh => (operand.asinh(), "asinh"), 127 | Self::Acosh => (operand.acosh(), "acosh"), 128 | Self::Atanh => (operand.atanh(), "atanh"), 129 | Self::Rad => (operand.rad(), "rad"), 130 | Self::Deg => (operand.deg(), "deg"), 131 | Self::Sqrt => (operand.sqrt(), "sqrt"), 132 | Self::Cbrt => (operand.cbrt(), "cbrt"), 133 | Self::Log => (operand.log(), "log"), 134 | Self::Lg => (operand.lg(), "lg"), 135 | Self::Ln => (operand.ln(), "ln"), 136 | Self::Exp => (operand.exp(), "exp"), 137 | }; 138 | result.ok_or_else(|| N::Err::unimplemented(symbol)) 139 | } 140 | } 141 | 142 | /// A constant. 143 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 144 | pub enum Constant { 145 | E, 146 | Pi, 147 | } 148 | 149 | /// What kind of history lookup is desired. 150 | /// 151 | /// Absolute history lookups begin at 0 and increment. 152 | /// Relative history lookups count backwards from the current expression. 153 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 154 | pub enum HistoryIndexKind { 155 | Relative, 156 | Absolute, 157 | } 158 | 159 | /// A term in the expression. 160 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 161 | pub enum Term<'input> { 162 | Literal(&'input str), 163 | HexLiteral(&'input str), 164 | OctLiteral(&'input str), 165 | BinLiteral(&'input str), 166 | Constant(Constant), 167 | History(HistoryIndexKind, usize), 168 | } 169 | 170 | impl<'input> Term<'input> { 171 | fn evaluate(&self, ctx: &Context) -> Result::Err> { 172 | match self { 173 | Self::Literal(s) => N::parse_decimal(s), 174 | Self::HexLiteral(s) => N::parse_hex(s), 175 | Self::OctLiteral(s) => N::parse_octal(s), 176 | Self::BinLiteral(s) => N::parse_binary(s), 177 | Self::Constant(Constant::E) => N::E.ok_or_else(|| N::Err::unimplemented("e")), 178 | Self::Constant(Constant::Pi) => N::PI.ok_or_else(|| N::Err::unimplemented("pi")), 179 | Self::History(kind, idx) => { 180 | let real_idx = match kind { 181 | HistoryIndexKind::Absolute => *idx, 182 | HistoryIndexKind::Relative => { 183 | ctx.history.len().checked_sub(*idx).ok_or_else(|| { 184 | N::Err::history_out_of_bounds(*kind, *idx, ctx.history.len()) 185 | })? 186 | } 187 | }; 188 | match ctx.history.get(real_idx) { 189 | Some(n) => Ok(n.clone()), 190 | None => Err(N::Err::history_out_of_bounds( 191 | *kind, 192 | *idx, 193 | ctx.history.len(), 194 | )), 195 | } 196 | } 197 | } 198 | } 199 | } 200 | 201 | /// An expression or subexpression 202 | #[derive(Debug, Clone, PartialEq, Eq)] 203 | pub enum Expr<'input> { 204 | Term(Term<'input>), 205 | Prefix(PrefixOperator, Box>), 206 | Infix(Box>, InfixOperator, Box>), 207 | Func(Function, Box>), 208 | Group(Box>), 209 | } 210 | 211 | impl<'input> Expr<'input> { 212 | /// Evaluate this expression into its mathematical result. 213 | pub(crate) fn evaluate( 214 | &self, 215 | ctx: &Context, 216 | ) -> Result::Err> { 217 | match self { 218 | Self::Term(term) => term.evaluate(ctx), 219 | Self::Prefix(prefix, expr) => prefix.evaluate(expr.evaluate(ctx)?), 220 | Self::Infix(left, infix, right) => { 221 | infix.evaluate(left.evaluate(ctx)?, right.evaluate(ctx)?) 222 | } 223 | Self::Func(func, expr) => func.evaluate(expr.evaluate(ctx)?), 224 | Self::Group(expr) => expr.evaluate(ctx), 225 | } 226 | } 227 | } 228 | 229 | /// Error produced by [`AnnotatedExpr`]. 230 | #[derive(Debug, thiserror::Error)] 231 | pub enum AnnotatedError 232 | where 233 | N: std::fmt::Debug + Calcable, 234 | ::Err: 'static, 235 | { 236 | #[error(transparent)] 237 | Calculation(::Err), 238 | #[error("failed to render calculation result in desired format")] 239 | Format(#[from] num_runtime_fmt::Error), 240 | } 241 | 242 | /// An expression annotated with some metadata. 243 | pub struct AnnotatedExpr<'input> { 244 | pub expr: Expr<'input>, 245 | pub format: NumFmt, 246 | } 247 | 248 | impl<'input> AnnotatedExpr<'input> { 249 | /// Evaluate this expression into its mathematical result. 250 | /// 251 | /// Return the result as a bare type and also formatted according to the 252 | /// requested format string. 253 | pub fn evaluate(&self, ctx: &Context) -> Result<(N, String), AnnotatedError> 254 | where 255 | N: std::fmt::Debug + Calcable + num_runtime_fmt::Numeric, 256 | ::Err: 'static, 257 | { 258 | let value = self 259 | .expr 260 | .evaluate(ctx) 261 | .map_err(AnnotatedError::Calculation)?; 262 | let formatted = self.format.fmt(value.clone())?; 263 | Ok((value, formatted)) 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Calculator library for evaluating freehand mathematical expressions. 2 | //! 3 | //! The general workflow goes like this: 4 | //! 5 | //! - Create a [`Context`]: a reusable type which contains expression history. 6 | //! 7 | //! This type is parametrized by the numeric type which all calculations will use. 8 | //! 9 | //! - Parse an [`ast::Expr`] with [`ast::parser::ExprParser`]. 10 | //! - Evaluate that expression with [`Context::evaluate`]. 11 | //! 12 | //! You can freely modify the parsed expression; the types in [`ast`] are all public. 13 | //! 14 | //! To enable calculation based on your custom numeric type, just impl [`types::Calcable`] for your type. 15 | 16 | pub mod ast; 17 | pub mod types; 18 | 19 | use ast::{ 20 | parser::{AnnotatedExprParser, ExprParser}, 21 | AnnotatedError, ParseError as UserParseError, 22 | }; 23 | use lalrpop_util::ParseError; 24 | use num_runtime_fmt::Numeric; 25 | use types::Calcable; 26 | 27 | /// Calculation context. 28 | /// 29 | /// Stores a history of calculated values, so that the history lookups (`@`) work properly. 30 | /// Also reifies the numeric type backing the calculations. 31 | #[derive(Default)] 32 | pub struct Context { 33 | pub history: Vec, 34 | } 35 | 36 | #[derive(Debug, thiserror::Error)] 37 | pub enum Error 38 | where 39 | N: std::fmt::Debug + Calcable, 40 | ::Err: 'static, 41 | { 42 | #[error("Parsing")] 43 | Parse(#[from] ParseError), 44 | #[error("Evaluating")] 45 | Eval(#[source] ::Err), 46 | #[error("Formatting")] 47 | Format(#[source] num_runtime_fmt::Error), 48 | } 49 | 50 | impl From> for Error 51 | where 52 | N: std::fmt::Debug + Calcable, 53 | ::Err: 'static, 54 | { 55 | fn from(err: AnnotatedError) -> Self { 56 | match err { 57 | AnnotatedError::Calculation(err) => Self::Eval(err), 58 | AnnotatedError::Format(err) => Self::Format(err), 59 | } 60 | } 61 | } 62 | 63 | impl Context 64 | where 65 | N: std::fmt::Debug + Calcable, 66 | ::Err: 'static, 67 | { 68 | /// Evaluate an expression in this context. 69 | /// 70 | /// This both returns the calculated value and stores a copy in the context's history. 71 | pub fn evaluate(&mut self, expr: &str) -> Result> { 72 | let parser = ExprParser::new(); 73 | let expr = parser.parse(expr).map_err(|err| err.map_token(|_| ""))?; 74 | let result = expr.evaluate(self).map_err(Error::Eval)?; 75 | self.history.push(result.clone()); 76 | Ok(result) 77 | } 78 | } 79 | 80 | impl Context 81 | where 82 | N: std::fmt::Debug + Calcable + Numeric, 83 | ::Err: 'static, 84 | { 85 | /// Evaluate an annotated expression in this context. 86 | /// 87 | /// Annotations can include output formatting directives. Therefore, the return value 88 | /// is a formatted `String`. 89 | /// 90 | /// This also stores a copy in the context's history. 91 | pub fn evaluate_annotated(&mut self, expr: &str) -> Result> { 92 | let parser = AnnotatedExprParser::new(); 93 | let expr = parser.parse(expr).map_err(|err| err.map_token(|_| ""))?; 94 | let (result, formatted) = expr.evaluate(self)?; 95 | self.history.push(result); 96 | Ok(formatted) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Result}; 2 | use calc::{types::Calcable, Context, Error}; 3 | use clap::Parser; 4 | use num_runtime_fmt::Numeric; 5 | 6 | #[derive(Debug, Parser)] 7 | #[structopt(about)] 8 | struct Opt { 9 | /// Use 64-bit floating-point values for calculation 10 | #[structopt(short, long)] 11 | f64: bool, 12 | 13 | /// Use unsigned 64-bit integer values for calculation 14 | #[structopt(short, long)] 15 | u64: bool, 16 | 17 | /// Use signed 64-bit integer values for calculation 18 | #[structopt(short, long)] 19 | i64: bool, 20 | 21 | /// Expression to evaluate 22 | expression: Vec, 23 | } 24 | 25 | impl Opt { 26 | fn get_type(&self) -> Result { 27 | Ok(match (self.f64, self.u64, self.i64) { 28 | (_, false, false) => Type::F64, 29 | (false, true, false) => Type::U64, 30 | (false, false, true) => Type::I64, 31 | _ => bail!("conflicting fundamental type options"), 32 | }) 33 | } 34 | 35 | fn expr(&self) -> String { 36 | self.expression.join(" ") 37 | } 38 | } 39 | 40 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 41 | enum Type { 42 | F64, 43 | U64, 44 | I64, 45 | } 46 | 47 | fn eval_as(ty: Type, expr: &str) -> Result<()> { 48 | match ty { 49 | Type::F64 => eval_and_print::(&mut Context::default(), expr), 50 | Type::U64 => eval_and_print::(&mut Context::default(), expr), 51 | Type::I64 => eval_and_print::(&mut Context::default(), expr), 52 | } 53 | } 54 | 55 | fn eval_and_print(ctx: &mut Context, expr: &str) -> Result<()> 56 | where 57 | N: std::fmt::Debug + Default + Calcable + Numeric, 58 | ::Err: 'static + std::error::Error + Send + Sync, 59 | { 60 | match ctx.evaluate_annotated(expr) { 61 | Ok(n) => println!("{}", n), 62 | Err(Error::Eval(err)) => bail!(err), 63 | Err(Error::Format(err)) => bail!(err), 64 | Err(Error::Parse(err)) => { 65 | use lalrpop_util::ParseError::{ 66 | ExtraToken, InvalidToken, UnrecognizedEof, UnrecognizedToken, User, 67 | }; 68 | match err { 69 | InvalidToken { location } => { 70 | bail!("invalid token\n{}\n{:>pos$}", expr, '^', pos = location + 1) 71 | } 72 | UnrecognizedEof { location, .. } => { 73 | bail!( 74 | "unexpected EOF\n{}\n{:>pos$}", 75 | expr, 76 | '^', 77 | pos = location + 1, 78 | ); 79 | } 80 | UnrecognizedToken { 81 | token: (start, _token, end), 82 | .. 83 | } => { 84 | bail!( 85 | "unexpected token\n{}\n{:pad_l$}{}", 86 | expr, 87 | "", 88 | vec!["^"; end - start].join(""), 89 | pad_l = start, 90 | ); 91 | } 92 | ExtraToken { 93 | token: (start, _token, end), 94 | .. 95 | } => { 96 | bail!( 97 | "extra token\n{}\n{:pad_l$}{}", 98 | expr, 99 | "", 100 | vec!["^"; end - start].join(""), 101 | pad_l = start, 102 | ); 103 | } 104 | User { error } => bail!(error), 105 | }; 106 | } 107 | } 108 | Ok(()) 109 | } 110 | 111 | fn shell_as(ty: Type) -> Result<()> { 112 | match ty { 113 | Type::F64 => shell::(), 114 | Type::U64 => shell::(), 115 | Type::I64 => shell::(), 116 | } 117 | } 118 | 119 | fn shell() -> Result<()> 120 | where 121 | N: std::fmt::Debug + Default + Calcable + Numeric, 122 | ::Err: 'static + std::error::Error + Send + Sync, 123 | { 124 | let mut ctx = Context::::default(); 125 | let mut rl = rustyline::Editor::<(), _>::new()?; 126 | 127 | loop { 128 | let expr = rl.readline(&format!("[{}]: ", ctx.history.len()))?; 129 | if let Err(err) = eval_and_print(&mut ctx, &expr) { 130 | println!("{}", err); 131 | } else { 132 | let _ = rl.add_history_entry(expr); 133 | } 134 | } 135 | } 136 | 137 | fn main() -> Result<()> { 138 | let opt = Opt::parse(); 139 | if opt.expression.is_empty() { 140 | shell_as(opt.get_type()?) 141 | } else { 142 | eval_as(opt.get_type()?, &opt.expr()) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/parser.lalrpop: -------------------------------------------------------------------------------- 1 | use crate::ast::{ 2 | AnnotatedExpr, 3 | Constant, 4 | Expr, 5 | Function, 6 | HistoryIndexKind, 7 | InfixOperator, 8 | ParseError as UserParseError, 9 | PrefixOperator, 10 | Term, 11 | }; 12 | use lalrpop_util::ParseError; 13 | 14 | grammar; 15 | 16 | extern { 17 | type Error = UserParseError; 18 | } 19 | 20 | match { 21 | // numeric terminals get top priority 22 | r"[_0-9]+(\.[_0-9]+)?", 23 | r"\.[_0-9]+", 24 | r"0b[_01]+", 25 | r"0o[_0-7]+", 26 | r"0d[_0-9]+", 27 | r"0x[_0-9a-fA-F]+", 28 | } else { 29 | // everything else 30 | _, 31 | } 32 | 33 | FuncName: Function = { 34 | "abs" => Function::Abs, 35 | "ceil" => Function::Ceil, 36 | "floor" => Function::Floor, 37 | "round" => Function::Round, 38 | "sin" => Function::Sin, 39 | "cos" => Function::Cos, 40 | "tan" => Function::Tan, 41 | "sinh" => Function::Sinh, 42 | "cosh" => Function::Cosh, 43 | "tanh" => Function::Tanh, 44 | "asin" => Function::Asin, 45 | "acos" => Function::Acos, 46 | "atan" => Function::Atan, 47 | "asinh" => Function::Asinh, 48 | "acosh" => Function::Acosh, 49 | "atanh" => Function::Atanh, 50 | "rad" => Function::Rad, 51 | "deg" => Function::Deg, 52 | "sqrt" => Function::Sqrt, 53 | "cbrt" => Function::Cbrt, 54 | "log" => Function::Log, 55 | "lg" => Function::Lg, 56 | "ln" => Function::Ln, 57 | "exp" => Function::Exp, 58 | }; 59 | 60 | Constant: Constant = { 61 | "e" => Constant::E, 62 | "pi" => Constant::Pi, 63 | "π" => Constant::Pi, 64 | }; 65 | 66 | Term: Term<'input> = { 67 | r"[_0-9]+(\.[_0-9]+)?" => Term::Literal(<>), 68 | r"\.[_0-9]+" => Term::Literal(<>), 69 | r"0b[_01]+" => Term::BinLiteral(<>), 70 | r"0o[_0-7]+" => Term::OctLiteral(<>), 71 | r"0d[_0-9]+" => Term::Literal(<>), 72 | r"0x[_0-9a-fA-F]+" => Term::HexLiteral(<>), 73 | => Term::Constant(<>), 74 | =>? Ok(Term::History( 75 | HistoryIndexKind::Absolute, 76 | h[2..h.len()-1].parse().map_err(|err| ParseError::User { error: UserParseError::Index(err) })? 77 | )), 78 | =>? Ok(Term::History( 79 | HistoryIndexKind::Relative, 80 | h[2..h.len()-1].parse().map_err(|err| ParseError::User { error: UserParseError::Index(err) })? 81 | )), 82 | => Term::History(HistoryIndexKind::Relative, <>.len()), 83 | }; 84 | 85 | // Expressions need to evolve from low precedence to high. 86 | // This ensures that when we recursively evaluate them, we end up with the correct results. 87 | // 88 | // This parses the lowest level of precedence: addition and subtraction. 89 | pub Expr: Expr<'input> = { 90 | "+" => Expr::Infix(Box::new(l), InfixOperator::Add, Box::new(r)), 91 | "-" => Expr::Infix(Box::new(l), InfixOperator::Sub, Box::new(r)), 92 | Factor, 93 | }; 94 | 95 | // This parses the next level of precedence: multiplication and division. 96 | Factor: Expr<'input> = { 97 | "*" => Expr::Infix(Box::new(l), InfixOperator::Mul, Box::new(r)), // asterisk 98 | "x" => Expr::Infix(Box::new(l), InfixOperator::Mul, Box::new(r)), // lowercase x 99 | "×" => Expr::Infix(Box::new(l), InfixOperator::Mul, Box::new(r)), // u+00d7 multiplication sign 100 | "·" => Expr::Infix(Box::new(l), InfixOperator::Mul, Box::new(r)), // u+00b7 middle dot 101 | "⋅" => Expr::Infix(Box::new(l), InfixOperator::Mul, Box::new(r)), // u+22c5 dot operator 102 | "✕" => Expr::Infix(Box::new(l), InfixOperator::Mul, Box::new(r)), // u+2715 multiplication x 103 | "✖" => Expr::Infix(Box::new(l), InfixOperator::Mul, Box::new(r)), // u+2716 heavy multiplication x 104 | "/" => Expr::Infix(Box::new(l), InfixOperator::Div, Box::new(r)), // slash 105 | "÷" => Expr::Infix(Box::new(l), InfixOperator::Div, Box::new(r)), // u+00f7 division sign 106 | "%" => Expr::Infix(Box::new(l), InfixOperator::Rem, Box::new(r)), 107 | "//" => Expr::Infix(Box::new(l), InfixOperator::TruncDiv, Box::new(r)), 108 | Bitwise, 109 | }; 110 | 111 | // This parses the next level of precedence: bitwise operations 112 | Bitwise: Expr<'input> = { 113 | "&" => Expr::Infix(Box::new(l), InfixOperator::BitAnd, Box::new(r)), 114 | "|" => Expr::Infix(Box::new(l), InfixOperator::BitOr, Box::new(r)), 115 | "^" => Expr::Infix(Box::new(l), InfixOperator::BitXor, Box::new(r)), 116 | ShiftExp, 117 | }; 118 | 119 | // This parses the next level of precedence: bit shifts and exponentiaton 120 | ShiftExp: Expr<'input> = { 121 | "<<" => Expr::Infix(Box::new(l), InfixOperator::Lshift, Box::new(r)), 122 | ">>" => Expr::Infix(Box::new(l), InfixOperator::Rshift, Box::new(r)), 123 | "<<<" => Expr::Infix(Box::new(l), InfixOperator::RotateL, Box::new(r)), 124 | ">>>" => Expr::Infix(Box::new(l), InfixOperator::RotateR, Box::new(r)), 125 | "**" => Expr::Infix(Box::new(l), InfixOperator::Pow, Box::new(r)), 126 | Unary, 127 | }; 128 | 129 | // This parses the next level of precedence: unary operations 130 | Unary: Expr<'input> = { 131 | "!" => Expr::Prefix(PrefixOperator::Not, Box::new(r)), 132 | "-" => Expr::Prefix(PrefixOperator::Negation, Box::new(r)), 133 | ExprTerm, 134 | }; 135 | 136 | // This parses the final level of precedence: terms, functions, and parentheses 137 | ExprTerm: Expr<'input> = { 138 | => Expr::Term(<>), 139 | "(" ")" => Expr::Func(f, Box::new(e)), 140 | "(" ")" => Expr::Group(Box::new(<>)), 141 | "⌈" "⌉" => Expr::Func(Function::Ceil, Box::new(<>)), 142 | "⌊" "⌋" => Expr::Func(Function::Floor, Box::new(<>)), 143 | }; 144 | 145 | pub AnnotatedExpr: AnnotatedExpr<'input> = { 146 | =>? Ok(AnnotatedExpr { 147 | expr, 148 | format: fmt[1..].parse().map_err(|err| ParseError::User { error: UserParseError::Format(err) })?, 149 | }), 150 | => AnnotatedExpr { expr, format: Default::default() }, 151 | }; 152 | -------------------------------------------------------------------------------- /src/types/f64.rs: -------------------------------------------------------------------------------- 1 | use super::{not_implemented, ArithmeticError, BasicError, Calcable}; 2 | 3 | pub type Error = BasicError; 4 | pub type Result = std::result::Result; 5 | 6 | impl Calcable for f64 { 7 | type Err = Error; 8 | 9 | const E: Option = Some(std::f64::consts::E); 10 | const PI: Option = Some(std::f64::consts::PI); 11 | 12 | fn parse_binary(_s: &str) -> Result { 13 | not_implemented("0b...") 14 | } 15 | 16 | fn parse_octal(_s: &str) -> Result { 17 | not_implemented("0o...") 18 | } 19 | 20 | fn parse_decimal(s: &str) -> Result { 21 | s.parse().map_err(BasicError::Parse) 22 | } 23 | 24 | fn parse_hex(_s: &str) -> Result { 25 | not_implemented("0x...") 26 | } 27 | 28 | fn from_f32(f: f32) -> Option { 29 | Some(f as Self) 30 | } 31 | 32 | fn neg(self) -> Option { 33 | Some(-self) 34 | } 35 | 36 | fn not(self) -> Option { 37 | None 38 | } 39 | 40 | fn add(self, other: Self) -> Result { 41 | Ok(self + other) 42 | } 43 | 44 | fn sub(self, other: Self) -> Result { 45 | Ok(self - other) 46 | } 47 | 48 | fn mul(self, other: Self) -> Result { 49 | Ok(self * other) 50 | } 51 | 52 | fn div(self, other: Self) -> Result { 53 | if other == 0.0 { 54 | Err(ArithmeticError::DivideBy0.into()) 55 | } else { 56 | Ok(self / other) 57 | } 58 | } 59 | 60 | fn trunc_div(self, other: Self) -> Result { 61 | self.div(other).map(|quot| quot.floor()) 62 | } 63 | 64 | fn pow(self, other: Self) -> Result { 65 | Ok(self.powf(other)) 66 | } 67 | 68 | fn rem(self, other: Self) -> Result { 69 | if other == 0.0 { 70 | Err(ArithmeticError::DivideBy0.into()) 71 | } else { 72 | Ok(self % other) 73 | } 74 | } 75 | 76 | fn shl(self, _other: Self) -> Result { 77 | not_implemented("<<") 78 | } 79 | 80 | fn shr(self, _other: Self) -> Result { 81 | not_implemented(">>") 82 | } 83 | 84 | fn rotate_left(self, _other: Self) -> Result { 85 | not_implemented("<<<") 86 | } 87 | 88 | fn rotate_right(self, _other: Self) -> Result { 89 | not_implemented(">>>") 90 | } 91 | 92 | fn bit_and(self, _other: Self) -> Option { 93 | None 94 | } 95 | 96 | fn bit_or(self, _other: Self) -> Option { 97 | None 98 | } 99 | 100 | fn bit_xor(self, _other: Self) -> Option { 101 | None 102 | } 103 | 104 | fn abs(self) -> Option { 105 | Some(self.abs()) 106 | } 107 | 108 | fn ceil(self) -> Option { 109 | Some(self.ceil()) 110 | } 111 | 112 | fn floor(self) -> Option { 113 | Some(self.floor()) 114 | } 115 | 116 | fn round(self) -> Option { 117 | Some(self.round()) 118 | } 119 | 120 | fn sin(self) -> Option { 121 | Some(self.sin()) 122 | } 123 | 124 | fn cos(self) -> Option { 125 | Some(self.cos()) 126 | } 127 | 128 | fn tan(self) -> Option { 129 | Some(self.tan()) 130 | } 131 | 132 | fn sinh(self) -> Option { 133 | Some(self.sinh()) 134 | } 135 | 136 | fn cosh(self) -> Option { 137 | Some(self.cosh()) 138 | } 139 | 140 | fn tanh(self) -> Option { 141 | Some(self.tanh()) 142 | } 143 | 144 | fn asin(self) -> Option { 145 | Some(self.asin()) 146 | } 147 | 148 | fn acos(self) -> Option { 149 | Some(self.acos()) 150 | } 151 | 152 | fn atan(self) -> Option { 153 | Some(self.atan()) 154 | } 155 | 156 | fn asinh(self) -> Option { 157 | Some(self.asinh()) 158 | } 159 | 160 | fn acosh(self) -> Option { 161 | Some(self.acosh()) 162 | } 163 | 164 | fn atanh(self) -> Option { 165 | Some(self.atanh()) 166 | } 167 | 168 | fn sqrt(self) -> Option { 169 | Some(self.sqrt()) 170 | } 171 | 172 | fn cbrt(self) -> Option { 173 | Some(self.cbrt()) 174 | } 175 | 176 | fn log(self) -> Option { 177 | Some(self.log10()) 178 | } 179 | 180 | fn lg(self) -> Option { 181 | Some(self.log2()) 182 | } 183 | 184 | fn ln(self) -> Option { 185 | Some(self.ln()) 186 | } 187 | 188 | fn exp(self) -> Option { 189 | Some(self.exp()) 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/types/i64.rs: -------------------------------------------------------------------------------- 1 | use super::{clean_input, ArithmeticError, BasicError, Calcable}; 2 | 3 | pub type Error = BasicError; 4 | pub type Result = std::result::Result; 5 | 6 | fn as_u32(n: i64) -> std::result::Result { 7 | if n > (u32::MAX as i64) { 8 | Err(Error::Arithmetic(ArithmeticError::Overflow)) 9 | } else if n < 0 { 10 | Err(Error::Arithmetic(ArithmeticError::Underflow)) 11 | } else { 12 | Ok(n as u32) 13 | } 14 | } 15 | 16 | impl Calcable for i64 { 17 | type Err = Error; 18 | 19 | const E: Option = None; 20 | const PI: Option = None; 21 | 22 | fn parse_binary(s: &str) -> Result { 23 | i64::from_str_radix(&clean_input(s, "0b"), 2).map_err(Error::Parse) 24 | } 25 | 26 | fn parse_octal(s: &str) -> Result { 27 | i64::from_str_radix(&clean_input(s, "0o"), 8).map_err(Error::Parse) 28 | } 29 | 30 | fn parse_decimal(s: &str) -> Result { 31 | // While we could just use `parse` here instead of `from_str_radix`, this 32 | // usage preserves the pattern. 33 | #[allow(clippy::from_str_radix_10)] 34 | i64::from_str_radix(&clean_input(s, "0d"), 10).map_err(Error::Parse) 35 | } 36 | 37 | fn parse_hex(s: &str) -> Result { 38 | i64::from_str_radix(&clean_input(s, "0x"), 16).map_err(Error::Parse) 39 | } 40 | 41 | fn from_f32(f: f32) -> Option { 42 | Some(f as Self) 43 | } 44 | 45 | fn neg(self) -> Option { 46 | Some(-self) 47 | } 48 | 49 | fn not(self) -> Option { 50 | Some(!self) 51 | } 52 | 53 | fn add(self, other: Self) -> Result { 54 | self.checked_add(other) 55 | .ok_or(BasicError::Arithmetic(ArithmeticError::Overflow)) 56 | } 57 | 58 | fn sub(self, other: Self) -> Result { 59 | self.checked_sub(other) 60 | .ok_or(BasicError::Arithmetic(ArithmeticError::Underflow)) 61 | } 62 | 63 | fn mul(self, other: Self) -> Result { 64 | self.checked_mul(other) 65 | .ok_or(BasicError::Arithmetic(ArithmeticError::Overflow)) 66 | } 67 | 68 | fn div(self, other: Self) -> Result { 69 | self.checked_div(other) 70 | .ok_or(BasicError::Arithmetic(ArithmeticError::DivideBy0)) 71 | } 72 | 73 | fn trunc_div(self, other: Self) -> Result { 74 | self.div(other) 75 | } 76 | 77 | fn pow(self, other: Self) -> Result { 78 | let other = as_u32(other)?; 79 | self.checked_pow(other) 80 | .ok_or(BasicError::Arithmetic(ArithmeticError::Overflow)) 81 | } 82 | 83 | fn rem(self, other: Self) -> Result { 84 | self.checked_rem(other) 85 | .ok_or(BasicError::Arithmetic(ArithmeticError::DivideBy0)) 86 | } 87 | 88 | fn shl(self, other: Self) -> Result { 89 | Ok(self << other) 90 | } 91 | 92 | fn shr(self, other: Self) -> Result { 93 | Ok(self >> other) 94 | } 95 | 96 | fn rotate_left(self, other: Self) -> Result { 97 | Ok(self.rotate_left(as_u32(other)?)) 98 | } 99 | 100 | fn rotate_right(self, other: Self) -> Result { 101 | Ok(self.rotate_right(as_u32(other)?)) 102 | } 103 | 104 | fn bit_and(self, other: Self) -> Option { 105 | Some(self & other) 106 | } 107 | 108 | fn bit_or(self, other: Self) -> Option { 109 | Some(self | other) 110 | } 111 | 112 | fn bit_xor(self, other: Self) -> Option { 113 | Some(self ^ other) 114 | } 115 | 116 | fn abs(self) -> Option { 117 | Some(self.abs()) 118 | } 119 | 120 | fn ceil(self) -> Option { 121 | Some(self) 122 | } 123 | 124 | fn floor(self) -> Option { 125 | Some(self) 126 | } 127 | 128 | fn round(self) -> Option { 129 | Some(self) 130 | } 131 | 132 | fn sin(self) -> Option { 133 | None 134 | } 135 | 136 | fn cos(self) -> Option { 137 | None 138 | } 139 | 140 | fn tan(self) -> Option { 141 | None 142 | } 143 | 144 | fn sinh(self) -> Option { 145 | None 146 | } 147 | 148 | fn cosh(self) -> Option { 149 | None 150 | } 151 | 152 | fn tanh(self) -> Option { 153 | None 154 | } 155 | 156 | fn asin(self) -> Option { 157 | None 158 | } 159 | 160 | fn acos(self) -> Option { 161 | None 162 | } 163 | 164 | fn atan(self) -> Option { 165 | None 166 | } 167 | 168 | fn asinh(self) -> Option { 169 | None 170 | } 171 | 172 | fn acosh(self) -> Option { 173 | None 174 | } 175 | 176 | fn atanh(self) -> Option { 177 | None 178 | } 179 | 180 | fn sqrt(self) -> Option { 181 | None 182 | } 183 | 184 | fn cbrt(self) -> Option { 185 | None 186 | } 187 | 188 | fn ln(self) -> Option { 189 | None 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/types/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::HistoryIndexKind; 2 | use std::ops::{Add, Div, Mul, Sub}; 3 | 4 | mod f64; 5 | mod i64; 6 | mod u64; 7 | 8 | #[derive(Debug, thiserror::Error)] 9 | pub enum ArithmeticError { 10 | #[error("overflow")] 11 | Overflow, 12 | #[error("underflow")] 13 | Underflow, 14 | #[error("attempt to divide by 0")] 15 | DivideBy0, 16 | } 17 | 18 | #[derive(Debug, thiserror::Error)] 19 | pub enum BasicError { 20 | #[error(transparent)] 21 | Arithmetic(#[from] ArithmeticError), 22 | #[error("operation `{0}` not implemented for {}", std::any::type_name::())] 23 | NotImplemented(&'static str, std::marker::PhantomData), 24 | #[error("parsing: {0}")] 25 | Parse(#[source] E), 26 | #[error("{0:?} history index {1} out of bounds: [0..{2})")] 27 | HistoryOOB(HistoryIndexKind, usize, usize), 28 | } 29 | 30 | pub(crate) fn not_implemented(symbol: &'static str) -> Result> 31 | where 32 | T: std::fmt::Debug, 33 | E: std::error::Error, 34 | { 35 | Err(BasicError::NotImplemented(symbol, std::marker::PhantomData)) 36 | } 37 | 38 | impl CalcableError for BasicError 39 | where 40 | T: std::fmt::Debug, 41 | E: 'static + std::error::Error, 42 | { 43 | fn unimplemented(operation: &'static str) -> Self { 44 | Self::NotImplemented(operation, std::marker::PhantomData) 45 | } 46 | 47 | fn history_out_of_bounds( 48 | kind: HistoryIndexKind, 49 | requested_index: usize, 50 | history_len: usize, 51 | ) -> Self { 52 | Self::HistoryOOB(kind, requested_index, history_len) 53 | } 54 | } 55 | 56 | /// A `CalcableError` can always have certain variants. 57 | pub trait CalcableError { 58 | fn unimplemented(operation: &'static str) -> Self; 59 | fn history_out_of_bounds( 60 | kind: HistoryIndexKind, 61 | requested_index: usize, 62 | history_len: usize, 63 | ) -> Self; 64 | } 65 | 66 | /// A trait indicating that this type is suitable for usage in this program. 67 | /// 68 | /// Every type used here has to have basic arithmetic operations defined, but the rest of its 69 | /// behaviors may or may not be defined. Attempts to evaluate an operation which returns `None` 70 | /// will result in an "unimplemented" error message bubbling up to the user. 71 | pub trait Calcable: 72 | Clone 73 | + std::fmt::Display 74 | + Add 75 | + Sub 76 | + Mul 77 | + Div 78 | { 79 | type Err: std::error::Error + CalcableError; 80 | 81 | const E: Option; 82 | const PI: Option; 83 | 84 | /// Parse a binary input without decimals. 85 | /// 86 | /// Should succeed with or without a leading `0b`. 87 | fn parse_binary(s: &str) -> Result::Err>; 88 | 89 | /// Parse an octal input without decimals. 90 | /// 91 | /// Should succeed with or without a leading `0o`. 92 | fn parse_octal(s: &str) -> Result::Err>; 93 | 94 | /// Parse a decimal input which may or may not contain a decimal point. 95 | /// 96 | /// Should succeed with or without a leading `0d`. 97 | fn parse_decimal(s: &str) -> Result::Err>; 98 | 99 | /// Parse an octal input without decimals. 100 | /// 101 | /// Should succeed with or without a leading `0o`. 102 | fn parse_hex(s: &str) -> Result::Err>; 103 | 104 | /// Instantiate an instance of `Self` from an `f32`. 105 | /// 106 | /// This should be possible with minimal loss for most reasonable types. 107 | fn from_f32(f: f32) -> Option; 108 | 109 | /// Negate this value. 110 | fn neg(self) -> Option; 111 | 112 | /// Bitwise not this value. 113 | fn not(self) -> Option; 114 | 115 | /// Add this value and another, returning an error on overflow. 116 | fn add(self, other: Self) -> Result::Err>; 117 | 118 | /// Subtract another value from this, returning an error on underflow. 119 | fn sub(self, other: Self) -> Result::Err>; 120 | 121 | /// Multiply this value and another, returning an error on overflow. 122 | fn mul(self, other: Self) -> Result::Err>; 123 | 124 | /// Divide this value by another, returning an error on divide by zero. 125 | fn div(self, other: Self) -> Result::Err>; 126 | 127 | /// Divide this value by another, flooring the result to the next lowest integer. 128 | fn trunc_div(self, other: Self) -> Result::Err>; 129 | 130 | /// Raise this value by another. 131 | fn pow(self, other: Self) -> Result::Err>; 132 | 133 | /// Compute the arithmetic remainder of this value and another. 134 | fn rem(self, other: Self) -> Result::Err>; 135 | 136 | /// Compute this value left-shifted by `other` bits. 137 | fn shl(self, other: Self) -> Result::Err>; 138 | 139 | /// Compute this value right-shifted by `other` bits. 140 | fn shr(self, other: Self) -> Result::Err>; 141 | 142 | /// Compute this value left-shifted by `other` bits, wrapping the bits around. 143 | fn rotate_left(self, other: Self) -> Result::Err>; 144 | 145 | /// Compute this value right-shifted by `other` bits, wrapping the bits around. 146 | fn rotate_right(self, other: Self) -> Result::Err>; 147 | 148 | /// Compute this value bitwise anded with another. 149 | fn bit_and(self, other: Self) -> Option; 150 | 151 | /// Compute this value bitwise or'd with another. 152 | fn bit_or(self, other: Self) -> Option; 153 | 154 | /// Compute this value bitwise xor'd with another. 155 | fn bit_xor(self, other: Self) -> Option; 156 | 157 | /// Compute the absolute value of this value. 158 | fn abs(self) -> Option; 159 | 160 | /// Compute the smallest integer greater than or equal to self. 161 | fn ceil(self) -> Option; 162 | 163 | /// Compute the greatest integer less than or equal to self. 164 | fn floor(self) -> Option; 165 | 166 | /// Round self to the nearest integer; halfway cases away from 0.0. 167 | fn round(self) -> Option; 168 | 169 | /// Compute the sine of self. 170 | fn sin(self) -> Option; 171 | 172 | /// Compute the cosine of self. 173 | fn cos(self) -> Option; 174 | 175 | /// Compute the tangent of self. 176 | fn tan(self) -> Option; 177 | 178 | /// Compute the hyperbolic sine of self. 179 | fn sinh(self) -> Option; 180 | 181 | /// Compute the hyperbolic cosine of self. 182 | fn cosh(self) -> Option; 183 | 184 | /// Compute the hyperbolic tangent of self. 185 | fn tanh(self) -> Option; 186 | 187 | /// Compute the arcsine of self. 188 | fn asin(self) -> Option; 189 | 190 | /// Compute the arccosine of self. 191 | fn acos(self) -> Option; 192 | 193 | /// Compute the arctangent of self. 194 | fn atan(self) -> Option; 195 | 196 | /// Compute the inverse hyperbolic sine of self. 197 | fn asinh(self) -> Option; 198 | 199 | /// Compute the inverse hyperbolic cosine of self. 200 | fn acosh(self) -> Option; 201 | 202 | /// Compute the inverse hyperbolic tangent of self. 203 | fn atanh(self) -> Option; 204 | 205 | /// Convert self as degrees to radians. 206 | fn rad(self) -> Option { 207 | Some(Self::PI? / Self::from_f32(180.0)? * self) 208 | } 209 | 210 | /// Convert self as radians to degrees. 211 | fn deg(self) -> Option { 212 | Some(Self::from_f32(180.0)? / Self::PI? * self) 213 | } 214 | 215 | /// Determine the square root of self. 216 | fn sqrt(self) -> Option; 217 | 218 | /// Determine the cube root of self. 219 | fn cbrt(self) -> Option; 220 | 221 | /// Determine the base-10 logarithm of self. 222 | fn log(self) -> Option { 223 | Some(self.ln()? / Self::from_f32(10.0)?.ln()?) 224 | } 225 | 226 | /// Determine the base-2 logarithm of self. 227 | fn lg(self) -> Option { 228 | Some(self.ln()? / Self::from_f32(2.0)?.ln()?) 229 | } 230 | 231 | /// Determine the base-`e` (natural) logarithm of self. 232 | fn ln(self) -> Option; 233 | 234 | /// Determine `e**self` 235 | fn exp(self) -> Option { 236 | Self::E?.pow(self).ok() 237 | } 238 | } 239 | 240 | /// Strip underscores and leading bit markers from the input string 241 | pub(crate) fn clean_input(s: &str, leading: &str) -> String { 242 | let mut input = String::with_capacity(s.len()); 243 | input.extend(s.chars().filter(|&c| c != '_')); 244 | input.trim_start_matches(leading).to_string() 245 | } 246 | -------------------------------------------------------------------------------- /src/types/u64.rs: -------------------------------------------------------------------------------- 1 | use super::{clean_input, ArithmeticError, BasicError, Calcable}; 2 | 3 | pub type Error = BasicError; 4 | pub type Result = std::result::Result; 5 | 6 | fn as_u32(n: u64) -> std::result::Result { 7 | if n > (u32::MAX as u64) { 8 | Err(Error::Arithmetic(ArithmeticError::Overflow)) 9 | } else { 10 | Ok(n as u32) 11 | } 12 | } 13 | 14 | impl Calcable for u64 { 15 | type Err = Error; 16 | 17 | const E: Option = None; 18 | const PI: Option = None; 19 | 20 | fn parse_binary(s: &str) -> Result { 21 | u64::from_str_radix(&clean_input(s, "0b"), 2).map_err(Error::Parse) 22 | } 23 | 24 | fn parse_octal(s: &str) -> Result { 25 | u64::from_str_radix(&clean_input(s, "0o"), 8).map_err(Error::Parse) 26 | } 27 | 28 | fn parse_decimal(s: &str) -> Result { 29 | // While we could just use `parse` here instead of `from_str_radix`, this 30 | // usage preserves the pattern. 31 | #[allow(clippy::from_str_radix_10)] 32 | u64::from_str_radix(&clean_input(s, "0d"), 10).map_err(Error::Parse) 33 | } 34 | 35 | fn parse_hex(s: &str) -> Result { 36 | u64::from_str_radix(&clean_input(s, "0x"), 16).map_err(Error::Parse) 37 | } 38 | 39 | fn from_f32(f: f32) -> Option { 40 | Some(f as Self) 41 | } 42 | 43 | fn neg(self) -> Option { 44 | None 45 | } 46 | 47 | fn not(self) -> Option { 48 | Some(!self) 49 | } 50 | 51 | fn add(self, other: Self) -> Result { 52 | self.checked_add(other) 53 | .ok_or(BasicError::Arithmetic(ArithmeticError::Overflow)) 54 | } 55 | 56 | fn sub(self, other: Self) -> Result { 57 | self.checked_sub(other) 58 | .ok_or(BasicError::Arithmetic(ArithmeticError::Underflow)) 59 | } 60 | 61 | fn mul(self, other: Self) -> Result { 62 | self.checked_mul(other) 63 | .ok_or(BasicError::Arithmetic(ArithmeticError::Overflow)) 64 | } 65 | 66 | fn div(self, other: Self) -> Result { 67 | self.checked_div(other) 68 | .ok_or(BasicError::Arithmetic(ArithmeticError::DivideBy0)) 69 | } 70 | 71 | fn trunc_div(self, other: Self) -> Result { 72 | self.div(other) 73 | } 74 | 75 | fn pow(self, other: Self) -> Result { 76 | let other = as_u32(other)?; 77 | self.checked_pow(other) 78 | .ok_or(BasicError::Arithmetic(ArithmeticError::Overflow)) 79 | } 80 | 81 | fn rem(self, other: Self) -> Result { 82 | self.checked_rem(other) 83 | .ok_or(BasicError::Arithmetic(ArithmeticError::DivideBy0)) 84 | } 85 | 86 | fn shl(self, other: Self) -> Result { 87 | Ok(self << other) 88 | } 89 | 90 | fn shr(self, other: Self) -> Result { 91 | Ok(self >> other) 92 | } 93 | 94 | fn rotate_left(self, other: Self) -> Result { 95 | Ok(self.rotate_left(as_u32(other)?)) 96 | } 97 | 98 | fn rotate_right(self, other: Self) -> Result { 99 | Ok(self.rotate_right(as_u32(other)?)) 100 | } 101 | 102 | fn bit_and(self, other: Self) -> Option { 103 | Some(self & other) 104 | } 105 | 106 | fn bit_or(self, other: Self) -> Option { 107 | Some(self | other) 108 | } 109 | 110 | fn bit_xor(self, other: Self) -> Option { 111 | Some(self ^ other) 112 | } 113 | 114 | fn abs(self) -> Option { 115 | Some(self) 116 | } 117 | 118 | fn ceil(self) -> Option { 119 | Some(self) 120 | } 121 | 122 | fn floor(self) -> Option { 123 | Some(self) 124 | } 125 | 126 | fn round(self) -> Option { 127 | Some(self) 128 | } 129 | 130 | fn sin(self) -> Option { 131 | None 132 | } 133 | 134 | fn cos(self) -> Option { 135 | None 136 | } 137 | 138 | fn tan(self) -> Option { 139 | None 140 | } 141 | 142 | fn sinh(self) -> Option { 143 | None 144 | } 145 | 146 | fn cosh(self) -> Option { 147 | None 148 | } 149 | 150 | fn tanh(self) -> Option { 151 | None 152 | } 153 | 154 | fn asin(self) -> Option { 155 | None 156 | } 157 | 158 | fn acos(self) -> Option { 159 | None 160 | } 161 | 162 | fn atan(self) -> Option { 163 | None 164 | } 165 | 166 | fn asinh(self) -> Option { 167 | None 168 | } 169 | 170 | fn acosh(self) -> Option { 171 | None 172 | } 173 | 174 | fn atanh(self) -> Option { 175 | None 176 | } 177 | 178 | fn sqrt(self) -> Option { 179 | None 180 | } 181 | 182 | fn cbrt(self) -> Option { 183 | None 184 | } 185 | 186 | fn ln(self) -> Option { 187 | None 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | These integration tests take apart examples and turn them into test cases. 2 | -------------------------------------------------------------------------------- /tests/expression_mode.rs: -------------------------------------------------------------------------------- 1 | use calc::Context; 2 | 3 | struct ExpressionCase { 4 | input: String, 5 | expect: String, 6 | } 7 | 8 | fn parse_expressions(input: &str) -> Vec { 9 | let mut lines = input.lines(); 10 | let mut out = Vec::new(); 11 | 12 | while let Some(line) = lines.next() { 13 | if line.trim().is_empty() { 14 | continue; 15 | } 16 | const START: &str = "$ calc \""; 17 | const END: &str = "\""; 18 | assert!( 19 | line.starts_with(START), 20 | "line must begin with a quoted calc invocation" 21 | ); 22 | assert!(line.ends_with(END), "line must end with an end quote"); 23 | let input = line[START.len()..(line.len() - END.len())].to_owned(); 24 | let expect = lines.next().expect("each input has an output").to_owned(); 25 | out.push(ExpressionCase { input, expect }); 26 | } 27 | 28 | out 29 | } 30 | 31 | fn assert_expressions(expressions: &[ExpressionCase]) { 32 | for ExpressionCase { input, expect } in expressions { 33 | let mut context = Context::::default(); 34 | let result = context.evaluate(&input).unwrap(); 35 | assert_eq!(&result.to_string(), expect); 36 | } 37 | } 38 | 39 | const CASE: &str = r#" 40 | $ calc "1/(2+(3*(4-5)))" 41 | -1 42 | $ calc "round(12345 / 543)" 43 | 23 44 | "#; 45 | 46 | #[test] 47 | fn readme_1_expression_mode() { 48 | let expressions = parse_expressions(CASE); 49 | assert_expressions(&expressions); 50 | } 51 | -------------------------------------------------------------------------------- /tests/shell_mode.rs: -------------------------------------------------------------------------------- 1 | use calc::{types::Calcable, Context}; 2 | use lazy_static::lazy_static; 3 | use regex::Regex; 4 | 5 | lazy_static! { 6 | static ref INPUT_RE: Regex = Regex::new(r"^\s*\[\d+\]: (.*)$").unwrap(); 7 | } 8 | 9 | #[derive(Debug)] 10 | struct ShellCase { 11 | input: String, 12 | expect: String, 13 | } 14 | 15 | fn parse_expressions(input: &str) -> Vec { 16 | let mut lines = input.lines(); 17 | let mut out = Vec::new(); 18 | 19 | while let Some(line) = lines.next() { 20 | if line.trim().is_empty() { 21 | continue; 22 | } 23 | 24 | let input = INPUT_RE 25 | .captures(line) 26 | .expect("line must match the RE") 27 | .get(1) 28 | .expect("capture group 1 always exists") 29 | .as_str() 30 | .to_owned(); 31 | let expect = lines 32 | .next() 33 | .expect("each input has an output") 34 | .trim() 35 | .to_owned(); 36 | out.push(ShellCase { input, expect }); 37 | } 38 | 39 | out 40 | } 41 | 42 | fn assert_expressions(expressions: &[ShellCase]) 43 | where 44 | N: std::fmt::Debug + Calcable, 45 | ::Err: 'static, 46 | Context: Default, 47 | { 48 | let mut context = Context::::default(); 49 | for ShellCase { input, expect } in expressions { 50 | let result = context.evaluate(&input).unwrap(); 51 | assert_eq!(&result.to_string(), expect); 52 | } 53 | } 54 | 55 | #[test] 56 | fn readme_2_shell_mode() { 57 | const CASE: &str = r#" 58 | [0]: 1 + 1 59 | 2 60 | [1]: 3*(5/(3-4)) 61 | -15 62 | [2]: 3*pi**2 63 | 29.608813203268074 64 | [3]: @+1 65 | 30.608813203268074 66 | [4]: @@@*2 67 | -30 68 | [5]: ln(-1) 69 | NaN 70 | "#; 71 | 72 | let expressions = parse_expressions(CASE); 73 | assert_expressions::(&expressions); 74 | } 75 | 76 | #[test] 77 | fn issue_14_example_1() { 78 | const CASE: &str = r#" 79 | [1]: 528500/100 80 | 5285 81 | [2]: @/2 82 | 2642 83 | "#; 84 | 85 | let expressions = parse_expressions(CASE); 86 | assert_expressions::(&expressions); 87 | } 88 | 89 | #[test] 90 | fn issue_14_example_2() { 91 | const CASE: &str = r#" 92 | [0]: 2+2*2 93 | 6 94 | [1]: @*100 95 | 600 96 | [2]: @*2 97 | 1200 98 | [3]: @+1 99 | 1201 100 | "#; 101 | 102 | let expressions = parse_expressions(CASE); 103 | assert_expressions::(&expressions); 104 | } 105 | --------------------------------------------------------------------------------