├── metadata └── en-US │ ├── title.txt │ ├── short_description.txt │ ├── images │ ├── icon.png │ ├── featureGraphic.jpg │ └── phoneScreenshots │ │ ├── 1.jpg │ │ └── 2.jpg │ └── full_description.txt ├── tests ├── groups.kalker ├── derivation.kalker ├── equations.kalker ├── ambiguities │ ├── comparison_in_function.kalker │ └── fn_call_no_parenthesis.kalker ├── radix.kalker ├── variables.kalker ├── matrices │ ├── transpose.kalker │ └── operations.kalker ├── unicode.kalker ├── precedence.kalker ├── sum.kalker ├── redefining.kalker ├── functions.kalker ├── integration.kalker ├── comprehensions.kalker ├── recursion.kalker ├── basics.kalker ├── comparisons.kalker └── vectors.kalker ├── kalk ├── fuzz │ ├── .gitignore │ ├── fuzz_targets │ │ └── parse.rs │ └── Cargo.toml ├── wasm-build.sh ├── src │ ├── lib.rs │ ├── kalk_value │ │ ├── regular.rs │ │ ├── with_rug.rs │ │ └── rounding.rs │ ├── integration_testing.rs │ ├── radix.rs │ ├── test_helpers.rs │ ├── text_utils.rs │ ├── calculation_result.rs │ ├── prelude │ │ ├── with_rug.rs │ │ └── regular.rs │ ├── symbol_table.rs │ ├── ast.rs │ ├── errors.rs │ └── numerical.rs ├── Cargo.toml ├── LICENSE ├── scripts │ └── generate_funcs_test_cases.py └── README.md ├── mobile ├── android │ ├── app │ │ ├── .npmignore │ │ ├── src │ │ │ └── main │ │ │ │ ├── ic_launcher-playstore.png │ │ │ │ ├── res │ │ │ │ ├── drawable-hdpi │ │ │ │ │ └── splash.9.png │ │ │ │ ├── drawable-mdpi │ │ │ │ │ └── splash.9.png │ │ │ │ ├── drawable-xhdpi │ │ │ │ │ └── splash.9.png │ │ │ │ ├── drawable-xxhdpi │ │ │ │ │ └── splash.9.png │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ ├── ic_launcher_round.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ ├── ic_launcher_round.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ ├── ic_launcher_round.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── drawable-xxxhdpi │ │ │ │ │ └── splash.9.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ ├── ic_launcher_round.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ ├── ic_launcher_round.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── values │ │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ │ ├── strings.xml │ │ │ │ │ └── styles.xml │ │ │ │ ├── xml │ │ │ │ │ ├── config.xml │ │ │ │ │ └── file_paths.xml │ │ │ │ ├── drawable │ │ │ │ │ └── launch_splash.xml │ │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ │ ├── ic_launcher.xml │ │ │ │ │ └── ic_launcher_round.xml │ │ │ │ └── layout │ │ │ │ │ └── activity_main.xml │ │ │ │ ├── assets │ │ │ │ └── capacitor.config.json │ │ │ │ ├── java │ │ │ │ └── net │ │ │ │ │ └── strct │ │ │ │ │ └── kalker │ │ │ │ │ └── MainActivity.java │ │ │ │ └── AndroidManifest.xml │ │ ├── capacitor.build.gradle │ │ ├── proguard-rules.pro │ │ └── build.gradle │ ├── .idea │ │ ├── .gitignore │ │ ├── compiler.xml │ │ ├── misc.xml │ │ └── jarRepositories.xml │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── settings.gradle │ ├── capacitor.settings.gradle │ ├── variables.gradle │ ├── build.gradle │ ├── gradle.properties │ ├── .gitignore │ ├── gradlew.bat │ └── gradlew ├── .gitignore ├── favicon.ico ├── .npmignore ├── www │ ├── favicon.ico │ ├── Hack-Regular.ttf │ ├── style │ │ ├── style.min.css │ │ ├── style.css │ │ └── style.sass │ ├── manifest.json │ └── index.html ├── .idea │ ├── vcs.xml │ ├── modules.xml │ ├── kalk_mobile.iml │ └── workspace.xml ├── capacitor.config.json ├── README.md ├── gulpfile.esm.js ├── package.json └── remove-proprietary.sh ├── logo.png ├── .idea ├── .gitignore ├── encodings.xml ├── vcs.xml ├── modules.xml └── kalker.iml ├── preview.png ├── cli ├── kalker.ico ├── build.rs ├── src │ ├── output.rs │ ├── main.rs │ └── repl.rs ├── Cargo.toml ├── LICENSE ├── wix │ ├── License.rtf │ └── main.wxs └── help.txt ├── web ├── preview.png ├── .gitignore ├── public │ ├── favicon.png │ └── index.html ├── src │ ├── main.ts │ ├── public-path.js │ └── ConsoleLine.svelte ├── tsconfig.json ├── README.md ├── package.json └── webpack.config.ts ├── res ├── icon16x16.png ├── icon32x32.png ├── icon64x64.png ├── icon128x128.png ├── icon16x16@2.png ├── icon256x256.png ├── icon32x32@2.png ├── icon512x512.png ├── icon64x64@2.png ├── icon1024x1024.png ├── icon1024x1024@2.png ├── icon128x128@2.png ├── icon256x256@2.png ├── icon512x512@2.png └── logo.svg ├── Cargo.toml ├── .gitignore ├── flake.lock ├── .github └── workflows │ ├── build.yml │ └── release.yml ├── LICENSE ├── flake.nix ├── README.md └── Cargo.lock /metadata/en-US/title.txt: -------------------------------------------------------------------------------- 1 | Kalker 2 | -------------------------------------------------------------------------------- /tests/groups.kalker: -------------------------------------------------------------------------------- 1 | |-3| + ⌊2.6⌋ + ⌈4.2⌉ = 10 -------------------------------------------------------------------------------- /tests/derivation.kalker: -------------------------------------------------------------------------------- 1 | f(x) = 2x^2 + x 2 | f'(2) = 9 -------------------------------------------------------------------------------- /tests/equations.kalker: -------------------------------------------------------------------------------- 1 | (3x^3 - 2x = x^2 + 2) = 1.270776326 -------------------------------------------------------------------------------- /kalk/fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | target 3 | corpus 4 | artifacts 5 | -------------------------------------------------------------------------------- /mobile/android/app/.npmignore: -------------------------------------------------------------------------------- 1 | /build/* 2 | !/build/.npmkeep 3 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/logo.png -------------------------------------------------------------------------------- /mobile/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | www/style/*.css.* 3 | www/dist 4 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /metadata/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | A calculator with math-like syntax 2 | -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/preview.png -------------------------------------------------------------------------------- /cli/kalker.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/cli/kalker.ico -------------------------------------------------------------------------------- /tests/ambiguities/comparison_in_function.kalker: -------------------------------------------------------------------------------- 1 | f(a, b, c) = (a * b = c) 2 | f(2, 2, 4) -------------------------------------------------------------------------------- /web/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/web/preview.png -------------------------------------------------------------------------------- /kalk/wasm-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | wasm-pack build --scope paddim8 -- --no-default-features 3 | -------------------------------------------------------------------------------- /mobile/android/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /mobile/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/mobile/favicon.ico -------------------------------------------------------------------------------- /res/icon16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/res/icon16x16.png -------------------------------------------------------------------------------- /res/icon32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/res/icon32x32.png -------------------------------------------------------------------------------- /res/icon64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/res/icon64x64.png -------------------------------------------------------------------------------- /tests/radix.kalker: -------------------------------------------------------------------------------- 1 | 0b1101.101 + 1101.101_2 + 0o13.5 + 13.5_8 + 11.5 + 0xb.5i = 62 + 11.3125i -------------------------------------------------------------------------------- /res/icon128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/res/icon128x128.png -------------------------------------------------------------------------------- /res/icon16x16@2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/res/icon16x16@2.png -------------------------------------------------------------------------------- /res/icon256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/res/icon256x256.png -------------------------------------------------------------------------------- /res/icon32x32@2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/res/icon32x32@2.png -------------------------------------------------------------------------------- /res/icon512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/res/icon512x512.png -------------------------------------------------------------------------------- /res/icon64x64@2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/res/icon64x64@2.png -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["kalk", "cli"] 3 | 4 | [profile.release] 5 | panic = "abort" 6 | -------------------------------------------------------------------------------- /mobile/.npmignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | .vscode/ 4 | *.map 5 | .DS_Store 6 | .sourcemaps 7 | -------------------------------------------------------------------------------- /mobile/www/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/mobile/www/favicon.ico -------------------------------------------------------------------------------- /res/icon1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/res/icon1024x1024.png -------------------------------------------------------------------------------- /res/icon1024x1024@2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/res/icon1024x1024@2.png -------------------------------------------------------------------------------- /res/icon128x128@2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/res/icon128x128@2.png -------------------------------------------------------------------------------- /res/icon256x256@2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/res/icon256x256@2.png -------------------------------------------------------------------------------- /res/icon512x512@2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/res/icon512x512@2.png -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /public/build/ 3 | yarn.lock 4 | package-lock.json 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /web/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/web/public/favicon.png -------------------------------------------------------------------------------- /tests/variables.kalker: -------------------------------------------------------------------------------- 1 | x = 2 2 | y = 3 3 | z = 5 4 | 5 | x = 2 and y = 3 and z = 5 and xy = 6 and xyz = 30 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | */target 3 | kalk/Cargo.lock 4 | cli/Cargo.lock 5 | cli/test 6 | .vscode/ 7 | result* 8 | -------------------------------------------------------------------------------- /mobile/www/Hack-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/mobile/www/Hack-Regular.ttf -------------------------------------------------------------------------------- /tests/matrices/transpose.kalker: -------------------------------------------------------------------------------- 1 | m = [1, 2 2 | 3, 4 3 | 5, 6] 4 | 5 | mᵀ = [1, 3, 5 6 | 2, 4, 6] -------------------------------------------------------------------------------- /tests/unicode.kalker: -------------------------------------------------------------------------------- 1 | x₂₃ = 3 2 | 3 | π + ϕ + τ + √(64) = 19.0428119495 and 4 | log₁₀(100) = 2 and 5 | 1 + x₂₃ = 4 -------------------------------------------------------------------------------- /metadata/en-US/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/metadata/en-US/images/icon.png -------------------------------------------------------------------------------- /tests/precedence.kalker: -------------------------------------------------------------------------------- 1 | x = 3 2 | 2sqrt(64)3x + 2 = 146 and 3 | 2/sqrt(64)3x + 2 = 4.25 and 4 | 2sqrt(64)/3x + 2 = 18 5 | -------------------------------------------------------------------------------- /tests/ambiguities/fn_call_no_parenthesis.kalker: -------------------------------------------------------------------------------- 1 | x = 3 2 | 3 | sin2deg = sin(2deg) and 4 | sin2x = sin(2x) and 5 | sin2 = sin(2) 6 | -------------------------------------------------------------------------------- /tests/sum.kalker: -------------------------------------------------------------------------------- 1 | n = 10 2 | sum(n=1, 5, 2n) = 30 and n = 10 and 3 | sum(k=1, 5, 2k) = 30 and 4 | sum(a=1, 3, Σ(b=1, 3, a + b)) = 36 -------------------------------------------------------------------------------- /metadata/en-US/images/featureGraphic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/metadata/en-US/images/featureGraphic.jpg -------------------------------------------------------------------------------- /tests/redefining.kalker: -------------------------------------------------------------------------------- 1 | x = 2 2 | x = 3 3 | f(x, y, z) = x * y = z 4 | f(x, 2, 6) 5 | f(x, y, z) = 2 * x * y = z 6 | 7 | f(2, 3, 12) and x = 3 -------------------------------------------------------------------------------- /metadata/en-US/images/phoneScreenshots/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/metadata/en-US/images/phoneScreenshots/1.jpg -------------------------------------------------------------------------------- /metadata/en-US/images/phoneScreenshots/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/metadata/en-US/images/phoneScreenshots/2.jpg -------------------------------------------------------------------------------- /mobile/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/mobile/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /tests/functions.kalker: -------------------------------------------------------------------------------- 1 | x = 3 2 | f(x) = 2*x 3 | g(x, long_name) = 2*x*long_name 4 | 5 | f(x) = 6 and fx = 6 and x = 3 and g(x, x + 1) = 24 and sqrt4 = 2 -------------------------------------------------------------------------------- /tests/integration.kalker: -------------------------------------------------------------------------------- 1 | a = integrate(0, pi, sinxdx) 2 | b = integrate(0, pi, sinx dx) 3 | c = integrate(0, pi, sinx, dx) 4 | 5 | a = 2 and b = 2 and c = 2 -------------------------------------------------------------------------------- /mobile/android/app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/mobile/android/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /mobile/android/app/src/main/res/drawable-hdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/mobile/android/app/src/main/res/drawable-hdpi/splash.9.png -------------------------------------------------------------------------------- /mobile/android/app/src/main/res/drawable-mdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/mobile/android/app/src/main/res/drawable-mdpi/splash.9.png -------------------------------------------------------------------------------- /tests/comprehensions.kalker: -------------------------------------------------------------------------------- 1 | [x : 0 ≤ x and 5 > x] = (0, 1, 2, 3, 4) and 2 | [(x, y) : x > 0 and x <= 3, y > 0 and y <= 2] = [(1, 1), (1, 2), (2, 1), (2, 2), (3, 1), (3, 2)] -------------------------------------------------------------------------------- /mobile/android/app/src/main/res/drawable-xhdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/mobile/android/app/src/main/res/drawable-xhdpi/splash.9.png -------------------------------------------------------------------------------- /mobile/android/app/src/main/res/drawable-xxhdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/mobile/android/app/src/main/res/drawable-xxhdpi/splash.9.png -------------------------------------------------------------------------------- /mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /mobile/android/app/src/main/res/drawable-xxxhdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/mobile/android/app/src/main/res/drawable-xxxhdpi/splash.9.png -------------------------------------------------------------------------------- /mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /mobile/android/app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #F5F5F5 4 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timokoesters/kalker/master/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /tests/recursion.kalker: -------------------------------------------------------------------------------- 1 | f(x) = { 2 | f(x - 1) if x >= 1 3 | x otherwise 4 | } 5 | 6 | g(x, y) = { 7 | g(x - 1, y + 1) if x >= 1 8 | y otherwise 9 | } 10 | 11 | f(5) = 0 and g(5, 0) = 5 -------------------------------------------------------------------------------- /web/src/main.ts: -------------------------------------------------------------------------------- 1 | import "./public-path.js"; 2 | import KalkCalculator from './KalkCalculator.svelte'; 3 | import ConsoleLine from './ConsoleLine.svelte'; 4 | 5 | export { 6 | KalkCalculator, 7 | ConsoleLine, 8 | } -------------------------------------------------------------------------------- /mobile/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /mobile/android/.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /mobile/www/style/style.min.css: -------------------------------------------------------------------------------- 1 | body{width:100%;height:100%;padding:0;margin:0}.calculator{width:100%;height:100vh;box-sizing:border-box;font-family:"Hack", sans-serif;color:white;background-color:#2e2e2e}.hint{color:#c3c3c3} 2 | -------------------------------------------------------------------------------- /tests/basics.kalker: -------------------------------------------------------------------------------- 1 | x = 2 2 | y = 3 3 | f(x) = 2x(x - 3)(y + 2) 4 | g(x) = x^x 5 | 6 | 2f(f(x) + y) * 2 = 13600 and 7 | g(2) = 4 8 | 9 | E = 3 10 | 11 | 1E-2 = 0.01 and 12 | 1.23E2 = 123 and 13 | 1E1 = 10 and 14 | 2E = 6 and 15 | 0E0 = 0 -------------------------------------------------------------------------------- /mobile/android/app/src/main/res/xml/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /mobile/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | include ':capacitor-cordova-android-plugins' 3 | project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/') 4 | 5 | apply from: 'capacitor.settings.gradle' -------------------------------------------------------------------------------- /mobile/android/app/src/main/res/drawable/launch_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /mobile/android/capacitor.settings.gradle: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN 2 | include ':capacitor-android' 3 | project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor') 4 | -------------------------------------------------------------------------------- /cli/build.rs: -------------------------------------------------------------------------------- 1 | #[cfg(windows)] 2 | use winres; 3 | 4 | #[cfg(windows)] 5 | fn main() { 6 | let mut res = winres::WindowsResource::new(); 7 | res.set_icon("kalker.ico"); 8 | res.compile().unwrap(); 9 | } 10 | 11 | #[cfg(unix)] 12 | fn main() {} 13 | -------------------------------------------------------------------------------- /mobile/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /mobile/android/app/src/main/res/xml/file_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /web/src/public-path.js: -------------------------------------------------------------------------------- 1 | // https://github.com/webpack/webpack/issues/7968#issuecomment-421058484 2 | const url = new URL(document.currentScript.src); 3 | const widgetLink = url.href.substring(0, url.href.lastIndexOf('/') + 1); 4 | if (!__webpack_public_path__) __webpack_public_path__ = widgetLink; -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/comparisons.kalker: -------------------------------------------------------------------------------- 1 | x = 2 2 | true = (1 = 1) 3 | false = (1 != 1) 4 | 5 | (x > 2) = false and 6 | (x < 2) = false and 7 | (x = 2) = true and 8 | (x >= 2) = true and 9 | (x <= 2) = true and 10 | (1 < x < 2) = false and 11 | (x < 3 < 4) = true and 12 | (1 > x > 2) = false and 13 | (x > 3 > 4) = false -------------------------------------------------------------------------------- /mobile/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /mobile/www/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "App", 3 | "short_name": "App", 4 | "start_url": "index.html", 5 | "display": "standalone", 6 | "icons": [{ 7 | "src": "assets/imgs/logo.png", 8 | "sizes": "512x512", 9 | "type": "image/png" 10 | }], 11 | "background_color": "#31d53d", 12 | "theme_color": "#31d53d" 13 | } 14 | -------------------------------------------------------------------------------- /mobile/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Kalker 4 | Kalker 5 | net.strct.kalker 6 | net.strct.kalker 7 | 8 | -------------------------------------------------------------------------------- /mobile/www/style/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | width: 100%; 3 | height: 100%; 4 | padding: 0; 5 | margin: 0; 6 | } 7 | 8 | .calculator { 9 | width: 100%; 10 | height: 100vh; 11 | box-sizing: border-box; 12 | font-family: "Hack", sans-serif; 13 | color: white; 14 | background-color: #2e2e2e; 15 | } 16 | 17 | .hint { 18 | color: #c3c3c3; 19 | } 20 | -------------------------------------------------------------------------------- /kalk/fuzz/fuzz_targets/parse.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use libfuzzer_sys::fuzz_target; 3 | 4 | fuzz_target!(|data: &str| { 5 | let mut ctx = kalk::parser::Context::new().set_timeout(Some(5)); 6 | 7 | // We don't care if it parses or not, we only care about if it panicked 8 | // while parsing 9 | let _ = kalk::parser::parse(&mut ctx, data); 10 | }); 11 | -------------------------------------------------------------------------------- /.idea/kalker.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /mobile/.idea/kalk_mobile.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/vectors.kalker: -------------------------------------------------------------------------------- 1 | (2, 3, 5) + (7, 11, 13) = (9, 14, 18) and 2 | (2, 3, 5) - (7, 11, 13) = (-5, -8, -8) and 3 | (2, 3, 5) * (7, 11, 13) = 112 and 4 | (8, 9, 25) / (2, 3, 5) = (4, 3, 5) and 5 | 6 | (2, 3, 5) + 2 = (4, 5, 7) and 7 | (2, 3, 5) - 2 = (0, 1, 3) and 8 | (2, 3, 5) * 2 = (4, 6, 10) and 9 | (4, 8, 16) / 2 = (2, 4, 8) and 10 | abs([-3, 2, -5]) = (3, 2, 5) and 11 | |[-3, 2, -5]| = (3, 2, 5) -------------------------------------------------------------------------------- /mobile/capacitor.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "appId": "net.strct.kalker", 3 | "appName": "Kalker", 4 | "bundledWebRuntime": true, 5 | "npmClient": "npm", 6 | "webDir": "www", 7 | "linuxAndroidStudioPath": "/opt/android-studio/bin/studio.sh", 8 | "plugins": { 9 | "SplashScreen": { 10 | "launchShowDuration": 0 11 | } 12 | }, 13 | "includePlugins": [ 14 | "SplashScreen" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /web/src/ConsoleLine.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 16 | 17 | 18 |

19 | 20 |

21 | -------------------------------------------------------------------------------- /mobile/android/app/src/main/assets/capacitor.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "appId": "net.strct.kalker", 3 | "appName": "Kalker", 4 | "bundledWebRuntime": true, 5 | "npmClient": "npm", 6 | "webDir": "www", 7 | "linuxAndroidStudioPath": "/opt/android-studio/bin/studio.sh", 8 | "plugins": { 9 | "SplashScreen": { 10 | "launchShowDuration": 0 11 | } 12 | }, 13 | "includePlugins": [ 14 | "SplashScreen" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /mobile/android/app/capacitor.build.gradle: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN 2 | 3 | android { 4 | compileOptions { 5 | sourceCompatibility JavaVersion.VERSION_1_8 6 | targetCompatibility JavaVersion.VERSION_1_8 7 | } 8 | } 9 | 10 | apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" 11 | dependencies { 12 | 13 | 14 | } 15 | 16 | 17 | if (hasProperty('postBuildExtras')) { 18 | postBuildExtras() 19 | } 20 | -------------------------------------------------------------------------------- /mobile/www/style/style.sass: -------------------------------------------------------------------------------- 1 | @font-face 2 | font-family: "Hack" 3 | src: url("../Hack-Regular.ttf") format("truetype") 4 | font-weight: normal 5 | font-style: normal 6 | 7 | body 8 | width: 100% 9 | height: 100% 10 | padding: 0 11 | margin: 0 12 | 13 | .calculator 14 | width: 100% 15 | height: 100vh 16 | box-sizing: border-box 17 | font-family: "Hack", sans-serif 18 | color: white 19 | background-color: lighten(#212121, 5%) 20 | 21 | .hint 22 | color: #c3c3c3 23 | -------------------------------------------------------------------------------- /mobile/android/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | -------------------------------------------------------------------------------- /kalk/fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [package] 3 | name = "kalk-fuzz" 4 | version = "0.0.0" 5 | authors = ["Automatically generated"] 6 | publish = false 7 | edition = "2018" 8 | 9 | [package.metadata] 10 | cargo-fuzz = true 11 | 12 | [dependencies] 13 | libfuzzer-sys = "0.4" 14 | 15 | [dependencies.kalk] 16 | path = ".." 17 | 18 | # Prevent this from interfering with workspaces 19 | [workspace] 20 | members = ["."] 21 | 22 | [[bin]] 23 | name = "parse" 24 | path = "fuzz_targets/parse.rs" 25 | test = false 26 | doc = false 27 | -------------------------------------------------------------------------------- /kalk/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::unused_unit)] 2 | #![allow(clippy::float_cmp)] 3 | #![allow(clippy::clone_on_copy)] // the float type needs explicit cloning if the rug feature is enabled 4 | mod analysis; 5 | pub mod ast; 6 | pub mod calculation_result; 7 | pub mod errors; 8 | mod integration_testing; 9 | mod interpreter; 10 | mod inverter; 11 | pub mod kalk_value; 12 | mod lexer; 13 | mod numerical; 14 | pub mod parser; 15 | mod prelude; 16 | mod radix; 17 | mod symbol_table; 18 | mod test_helpers; 19 | pub mod text_utils; 20 | -------------------------------------------------------------------------------- /web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*"], 3 | "exclude": ["node_modules/*", "__sapper__/*", "public/*"], 4 | "compilerOptions": { 5 | "moduleResolution": "node", 6 | "target": "es2017", 7 | "importsNotUsedAsValues": "remove", 8 | "isolatedModules": true, 9 | "esModuleInterop": true, 10 | "sourceMap": true, 11 | "baseUrl": ".", 12 | "paths": {}, 13 | "types": [ 14 | "svelte", 15 | "svelte/store", 16 | "svelte/motion", 17 | "svelte/transition", 18 | "svelte/animate", 19 | "svelte/easing" 20 | ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /mobile/android/variables.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | minSdkVersion = 21 3 | compileSdkVersion = 29 4 | targetSdkVersion = 29 5 | androidxAppCompatVersion = '1.1.0' 6 | androidxCoreVersion = '1.2.0' 7 | androidxMaterialVersion = '1.1.0-rc02' 8 | androidxBrowserVersion = '1.2.0' 9 | androidxLocalbroadcastmanagerVersion = '1.0.0' 10 | androidxExifInterfaceVersion = '1.2.0' 11 | junitVersion = '4.12' 12 | androidxJunitVersion = '1.1.1' 13 | androidxEspressoCoreVersion = '3.2.0' 14 | cordovaAndroidVersion = '7.0.0' 15 | } -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1632255010, 6 | "narHash": "sha256-p/BYR6qhbCtDpOfw9uMYwjwyhuCVKgiSUpKZaV/63UE=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "bfc38d3d0d22c528e8abc46c5dd5f3f50fc4554b", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "id": "nixpkgs", 14 | "type": "indirect" 15 | } 16 | }, 17 | "root": { 18 | "inputs": { 19 | "nixpkgs": "nixpkgs" 20 | } 21 | } 22 | }, 23 | "root": "root", 24 | "version": 7 25 | } 26 | -------------------------------------------------------------------------------- /mobile/README.md: -------------------------------------------------------------------------------- 1 | ## Created with Capacitor Create 2 | 3 | This app was created using the `npx @capacitor/cli create` command, and comes with a very 4 | minimal shell for building an app. 5 | 6 | Note: unless you know what you're doing, we don't recommend using the `create` way of building an app. Instead, you'll 7 | likely want to create an app using your framework's tooling (such as `create-react-app`), and then *add* capacitor 8 | to *that* project (using `npx @capacitor/cli init`). 9 | 10 | ### Running this example 11 | 12 | To run the provided example, can use serve command: 13 | 14 | ```bash 15 | npx cap serve 16 | ``` 17 | -------------------------------------------------------------------------------- /mobile/android/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /mobile/android/app/src/main/java/net/strct/kalker/MainActivity.java: -------------------------------------------------------------------------------- 1 | package net.strct.kalker; 2 | 3 | import android.os.Bundle; 4 | 5 | import com.getcapacitor.BridgeActivity; 6 | import com.getcapacitor.Plugin; 7 | 8 | import java.util.ArrayList; 9 | 10 | public class MainActivity extends BridgeActivity { 11 | @Override 12 | public void onCreate(Bundle savedInstanceState) { 13 | super.onCreate(savedInstanceState); 14 | 15 | // Initializes the Bridge 16 | this.init(savedInstanceState, new ArrayList>() {{ 17 | // Additional plugins you've installed go here 18 | // Ex: add(TotallyAwesomePlugin.class); 19 | }}); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /mobile/gulpfile.esm.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { task, src, dest, watch, series } from 'gulp'; 4 | import sass, { compiler, logError } from 'gulp-dart-sass'; 5 | 6 | compiler = require('sass'); 7 | 8 | task('sass', async () => { 9 | return src('./www/style/**/*.sass') 10 | .pipe(sass().on('error', logError)) 11 | .pipe(dest('./www/dist')); 12 | }); 13 | 14 | task('copyKalkComponent', async () => { 15 | return src('./node_modules/@paddim8/kalk-component/public/build/*') 16 | .pipe(dest('./www/dist/')); 17 | }); 18 | 19 | task('watch', () => { 20 | watch('./style/**/*.sass', series('sass')); 21 | 22 | }); 23 | 24 | task('default', series('sass', 'copyKalkComponent')); 25 | -------------------------------------------------------------------------------- /mobile/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kalk_mobile", 3 | "version": "2.1.0", 4 | "description": "kalk mobile", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "build": "gulp; sh remove-proprietary.sh" 8 | }, 9 | "keywords": [ 10 | "capacitor", 11 | "mobile" 12 | ], 13 | "author": "", 14 | "license": "MIT", 15 | "dependencies": { 16 | "@capacitor/android": "^2.4.5", 17 | "@capacitor/cli": "^2.4.5", 18 | "@capacitor/core": "^2.4.5", 19 | "@paddim8/kalk-component": "^2.1.0" 20 | }, 21 | "devDependencies": { 22 | "@capacitor-community/electron": "^1.3.2", 23 | "esm": "^3.2.25", 24 | "gulp": "^4.0.2", 25 | "gulp-dart-sass": "^1.0.2", 26 | "sass": "^1.32.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mobile/android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.6.1' 11 | //classpath 'com.google.gms:google-services:4.3.3' 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | apply from: "variables.gradle" 19 | 20 | allprojects { 21 | repositories { 22 | google() 23 | jcenter() 24 | } 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /mobile/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | - uses: Swatinem/rust-cache@v1.0.1 18 | - name: Build 19 | run: cargo build --verbose 20 | - name: Run tests 21 | run: cargo test --verbose 22 | 23 | build_wasm: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@master 27 | - uses: Swatinem/rust-cache@v1.0.1 28 | - uses: actions-rs/toolchain@v1 29 | with: 30 | profile: minimal 31 | toolchain: stable 32 | target: wasm32-unknown-unknown 33 | default: true 34 | - run: cd kalk; cargo build --target wasm32-unknown-unknown --no-default-features -------------------------------------------------------------------------------- /kalk/src/kalk_value/regular.rs: -------------------------------------------------------------------------------- 1 | use crate::kalk_value::*; 2 | 3 | impl KalkValue { 4 | pub fn to_f64(&self) -> f64 { 5 | if let KalkValue::Number(real, _, _) = self { 6 | *real 7 | } else { 8 | f64::NAN 9 | } 10 | } 11 | 12 | pub fn to_float(&self) -> f64 { 13 | self.to_f64() 14 | } 15 | 16 | pub fn imaginary_to_float(&self) -> f64 { 17 | self.imaginary_to_f64() 18 | } 19 | 20 | pub fn imaginary_to_f64(&self) -> f64 { 21 | if let KalkValue::Number(_, imaginary, _) = self { 22 | *imaginary 23 | } else { 24 | f64::NAN 25 | } 26 | } 27 | 28 | pub fn values(self) -> (f64, f64) { 29 | if let KalkValue::Number(real, imaginary, _) = self { 30 | (real, imaginary) 31 | } else { 32 | (0f64, 0f64) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /mobile/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 17 | 18 | 19 | 22 | -------------------------------------------------------------------------------- /kalk/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kalk" 3 | version = "3.1.0" 4 | authors = ["PaddiM8"] 5 | edition = "2018" 6 | readme = "README.md" 7 | description = "A math evaluator library that supports user-defined functions, variables and units, and can handle fairly ambiguous syntax." 8 | repository = "https://github.com/PaddiM8/kalker/tree/master/kalk" 9 | license = "MIT" 10 | keywords = ["math", "calculator", "evaluator"] 11 | categories = ["mathematics", "parser-implementations"] 12 | 13 | [lib] 14 | crate-type = ["cdylib", "rlib"] 15 | 16 | [dependencies] 17 | rug = { version = "1.24.0", features = ["float"], optional = true } 18 | lazy_static = "1.4.0" 19 | wasm-bindgen = "0.2.69" 20 | 21 | [dev-dependencies] 22 | wasm-bindgen-test = "0.3.19" 23 | test-case = "1.0.0" 24 | regex = "1" 25 | 26 | [features] 27 | default = ["rug"] 28 | 29 | # Breaks when optimizing for some reason. 30 | [package.metadata.wasm-pack.profile.release] 31 | wasm-opt = ["-Oz", "--enable-mutable-globals"] 32 | -------------------------------------------------------------------------------- /cli/src/output.rs: -------------------------------------------------------------------------------- 1 | use ansi_term::Colour::Red; 2 | use kalk::{kalk_value::ScientificNotationFormat, parser}; 3 | 4 | pub(crate) const DEFAULT_PRECISION: u32 = 1024; 5 | 6 | pub fn eval(parser: &mut parser::Context, input: &str, precision: u32, base: u8, format: ScientificNotationFormat) { 7 | match parser::eval(parser, input, precision) { 8 | Ok(Some(mut result)) => { 9 | if !result.set_radix(base) { 10 | print_err("Invalid base. Change it by typing eg. `base 10`."); 11 | 12 | return; 13 | } 14 | 15 | if precision == DEFAULT_PRECISION { 16 | println!("{}", result.to_string_pretty_format(format)); 17 | 18 | return; 19 | } 20 | 21 | println!("{}", result.to_string_big()) 22 | } 23 | Ok(None) => print!(""), 24 | Err(err) => print_err(&err.to_string()), 25 | } 26 | } 27 | 28 | pub fn print_err(msg: &str) { 29 | Red.paint(msg).to_string(); 30 | eprintln!("{}", msg); 31 | } 32 | -------------------------------------------------------------------------------- /mobile/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Kalker 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | kalker 21 | 22 | Type 'help' for instructions. 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["PaddiM8"] 3 | build = "build.rs" 4 | categories = ["mathematics", "command-line-utilities"] 5 | description = "A calculator that supports user-defined functions, variables and units, and can handle fairly ambiguous syntax." 6 | edition = "2018" 7 | keywords = ["math", "calculator", "cli", "command-line"] 8 | license = "MIT" 9 | name = "kalker" 10 | readme = "../README.md" 11 | repository = "https://github.com/PaddiM8/kalker" 12 | version = "2.1.0" 13 | 14 | [dependencies] 15 | ansi_term = "0.12.1" 16 | kalk = { path = "../kalk", version = "^3.1.0" } 17 | lazy_static = "1.4.0" 18 | regex = "1" 19 | rustyline = "7.1.0" 20 | seahorse = "1.1.1" 21 | atty = "0.2.14" 22 | dirs = "3.0.2" 23 | 24 | [target.'cfg(windows)'.build-dependencies] 25 | winres = "0.1" 26 | 27 | [package.metadata.bundle] 28 | icon = ["../res/icon*"] 29 | identifier = "net.strct.kalker" 30 | name = "kalker" 31 | short_description = "A calculator that supports user-defined functions, variables and units, and can handle fairly ambiguous syntax." 32 | 33 | [package.metadata.bundle.bin.kalker] 34 | name = "kalker" 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Oliver Waldemar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cli/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Oliver Waldemar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /kalk/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Oliver Waldemar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /mobile/remove-proprietary.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | dir="./node_modules/@capacitor/android/capacitor" 4 | 5 | # remove lines related to play service/firebase dependencies 6 | sed -i '/playServices/d' "$dir/build.gradle" 7 | sed -i '/firebase/d' "$dir/build.gradle" 8 | 9 | # remove the files that rely on the proprietary services 10 | rm -f "$dir/src/main/java/com/getcapacitor/plugin/Geolocation.java" 11 | rm -f "$dir/src/main/java/com/getcapacitor/plugin/PushNotifications.java" 12 | rm -f "$dir/src/main/java/com/getcapacitor/CapacitorFirebaseMessagingService.java" 13 | 14 | # remove the references to the plugins in Bridge.java 15 | sed -i '/Geolocation/d' "$dir/src/main/java/com/getcapacitor/Bridge.java" 16 | sed -i '/PushNotifications/d' "$dir/src/main/java/com/getcapacitor/Bridge.java" 17 | 18 | # remove unecessary permissions and now redundant lines from manifest 19 | sed -i '/WAKE_LOCK/d' "$dir/src/main/AndroidManifest.xml" 20 | sed -i '/RECEIVE_BOOT_COMPLETED/d' "$dir/src/main/AndroidManifest.xml" 21 | i=$(grep -n "firebase_messaging_auto_init_enabled" "$dir/src/main/AndroidManifest.xml" | cut -d : -f 1) 22 | sed -i "$i,$(($i+12))d" "$dir/src/main/AndroidManifest.xml" 2> /dev/null 23 | -------------------------------------------------------------------------------- /mobile/android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | 19 | # AndroidX package structure to make it clearer which packages are bundled with the 20 | # Android operating system, and which are packaged with your app's APK 21 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 22 | android.useAndroidX=true 23 | # Automatically convert third-party libraries to use AndroidX 24 | android.enableJetifier=true 25 | -------------------------------------------------------------------------------- /tests/matrices/operations.kalker: -------------------------------------------------------------------------------- 1 | m_1 = [1, 2, 3 2 | 4, 5, 6 3 | 7, 8, 9] 4 | 5 | m_2 = [2, 3, 4 6 | 5, 6, 7 7 | 8, 9, 10] 8 | 9 | m_3 = [1, 2, 1 10 | 0, 1, 0 11 | 2, 3, 4] 12 | 13 | m_4 = [2, 5 14 | 6, 7 15 | 1, 8] 16 | 17 | v = (1, 2, 3) 18 | 19 | m_1 + m_2 = [3, 5, 7 20 | 9, 11, 13 21 | 15, 17, 19] and 22 | m_2 - m_1 = [1, 1, 1 23 | 1, 1, 1 24 | 1, 1, 1] and 25 | m_1 * m_2 = [36, 42, 48 26 | 81, 96, 111 27 | 126, 150, 174] and 28 | [4, 9; 12, 3] / [2, 3; 4, 3] = [2, 3; 3, 1] and 29 | 30 | m_1 + v = [2, 3, 4; 6, 7, 8; 10, 11, 12] and 31 | m_1 - v = [0, 1, 2; 2, 3, 4; 4, 5, 6] and 32 | m_1 * v = (14, 32, 50) and 33 | m_1 / v = [1, 2, 3; 2, 5/2, 3; 7/3, 8/3, 3] and 34 | 35 | m_1 + 2 = [3, 4, 5 36 | 6, 7, 8 37 | 9, 10, 11] and 38 | m_1 - 2 = [-1, 0, 1 39 | 2, 3, 4 40 | 5, 6, 7] and 41 | m_1 * 2 = [2, 4, 6 42 | 8, 10, 12 43 | 14, 16, 18] and 44 | m_1 / 2 = [1/2, 1, 3/2 45 | 2, 5/2, 3 46 | 7/2, 4, 9/2] and 47 | 48 | m_3 * m_4 = [15, 27 49 | 6, 7 50 | 26, 63] -------------------------------------------------------------------------------- /kalk/src/kalk_value/with_rug.rs: -------------------------------------------------------------------------------- 1 | use crate::kalk_value::*; 2 | 3 | impl KalkValue { 4 | pub fn to_f64(&self) -> f64 { 5 | if let KalkValue::Number(real, _, _) = self { 6 | real.to_f64_round(rug::float::Round::Nearest) 7 | } else { 8 | f64::NAN 9 | } 10 | } 11 | 12 | pub fn to_float(&self) -> Float { 13 | if let KalkValue::Number(real, _, _) = self { 14 | real.clone() 15 | } else { 16 | crate::float!(f64::NAN) 17 | } 18 | } 19 | 20 | pub fn imaginary_to_f64(&self) -> f64 { 21 | if let KalkValue::Number(_, imaginary, _) = self { 22 | imaginary.to_f64_round(rug::float::Round::Nearest) 23 | } else { 24 | f64::NAN 25 | } 26 | } 27 | 28 | pub fn imaginary_to_float(&self) -> Float { 29 | if let KalkValue::Number(_, img, _) = self { 30 | img.clone() 31 | } else { 32 | use crate::float; 33 | float!(f64::NAN) 34 | } 35 | } 36 | 37 | pub fn values(self) -> (Float, Float) { 38 | if let KalkValue::Number(real, imaginary, _) = self { 39 | (real, imaginary) 40 | } else { 41 | (Float::with_val(63, 0), Float::with_val(63, 0)) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /cli/wix/License.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\deff0\nouicompat{\fonttbl{\f0\fnil\fcharset0 Arial;}{\f1\fnil\fcharset0 Courier New;}} 2 | {\*\generator Riched20 10.0.15063}\viewkind4\uc1 3 | \pard\sa180\fs24\lang9 Copyright (c) 2020 PaddiM8\par 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | } 22 | 23 | -------------------------------------------------------------------------------- /mobile/android/.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | -------------------------------------------------------------------------------- /mobile/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /web/README.md: -------------------------------------------------------------------------------- 1 | # Kalker Web Component 2 | ![npm](https://img.shields.io/npm/v/@paddim8/kalk-component) 3 | ![license](https://img.shields.io/npm/l/@paddim8/kalk-component) 4 | 5 | A web component built with Svelte that uses the kalk library (WebAssembly). 6 | 7 | ![preview](preview.png) 8 | 9 | ## Installation & Setup 10 | `npm install --save @paddim8/kalk-component` 11 | 12 | ### Module 13 | ```js 14 | import { KalkCalculator, ConsoleLine } from "@paddim8/kalk-component" 15 | ``` 16 | If your bundler doesn't include all the files in the output folder, 17 | you may need to eg. modify your build script to copy them over manually. 18 | I am not aware of a better way. 19 | 20 | ### Or only with HTML 21 | ```html 22 | 23 | ``` 24 | 25 | ## Usage 26 | ```html 27 |
28 | 29 | kalker 30 | 31 | Type 'help' for instructions. 32 | 33 | 34 |
35 | ``` 36 | 37 | ## Customizing 38 | The colours in the component can be changed by using these attributes. 39 | ```html 40 | 50 | ``` -------------------------------------------------------------------------------- /web/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Svelte app 9 | 10 | 11 | 12 | 13 | 64 | 65 | 66 | 67 |
68 |
69 | 70 | kalk 71 | 72 | Type 'help' for instructions. 73 | 74 | 75 |
76 |
77 | 78 | 79 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "A calculator program/website"; 3 | 4 | outputs = { self, nixpkgs }: 5 | let 6 | systems = 7 | [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; 8 | 9 | forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system); 10 | 11 | nixpkgsFor = forAllSystems (system: 12 | import nixpkgs { 13 | inherit system; 14 | overlays = [ self.overlay ]; 15 | }); 16 | in rec { 17 | overlay = final: prev: { 18 | kalker = final.rustPlatform.buildRustPackage { 19 | pname = "kalker"; 20 | version = "unstable"; 21 | description = "A CLI calculator"; 22 | 23 | src = self; 24 | 25 | nativeBuildInputs = with final; [ gcc ]; 26 | 27 | outputs = [ "out" "lib" ]; 28 | 29 | postInstall = '' 30 | moveToOutput "lib" "$lib" 31 | ''; 32 | 33 | cargoLock = { lockFile = self + "/Cargo.lock"; }; 34 | 35 | buildInputs = with final; [ gmp mpfr libmpc ]; 36 | 37 | CARGO_FEATURE_USE_SYSTEM_LIBS = "1"; 38 | }; 39 | }; 40 | 41 | packages = 42 | forAllSystems (system: { inherit (nixpkgsFor.${system}) kalker; }); 43 | 44 | defaultPackage = forAllSystems (system: self.packages.${system}.kalker); 45 | 46 | apps = forAllSystems (system: { 47 | kalker = { 48 | type = "app"; 49 | program = "${self.packages.${system}.kalker}/bin/kalker"; 50 | }; 51 | }); 52 | 53 | defaultApp = forAllSystems (system: self.apps.${system}.kalker); 54 | 55 | devShell = forAllSystems (system: 56 | nixpkgs.legacyPackages.${system}.mkShell { 57 | inputsFrom = builtins.attrValues (packages.${system}); 58 | }); 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /mobile/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.compileSdkVersion 5 | 6 | def version = System.getenv("APP_VERSION_NAME") ?: "1.0.0" 7 | def parts = version.split('\\.') 8 | def major = parts[0] 9 | def minor = parts[1] 10 | def patch = parts[2] 11 | 12 | if (major.size() == 1) major = major + "0" 13 | if (minor.size() == 1) minor = minor + "0" 14 | if (patch.size() == 1) patch = patch + "0" 15 | 16 | def code = (major + minor + patch) as Integer 17 | 18 | defaultConfig { 19 | applicationId "net.strct.kalker" 20 | minSdkVersion rootProject.ext.minSdkVersion 21 | targetSdkVersion rootProject.ext.targetSdkVersion 22 | versionCode code 23 | versionName version 24 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 25 | } 26 | buildTypes { 27 | release { 28 | minifyEnabled false 29 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 30 | } 31 | } 32 | } 33 | 34 | repositories { 35 | flatDir{ 36 | dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs' 37 | } 38 | } 39 | 40 | dependencies { 41 | implementation fileTree(include: ['*.jar'], dir: 'libs') 42 | implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion" 43 | implementation project(':capacitor-android') 44 | implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0' 45 | testImplementation "junit:junit:$junitVersion" 46 | androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" 47 | androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" 48 | implementation project(':capacitor-cordova-android-plugins') 49 | } 50 | 51 | apply from: 'capacitor.build.gradle' -------------------------------------------------------------------------------- /kalk/scripts/generate_funcs_test_cases.py: -------------------------------------------------------------------------------- 1 | #!/bin/python 2 | 3 | from random import randint 4 | import mpmath 5 | 6 | 7 | funcs = [ 8 | (mpmath.arg, "arg"), 9 | (mpmath.ceil, "ceil"), 10 | (mpmath.exp, "exp"), 11 | (mpmath.floor, "floor"), 12 | (mpmath.log10, "log"), 13 | (mpmath.ln, "ln"), 14 | (mpmath.sqrt, "sqrt"), 15 | (mpmath.acos, "acos"), 16 | (mpmath.acosh, "acosh"), 17 | (mpmath.acsc, "acsc"), 18 | (mpmath.acsch, "acsch"), 19 | (mpmath.acot, "acot"), 20 | (mpmath.acoth, "acoth"), 21 | (mpmath.asec, "asec"), 22 | (mpmath.asech, "asech"), 23 | (mpmath.asin, "asin"), 24 | (mpmath.asinh, "asinh"), 25 | (mpmath.atan, "atan"), 26 | (mpmath.atanh, "atanh"), 27 | (mpmath.cos, "cos"), 28 | (mpmath.cosh, "cosh"), 29 | (mpmath.csc, "csc"), 30 | (mpmath.csch, "csch"), 31 | (mpmath.cot, "cot"), 32 | (mpmath.coth, "coth"), 33 | (mpmath.sec, "sec"), 34 | (mpmath.sech, "sech"), 35 | (mpmath.sin, "sin"), 36 | (mpmath.sinh, "sinh"), 37 | (mpmath.tan, "tan"), 38 | (mpmath.tanh, "tanh"), 39 | ] 40 | 41 | for func in funcs: 42 | args = [ 43 | (randint(-10, 10) / 10, 0), 44 | (0, randint(-10, 10) / 10), 45 | (randint(-10, 10) / 10, randint(-10, 10) / 10), 46 | (randint(-10, 10) / 10, randint(-10, 10) / 10), 47 | (randint(15, 30) / 10, 0), 48 | (randint(-30, -15) / 10, 0), 49 | (0, 0) 50 | ] 51 | 52 | for arg in args: 53 | result = 0 54 | defined = True 55 | try: 56 | result = func[0](complex(arg[0], arg[1])) 57 | except: 58 | defined = False 59 | 60 | print("({0}, ({1}f64, {2}f64), ({3}, {4})),".format( 61 | func[1], 62 | arg[0], 63 | arg[1], 64 | str(round(result.real, 7)) + "f64" if defined else "f64::NAN", 65 | str(round(result.imag, 7)) + "f64" if defined else "f64::NAN")) 66 | -------------------------------------------------------------------------------- /mobile/android/.gitignore: -------------------------------------------------------------------------------- 1 | # NPM renames .gitignore to .npmignore 2 | # In order to prevent that, we remove the initial "." 3 | # And the CLI then renames it 4 | 5 | # Using Android gitignore template: https://github.com/github/gitignore/blob/master/Android.gitignore 6 | 7 | # Built application files 8 | *.apk 9 | *.ap_ 10 | *.aab 11 | 12 | # Files for the ART/Dalvik VM 13 | *.dex 14 | 15 | # Java class files 16 | *.class 17 | 18 | # Generated files 19 | bin/ 20 | gen/ 21 | out/ 22 | release/ 23 | 24 | # Gradle files 25 | .gradle/ 26 | build/ 27 | 28 | # Local configuration file (sdk path, etc) 29 | local.properties 30 | 31 | # Proguard folder generated by Eclipse 32 | proguard/ 33 | 34 | # Log Files 35 | *.log 36 | 37 | # Android Studio Navigation editor temp files 38 | .navigation/ 39 | 40 | # Android Studio captures folder 41 | captures/ 42 | 43 | # IntelliJ 44 | *.iml 45 | .idea/workspace.xml 46 | .idea/tasks.xml 47 | .idea/gradle.xml 48 | .idea/assetWizardSettings.xml 49 | .idea/dictionaries 50 | .idea/libraries 51 | # Android Studio 3 in .gitignore file. 52 | .idea/caches 53 | .idea/modules.xml 54 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 55 | .idea/navEditor.xml 56 | 57 | # Keystore files 58 | # Uncomment the following lines if you do not want to check your keystore files in. 59 | #*.jks 60 | #*.keystore 61 | 62 | # External native build folder generated in Android Studio 2.2 and later 63 | .externalNativeBuild 64 | 65 | # Freeline 66 | freeline.py 67 | freeline/ 68 | freeline_project_description.json 69 | 70 | # fastlane 71 | fastlane/report.xml 72 | fastlane/Preview.html 73 | fastlane/screenshots 74 | fastlane/test_output 75 | fastlane/readme.md 76 | 77 | # Version control 78 | vcs.xml 79 | 80 | # lint 81 | lint/intermediates/ 82 | lint/generated/ 83 | lint/outputs/ 84 | lint/tmp/ 85 | # lint/reports/ 86 | 87 | # Cordova plugins for Capacitor 88 | capacitor-cordova-android-plugins 89 | 90 | # Copied web assets 91 | app/src/main/assets/public 92 | -------------------------------------------------------------------------------- /kalk/src/integration_testing.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use std::{fs::File, io::Read, path::PathBuf}; 4 | use test_case::test_case; 5 | 6 | use crate::kalk_value::KalkValue; 7 | 8 | fn eval_file(name: &str) -> KalkValue { 9 | let mut path = PathBuf::new(); 10 | path.push(env!("CARGO_MANIFEST_DIR")); 11 | path.push(".."); 12 | path.push("tests"); 13 | path.push(name); 14 | path.set_extension("kalker"); 15 | 16 | let mut file_content = String::new(); 17 | File::open(path) 18 | .unwrap() 19 | .read_to_string(&mut file_content) 20 | .unwrap(); 21 | let mut context = crate::parser::Context::new(); 22 | 23 | #[cfg(feature = "rug")] 24 | return crate::parser::eval(&mut context, &file_content, 58) 25 | .unwrap() 26 | .unwrap() 27 | .get_value(); 28 | 29 | #[cfg(not(feature = "rug"))] 30 | crate::parser::eval(&mut context, &file_content) 31 | .unwrap() 32 | .unwrap() 33 | .get_value() 34 | } 35 | 36 | fn is_true(x: KalkValue) -> bool { 37 | if let KalkValue::Boolean(boolean) = x { 38 | boolean 39 | } else { 40 | false 41 | } 42 | } 43 | 44 | #[test_case("ambiguities/comparison_in_function")] 45 | #[test_case("ambiguities/fn_call_no_parenthesis")] 46 | #[test_case("basics")] 47 | #[test_case("comparisons")] 48 | #[test_case("comprehensions")] 49 | #[test_case("equations")] 50 | #[test_case("derivation")] 51 | #[test_case("functions")] 52 | #[test_case("groups")] 53 | #[test_case("integration")] 54 | #[test_case("matrices/operations")] 55 | #[test_case("matrices/transpose")] 56 | #[test_case("radix")] 57 | #[test_case("recursion")] 58 | #[test_case("redefining")] 59 | #[test_case("sum")] 60 | #[test_case("variables")] 61 | #[test_case("vectors")] 62 | fn test_file(name: &str) { 63 | assert!(is_true(eval_file(name))); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /metadata/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 | Kalker is a calculator that supports user-defined variables, functions, derivation, and integration. It runs on Windows, macOS, Linux, Android, and in web browsers (with WebAssembly). 2 | 3 | Features: 4 |
    5 |
  • Operators: +, -, *, /, !
  • 6 |
  • Groups: (), [], ⌈ceil⌉, ⌊floor⌋
  • 7 |
  • Vectors: (x, y, z, ...)
  • 8 |
  • Matrices: [x, y, z; a, b, c; ...]
  • 9 |
  • Pre-defined functions and constants
  • 10 |
  • User-defined functions and variables. f(x, y) = xy, x = 5
  • 11 |
  • Root finding using Newton's method (eg. x^2 = 64). Note: estimation and limited to one root
  • 12 |
  • Derivative of functions (derivatives of noisy functions or of higher order can be a bit inaccurate). f'(2), sin'(-pi)
  • 13 |
  • Integration. ∫(0, pi, sin(x) dx) or ∫(0, π, sin(x) dx), maybe sometimes be slightly off
  • 14 |
  • Understands fairly ambiguous syntax. Eg. 2sin50 + 2xy
  • 15 |
  • Syntax highlighting
  • 16 |
  • Special-symbol completion on tab. Eg. write sqrt and press tab. It will be turned into √
  • 17 |
  • Sum function: sum(start, to, expression) Eg. sum(1, 3, 2n+1) is the same as 2*1+1 + 2*2+1 + 2*3+1 = 15
  • 18 |
  • Piecewise functions: f(x) = { f(x + 1) if x <= 1; x otherwise }, pressing enter before typing the final } will make a new line without submitting
  • 19 |
  • Load a file including predefined functions and constants. For example, if you're going to use kalker for physics, you load up your file with physics functions/constants when starting kalker. This is done either using the -i file flag or by putting files in a certain directory and then doing load filename inside kalker. More about files here
  • 20 |
  • Different number bases: Either with a format like 0b1101, 0o5.3, 0xff or a format like 1101_2. The latter does not support letters, as they would be interpreted as variables
  • 21 |
  • Misc: separate expressions by a semicolon to write them on the same line, use the ans variable to get the value of the previously calculated expression
  • 22 |
23 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@paddim8/kalk-component", 3 | "version": "2.1.0", 4 | "description": "A Svelte component for kalk, a calculator that supports user-defined functions and variables.", 5 | "svelte": "src/main.ts", 6 | "main": "public/build/bundle.js", 7 | "module": "public/build/bundle.js", 8 | "author": "PaddiM8", 9 | "license": "MIT", 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/PaddiM8/kalk" 13 | }, 14 | "files": [ 15 | "src", 16 | "public/build" 17 | ], 18 | "scripts": { 19 | "build": "cross-env NODE_ENV=production webpack", 20 | "dev": "NODE_OPTIONS=--openssl-legacy-provider webpack-dev-server --content-base public --hot", 21 | "validate": "svelte-check", 22 | "prepublishOnly": "cross-env NODE_ENV=production webpack" 23 | }, 24 | "devDependencies": { 25 | "@babel/core": "^7.11.5", 26 | "@babel/plugin-transform-runtime": "^7.11.5", 27 | "@babel/preset-env": "^7.11.5", 28 | "@babel/runtime": "^7.11.2", 29 | "@types/mini-css-extract-plugin": "^0.9.0", 30 | "@types/node": "^14.14.7", 31 | "@types/optimize-css-assets-webpack-plugin": "^5.0.1", 32 | "@types/terser-webpack-plugin": "^4.2.0", 33 | "@types/webpack": "^4.41.25", 34 | "@types/webpack-dev-server": "^3.11.1", 35 | "autoprefixer": "^9.8.6", 36 | "babel-loader": "^8.1.0", 37 | "clean-webpack-plugin": "^3.0.0", 38 | "cross-env": "^7.0.2", 39 | "css-loader": "^4.2.0", 40 | "mini-css-extract-plugin": "^0.9.0", 41 | "optimize-css-assets-webpack-plugin": "^5.0.3", 42 | "postcss-loader": "^3.0.0", 43 | "sass": "^1.26.10", 44 | "sass-loader": "^10.0.1", 45 | "svelte": "^3.29.7", 46 | "svelte-check": "^1.1.15", 47 | "svelte-loader-hot": "^0.3.1", 48 | "svelte-preprocess": "^4.6.1", 49 | "terser-webpack-plugin": "^4.2.3", 50 | "ts-loader": "^8.0.2", 51 | "ts-node": "^10.8.0", 52 | "typescript": "^4.7.2", 53 | "webpack": "^4.44.1", 54 | "webpack-cli": "^3.3.12", 55 | "webpack-dev-server": "^3.11.0" 56 | }, 57 | "dependencies": { 58 | "@paddim8/kalk": "^3.1.0", 59 | "shadow-selection-polyfill": "^1.1.0" 60 | }, 61 | "browserslist": [ 62 | "defaults" 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /kalk/src/radix.rs: -------------------------------------------------------------------------------- 1 | pub fn parse_float_radix(value: &str, radix: u8) -> Option { 2 | if radix == 10 { 3 | return if let Ok(result) = value.parse::() { 4 | Some(result) 5 | } else { 6 | None 7 | }; 8 | } 9 | 10 | let mut sum = 0f64; 11 | let length = value.find('_').unwrap_or(value.len()); 12 | let mut i = (value.find('.').unwrap_or(length) as i32) - 1; 13 | for c in value.chars() { 14 | if c == '_' { 15 | break; 16 | } 17 | 18 | if c == '.' { 19 | continue; 20 | } 21 | 22 | let digit = c.to_digit(radix as u32)? as f64; 23 | sum += digit * (radix as f64).powi(i); 24 | i -= 1; 25 | } 26 | 27 | Some(sum) 28 | } 29 | 30 | const DIGITS: &str = "0123456789abcdefghijklmnopqrstuvwxyz"; 31 | pub fn int_to_radix(value: i64, radix: u8) -> String { 32 | let mut num = value.abs(); 33 | let mut result_str = String::new(); 34 | while num > 0 { 35 | let digit_index = (num % radix as i64) as usize; 36 | result_str.insert(0, DIGITS.as_bytes()[digit_index] as char); 37 | num /= radix as i64; 38 | } 39 | 40 | if result_str.is_empty() { 41 | return String::from("0"); 42 | } 43 | 44 | let sign = if value.is_positive() { "" } else { "-" }; 45 | format!("{}{}", sign, result_str) 46 | } 47 | 48 | pub fn float_to_radix(value: f64, radix: u8) -> String { 49 | let mut result = int_to_radix(value.floor() as i64, radix); 50 | let fract = value.fract(); 51 | if fract != 0f64 { 52 | result.push('.'); 53 | let precision = 10; 54 | let fract_digits = (fract * (radix as i64).pow(precision) as f64) as i64; 55 | result.push_str(int_to_radix(fract_digits, radix).trim_end_matches('0')) 56 | } 57 | 58 | result 59 | } 60 | 61 | pub fn to_radix_pretty(value: f64, radix: u8) -> String { 62 | if radix == 10 { 63 | crate::kalk_value::format_number(value) 64 | } else { 65 | format!( 66 | "{}{}", 67 | float_to_radix(value, radix), 68 | crate::text_utils::normal_to_subscript(radix.to_string().chars()) 69 | ) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /kalk/src/test_helpers.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use crate::ast::Expr; 3 | use crate::ast::Identifier; 4 | use crate::ast::Stmt; 5 | use crate::lexer::Token; 6 | use crate::lexer::TokenKind; 7 | 8 | pub fn token(kind: TokenKind, value: &str) -> Token { 9 | Token { 10 | kind, 11 | value: value.into(), 12 | span: (0, 0), 13 | } 14 | } 15 | 16 | pub fn cmp(x: f64, y: f64) -> bool { 17 | (x - y).abs() < 0.0001 18 | } 19 | 20 | pub fn f64_to_float_literal(x: f64) -> Box { 21 | literal(crate::float!(x)) 22 | } 23 | 24 | #[cfg(feature = "rug")] 25 | pub fn literal(value: rug::Float) -> Box { 26 | Box::new(Expr::Literal(value)) 27 | } 28 | #[cfg(not(feature = "rug"))] 29 | pub fn literal(value: f64) -> Box { 30 | Box::new(Expr::Literal(value)) 31 | } 32 | 33 | pub fn var(identifier: &str) -> Box { 34 | Box::new(Expr::Var(Identifier::from_full_name(identifier))) 35 | } 36 | 37 | pub fn param_var(function: &str, identifier: &str) -> Box { 38 | Box::new(Expr::Var(Identifier::parameter_from_name( 39 | identifier, function, 40 | ))) 41 | } 42 | 43 | pub fn fn_call(identifier: &str, arguments: Vec) -> Box { 44 | Box::new(Expr::FnCall( 45 | Identifier::from_full_name(identifier), 46 | arguments, 47 | )) 48 | } 49 | 50 | pub fn binary(left: Box, op: TokenKind, right: Box) -> Box { 51 | Box::new(Expr::Binary(left, op, right)) 52 | } 53 | 54 | pub fn unary(op: TokenKind, expr: Box) -> Box { 55 | Box::new(Expr::Unary(op, expr)) 56 | } 57 | 58 | pub fn group(expr: Box) -> Box { 59 | Box::new(Expr::Group(expr)) 60 | } 61 | 62 | pub fn unit(identifier: &str, expr: Box) -> Box { 63 | Box::new(Expr::Unit(identifier.into(), expr)) 64 | } 65 | 66 | pub fn var_decl(identifier: &str, value: Box) -> Stmt { 67 | Stmt::VarDecl(Identifier::from_full_name(identifier), value) 68 | } 69 | 70 | pub fn fn_decl(identifier: &str, parameters: Vec, value: Box) -> Stmt { 71 | Stmt::FnDecl(Identifier::from_full_name(identifier), parameters, value) 72 | } 73 | 74 | pub fn unit_decl(unit: &str, base_unit: &str, expr: Box) -> Stmt { 75 | Stmt::UnitDecl(unit.into(), base_unit.into(), expr) 76 | } 77 | -------------------------------------------------------------------------------- /kalk/README.md: -------------------------------------------------------------------------------- 1 | # kalk 2 | Kalk is a math parser library that supports user-defined variables and functions. An example of what it can parse: 3 | 4 | ``` 5 | f(x, y) = sum(1, 3, (2sin4/x!)^y) + cos(n deg) 6 | a = 3 7 | f(a, 2) 8 | ``` 9 | `>> 1.1899401098014355` 10 | 11 | ## Features 12 | * Operators: `+`, `-`, `*`, `/`, `!` 13 | * Groups: `()`, `[]`, `⌈ceil⌉`, `⌊floor⌋` 14 | * [Vectors](https://kalker.xyz/#vectors): (x, y, z, ...) 15 | * [Matrices](https://kalker.xyz/#matrices): [x, y, z; a, b, c; ...] 16 | * [Pre-defined functions and constants](https://kalker.xyz/#functions) 17 | * User-defined functions and variables. `f(x, y) = xy`, `x = 5` 18 | * Root finding using Newton's method (eg. x^2 = 64). Note: estimation and limited to one root 19 | * Derivative of functions (derivatives of noisy functions or of higher order can be a bit inaccurate). `f'(2)`, `sin'(-pi)` 20 | * Integration. `∫(0, pi, sin(x) dx)` or `∫(0, π, sin(x) dx)`, maybe sometimes be slightly off 21 | * Understands fairly ambiguous syntax. Eg. `2sin50 + 2xy` 22 | * Sum function: `sum(start, to, expression)` Eg. `sum(1, 3, 2n+1)` is the same as `2*1+1 + 2*2+1 + 2*3+1` = `15` 23 | * Piecewise functions: `f(x) = { f(x + 1) if x <= 1; x otherwise }`, pressing enter before typing the final `}` will make a new line without submitting 24 | * Different number bases: Either with a format like `0b1101`, `0o5.3`, `0xff` or a format like `1101_2`. The latter does not support letters, as they would be interpreted as variables 25 | * Misc: separate expressions by a semicolon to write them on the same line, use the `ans` variable to get the value of the previously calculated expression 26 | 27 | ## Rust Usage 28 | ```rust 29 | use kalk::parser; 30 | let mut parser_context = parser::Context::new(); 31 | let precision = 53; 32 | let result = parser::eval(&mut parser_context, "5*3", precision).unwrap().unwrap(); 33 | assert_eq!(result.to_f64(), 15f64); 34 | ``` 35 | 36 | ### Using f64 instead of rug::Float 37 | The cargo feature `rug` enables rug, and is used by default. If you disable this, kalk will use `f64` instead, making it more portable. 38 | 39 | ### Compiling 40 | Make sure you have `diffutils` `gcc` `make` and `m4` installed. 41 | 42 | ## JavaScript Usage 43 | ```js 44 | const kalk = await import("@paddim8/kalk"); 45 | const context = new kalk.Context(); 46 | console.log(context.evaluate("2pi + 3").toScientificNotation().toString()); 47 | ``` 48 | -------------------------------------------------------------------------------- /res/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /mobile/.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 21 | 22 | 23 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 1614028812239 42 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /kalk/src/text_utils.rs: -------------------------------------------------------------------------------- 1 | pub fn is_superscript(c: &char) -> bool { 2 | matches!( 3 | c, 4 | '⁰' | '¹' 5 | | '²' 6 | | '³' 7 | | '⁴' 8 | | '⁵' 9 | | '⁶' 10 | | '⁷' 11 | | '⁸' 12 | | '⁹' 13 | | '⁺' 14 | | '⁻' 15 | | '⁼' 16 | | '⁽' 17 | | '⁾' 18 | | 'ᵀ' 19 | ) 20 | } 21 | 22 | pub fn is_subscript(c: &char) -> bool { 23 | matches!( 24 | c, 25 | '₀' | '₁' 26 | | '₂' 27 | | '₃' 28 | | '₄' 29 | | '₅' 30 | | '₆' 31 | | '₇' 32 | | '₈' 33 | | '₉' 34 | | '₊' 35 | | '₋' 36 | | '₌' 37 | | '₍' 38 | | '₎' 39 | | 'ₖ' 40 | | 'ₗ' 41 | | 'ₘ' 42 | | 'ₙ' 43 | | 'ₓ' 44 | ) 45 | } 46 | 47 | pub fn parse_subscript(chars: impl Iterator) -> Option { 48 | if let Ok(result) = subscript_to_normal(chars).parse::() { 49 | Some(result) 50 | } else { 51 | None 52 | } 53 | } 54 | 55 | pub fn subscript_to_normal(chars: impl Iterator) -> String { 56 | let mut regular = String::new(); 57 | for c in chars { 58 | regular.push(match c { 59 | '₀' => '0', 60 | '₁' => '1', 61 | '₂' => '2', 62 | '₃' => '3', 63 | '₄' => '4', 64 | '₅' => '5', 65 | '₆' => '6', 66 | '₇' => '7', 67 | '₈' => '8', 68 | '₉' => '9', 69 | '₊' => '+', 70 | '₋' => '-', 71 | '₌' => '=', 72 | '₍' => '(', 73 | '₎' => ')', 74 | 'ₖ' => 'k', 75 | 'ₗ' => 'l', 76 | 'ₘ' => 'm', 77 | 'ₙ' => 'n', 78 | 'ᵀ' => 'T', 79 | _ => c, 80 | }); 81 | } 82 | 83 | return regular.trim().to_string(); 84 | } 85 | 86 | pub fn normal_to_subscript(chars: impl Iterator) -> String { 87 | let mut subscript = String::new(); 88 | for c in chars { 89 | subscript.push(match c { 90 | '0' => '₀', 91 | '1' => '₁', 92 | '2' => '₂', 93 | '3' => '₃', 94 | '4' => '₄', 95 | '5' => '₅', 96 | '6' => '₆', 97 | '7' => '₇', 98 | '8' => '₈', 99 | '9' => '₉', 100 | '+' => '₊', 101 | '-' => '₋', 102 | '=' => '₌', 103 | '(' => '₍', 104 | ')' => '₎', 105 | 'k' => 'ₖ', 106 | 'l' => 'ₗ', 107 | 'm' => 'ₘ', 108 | 'n' => 'ₙ', 109 | 'x' => 'ₓ', 110 | _ => c, 111 | }); 112 | } 113 | 114 | subscript 115 | } 116 | -------------------------------------------------------------------------------- /kalk/src/calculation_result.rs: -------------------------------------------------------------------------------- 1 | use wasm_bindgen::prelude::wasm_bindgen; 2 | 3 | use crate::kalk_value::{ComplexNumberType, KalkValue, ScientificNotation, ScientificNotationFormat}; 4 | 5 | #[wasm_bindgen] 6 | pub struct CalculationResult { 7 | value: KalkValue, 8 | radix: u8, 9 | is_approximation: bool, 10 | } 11 | 12 | // Wraps around KalkValue since enums don't work 13 | // with the javascript bindings. 14 | #[wasm_bindgen] 15 | impl CalculationResult { 16 | pub(crate) fn new(value: KalkValue, radix: u8, is_approximation: bool) -> Self { 17 | CalculationResult { 18 | value, 19 | radix, 20 | is_approximation, 21 | } 22 | } 23 | 24 | #[allow(dead_code)] 25 | pub(crate) fn get_value(self) -> KalkValue { 26 | self.value 27 | } 28 | 29 | #[wasm_bindgen(js_name = toString)] 30 | pub fn to_js_string(&self) -> String { 31 | self.to_string() 32 | } 33 | 34 | #[wasm_bindgen(js_name = toStringBig)] 35 | pub fn to_string_big(&self) -> String { 36 | self.value.to_string_big() 37 | } 38 | 39 | #[wasm_bindgen(js_name = toPrettyStringWithFormat)] 40 | pub fn to_string_pretty_format(&self, format: ScientificNotationFormat) -> String { 41 | let value = if self.radix == 10 { 42 | self.value.to_string_pretty_radix(10, format) 43 | } else { 44 | format!( 45 | "{}\n{}", 46 | self.value.to_string_pretty_radix(10, format), 47 | self.value.to_string_pretty_radix(self.radix, format), 48 | ) 49 | }; 50 | 51 | if self.is_approximation { 52 | format!("≈ {}", value) 53 | } else { 54 | value 55 | } 56 | } 57 | 58 | #[wasm_bindgen(js_name = toPrettyString)] 59 | pub fn to_string_pretty(&self) -> String { 60 | self.to_string_pretty_format(ScientificNotationFormat::Normal) 61 | } 62 | 63 | #[wasm_bindgen(js_name = getValue)] 64 | pub fn to_f64(&self) -> f64 { 65 | self.value.to_f64() 66 | } 67 | 68 | #[wasm_bindgen(js_name = getImaginaryValue)] 69 | pub fn imaginary_to_f64(&self) -> f64 { 70 | self.value.imaginary_to_f64() 71 | } 72 | 73 | #[wasm_bindgen(js_name = setRadix)] 74 | pub fn set_radix(&mut self, radix: u8) -> bool { 75 | if radix <= 1 || radix >= 50 { 76 | return false; 77 | } 78 | 79 | self.radix = radix; 80 | 81 | true 82 | } 83 | 84 | #[wasm_bindgen(js_name = toScientificNotation)] 85 | pub fn to_scientific_notation_js( 86 | &self, 87 | complex_number_type: ComplexNumberType, 88 | ) -> ScientificNotation { 89 | self.value.to_scientific_notation(complex_number_type) 90 | } 91 | 92 | #[wasm_bindgen(js_name = estimate)] 93 | pub fn estimate_js(&self) -> Option { 94 | self.value.estimate() 95 | } 96 | } 97 | 98 | impl std::fmt::Display for CalculationResult { 99 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 100 | write!(f, "{}", self.value) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /mobile/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /cli/wix/main.wxs: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /kalk/src/prelude/with_rug.rs: -------------------------------------------------------------------------------- 1 | pub mod special_funcs { 2 | use crate::{as_number_or_return, errors::KalkError, float, prelude::KalkValue}; 3 | 4 | pub fn factorial(x: KalkValue) -> Result { 5 | let (real, _, unit) = as_number_or_return!(x); 6 | 7 | Ok(KalkValue::Number((real + 1f64).gamma(), float!(0), unit)) 8 | } 9 | } 10 | 11 | pub(crate) mod funcs { 12 | use crate::errors::KalkError; 13 | use crate::kalk_value::KalkValue; 14 | use crate::prelude::funcs::abs; 15 | use crate::{as_number_or_return, float}; 16 | 17 | pub fn arg(x: KalkValue) -> Result { 18 | let (real, imaginary, unit) = as_number_or_return!(x); 19 | 20 | Ok(KalkValue::Number(imaginary.atan2(&real), float!(0), unit)) 21 | } 22 | 23 | pub fn gamma(x: KalkValue) -> Result { 24 | let (real, _, unit) = as_number_or_return!(x); 25 | 26 | Ok(KalkValue::Number(real.gamma(), float!(0), unit)) 27 | } 28 | 29 | pub fn bitcmp(x: KalkValue) -> Result { 30 | let (real, _, _) = as_number_or_return!(x); 31 | 32 | Ok(KalkValue::from( 33 | !real.to_i32_saturating().unwrap_or(i32::MAX), 34 | )) 35 | } 36 | 37 | pub fn bitand(x: KalkValue, y: KalkValue) -> Result { 38 | let (real, _, _) = as_number_or_return!(x); 39 | let (real_rhs, _, _) = as_number_or_return!(y); 40 | 41 | Ok(KalkValue::from( 42 | real.to_i32_saturating().unwrap_or(i32::MAX) 43 | & real_rhs.to_i32_saturating().unwrap_or(i32::MAX), 44 | )) 45 | } 46 | 47 | pub fn bitor(x: KalkValue, y: KalkValue) -> Result { 48 | let (real, _, _) = as_number_or_return!(x); 49 | let (real_rhs, _, _) = as_number_or_return!(y); 50 | 51 | Ok(KalkValue::from( 52 | real.to_i32_saturating().unwrap_or(i32::MAX) 53 | | real_rhs.to_i32_saturating().unwrap_or(i32::MAX), 54 | )) 55 | } 56 | 57 | pub fn bitxor(x: KalkValue, y: KalkValue) -> Result { 58 | let (real, _, _) = as_number_or_return!(x); 59 | let (real_rhs, _, _) = as_number_or_return!(y); 60 | 61 | Ok(KalkValue::from( 62 | real.to_i32_saturating().unwrap_or(i32::MAX) 63 | ^ real_rhs.to_i32_saturating().unwrap_or(i32::MAX), 64 | )) 65 | } 66 | 67 | pub fn bitshift(x: KalkValue, y: KalkValue) -> Result { 68 | let (real, _, _) = as_number_or_return!(x); 69 | let (real_rhs, _, _) = as_number_or_return!(y); 70 | 71 | let x = real.to_i32_saturating().unwrap_or(i32::MAX) as i32; 72 | let y = real_rhs.to_i32_saturating().unwrap_or(i32::MAX) as i32; 73 | if y < 0 { 74 | Ok(KalkValue::from(x >> y.abs())) 75 | } else { 76 | Ok(KalkValue::from(x << y)) 77 | } 78 | } 79 | 80 | pub fn hypot(x: KalkValue, y: KalkValue) -> Result { 81 | let is_complex = x.has_imaginary() || y.has_imaginary(); 82 | let (real, imaginary, unit) = as_number_or_return!(x); 83 | let (real_rhs, imaginary_rhs, unit_rhs) = as_number_or_return!(y); 84 | if is_complex { 85 | let abs_x = abs(KalkValue::Number(real, imaginary, unit))?; 86 | let abs_y = abs(KalkValue::Number(real_rhs, imaginary_rhs, unit_rhs))?; 87 | crate::prelude::funcs::sqrt( 88 | abs_x 89 | .clone() 90 | .mul_without_unit(&abs_x)? 91 | .add_without_unit(&abs_y.clone().mul_without_unit(&abs_y)?)?, 92 | ) 93 | } else { 94 | Ok(KalkValue::Number(real.hypot(&real_rhs), float!(0), unit)) 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /kalk/src/symbol_table.rs: -------------------------------------------------------------------------------- 1 | use crate::{ast::Expr, ast::Identifier, ast::Stmt, prelude}; 2 | use std::collections::HashMap; 3 | 4 | #[derive(Debug)] 5 | pub struct SymbolTable { 6 | pub(crate) hashmap: HashMap, 7 | pub(crate) unit_types: HashMap, 8 | } 9 | 10 | impl SymbolTable { 11 | pub fn new() -> Self { 12 | let mut symbol_table = SymbolTable { 13 | hashmap: HashMap::new(), 14 | unit_types: HashMap::new(), 15 | }; 16 | 17 | // i = sqrt(-1) 18 | symbol_table.insert(Stmt::VarDecl( 19 | Identifier::from_full_name("i"), 20 | Box::new(Expr::FnCall( 21 | Identifier::from_full_name("sqrt"), 22 | vec![Expr::Literal(crate::float!(-1f64))], 23 | )), 24 | )); 25 | 26 | symbol_table 27 | } 28 | 29 | pub fn insert(&mut self, value: Stmt) -> &mut Self { 30 | match &value { 31 | Stmt::VarDecl(identifier, _) => { 32 | self.hashmap 33 | .insert(format!("var.{}", identifier.full_name), value); 34 | } 35 | Stmt::UnitDecl(identifier, to_unit, _) => { 36 | self.unit_types.insert(identifier.to_string(), ()); 37 | self.unit_types.insert(to_unit.to_string(), ()); 38 | self.hashmap 39 | .insert(format!("unit.{}.{}", identifier, to_unit), value); 40 | } 41 | Stmt::FnDecl(identifier, _, _) => { 42 | self.hashmap 43 | .insert(format!("fn.{}", identifier.full_name), value); 44 | } 45 | _ => panic!("Can only insert VarDecl, UnitDecl and FnDecl into symbol table."), 46 | } 47 | 48 | self 49 | } 50 | 51 | pub fn get_var(&self, key: &str) -> Option<&Stmt> { 52 | self.hashmap.get(&format!("var.{}", key)) 53 | } 54 | 55 | pub fn get_unit(&self, key: &str, to_unit: &str) -> Option<&Stmt> { 56 | self.hashmap.get(&format!("unit.{}.{}", key, to_unit)) 57 | } 58 | 59 | pub fn get_fn(&self, key: &str) -> Option<&Stmt> { 60 | self.hashmap.get(&format!("fn.{}", key)) 61 | } 62 | 63 | pub fn set(&mut self, value: Stmt) { 64 | let existing_item = match &value { 65 | Stmt::VarDecl(identifier, _) => self 66 | .hashmap 67 | .get_mut(&format!("var.{}", identifier.full_name)), 68 | Stmt::UnitDecl(identifier, to_unit, _) => self 69 | .hashmap 70 | .get_mut(&format!("unit.{}.{}", identifier, to_unit)), 71 | Stmt::FnDecl(identifier, _, _) => self 72 | .hashmap 73 | .get_mut(&format!("fn.{}", identifier.full_name)), 74 | _ => panic!("Can only set VarDecl, UnitDecl and FnDecl in symbol table."), 75 | }; 76 | 77 | if let Some(stmt) = existing_item { 78 | *stmt = value; 79 | } else { 80 | self.insert(value); 81 | } 82 | } 83 | 84 | pub fn get_and_remove_fn(&mut self, identifier: &str) -> Option { 85 | self.hashmap.remove(&format!("fn.{}", identifier)) 86 | } 87 | 88 | pub fn get_and_remove_var(&mut self, identifier: &str) -> Option { 89 | self.hashmap.remove(&format!("var.{}", identifier)) 90 | } 91 | 92 | pub fn contains_var(&self, identifier: &str) -> bool { 93 | prelude::is_constant(identifier) 94 | || identifier == "i" 95 | || self.hashmap.contains_key(&format!("var.{}", identifier)) 96 | } 97 | 98 | pub fn contains_unit(&self, identifier: &str) -> bool { 99 | self.unit_types.contains_key(identifier) 100 | } 101 | 102 | pub fn contains_fn(&self, identifier: &str) -> bool { 103 | prelude::is_prelude_func(identifier) 104 | || self.hashmap.contains_key(&format!("fn.{}", identifier)) 105 | } 106 | } 107 | 108 | impl Default for SymbolTable { 109 | fn default() -> Self { 110 | Self::new() 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /kalk/src/ast.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::TokenKind; 2 | 3 | /// A tree structure of a statement. 4 | #[derive(Debug, Clone, PartialEq)] 5 | pub enum Stmt { 6 | VarDecl(Identifier, Box), 7 | FnDecl(Identifier, Vec, Box), 8 | UnitDecl(String, String, Box), 9 | /// For simplicity, expressions can be put into statements. This is the form in which expressions are passed to the interpreter. 10 | Expr(Box), 11 | } 12 | 13 | /// A tree structure of an expression. 14 | #[derive(Debug, Clone, PartialEq)] 15 | pub enum Expr { 16 | Binary(Box, TokenKind, Box), 17 | Unary(TokenKind, Box), 18 | Unit(String, Box), 19 | Var(Identifier), 20 | Group(Box), 21 | FnCall(Identifier, Vec), 22 | #[cfg(feature="rug")] 23 | Literal(rug::Float), 24 | #[cfg(not(feature="rug"))] 25 | Literal(f64), 26 | Boolean(bool), 27 | Piecewise(Vec), 28 | Vector(Vec), 29 | Matrix(Vec>), 30 | Indexer(Box, Vec), 31 | Comprehension(Box, Vec, Vec), 32 | Equation(Box, Box, Identifier), 33 | } 34 | 35 | #[derive(Debug, Clone, PartialEq)] 36 | pub struct ConditionalPiece { 37 | pub expr: Expr, 38 | pub condition: Expr, 39 | } 40 | 41 | #[derive(Debug, Clone, PartialEq)] 42 | pub struct RangedVar { 43 | pub name: String, 44 | pub max: Expr, 45 | pub min: Expr, 46 | } 47 | 48 | #[derive(Debug, Clone, PartialEq, Eq)] 49 | pub struct Identifier { 50 | pub full_name: String, 51 | pub pure_name: String, 52 | pub parameter_of_function: Option, 53 | pub prime_count: u32, 54 | } 55 | 56 | impl Identifier { 57 | pub fn from_full_name(full_name: &str) -> Self { 58 | let (pure_name, prime_count) = separate_identifier_and_prime(full_name); 59 | 60 | Identifier { 61 | full_name: full_name.to_string(), 62 | pure_name, 63 | parameter_of_function: None, 64 | prime_count, 65 | } 66 | } 67 | 68 | pub fn from_name_and_primes(pure_name: &str, prime_count: u32) -> Self { 69 | Identifier { 70 | full_name: format!("{}{}", pure_name, "'".repeat(prime_count as usize)), 71 | pure_name: pure_name.into(), 72 | parameter_of_function: None, 73 | prime_count, 74 | } 75 | } 76 | 77 | pub fn parameter_from_name(name: &str, function: &str) -> Self { 78 | Identifier { 79 | full_name: format!("{}-{}", function, name), 80 | pure_name: name.into(), 81 | parameter_of_function: Some(function.into()), 82 | prime_count: 0u32, 83 | } 84 | } 85 | 86 | pub fn get_name_without_lowered(&self) -> &str { 87 | if let Some(underscore_pos) = self.pure_name.find('_') { 88 | &self.pure_name[0..underscore_pos] 89 | } else { 90 | &self.pure_name 91 | } 92 | } 93 | 94 | pub fn get_lowered_part(&self) -> Option<&str> { 95 | if let Some(underscore_pos) = self.pure_name.find('_') { 96 | Some(&self.pure_name[underscore_pos + 1..]) 97 | } else { 98 | None 99 | } 100 | } 101 | } 102 | 103 | pub fn build_literal_ast(kalk_value: &crate::kalk_value::KalkValue) -> Expr { 104 | if kalk_value.has_imaginary() { 105 | Expr::Binary( 106 | Box::new(Expr::Literal(kalk_value.to_float())), 107 | TokenKind::Plus, 108 | Box::new(Expr::Binary( 109 | Box::new(Expr::Literal(kalk_value.imaginary_to_float())), 110 | TokenKind::Star, 111 | Box::new(Expr::Var(Identifier::from_full_name("i"))), 112 | )), 113 | ) 114 | } else { 115 | Expr::Literal(kalk_value.to_float()) 116 | } 117 | } 118 | 119 | fn separate_identifier_and_prime(identifier: &str) -> (String, u32) { 120 | let mut prim_count = 0; 121 | let mut pure_identifier = identifier.to_string(); 122 | 123 | loop { 124 | if pure_identifier.ends_with('\'') { 125 | pure_identifier.pop(); 126 | prim_count += 1; 127 | } else { 128 | return (pure_identifier, prim_count); 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /cli/src/main.rs: -------------------------------------------------------------------------------- 1 | mod output; 2 | mod repl; 3 | 4 | use kalk::kalk_value::ScientificNotationFormat; 5 | use kalk::parser; 6 | use seahorse::{App, Context, Flag, FlagType}; 7 | use std::env; 8 | use std::fs::File; 9 | use std::io::Read; 10 | 11 | fn main() { 12 | let args: Vec = env::args().collect(); 13 | let app = App::new("kalker") 14 | .author(env!("CARGO_PKG_AUTHORS")) 15 | .version(env!("CARGO_PKG_VERSION")) 16 | .usage("kalker [options] [input]") 17 | .action(default_action) 18 | .flag( 19 | Flag::new("input-file", FlagType::String) 20 | .description("Load a file with predefined variables and functions. End lines with a semicolon.") 21 | .alias("i"), 22 | ) 23 | .flag( 24 | Flag::new("precision", FlagType::Int) 25 | .description("Specify number precision") 26 | .alias("p"), 27 | ) 28 | .flag( 29 | Flag::new("eng", FlagType::Bool) 30 | .description("Engineering mode. Modes can also be switched between by typing `mode [normal|eng]`.") 31 | ) 32 | .flag( 33 | Flag::new("angle-unit", FlagType::String) 34 | .description("Unit used for angles, either rad or deg. This can also be specified using an environment variable with the name 'ANGLE_UNIT'.") 35 | .alias("a"), 36 | ) 37 | .flag( 38 | Flag::new("max-recursion-depth", FlagType::Int) 39 | .description("The maximum allowed recursion depth. This is used to avoid crashes."), 40 | ); 41 | 42 | app.run(args); 43 | } 44 | 45 | fn default_action(context: &Context) { 46 | #[cfg(windows)] 47 | ansi_term::enable_ansi_support().unwrap_or_default(); 48 | 49 | let angle_unit = if let Ok(angle_unit) = context.string_flag("angle-unit") { 50 | match angle_unit.as_ref() { 51 | "rad" | "deg" => angle_unit, 52 | _ => { 53 | output::print_err("Invalid angle unit. Expected 'rad' or 'deg'."); 54 | std::process::exit(1); 55 | } 56 | } 57 | } else { 58 | get_env_angle_unit() 59 | }; 60 | let mut parser_context = parser::Context::new() 61 | .set_angle_unit(&angle_unit) 62 | .set_timeout(None); 63 | let precision = context 64 | .int_flag("precision") 65 | .unwrap_or(output::DEFAULT_PRECISION as isize) as u32; 66 | let format = if context.bool_flag("eng") { 67 | ScientificNotationFormat::Engineering 68 | } else { 69 | ScientificNotationFormat::Normal 70 | }; 71 | 72 | if let Ok(max_recursion_depth) = context.int_flag("max-recursion-depth") { 73 | parser_context = parser_context.set_max_recursion_depth(max_recursion_depth as u32); 74 | } 75 | 76 | if let Some(input_file_path) = get_input_file_by_name("default") { 77 | load_input_file(&input_file_path, precision, &mut parser_context); 78 | } 79 | 80 | if let Ok(input_file_path) = context.string_flag("input-file") { 81 | load_input_file(&input_file_path, precision, &mut parser_context); 82 | } 83 | 84 | if context.args.is_empty() { 85 | // REPL 86 | repl::start(&mut parser_context, precision, format); 87 | } else { 88 | // Direct output 89 | output::eval( 90 | &mut parser_context, 91 | &context.args.join(" "), 92 | precision, 93 | 10u8, 94 | format, 95 | ); 96 | } 97 | } 98 | 99 | pub(crate) fn get_input_file_by_name(name: &str) -> Option { 100 | let mut path = dirs::config_dir()?; 101 | path.push("kalker"); 102 | path.push(name); 103 | path.set_extension("kalker"); 104 | 105 | if path.exists() { 106 | Some(path.to_str()?.to_string()) 107 | } else { 108 | None 109 | } 110 | } 111 | 112 | pub fn load_input_file(file_name: &str, precision: u32, parser_context: &mut parser::Context) { 113 | let mut file_content = String::new(); 114 | File::open(file_name) 115 | .expect("Couldn't find file.") 116 | .read_to_string(&mut file_content) 117 | .expect("Failed to read input file."); 118 | 119 | // Parse the input file content, resulting in the symbol table being filled out. 120 | // Output is not needed here. 121 | if let Err(error) = parser::eval(parser_context, &file_content, precision) { 122 | eprintln!("{}", error.to_string()); 123 | } 124 | } 125 | 126 | fn get_env_angle_unit() -> String { 127 | if let Ok(angle_unit_var) = env::var("ANGLE_UNIT") { 128 | angle_unit_var 129 | } else { 130 | String::from("rad") 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /kalk/src/prelude/regular.rs: -------------------------------------------------------------------------------- 1 | pub mod special_funcs { 2 | use crate::{as_number_or_return, errors::KalkError, float, kalk_value::KalkValue}; 3 | 4 | pub fn factorial(x: KalkValue) -> Result { 5 | let (real, _, unit) = as_number_or_return!(x); 6 | 7 | // Round it a bit, to prevent floating point errors. 8 | Ok(KalkValue::Number( 9 | (super::funcs::precise_gamma(real + 1f64) * 10e6f64).round() / 10e6f64, 10 | float!(0), 11 | unit, 12 | )) 13 | } 14 | } 15 | 16 | pub(crate) mod funcs { 17 | use crate::errors::KalkError; 18 | use crate::kalk_value::KalkValue; 19 | use crate::prelude::funcs::abs; 20 | use crate::{as_number_or_return, float}; 21 | 22 | pub fn arg(x: KalkValue) -> Result { 23 | let (real, imaginary, unit) = as_number_or_return!(x); 24 | 25 | // i(ln|x| - ln(x)) 26 | Ok(KalkValue::Number(imaginary.atan2(real), float!(0), unit)) 27 | } 28 | 29 | pub fn gamma(x: KalkValue) -> Result { 30 | let (real, _, unit) = as_number_or_return!(x); 31 | 32 | // Round it a bit, to prevent floating point errors. 33 | Ok(KalkValue::Number( 34 | (precise_gamma(real) * 10e6f64).round() / 10e6f64, 35 | float!(0), 36 | unit, 37 | )) 38 | } 39 | 40 | // Matthias Eiholzer - https://gitlab.com/matthiaseiholzer/mathru/-/tree/master 41 | pub(super) fn precise_gamma(x: f64) -> f64 { 42 | let pi = 3.1415926535897932384626433832795028841971693993751058209749445923f64; 43 | if x == 0f64 { 44 | return f64::NAN; 45 | } 46 | 47 | if x < 0.5f64 { 48 | return pi / precise_gamma((pi * x).sin() * (1f64 - x)); 49 | } 50 | 51 | let t = x + 6.5; 52 | let a = 0.99999999999980993 + 676.5203681218851 / x - 1259.1392167224028 / (x + 1f64) 53 | + 771.32342877765313 / (x + 2f64) 54 | - 176.61502916214059 / (x + 3f64) 55 | + 12.507343278686905 / (x + 4f64) 56 | - 0.13857109526572012 / (x + 5f64) 57 | + 9.9843695780195716e-6 / (x + 6f64) 58 | + 1.5056327351493116e-7 / (x + 7f64); 59 | 60 | 2f64.sqrt() * pi.sqrt() * t.powf(x - 0.5f64) * (-t).exp() * a 61 | } 62 | 63 | pub fn bitcmp(x: KalkValue) -> Result { 64 | let (real, _, _) = as_number_or_return!(x); 65 | 66 | Ok(KalkValue::from(!(real.round() as i32))) 67 | } 68 | 69 | pub fn bitand(x: KalkValue, y: KalkValue) -> Result { 70 | let (real, _, _) = as_number_or_return!(x); 71 | let (real_rhs, _, _) = as_number_or_return!(y); 72 | 73 | Ok(KalkValue::from( 74 | real.round() as i32 & real_rhs.round() as i32, 75 | )) 76 | } 77 | 78 | pub fn bitor(x: KalkValue, y: KalkValue) -> Result { 79 | let (real, _, _) = as_number_or_return!(x); 80 | let (real_rhs, _, _) = as_number_or_return!(y); 81 | 82 | Ok(KalkValue::from( 83 | real.round() as i32 | real_rhs.round() as i32, 84 | )) 85 | } 86 | 87 | pub fn bitxor(x: KalkValue, y: KalkValue) -> Result { 88 | let (real, _, _) = as_number_or_return!(x); 89 | let (real_rhs, _, _) = as_number_or_return!(y); 90 | 91 | Ok(KalkValue::from( 92 | real.round() as i32 ^ real_rhs.round() as i32, 93 | )) 94 | } 95 | 96 | pub fn bitshift(x: KalkValue, y: KalkValue) -> Result { 97 | let (real, _, _) = as_number_or_return!(x); 98 | let (real_rhs, _, _) = as_number_or_return!(y); 99 | let x = real.round() as i32; 100 | let y = real_rhs.round() as i32; 101 | if y < 0 { 102 | Ok(KalkValue::from(x >> y.abs())) 103 | } else { 104 | Ok(KalkValue::from(x << y)) 105 | } 106 | } 107 | 108 | pub fn hypot(x: KalkValue, y: KalkValue) -> Result { 109 | let (real, _, unit) = as_number_or_return!(x.clone()); 110 | let (real_rhs, _, _) = as_number_or_return!(y.clone()); 111 | if x.has_imaginary() || y.has_imaginary() { 112 | let abs_x = abs(x)?; 113 | let abs_y = abs(y)?; 114 | crate::prelude::funcs::sqrt( 115 | abs_x 116 | .clone() 117 | .mul_without_unit(&abs_x)? 118 | .add_without_unit(&abs_y.clone().mul_without_unit(&abs_y)?)?, 119 | ) 120 | } else { 121 | Ok(KalkValue::Number(real.hypot(real_rhs), float!(0), unit)) 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /kalk/src/errors.rs: -------------------------------------------------------------------------------- 1 | use crate::lexer::TokenKind; 2 | 3 | /// Error that occured during parsing or evaluation. 4 | #[derive(Debug, Clone, PartialEq)] 5 | pub enum KalkError { 6 | CannotIndexByImaginary, 7 | CanOnlyIndexX, 8 | Expected(String), 9 | ExpectedDx, 10 | ExpectedIf, 11 | ExpectedReal, 12 | IncompatibleTypesForOperation(String, String, String), 13 | IncompatibleVectorsMatrixes, 14 | IncorrectAmountOfArguments(usize, String, usize), 15 | IncorrectAmountOfIndexes(usize, usize), 16 | ItemOfIndexDoesNotExist(Vec), 17 | InconsistentColumnWidths, 18 | InvalidBase, 19 | InvalidComprehension(String), 20 | InvalidNumberLiteral(String), 21 | InvalidOperator, 22 | InvalidUnit, 23 | StackOverflow, 24 | TimedOut, 25 | VariableReferencesItself, 26 | PiecewiseConditionsAreFalse, 27 | EvaluationError(String), 28 | UnexpectedToken(TokenKind, Option), 29 | UnexpectedType(String, Vec), 30 | UndefinedFn(String), 31 | UndefinedVar(String), 32 | UnableToInvert(String), 33 | UnableToSolveEquation, 34 | UnableToOverrideConstant(String), 35 | UnableToParseExpression, 36 | UnrecognizedBase, 37 | Unknown, 38 | WasStmt(crate::ast::Stmt), 39 | } 40 | 41 | impl ToString for KalkError { 42 | fn to_string(&self) -> String { 43 | match self { 44 | KalkError::CannotIndexByImaginary => String::from("Cannot index by imaginary numbers."), 45 | KalkError::CanOnlyIndexX => String::from("Indexing (getting an item with a specific index) is only possible on vectors and matrices."), 46 | KalkError::Expected(description) => format!("Expected: {}", description), 47 | KalkError::ExpectedDx => String::from("Expected eg. dx, to specify for which variable the operation is being done to. Example with integration: ∫(0, 1, x dx) or ∫(0, 1, x, dx). You may need to put parenthesis around the expression before dx/dy/du/etc."), 48 | KalkError::ExpectedIf => String::from("Expected 'if', with a condition after it."), 49 | KalkError::ExpectedReal => String::from("Expected a real value but got imaginary."), 50 | KalkError::IncompatibleTypesForOperation(operation, got1, got2) => format!("Incompatible types for operation '{}': {} and {}.", operation, got1, got2), 51 | KalkError::IncompatibleVectorsMatrixes => String::from("Incompatible vectors/matrixes."), 52 | KalkError::IncorrectAmountOfArguments(expected, func, got) => format!( 53 | "Expected {} arguments for function {}, but got {}.", 54 | expected, func, got 55 | ), 56 | KalkError::IncorrectAmountOfIndexes(expected, got) => format!( 57 | "Expected {} indexes but got {}.", 58 | expected, got 59 | ), 60 | KalkError::ItemOfIndexDoesNotExist(indices) => format!("Item of index ⟦{}⟧ does not exist.", indices.iter().map(|x| x.to_string()).collect::>().join(", ")), 61 | KalkError::InconsistentColumnWidths => String::from("Inconsistent column widths. Matrix columns must be the same size."), 62 | KalkError::InvalidBase => String::from("Invalid base."), 63 | KalkError::InvalidComprehension(x) => format!("Invalid comprehension: {}", x), 64 | KalkError::InvalidNumberLiteral(x) => format!("Invalid number literal: '{}'.", x), 65 | KalkError::InvalidOperator => String::from("Invalid operator."), 66 | KalkError::InvalidUnit => String::from("Invalid unit."), 67 | KalkError::StackOverflow => String::from("Operation recursed too deeply."), 68 | KalkError::TimedOut => String::from("Operation took too long."), 69 | KalkError::VariableReferencesItself => String::from("Variable references itself."), 70 | KalkError::PiecewiseConditionsAreFalse => String::from("All the conditions in the piecewise are false."), 71 | KalkError::EvaluationError(msg) => format!("Evaluation error: {}", msg), 72 | KalkError::UnexpectedToken(got, expected) => { 73 | if let Some(expected) = expected { 74 | format!("Unexpected token: '{:?}', expected '{:?}'.", got, expected) 75 | } else { 76 | format!("Unexpected token: '{:?}'.", got) 77 | } 78 | } 79 | KalkError::UnexpectedType(got, expected) => { 80 | format!("Unexpected type. Got {:?} but expected: {:?}.", got, expected.join(", ")) 81 | } 82 | KalkError::UnableToInvert(msg) => format!("Unable to invert: {}", msg), 83 | KalkError::UndefinedFn(name) => format!("Undefined function: '{}'.", name), 84 | KalkError::UndefinedVar(name) => format!("Undefined variable: '{}'.", name), 85 | KalkError::UnableToParseExpression => String::from("Unable to parse expression."), 86 | KalkError::UnableToSolveEquation => String::from("Unable to solve equation."), 87 | KalkError::UnableToOverrideConstant(name) => format!("Unable to override constant: '{}'.", name), 88 | KalkError::UnrecognizedBase => String::from("Unrecognized base."), 89 | KalkError::Unknown | KalkError::WasStmt(_) => String::from("Unknown error."), 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](logo.png) 2 | 3 | Kalker is a calculator program/website that supports user-defined variables, functions, differentiation, and integration. It runs on Windows, macOS, Linux, Android, and in web browsers (with WebAssembly). 4 | 5 | [![Crates.io](https://img.shields.io/crates/v/kalker)](https://crates.io/crates/kalker) ![npm](https://img.shields.io/npm/v/@paddim8/kalk) [![GitHub](https://img.shields.io/github/license/PaddiM8/kalk)](https://github.com/PaddiM8/kalker/blob/master/LICENSE) [![Docs.rs](https://docs.rs/kalk/badge.svg)](https://docs.rs/kalk/latest/kalk/) ![Build status](https://img.shields.io/github/actions/workflow/status/PaddiM8/kalker/build.yml?branch=master&label=build%20%26%20test) 6 | 7 | [Website - Try it out here!](https://kalker.xyz) 8 | 9 | 10 | 11 | # Features 12 | 13 | * Operators: `+`, `-`, `*`, `/`, `!` 14 | * Groups: `()`, `[]`, `⌈ceil⌉`, `⌊floor⌋` 15 | * [Vectors](https://kalker.xyz/#vectors): (x, y, z, ...) 16 | * [Matrices](https://kalker.xyz/#matrices): [x, y, z; a, b, c; ...] 17 | * [Pre-defined functions and constants](https://kalker.xyz/#functions) 18 | * User-defined functions and variables. `f(x, y) = xy`, `x = 5` 19 | * Root finding using Newton's method (eg. x^2 = 64). Note: estimation and limited to one root 20 | * Derivative of functions (derivatives of noisy functions or of higher order can be a bit inaccurate). `f'(2)`, `sin'(-pi)` 21 | * Integration. `∫(0, pi, sin(x) dx)` or `∫(0, π, sin(x) dx)`, maybe sometimes be slightly off 22 | * Understands fairly ambiguous syntax. Eg. `2sin50 + 2xy` 23 | * Syntax highlighting 24 | * Special-symbol completion on tab. Eg. write `sqrt` and press tab. It will be turned into `√` 25 | * Sum function: `sum(start, to, expression)` Eg. `sum(1, 3, 2n+1)` is the same as `2*1+1 + 2*2+1 + 2*3+1` = `15` 26 | * Piecewise functions: `f(x) = { f(x + 1) if x <= 1; x otherwise }`, pressing enter before typing the final `}` will make a new line without submitting 27 | * Load a file including predefined functions and constants. For example, if you're going to use kalker for physics, you load up your file with physics functions/constants when starting kalker. This is done either using the `-i file` flag or by putting files in a certain directory and then doing `load filename` inside kalker. [More about files here](https://kalker.xyz/#files) 28 | * Different number bases: Either with a format like `0b1101`, `0o5.3`, `0xff` or a format like `1101_2`. The latter does not support letters, as they would be interpreted as variables 29 | * Misc: separate expressions by a semicolon to write them on the same line, use the `ans` variable to get the value of the previously calculated expression 30 | 31 | # Installation 32 | 33 | ## Package managers 34 | 35 | ### macOS 36 | `brew install kalker` 37 | 38 | ### Arch Linux 39 | `kalker` in the AUR, eg. `yay -S kalker` 40 | 41 | ### Nix/NixOS 42 | `kalker` in the [`nixpkgs`](https://search.nixos.org/packages?channel=unstable&show=kalker&from=0&size=50&sort=relevance&type=packages&query=kalker) repository. 43 | The most up to date version is also available as a [`flake`](https://search.nixos.org/flakes?channel=unstable&show=kalker&from=0&size=50&sort=relevance&type=packages&query=kalker). 44 | 45 | ### NetBSD 46 | `pkgin install kalker` (from the [`official repositories`](https://pkgsrc.se/math/kalker)) 47 | 48 | ## Binaries 49 | 50 | Pre-compiled binaries for Linux, Windows, and macOS (64-bit) are available in the [releases page](https://github.com/PaddiM8/kalker/releases). 51 | 52 | ## Compiling 53 | 54 | **Minimum rust version: v1.36.0**. Make sure you have `diffutils` `gcc` `make` and `m4` installed. **If you use windows:** [follow the instructions here](https://docs.rs/gmp-mpfr-sys/1.2.3/gmp_mpfr_sys/index.html#building-on-windows) (don't forget to install `mingw-w64-x86_64-rust` in MSYS2). 55 | 56 | ### Cargo 57 | 58 | Run `cargo install kalker` 59 | 60 | ### Manually 61 | 62 | 1. Go into the `cli` directory. 63 | 2. Run `cargo build --release` 64 | 3. Grab the binary from `targets/release` 65 | 66 | # Libraries 67 | 68 | There are currently three different libraries related to kalker. 69 | 70 | * [kalk](https://crates.io/crates/kalk): The Rust crate that powers it all. 71 | * [@paddim8/kalk](https://www.npmjs.com/package/@paddim8/kalk): JavaScript bindings for `kalk`. This lets you use it in the browser thanks to WebAssembly. 72 | * [@paddim8/kalk-component](https://www.npmjs.com/package/@paddim8/kalk-component): A web component that runs `@paddim8/kalk`, which let's you use kalk in the browser with a command line-like interface. 73 | 74 | # Syntax 75 | 76 | A complete reference can be found on [the website](https://kalker.xyz). 77 | 78 | # Contributing 79 | 80 | ## kalk and cli (Rust) 81 | 82 | After making changes to the kalk library (in `kalk/`), you can easily try them out by going to the root of the project directory, and doing `cargo run`. This will start kalker (cli), with the new changes. If you're using Windows, you will need to [follow the instructions here](https://docs.rs/gmp-mpfr-sys/1.2.3/gmp_mpfr_sys/index.html#building-on-windows), but also make sure to install `mingw-w64-x86_64-rust` in MSYS2. 83 | 84 | All Rust code is expected to be formatted with `rustfmt 85 | 86 | ## web (Svelte, TypeScript, Sass) 87 | 88 | Run: 89 | 1. `npm install` 90 | 2. `npm run dev` - this will automatically re-compile the project when changes are made 91 | 92 | ## mobile (Android) 93 | 94 | Run: 95 | 1. `npm install` 96 | 2. `npm run build` 97 | 3. `npx cap sync` 98 | 4. Build the project using Android Studio, or Gradle directly. 99 | -------------------------------------------------------------------------------- /web/webpack.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import MiniCssExtractPlugin from 'mini-css-extract-plugin'; 3 | import OptimizeCSSAssetsPlugin from 'optimize-css-assets-webpack-plugin'; 4 | import TerserPlugin from 'terser-webpack-plugin'; 5 | import { CleanWebpackPlugin } from 'clean-webpack-plugin'; 6 | import Preprocess from 'svelte-preprocess'; 7 | import webpack from 'webpack'; 8 | import WebpackDevServer from 'webpack-dev-server'; 9 | 10 | const mode = 11 | (process.env.NODE_ENV as 'production' | 'development') || 'development'; 12 | const prod = mode === 'production'; 13 | const sveltePath = path.resolve('node_modules', 'svelte'); 14 | 15 | /** 16 | * Should source maps be generated alongside your production bundle? This will expose your raw source code, so it's 17 | * disabled by default. 18 | */ 19 | const sourceMapsInProduction = false; 20 | 21 | /** 22 | * Should we run Babel on builds? This will transpile your bundle in order to work on your target browsers (see the 23 | * `browserslist` property in your package.json), but will impact bundle size and build speed. 24 | */ 25 | const useBabel = false; 26 | 27 | /** 28 | * Should we run Babel on development builds? If set to `false`, only production builds will be transpiled. If you're 29 | * only testing in modern browsers and don't need transpiling in development, it is recommended to keep this disabled 30 | * as it will greatly speed up your builds. 31 | */ 32 | const useBabelInDevelopment = false; 33 | 34 | const config: webpack.Configuration & WebpackDevServer.Configuration = { 35 | entry: { 36 | bundle: [ 37 | './src/main.ts', 38 | ], 39 | }, 40 | resolve: { 41 | alias: { 42 | // Note: Additional aliases will be loaded automatically from `tsconfig.compilerOptions.paths` 43 | svelte: path.resolve('node_modules', 'svelte'), 44 | }, 45 | extensions: ['.mjs', '.js', '.ts', '.svelte'], 46 | mainFields: ['svelte', 'browser', 'module', 'main'], 47 | }, 48 | output: { 49 | publicPath: prod ? '' : '/build/', 50 | path: __dirname + '/public/build', 51 | filename: '[name].js', 52 | chunkFilename: '[name].[id].js', 53 | library: "kalkComponent", 54 | libraryTarget: 'window', 55 | libraryExport: 'default' 56 | }, 57 | module: { 58 | rules: [ 59 | { 60 | test: /\.svelte$/, 61 | use: { 62 | loader: 'svelte-loader-hot', 63 | options: { 64 | customElement: true, 65 | dev: !prod, 66 | emitCss: prod, 67 | hotReload: !prod, 68 | hotOptions: { 69 | // List of options and defaults: https://www.npmjs.com/package/svelte-loader-hot#usage 70 | noPreserveState: false, 71 | optimistic: true, 72 | }, 73 | preprocess: Preprocess({ 74 | scss: true, 75 | postcss: { 76 | plugins: [require('autoprefixer')], 77 | }, 78 | }), 79 | }, 80 | }, 81 | }, 82 | { 83 | test: /\.(scss|sass)$/, 84 | use: [ 85 | { 86 | loader: MiniCssExtractPlugin.loader, 87 | options: { 88 | hmr: !prod, 89 | sourceMap: !prod || sourceMapsInProduction, 90 | }, 91 | }, 92 | 'css-loader', 93 | { 94 | loader: 'postcss-loader', 95 | options: { 96 | plugins: [require('autoprefixer')], 97 | }, 98 | }, 99 | 'sass-loader', 100 | ], 101 | }, 102 | { 103 | test: /\.css$/, 104 | use: [ 105 | { 106 | loader: MiniCssExtractPlugin.loader, 107 | options: { 108 | hmr: !prod, 109 | sourceMap: !prod || sourceMapsInProduction, 110 | }, 111 | }, 112 | 'css-loader', 113 | ], 114 | }, 115 | { 116 | test: /\.ts$/, 117 | use: 'ts-loader', 118 | exclude: /node_modules/, 119 | }, 120 | ], 121 | }, 122 | devServer: { 123 | hot: true, 124 | stats: 'minimal', 125 | contentBase: 'public', 126 | watchContentBase: true, 127 | }, 128 | mode, 129 | plugins: [ 130 | new MiniCssExtractPlugin({ 131 | filename: '[name].css', 132 | }), 133 | ], 134 | optimization: { 135 | minimizer: [], 136 | }, 137 | devtool: prod && !sourceMapsInProduction ? false : 'source-map', 138 | }; 139 | 140 | // Load path mapping from tsconfig 141 | const tsconfigPath = path.resolve(__dirname, 'tsconfig.json'); 142 | const tsconfig = require('fs').existsSync(tsconfigPath) ? require(tsconfigPath) : {}; 143 | 144 | if ('compilerOptions' in tsconfig && 'paths' in tsconfig.compilerOptions) { 145 | const aliases = tsconfig.compilerOptions.paths; 146 | for (const alias in aliases) { 147 | const paths = aliases[alias].map((p: string) => path.resolve(__dirname, p)); 148 | 149 | // Our tsconfig uses glob path formats, whereas webpack just wants directories 150 | // We'll need to transform the glob format into a format acceptable to webpack 151 | const wpAlias = alias.replace(/(\\|\/)\*$/, ''); 152 | const wpPaths = paths.map((p: string) => p.replace(/(\\|\/)\*$/, '')); 153 | 154 | if (!(wpAlias in config.resolve.alias) && wpPaths.length) { 155 | config.resolve.alias[wpAlias] = wpPaths.length > 1 ? wpPaths : wpPaths[0]; 156 | } 157 | } 158 | } 159 | 160 | // These options should only apply to production builds 161 | if (prod) { 162 | // Clean the build directory for production builds 163 | config.plugins.push(new CleanWebpackPlugin()); 164 | 165 | // Minify CSS 166 | config.optimization.minimizer.push( 167 | new OptimizeCSSAssetsPlugin({ 168 | cssProcessorOptions: { 169 | map: sourceMapsInProduction 170 | ? { 171 | inline: false, 172 | annotation: true, 173 | } 174 | : false, 175 | }, 176 | cssProcessorPluginOptions: { 177 | preset: [ 178 | 'default', 179 | { 180 | discardComments: { 181 | removeAll: !sourceMapsInProduction, 182 | }, 183 | }, 184 | ], 185 | }, 186 | }) 187 | ); 188 | 189 | // Minify and treeshake JS 190 | config.optimization.minimizer.push( 191 | new TerserPlugin({ 192 | sourceMap: sourceMapsInProduction, 193 | extractComments: false, 194 | }) 195 | ); 196 | } 197 | 198 | // Add babel if enabled 199 | if (useBabel && (prod || useBabelInDevelopment)) { 200 | config.module.rules.unshift({ 201 | test: /\.(?:svelte|m?js)$/, 202 | include: [path.resolve(__dirname, 'src'), path.dirname(sveltePath)], 203 | use: { 204 | loader: 'babel-loader', 205 | options: { 206 | sourceType: 'unambiguous', 207 | presets: ['@babel/preset-env'], 208 | plugins: ['@babel/plugin-transform-runtime'], 209 | }, 210 | }, 211 | }); 212 | } 213 | 214 | export default config; 215 | -------------------------------------------------------------------------------- /mobile/android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=$((i+1)) 158 | done 159 | case $i in 160 | (0) set -- ;; 161 | (1) set -- "$args0" ;; 162 | (2) set -- "$args0" "$args1" ;; 163 | (3) set -- "$args0" "$args1" "$args2" ;; 164 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=$(save "$@") 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 184 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 185 | cd "$(dirname "$0")" 186 | fi 187 | 188 | exec "$JAVACMD" "$@" 189 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | create: 5 | tags: 6 | - 'v*' # Version tag 7 | workflow_dispatch: 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | 14 | publish: 15 | name: publish to crates.io 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@master 19 | - uses: Swatinem/rust-cache@v1.0.1 20 | - name: Publish crate 21 | continue-on-error: true 22 | run: cargo publish --manifest-path kalk/Cargo.toml --token ${{ secrets.CARGO_REGISTRY_TOKEN }} --verbose 23 | - name: Publish CLI 24 | run: sleep 20 && cargo publish --manifest-path cli/Cargo.toml --token ${{ secrets.CARGO_REGISTRY_TOKEN }} --verbose 25 | 26 | publish_npm: 27 | name: publish to npm 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@master 31 | - uses: Swatinem/rust-cache@v1.0.1 32 | - uses: actions/setup-node@v1 33 | with: 34 | node-version: 14 35 | registry-url: https://registry.npmjs.org/ 36 | - name: Install wasm-pack 37 | run: cargo install wasm-pack 38 | - name: Build 39 | run: | 40 | yarn install 41 | cd kalk 42 | wasm-pack build --scope paddim8 -- --no-default-features 43 | cd .. 44 | - uses: JS-DevTools/npm-publish@v1 45 | with: 46 | token: ${{ secrets.NPM_TOKEN }} 47 | package: ./kalk/pkg/package.json 48 | access: public 49 | 50 | publish_kalk_web: 51 | name: publish kalk_web 52 | if: always() 53 | runs-on: ubuntu-latest 54 | needs: [publish_npm] 55 | steps: 56 | - uses: actions/checkout@master 57 | - uses: actions/setup-node@v1 58 | with: 59 | node-version: 14 60 | registry-url: https://registry.npmjs.org/ 61 | - name: build 62 | run: | 63 | cd web 64 | npm install 65 | cd .. 66 | - uses: JS-DevTools/npm-publish@v1 67 | with: 68 | token: ${{ secrets.NPM_TOKEN }} 69 | package: ./web/package.json 70 | access: public 71 | 72 | release_linux: 73 | name: release linux 74 | runs-on: ubuntu-latest 75 | steps: 76 | - uses: actions/checkout@master 77 | - uses: Swatinem/rust-cache@v1.0.1 78 | - name: Compile 79 | run: | 80 | cargo build --release --verbose 81 | cp target/release/kalker target/release/kalker-linux 82 | - uses: actions/upload-artifact@v2 83 | with: 84 | name: binaries 85 | path: target/release/kalker-linux 86 | 87 | release_mac: 88 | name: release mac 89 | runs-on: macos-latest 90 | steps: 91 | - uses: actions/checkout@master 92 | - uses: actions/cache@v2 93 | with: 94 | path: | 95 | ~/.cargo/registry/index 96 | ~/.cargo/registry/cache 97 | target 98 | key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} 99 | - name: Compile 100 | run: | 101 | cargo build --release --verbose 102 | zip -r kalker-macOS.zip target/release/kalker 103 | - uses: actions/upload-artifact@v2 104 | with: 105 | name: binaries 106 | path: kalker-macOS.zip 107 | 108 | release_windows: 109 | name: release windows 110 | runs-on: windows-latest 111 | 112 | steps: 113 | - uses: actions/checkout@master 114 | - uses: actions/cache@v2 115 | with: 116 | key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} 117 | path: | 118 | C:\Users\runneradmin\.cargo\bin\cargo-wix 119 | C:\Users\runneradmin\.cargo\registry\index 120 | C:\Users\runneradmin\.cargo\registry\cache 121 | C:\Users\runneradmin\.cargo\registry\cache 122 | {FOLDERID_LocalAppData}\gmp-mpfr-sys 123 | D:\a\kalker\kalker\target 124 | C:\msys64\home\paddi\kalker\target 125 | - uses: msys2/setup-msys2@v2 126 | with: 127 | update: true 128 | install: >- 129 | base-devel 130 | pacman-mirrors 131 | diffutils 132 | m4 133 | make 134 | mingw-w64-x86_64-gcc 135 | mingw-w64-x86_64-rust 136 | continue-on-error: true 137 | - shell: msys2 {0} 138 | run: | 139 | cargo build --release --verbose 140 | 141 | - uses: crazy-max/ghaction-chocolatey@v1 142 | with: 143 | args: install wixtoolset -y 144 | - shell: msys2 {0} 145 | run: | 146 | [ ! -f /c/Users/runneradmin/.cargo/bin/cargo-wix.exe ] && cargo install cargo-wix --version 0.3.1 147 | cd cli 148 | cargo wix --no-build --nocapture 149 | mv target/wix/*.msi target/wix/kalker-windows.msi 150 | - uses: actions/upload-artifact@v2 151 | with: 152 | name: binaries 153 | path: cli/target/wix/kalker-windows.msi 154 | 155 | release_android: 156 | name: release android 157 | if: always() 158 | runs-on: ubuntu-latest 159 | needs: [publish_kalk_web] 160 | defaults: 161 | run: 162 | working-directory: ./mobile 163 | 164 | steps: 165 | - uses: actions/checkout@master 166 | - uses: actions/cache@v2 167 | with: 168 | path: | 169 | mobile/android/.gradle 170 | mobile/android/build 171 | mobile/android/app/build 172 | key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} 173 | - name: npm 174 | run: | 175 | npm install 176 | npm run build 177 | - name: build app 178 | run: | 179 | npx cap sync 180 | cd android 181 | version=$(cat ../../web/package.json | grep version | head -1 | awk -F: '{ print $2 }' | sed 's/[",]//g' | tr -d '[[:space:]]') 182 | APP_VERSION_NAME=$version ./gradlew assembleRelease 183 | - name: Setup build tool version variable 184 | shell: bash 185 | run: | 186 | BUILD_TOOL_VERSION=$(ls /usr/local/lib/android/sdk/build-tools/ | tail -n 1) 187 | echo "BUILD_TOOL_VERSION=$BUILD_TOOL_VERSION" >> $GITHUB_ENV 188 | echo Last build tool version is: $BUILD_TOOL_VERSION 189 | - uses: r0adkll/sign-android-release@v1 190 | name: sign apk 191 | with: 192 | releaseDirectory: mobile/android/app/build/outputs/apk/release 193 | signingKeyBase64: ${{ secrets.SIGNING_KEY }} 194 | alias: ${{ secrets.ALIAS }} 195 | keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }} 196 | keyPassword: ${{ secrets.KEY_PASSWORD }} 197 | env: 198 | BUILD_TOOLS_VERSION: ${{ env.BUILD_TOOL_VERSION }} 199 | - run: mv $(ls -Art android/app/build/outputs/apk/release/*.apk | tail -n 1) ../kalker-android.apk 200 | - uses: actions/upload-artifact@v2 201 | with: 202 | name: binaries 203 | path: kalker-android.apk 204 | 205 | create_release: 206 | name: Create Release 207 | if: always() 208 | needs: [release_linux, release_mac, release_windows, release_android] 209 | runs-on: ubuntu-latest 210 | steps: 211 | - name: Create Release 212 | id: create_release 213 | uses: actions/create-release@v1 214 | env: 215 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 216 | with: 217 | tag_name: ${{ github.ref }} 218 | release_name: ${{ github.ref }} 219 | draft: false 220 | prerelease: false 221 | - name: Download Artifact 222 | uses: actions/download-artifact@v2 223 | with: 224 | name: binaries 225 | - name: Upload Assets 226 | uses: softprops/action-gh-release@v1 227 | with: 228 | files: ./* 229 | env: 230 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 231 | -------------------------------------------------------------------------------- /cli/help.txt: -------------------------------------------------------------------------------- 1 | Overview of features 2 | Operators: +, -, *, /, !, % 3 | Groups: (), ⌈⌉, ⌊⌋, [] 4 | Vectors: (x, y, z, ...) 5 | Matrices: [x, y, z; a, b, c; ...] 6 | Pre-defined functions and constants 7 | User-defined functions and variables 8 | Understands fairly ambiguous syntax. Eg. 2sinx + 2xy 9 | Complex numbers 10 | 11 | Piecewise functions: f(x) = { f(x + 1) if x <= 1; x otherwise }, 12 | pressing enter before typing the final "}" will make a new line without 13 | submitting. Semicolons are only needed when writing everything on the 14 | same line. 15 | 16 | Different number bases: Either with a format like 0b1101, 0o5.3, 0xff 17 | or a format like 1101_2. The latter does not support letters, as they 18 | would be interpreted as variables. The "base" command can be used to 19 | tell the REPL to also show output in another number base. For example, 20 | "base 16" would make it show results in hexadecimal as well as decimal. 21 | 22 | Root finding using Newton's method (eg. x^2 = 64). Note: estimation and 23 | limited to one root. 24 | 25 | Derivation (prime notation) and integration (eg. integral(a, b, x dx) 26 | The value of an integral is estimated using Simpson's 3/8 rule, 27 | while derivatives are estimated using the symmetric difference 28 | quotient (and derivatives of higher order can be a bit inaccurate as of now) 29 | 30 | Syntax highlighting 31 | Completion for special symbols on tab 32 | Sum/prod functions 33 | 34 | Load files that can contain predefined variable and function declarations. 35 | (you can also have automatically loaded files) 36 | 37 | Operators 38 | +, -, *, / 39 | ! Factorial, eg. 5! gives 120 40 | % Percent, eg. 5% gives 0.05, 10 + 50% gives 15 41 | % Modulus (remainder), eg. 23 % 3 gives 2 42 | and, or, not 43 | 44 | Completion for special symbols 45 | You can type special symbols (such as √) by typing the normal function or constant name and pressing tab. 46 | 47 | * becomes × 48 | / becomes ÷ 49 | and becomes ∧ 50 | not becomes ¬ 51 | or becomes ∨ 52 | [[ becomes ⟦⟧ 53 | _123 becomes ₁₂₃ 54 | asin, acos, etc. become sin⁻¹(), cos⁻¹(), etc 55 | sqrt becomes √ 56 | deg becomes ° 57 | pi becomes π 58 | sum becomes Σ() 59 | prod becomes ∏() 60 | integrate becomes ∫() 61 | tau becomes τ 62 | phi becomes ϕ 63 | floor becomes ⌊⌋ 64 | ceil becomes ⌈⌉ 65 | gamma becomes Γ 66 | ( becomes () 67 | 68 | Variables 69 | Variables are defined with the following syntax: name = value 70 | Example: x = 3/4 71 | 72 | Predefined variables 73 | ans - receives the value computed of the most recent expression 74 | 75 | Functions 76 | Functions are defined with the following syntax: name(param1, param2, etc.) = value 77 | Examples: f(x) = 2x+3; A(x, y) = (xy)/2 78 | They are used like this: name(arg1, arg2, etc.) 79 | Example: f(3) + 3A(2, 3) 80 | 81 | Predefined functions 82 | sin, cos, tan, cot, cosec, sec 83 | sinh, cosh, tanh, coth, cosech, sech 84 | asin, acos, atan, acot, acosec, asec 85 | ashin, acosh, atanh, acoth, acosech, asech 86 | abs, ceil or ⌈⌉, floor or ⌊⌋, frac, round, trunc 87 | sqrt or √, cbrt, exp, log, ln, arg, Re, Im 88 | gamma or Γ 89 | asinh, acosh, atanh, acoth, acosech, asech 90 | bitcmp, bitand, bitor, bitxor, bitshift 91 | comb or nCr, perm or nPr 92 | gcd, lcm 93 | min, max, hypot 94 | log - eg. log(1000, 10) is the same as log10(1000) 95 | root - eg. root(16, 3) is the same as 3√16 96 | average, perms, sort 97 | transpose 98 | matrix - takes a vector of vectors and returns a matrix 99 | integrate - eg. integrate(0, pi, sin(x) dx) 100 | sum Eg. sum(n=1, 4, 2n), example below 101 | 102 | Sum function 103 | The sum function lets you sum an expression with an incrementing variable. 104 | It takes three arguments: start value, end value, and expression. 105 | If you press tab after typing out "sum", it will be replaced with a sigma symbol. 106 | The expression is what will be summed, and will be able to use the variable defined 107 | in first argument (eg. n=1). The value of the variable increments by one. 108 | Example: sum(n=1, 4, 2n) will be the same as 2*1 + 2*2 + 2*3 + 2*4 = 20 109 | This can for example be used to calculate e: Σ(n=0, 10000, 1/n!) = 2.7182818284590455 110 | More precision can be gotten by changing the "--precision" flag. Run `kalker --help` for more info. 111 | 112 | The sum function can also be used to sum vectors, eg. sum(1, 2, 3) or sum(v) or sum[1, 2, 3]. 113 | 114 | Prod function 115 | The prod function works the same way as the sum function but performs 116 | multiplication instead of addition. 117 | 118 | Constants 119 | pi or π = 3.14159265 120 | e = 2.71828182 121 | tau or τ = 6.2831853 122 | phi or ϕ = 1.61803398 123 | 124 | Vectors 125 | A vector in kalker is an immutable list of values, defined with the syntax (x, y, z) 126 | which may contain an arbitrary amount of items. Generally, when an operation is 127 | performed on a vector, it is done on each individual item. This means that (2, 4, 8) / 2 128 | gives the result (1, 2, 4). An exception to this is multiplication with two vectors, 129 | for which the result is the dot product of the vectors. When a vector is given to a 130 | regular function, the function is applied to all of the items in the vector. 131 | 132 | Indexing 133 | A specific item can be retrieved from a vector using an indexer, with the 134 | syntax vector[[index]]. Indexes start at 1. 135 | 136 | Vector comprehensions (experimental) 137 | Vectors can be created dynamically using vector comprehension notation, which is 138 | similar to set-builder notation. The following example creates a vector containing 139 | the square of every number between one and nine except five: [n^2 : 0 < n < 10 and n != 5]. 140 | A comprehension consists of two parts. The first part defines what should be done to each 141 | number, while the second part defines the numbers which should be handled in the first 142 | part. At the moment, it is mandatory to begin the second part with a range of the 143 | format a < n < b where n defines the variable which should be used in the comprehension. 144 | Several of these variables can be created by separating the conditions by a comma, 145 | for example [ab : 0 < a < 5, 0 < b < 5]. 146 | 147 | Matrices 148 | A matrix is an immutable two-dimensional list of values, defined with the syntax [x, y, z; a, b, c] 149 | where semicolons are used to separate rows and commas are used to separate items. It is also 150 | possible to press the enter key to create a new row, instead of writing a semicolon. Pressing 151 | enter will not submit if there is no closing square bracket. Operations on matrices work the 152 | same way as with vectors, except that two matrices multiplied result in matrix multiplication. 153 | It is also possible to obtain the transpose of a matrix with the syntax A^T, where A is a matrix 154 | and T is a literal T. 155 | 156 | Indexing 157 | A specific item can be retrieved from a matrix using an indexer, with the 158 | syntax matrix[[rowIndex, columnIndex]]. Indexes start at 1. 159 | 160 | Files 161 | Kalker looks for kalker files in the system config directory. 162 | 163 | Linux: ~/.config/kalker/ 164 | macOS: ~/Library/Application Support/kalker/ or ~/Library/Preferences/kalker 165 | Windows: %appdata%/kalker/ 166 | 167 | If a file with the name default.kalker is found, it will be loaded automatically every time 168 | kalker starts. Any other files in this directory with the .kalker extension can be loaded 169 | at any time by doing load filename in kalker. Note that the extension should not be included here. 170 | -------------------------------------------------------------------------------- /cli/src/repl.rs: -------------------------------------------------------------------------------- 1 | use crate::output; 2 | use crate::output::print_err; 3 | use ansi_term::Colour::{self, Cyan}; 4 | use kalk::kalk_value::ScientificNotationFormat; 5 | use kalk::parser; 6 | use lazy_static::lazy_static; 7 | use regex::Captures; 8 | use regex::Regex; 9 | use rustyline::completion::Completer; 10 | use rustyline::config::Configurer; 11 | use rustyline::error::ReadlineError; 12 | use rustyline::highlight::Highlighter; 13 | use rustyline::hint::Hinter; 14 | use rustyline::validate::MatchingBracketValidator; 15 | use rustyline::validate::ValidationContext; 16 | use rustyline::validate::ValidationResult; 17 | use rustyline::validate::Validator; 18 | use rustyline::{Editor, Helper}; 19 | use std::borrow::Cow; 20 | use std::borrow::Cow::Owned; 21 | use std::collections::HashMap; 22 | use std::fs; 23 | use std::process; 24 | 25 | struct Context { 26 | base: u8, 27 | mode: ScientificNotationFormat, 28 | } 29 | 30 | pub fn start(parser: &mut parser::Context, precision: u32, format: ScientificNotationFormat) { 31 | let mut editor = Editor::::new(); 32 | editor.set_helper(Some(RLHelper { 33 | highlighter: LineHighlighter {}, 34 | validator: MatchingBracketValidator::new(), 35 | })); 36 | editor.set_max_history_size(30); 37 | 38 | // Load history 39 | let mut history_path = None; 40 | if let Some(cache_path) = dirs::cache_dir() { 41 | let mut cache_path = cache_path; 42 | cache_path.push("kalker"); 43 | if fs::create_dir_all(cache_path.as_path()).is_ok() { 44 | cache_path.push("history.txt"); 45 | let history = cache_path.into_os_string().into_string().unwrap(); 46 | editor.load_history(&history).ok(); 47 | history_path = Some(history) 48 | } 49 | } 50 | 51 | // If in tty, print the welcome message 52 | if atty::is(atty::Stream::Stdin) && atty::is(atty::Stream::Stdout) { 53 | println!("kalker"); 54 | println!( 55 | "{}", 56 | ansi_term::Color::Fixed(246).paint("Type 'help' for instructions.") 57 | ); 58 | } 59 | 60 | let mut repl = Context { 61 | base: 10u8, 62 | mode: format, 63 | }; 64 | 65 | loop { 66 | let prompt = if cfg!(windows) { 67 | String::from(">> ") 68 | } else { 69 | Cyan.paint(">> ").to_string() 70 | }; 71 | let readline = editor.readline(&prompt); 72 | 73 | match readline { 74 | Ok(input) => { 75 | editor.add_history_entry(input.as_str()); 76 | eval_repl(&mut repl, parser, &input, precision); 77 | } 78 | Err(ReadlineError::Interrupted) => break, 79 | _ => break, 80 | } 81 | } 82 | 83 | if let Some(history_path) = history_path { 84 | editor.save_history(&history_path).ok(); 85 | } 86 | } 87 | 88 | fn eval_repl(repl: &mut self::Context, parser: &mut parser::Context, input: &str, precision: u32) { 89 | if let Some(file_name) = input.strip_prefix("load ") { 90 | if let Some(file_path) = crate::get_input_file_by_name(file_name) { 91 | crate::load_input_file(&file_path, precision, parser); 92 | } else { 93 | eprintln!("Unable to find '{}'", file_name); 94 | } 95 | 96 | return; 97 | } 98 | 99 | if let Some(base_str) = input.strip_prefix("base ") { 100 | if !base_str.is_empty() && base_str.chars().next().unwrap().is_ascii_digit() { 101 | if let Ok(base) = base_str.parse::() { 102 | if base > 1 && base < 50 { 103 | repl.base = base; 104 | return; 105 | } 106 | } 107 | 108 | eprintln!("Invalid number base"); 109 | 110 | return; 111 | } 112 | } 113 | 114 | if input.starts_with("mode") { 115 | let mut parts = input.split(' '); 116 | let mode = match parts.nth(1) { 117 | Some("normal") => ScientificNotationFormat::Normal, 118 | Some("eng") => ScientificNotationFormat::Engineering, 119 | _ => { 120 | print_err("Invalid mode name. Available modes: normal, eng"); 121 | 122 | return; 123 | }, 124 | }; 125 | 126 | repl.mode = mode; 127 | 128 | return; 129 | } 130 | 131 | match input { 132 | "" => eprint!(""), 133 | "clear" => print!("\x1B[2J"), 134 | "exit" => process::exit(0), 135 | "help" => print_cli_help(), 136 | _ => output::eval(parser, input, precision, repl.base, repl.mode), 137 | } 138 | } 139 | 140 | fn print_cli_help() { 141 | let help_text = include_str!("../help.txt"); 142 | println!("{}", help_text); 143 | } 144 | 145 | struct LineHighlighter {} 146 | 147 | impl Highlighter for LineHighlighter { 148 | fn highlight<'l>(&self, line: &'l str, _: usize) -> Cow<'l, str> { 149 | let mut coloured = line.to_string(); 150 | 151 | let reg = Regex::new( 152 | r"(?x) 153 | (?P([+\-/*%^!×÷⋅∧∨¬ᵀ]|if|otherwise|\b(and|or|mod|true|false|not)\b|load|exit|clear|help)) | 154 | (?P0[box][a-zA-Z0-9]+) | 155 | (?P[^!-@\s_|^⌊⌋⌈⌉\[\]\{\}⟦⟧≠≥≤⁰¹²³⁴⁵⁶⁷⁸⁹⁺⁻⁼⁽⁾₀₁₂₃₄₅₆₇₈₉₊₋₌₍₎ᵀ]+(_\d+)?)", 156 | ) 157 | .unwrap(); 158 | 159 | coloured = reg 160 | .replace_all(&coloured, |caps: &Captures| { 161 | if let Some(cap) = caps.name("identifier") { 162 | match cap.as_str() { 163 | "rad" | "deg" | "°" => Colour::Yellow.paint(cap.as_str()).to_string(), 164 | _ => Colour::Fixed(32).paint(cap.as_str()).to_string(), 165 | } 166 | } else if let Some(cap) = caps.name("op") { 167 | Colour::Fixed(172).paint(cap.as_str()).to_string() 168 | } else { 169 | caps[0].to_string() 170 | } 171 | }) 172 | .to_string(); 173 | 174 | Owned(coloured) 175 | } 176 | } 177 | 178 | struct RLHelper { 179 | highlighter: LineHighlighter, 180 | validator: MatchingBracketValidator, 181 | } 182 | 183 | impl Helper for RLHelper {} 184 | 185 | lazy_static! { 186 | pub static ref COMPLETION_FUNCS: HashMap<&'static str, &'static str> = { 187 | let mut m = HashMap::new(); 188 | m.insert("ceil", "⌈⌉"); 189 | m.insert("deg", "°"); 190 | m.insert("floor", "⌊⌋"); 191 | m.insert("gamma", "Γ"); 192 | m.insert("sum", "Σ()"); 193 | m.insert("prod", "∏()"); 194 | m.insert("integrate", "∫()"); 195 | m.insert("integral", "∫()"); 196 | m.insert("phi", "ϕ"); 197 | m.insert("pi", "π"); 198 | m.insert("sqrt", "√"); 199 | m.insert("tau", "τ"); 200 | m.insert("(", "()"); 201 | m.insert("[[", "⟦⟧"); 202 | m.insert("!=", "≠"); 203 | m.insert(">=", "≥"); 204 | m.insert("<=", "≤"); 205 | m.insert(" and", " ∧"); 206 | m.insert(" or", " ∨"); 207 | m.insert(" not", " ¬"); 208 | m.insert("*", "×"); 209 | m.insert("/", "÷"); 210 | m.insert("^T", "ᵀ"); 211 | m.insert("asin", "sin⁻¹()"); 212 | m.insert("acos", "cos⁻¹()"); 213 | m.insert("atan", "tan⁻¹()"); 214 | m.insert("acot", "cot⁻¹()"); 215 | m.insert("acosec", "cosec⁻¹()"); 216 | m.insert("asec", "sec⁻¹()"); 217 | m.insert("asinh", "sinh⁻¹()"); 218 | m.insert("acosh", "cosh⁻¹()"); 219 | m.insert("atanh", "tanh⁻¹()"); 220 | m.insert("acoth", "coth⁻¹()"); 221 | m.insert("acosech", "cosech⁻¹()"); 222 | m.insert("asech", "sech⁻¹()"); 223 | m.insert("cbrt", "∛"); 224 | m 225 | }; 226 | } 227 | 228 | impl Completer for RLHelper { 229 | type Candidate = String; 230 | fn complete( 231 | &self, 232 | line: &str, 233 | pos: usize, 234 | _ctx: &rustyline::Context<'_>, 235 | ) -> Result<(usize, Vec), ReadlineError> { 236 | for key in COMPLETION_FUNCS.keys() { 237 | let slice = &line[..pos]; 238 | if slice.ends_with(key) { 239 | let value = *COMPLETION_FUNCS.get(key).unwrap(); 240 | return Ok((pos - key.len(), vec![value.to_string()])); 241 | } 242 | 243 | // If the key starts with a space, it should also be expanded 244 | // if it is at the start of the line. To do this, the strings 245 | // are compared with the space removed in these situations. 246 | if key.starts_with(' ') && slice.len() == key.len() - 1 && slice == &key[1..] { 247 | let value = &(*COMPLETION_FUNCS.get(key).unwrap())[1..]; 248 | return Ok((pos - (key.len() - 1), vec![value.to_string()])); 249 | } 250 | 251 | let mut subscript_digits = String::new(); 252 | for c in slice.chars().rev() { 253 | if c.is_ascii_digit() { 254 | subscript_digits.insert(0, c); 255 | } else { 256 | break; 257 | } 258 | } 259 | 260 | let subscript_char_count = subscript_digits.chars().count(); 261 | if subscript_char_count > 0 && pos - subscript_char_count > 0 { 262 | let value = kalk::text_utils::normal_to_subscript(subscript_digits.chars()); 263 | return Ok((pos - subscript_char_count - 1, vec![value])); 264 | } 265 | } 266 | 267 | Ok((0, vec![line.to_string()])) 268 | } 269 | 270 | fn update(&self, line: &mut rustyline::line_buffer::LineBuffer, start: usize, elected: &str) { 271 | line.backspace(line.pos() - start); 272 | line.insert_str(line.pos(), elected); 273 | line.move_forward(if elected.ends_with(')') || elected.ends_with('⟧') { 274 | elected.chars().count() - 1 275 | } else { 276 | elected.chars().count() 277 | }); 278 | } 279 | } 280 | 281 | impl Highlighter for RLHelper { 282 | fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> { 283 | Owned(Colour::Fixed(244).paint(hint).to_string()) 284 | } 285 | 286 | fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> { 287 | self.highlighter.highlight(line, pos) 288 | } 289 | 290 | fn highlight_candidate<'c>( 291 | &self, 292 | candidate: &'c str, 293 | _completion: rustyline::CompletionType, 294 | ) -> Cow<'c, str> { 295 | self.highlighter.highlight(candidate, 0) 296 | } 297 | 298 | fn highlight_char(&self, line: &str, _: usize) -> bool { 299 | !line.is_empty() 300 | } 301 | } 302 | 303 | impl Hinter for RLHelper { 304 | type Hint = String; 305 | 306 | fn hint(&self, _: &str, _: usize, _: &rustyline::Context) -> Option { 307 | None 308 | } 309 | } 310 | 311 | impl Validator for RLHelper { 312 | fn validate(&self, ctx: &mut ValidationContext) -> Result { 313 | let mut group_symbol_count = vec![0i32, 0i32, 0i32]; 314 | 315 | for c in ctx.input().chars() { 316 | match c { 317 | '⌈' | '⌉' => group_symbol_count[0] += 1, 318 | '⌊' | '⌋' => group_symbol_count[1] += 1, 319 | '|' => group_symbol_count[2] += 1, 320 | _ => (), 321 | } 322 | } 323 | 324 | if !group_symbol_count.into_iter().all(|x| x % 2 == 0) { 325 | Ok(ValidationResult::Incomplete) 326 | } else { 327 | self.validator.validate(ctx) 328 | } 329 | } 330 | 331 | fn validate_while_typing(&self) -> bool { 332 | self.validator.validate_while_typing() 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /kalk/src/numerical.rs: -------------------------------------------------------------------------------- 1 | use crate::as_number_or_zero; 2 | use crate::ast; 3 | use crate::ast::Expr; 4 | use crate::ast::Identifier; 5 | use crate::ast::Stmt; 6 | use crate::errors::KalkError; 7 | use crate::float; 8 | use crate::interpreter; 9 | use crate::kalk_value::KalkValue; 10 | use crate::lexer::TokenKind; 11 | use crate::test_helpers::f64_to_float_literal; 12 | 13 | pub fn derive_func( 14 | context: &mut interpreter::Context, 15 | name: &Identifier, 16 | argument: KalkValue, 17 | ) -> Result { 18 | const H: f64 = 0.000001; 19 | 20 | let unit = argument.get_unit().cloned(); 21 | let argument_with_h = ast::build_literal_ast(&argument.clone().add_without_unit(&H.into())?); 22 | let argument_without_h = ast::build_literal_ast(&argument.sub_without_unit(&H.into())?); 23 | let new_identifier = Identifier::from_name_and_primes(&name.pure_name, name.prime_count - 1); 24 | 25 | let f_x_h = interpreter::eval_fn_call_expr( 26 | context, 27 | &new_identifier, 28 | &[argument_with_h], 29 | unit.as_ref(), 30 | )?; 31 | let f_x = interpreter::eval_fn_call_expr( 32 | context, 33 | &new_identifier, 34 | &[argument_without_h], 35 | unit.as_ref(), 36 | )?; 37 | 38 | Ok(f_x_h 39 | .sub_without_unit(&f_x)? 40 | .div_without_unit(&(2f64 * H).into())? 41 | .round_if_needed()) 42 | } 43 | 44 | pub fn integrate_with_unknown_variable( 45 | context: &mut interpreter::Context, 46 | a: &Expr, 47 | b: &Expr, 48 | expr: &Expr, 49 | ) -> Result { 50 | let mut integration_variable: Option<&str> = None; 51 | 52 | // integral(a, b, expr dx) 53 | if let Expr::Binary(_, TokenKind::Star, right) = expr { 54 | if let Expr::Var(right_name) = &**right { 55 | if right_name.full_name.starts_with('d') { 56 | // Take the value, but remove the d, so that only eg. x is left from dx 57 | integration_variable = Some(&right_name.full_name[1..]); 58 | } 59 | } 60 | } 61 | 62 | if integration_variable.is_none() { 63 | return Err(KalkError::ExpectedDx); 64 | } 65 | 66 | // "dx" is still in the expression. Set dx = 1, so that it doesn't affect the expression value. 67 | context.symbol_table.set(Stmt::VarDecl( 68 | Identifier::from_full_name(&format!("d{}", integration_variable.unwrap())), 69 | f64_to_float_literal(1f64), 70 | )); 71 | 72 | Ok(integrate(context, a, b, expr, integration_variable.unwrap())?.round_if_needed()) 73 | } 74 | 75 | pub fn integrate( 76 | context: &mut interpreter::Context, 77 | a: &Expr, 78 | b: &Expr, 79 | expr: &Expr, 80 | integration_variable: &str, 81 | ) -> Result { 82 | Ok(simpsons_rule(context, a, b, expr, integration_variable)?.round_if_needed()) 83 | } 84 | 85 | /// Composite Simpson's 3/8 rule 86 | fn simpsons_rule( 87 | context: &mut interpreter::Context, 88 | a_expr: &Expr, 89 | b_expr: &Expr, 90 | expr: &Expr, 91 | integration_variable: &str, 92 | ) -> Result { 93 | let mut result_real = float!(0); 94 | let mut result_imaginary = float!(0); 95 | let original_variable_value = context 96 | .symbol_table 97 | .get_and_remove_var(integration_variable); 98 | 99 | const N: i32 = 900; 100 | let a = interpreter::eval_expr(context, a_expr, None)?; 101 | let b = interpreter::eval_expr(context, b_expr, None)?; 102 | let h = (b.sub_without_unit(&a))?.div_without_unit(&KalkValue::from(N))?; 103 | for i in 0..=N { 104 | let variable_value = a 105 | .clone() 106 | .add_without_unit(&KalkValue::from(i).mul_without_unit(&h.clone())?)?; 107 | context.symbol_table.set(Stmt::VarDecl( 108 | Identifier::from_full_name(integration_variable), 109 | Box::new(crate::ast::build_literal_ast(&variable_value)), 110 | )); 111 | 112 | let factor = KalkValue::from(match i { 113 | 0 | N => 1, 114 | _ if i % 3 == 0 => 2, 115 | _ => 3, 116 | } as f64); 117 | 118 | // factor * f(x_n) 119 | let (mul_real, mul_imaginary, _) = as_number_or_zero!( 120 | factor.mul_without_unit(&interpreter::eval_expr(context, expr, None)?)? 121 | ); 122 | result_real += mul_real; 123 | result_imaginary += mul_imaginary; 124 | } 125 | 126 | if let Some(value) = original_variable_value { 127 | context.symbol_table.insert(value); 128 | } else { 129 | context 130 | .symbol_table 131 | .get_and_remove_var(integration_variable); 132 | } 133 | 134 | let result = KalkValue::Number(result_real, result_imaginary, None); 135 | let (h_real, h_imaginary, h_unit) = as_number_or_zero!(h); 136 | 137 | result.mul_without_unit(&KalkValue::Number( 138 | 3f64 / 8f64 * h_real, 139 | 3f64 / 8f64 * h_imaginary, 140 | h_unit, 141 | )) 142 | } 143 | 144 | pub fn find_root( 145 | context: &mut interpreter::Context, 146 | expr: &Expr, 147 | var_name: &str, 148 | ) -> Result { 149 | const FN_NAME: &str = "tmp."; 150 | let f = Stmt::FnDecl( 151 | Identifier::from_full_name(FN_NAME), 152 | vec![var_name.into()], 153 | Box::new(expr.clone()), 154 | ); 155 | context.symbol_table.set(f); 156 | let mut approx = KalkValue::from(1f64); 157 | for _ in 0..100 { 158 | let (new_approx, done) = 159 | newton_method(context, approx, &Identifier::from_full_name(FN_NAME))?; 160 | approx = new_approx; 161 | if done { 162 | break; 163 | } 164 | } 165 | 166 | // Confirm that the approximation is correct 167 | let (test_real, test_imaginary) = interpreter::eval_fn_call_expr( 168 | context, 169 | &Identifier::from_full_name(FN_NAME), 170 | &[crate::ast::build_literal_ast(&approx)], 171 | None, 172 | )? 173 | .values(); 174 | 175 | context.symbol_table.get_and_remove_var(var_name); 176 | 177 | if test_real.is_nan() || test_real.abs() > 0.0001f64 || test_imaginary.abs() > 0.0001f64 { 178 | return Err(KalkError::UnableToSolveEquation); 179 | } 180 | 181 | Ok(approx) 182 | } 183 | 184 | fn newton_method( 185 | context: &mut interpreter::Context, 186 | initial: KalkValue, 187 | fn_name: &Identifier, 188 | ) -> Result<(KalkValue, bool), KalkError> { 189 | let f = interpreter::eval_fn_call_expr( 190 | context, 191 | fn_name, 192 | &[crate::ast::build_literal_ast(&initial)], 193 | None, 194 | )?; 195 | 196 | // If it ends up solving the equation early, abort 197 | const PRECISION: f64 = 0.0000001f64; 198 | match f { 199 | KalkValue::Number(x, y, _) 200 | if x < PRECISION && x > -PRECISION && y < PRECISION && y > -PRECISION => 201 | { 202 | return Ok((initial, true)); 203 | } 204 | _ => (), 205 | } 206 | 207 | let f_prime_name = Identifier::from_name_and_primes(&fn_name.pure_name, 1); 208 | let f_prime = derive_func(context, &f_prime_name, initial.clone())?; 209 | 210 | Ok(( 211 | initial.sub_without_unit(&f.div_without_unit(&f_prime)?)?, 212 | false, 213 | )) 214 | } 215 | 216 | #[cfg(test)] 217 | mod tests { 218 | use crate::ast; 219 | use crate::float; 220 | use crate::interpreter; 221 | use crate::kalk_value::KalkValue; 222 | use crate::lexer::TokenKind::*; 223 | use crate::numerical::Identifier; 224 | use crate::numerical::Stmt; 225 | use crate::symbol_table::SymbolTable; 226 | use crate::test_helpers::*; 227 | 228 | fn get_context(symbol_table: &mut SymbolTable) -> interpreter::Context { 229 | interpreter::Context::new( 230 | symbol_table, 231 | "", 232 | #[cfg(feature = "rug")] 233 | 63u32, 234 | None, 235 | ) 236 | } 237 | 238 | #[test] 239 | fn test_derive_func() { 240 | let mut symbol_table = SymbolTable::new(); 241 | let mut context = get_context(&mut symbol_table); 242 | context.symbol_table.insert(Stmt::FnDecl( 243 | Identifier::from_full_name("f"), 244 | vec![String::from("x")], 245 | binary( 246 | f64_to_float_literal(2.5f64), 247 | Star, 248 | binary(var("x"), Power, f64_to_float_literal(3f64)), 249 | ), 250 | )); 251 | 252 | let call = Stmt::Expr(fn_call("f'", vec![*f64_to_float_literal(12.3456f64)])); 253 | assert!(cmp( 254 | context.interpret(vec![call]).unwrap().unwrap().to_f64(), 255 | 1143.10379f64 256 | )); 257 | } 258 | 259 | #[test] 260 | fn test_derive_complex_func() { 261 | let mut symbol_table = SymbolTable::new(); 262 | let mut context = get_context(&mut symbol_table); 263 | context.symbol_table.insert(Stmt::FnDecl( 264 | Identifier::from_full_name("f"), 265 | vec![String::from("x")], 266 | binary( 267 | binary( 268 | f64_to_float_literal(1.5f64), 269 | Star, 270 | binary(var("x"), Power, f64_to_float_literal(2f64)), 271 | ), 272 | Plus, 273 | binary( 274 | binary(var("x"), Power, f64_to_float_literal(2f64)), 275 | Star, 276 | var("i"), 277 | ), 278 | ), 279 | )); 280 | 281 | let call = Stmt::Expr(fn_call("f'", vec![*var("e")])); 282 | let result = context.interpret(vec![call]).unwrap().unwrap(); 283 | assert!(cmp(result.to_f64(), 8.15484f64)); 284 | assert!(cmp(result.imaginary_to_f64(), 5.43656)); 285 | } 286 | 287 | #[test] 288 | fn test_derive_func_with_complex_argument() { 289 | let mut symbol_table = SymbolTable::new(); 290 | let mut context = get_context(&mut symbol_table); 291 | context.symbol_table.insert(Stmt::FnDecl( 292 | Identifier::from_full_name("f"), 293 | vec![String::from("x")], 294 | binary( 295 | binary(f64_to_float_literal(3f64), Star, var("x")), 296 | Plus, 297 | binary( 298 | f64_to_float_literal(0.5f64), 299 | Star, 300 | binary(var("x"), Power, f64_to_float_literal(3f64)), 301 | ), 302 | ), 303 | )); 304 | 305 | let result = super::derive_func( 306 | &mut context, 307 | &Identifier::from_full_name("f'"), 308 | KalkValue::Number(float!(2f64), float!(3f64), None), 309 | ) 310 | .unwrap(); 311 | assert!(cmp(result.to_f64(), -4.5f64) || cmp(result.to_f64(), -4.499999f64)); 312 | assert!(cmp(result.imaginary_to_f64(), 18f64)); 313 | } 314 | 315 | #[test] 316 | fn test_integrate_with_unknown_variable() { 317 | let mut symbol_table = SymbolTable::new(); 318 | let mut context = get_context(&mut symbol_table); 319 | let result = super::integrate_with_unknown_variable( 320 | &mut context, 321 | &f64_to_float_literal(2f64), 322 | &f64_to_float_literal(4f64), 323 | &binary(var("x"), Star, var("dx")), 324 | ) 325 | .unwrap(); 326 | 327 | assert!(cmp(result.to_f64(), 6f64)); 328 | } 329 | 330 | #[test] 331 | fn test_integrate() { 332 | let mut symbol_table = SymbolTable::new(); 333 | let mut context = get_context(&mut symbol_table); 334 | let result = super::integrate( 335 | &mut context, 336 | &f64_to_float_literal(2f64), 337 | &f64_to_float_literal(4f64), 338 | &var("x"), 339 | "x", 340 | ) 341 | .unwrap(); 342 | 343 | assert!(cmp(result.to_f64(), 6f64)); 344 | } 345 | 346 | #[test] 347 | fn test_integrate_complex() { 348 | let mut symbol_table = SymbolTable::new(); 349 | let mut context = get_context(&mut symbol_table); 350 | let result = super::integrate( 351 | &mut context, 352 | &f64_to_float_literal(2f64), 353 | &ast::build_literal_ast(&KalkValue::Number(float!(3f64), float!(4f64), None)), 354 | &binary(var("x"), Star, var("i")), 355 | "x", 356 | ) 357 | .unwrap(); 358 | 359 | assert!(cmp(result.to_f64(), -12f64)); 360 | assert!(cmp(result.imaginary_to_f64(), -5.5f64)); 361 | } 362 | 363 | #[test] 364 | fn test_find_root() { 365 | let mut symbol_table = SymbolTable::new(); 366 | let mut context = get_context(&mut symbol_table); 367 | let ast = &*binary( 368 | binary(var("x"), Power, f64_to_float_literal(3f64)), 369 | Plus, 370 | f64_to_float_literal(3f64), 371 | ); 372 | let result = super::find_root(&mut context, ast, "x").unwrap(); 373 | 374 | assert!(cmp(result.to_f64(), -1.4422495709)); 375 | assert!(!result.has_imaginary()); 376 | } 377 | } 378 | -------------------------------------------------------------------------------- /kalk/src/kalk_value/rounding.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::{float, primitive}; 4 | use lazy_static::lazy_static; 5 | 6 | use super::{ComplexNumberType, KalkValue}; 7 | 8 | lazy_static! { 9 | static ref CONSTANTS: HashMap = { 10 | let mut m = HashMap::new(); 11 | m.insert(141592, (3, "π")); 12 | m.insert(869604, (9, "π²")); 13 | m.insert(318909, (0, "1/π")); 14 | m.insert(636619, (0, "2/π")); 15 | m.insert(718281, (2, "e")); 16 | m.insert(389056, (7, "e²")); 17 | m.insert(283185, (6, "τ")); 18 | m.insert(618033, (1, "ϕ")); 19 | m.insert(414213, (1, "√2")); 20 | m.insert(707106, (0, "1/√2")); 21 | m.insert(693147, (0, "ln(2)")); 22 | m.insert(302585, (2, "ln(10)")); 23 | // Radian values for common angles 24 | m.insert(392699, (0, "π/8")); 25 | m.insert(523598, (0, "π/6")); 26 | m.insert(785398, (0, "π/4")); 27 | m.insert(47197, (1, "π/3")); 28 | m.insert(570796, (1, "π/2")); 29 | m.insert(94395, (2, "2π/3")); 30 | m.insert(356194, (2, "3π/4")); 31 | m.insert(617993, (2, "5π/6")); 32 | m.insert(665191, (3, "7π/6")); 33 | m.insert(926990, (3, "5π/4")); 34 | m.insert(188790, (4, "4π/3")); 35 | m.insert(712388, (4, "3π/2")); 36 | m.insert(23598, (5, "5π/3")); 37 | m.insert(497787, (5, "7π/4")); 38 | m.insert(759586, (5, "11π/6")); 39 | m.insert(283185, (6, "2π")); 40 | m.insert(866025, (0, "√3/2")); 41 | m 42 | }; 43 | } 44 | 45 | pub(super) fn estimate( 46 | input: &KalkValue, 47 | complex_number_type: ComplexNumberType, 48 | ) -> Option { 49 | let (real, imaginary, _) = if let KalkValue::Number(real, imaginary, unit) = input { 50 | (real, imaginary, unit) 51 | } else { 52 | return None; 53 | }; 54 | 55 | let value = match complex_number_type { 56 | ComplexNumberType::Real => real, 57 | ComplexNumberType::Imaginary => imaginary, 58 | }; 59 | 60 | if value >= &f64::MAX { 61 | return None; 62 | } 63 | 64 | let value = primitive!(value); 65 | 66 | // If it's an integer, there's nothing that would be done to it. 67 | if value.fract() == 0f64 { 68 | return None; 69 | } 70 | 71 | if let Some(equivalent_fraction) = equivalent_fraction(value) { 72 | return Some(equivalent_fraction); 73 | } 74 | 75 | // Match with common numbers, eg. π, 2π/3, √2 76 | if let Some(equivalent_constant) = equivalent_constant(value) { 77 | return Some(equivalent_constant); 78 | } 79 | 80 | // If the value squared (and rounded) is an integer, 81 | // eg. x² is an integer, 82 | // then it can be expressed as sqrt(x²). 83 | // Ignore it if the square root of the result is an integer. 84 | if let Some(equivalent_root) = equivalent_root(value) { 85 | return Some(equivalent_root); 86 | } 87 | 88 | // If nothing above was relevant, simply round it off a bit, eg. from 0.99999 to 1 89 | let rounded = match complex_number_type { 90 | ComplexNumberType::Real => round(input, complex_number_type)?.values().0, 91 | ComplexNumberType::Imaginary => round(input, complex_number_type)?.values().1, 92 | }; 93 | let rounded_str = rounded.to_string(); 94 | Some(trim_zeroes(if rounded_str == "-0" { 95 | "0" 96 | } else { 97 | &rounded_str 98 | })) 99 | } 100 | 101 | fn equivalent_fraction(value: f64) -> Option { 102 | fn gcd(mut a: i64, mut b: i64) -> i64 { 103 | while a != 0 { 104 | let old_a = a; 105 | a = b % a; 106 | b = old_a; 107 | } 108 | 109 | b.abs() 110 | } 111 | 112 | let original_sign = value.signum(); 113 | let value = value.abs(); 114 | let abs_value_str = value.to_string(); 115 | 116 | // https://goodcalculators.com/repeating-decimal-to-fraction-conversion-calculator/ 117 | let (mut numer, mut denom) = if let Some(repeatend_str) = find_repeatend(&abs_value_str) { 118 | let dot_pos = abs_value_str.find('.')?; 119 | let repeatend_pos = abs_value_str[dot_pos..].find(&repeatend_str)? + dot_pos; 120 | let non_repeating_str = &abs_value_str[..repeatend_pos].trim_end_matches('.'); 121 | 122 | // non-repeating 123 | let non_repeating = non_repeating_str.parse::().unwrap_or(0f64); 124 | let non_repeating_dec_count = if let Some(dot_pos) = non_repeating_str.find('.') { 125 | non_repeating_str.len() - dot_pos - 1 126 | } else { 127 | 0 128 | }; 129 | let a = non_repeating.fract(); 130 | 131 | // repeatend 132 | let b = match repeatend_str.parse::() { 133 | Ok(b) => b, 134 | Err(_) => return None, 135 | }; 136 | 137 | let factor = 10i64.pow(non_repeating_dec_count as u32) as f64; 138 | let nines = (10i64.pow(repeatend_str.len() as u32) - 1) as f64; 139 | 140 | let a_numer = a as f64 * factor * nines; 141 | let b_numer = b as f64; 142 | let ab_denom = nines * factor; 143 | let integer_part_as_numer = non_repeating.trunc() * ab_denom; 144 | 145 | (a_numer + b_numer + integer_part_as_numer, ab_denom) 146 | } else { 147 | const PREC: f64 = 10e10f64; 148 | 149 | (value * PREC, PREC) 150 | }; 151 | 152 | let gcd = gcd(numer as i64, denom as i64) as f64; 153 | numer /= gcd; 154 | denom /= gcd; 155 | 156 | numer = numer.trunc(); 157 | denom = denom.trunc(); 158 | 159 | if denom <= 1f64 || denom >= 100f64 || denom as i64 == 10 { 160 | return None; 161 | } 162 | 163 | let integer_part = (numer / denom).trunc(); 164 | if integer_part > 1f64 { 165 | numer -= integer_part * denom; 166 | let sign = if original_sign.is_sign_positive() { 167 | "+" 168 | } else { 169 | "-" 170 | }; 171 | 172 | Some(format!( 173 | "{} {} {}/{}", 174 | integer_part * original_sign, 175 | sign, 176 | numer.abs(), 177 | denom.abs() 178 | )) 179 | } else { 180 | Some(format!("{}/{}", numer * original_sign, denom)) 181 | } 182 | } 183 | 184 | fn find_repeatend(input: &str) -> Option { 185 | if input.len() < 10 { 186 | return None; 187 | } 188 | 189 | for len in 1..=9 { 190 | for offset in 1..=len { 191 | let chars: Vec = input[..input.len() - offset].chars().rev().collect(); 192 | let mut chunks = chars.chunks(len); 193 | let mut repeats = 1; 194 | let mut prev: &[char] = chunks.next()?; 195 | for chunk in chunks { 196 | if chunk == prev { 197 | repeats += 1; 198 | } else { 199 | repeats = 0; 200 | prev = chunk; 201 | } 202 | 203 | let required_repeats = match len { 204 | 1..=3 => 4, 205 | _ => 2, 206 | }; 207 | if repeats >= required_repeats && !prev.iter().all(|x| x == &'0') { 208 | return Some(prev.iter().rev().cloned().collect::()); 209 | } 210 | } 211 | } 212 | } 213 | 214 | None 215 | } 216 | 217 | fn equivalent_constant(value: f64) -> Option { 218 | if let Some((constant_trunc, constant)) = CONSTANTS.get(&((value.abs().fract() * 10e5) as u32)) 219 | { 220 | let additional = value.trunc() as i32 - (*constant_trunc as f64 * value.signum()) as i32; 221 | let constant_sign = if value.is_sign_positive() { "" } else { "-" }; 222 | 223 | if additional == 0 { 224 | Some(format!("{}{}", constant_sign, constant)) 225 | } else { 226 | let additional_sign = if additional.is_positive() { "+" } else { "-" }; 227 | 228 | Some(format!( 229 | "{}{} {} {}", 230 | constant_sign, 231 | constant, 232 | additional_sign, 233 | additional.abs() 234 | )) 235 | } 236 | } else { 237 | None 238 | } 239 | } 240 | 241 | fn equivalent_root(value: f64) -> Option { 242 | if value.fract().abs() == 0f64 || value > 10e5f64 { 243 | return None; 244 | } 245 | 246 | let squared = KalkValue::Number(float!(value * value), float!(0), None) 247 | .round_if_needed() 248 | .values() 249 | .0; 250 | 251 | if squared.clone().sqrt().fract() != 0f64 && squared.clone().fract() == 0f64 { 252 | Some(format!("√{}", primitive!(squared) as i32)) 253 | } else { 254 | None 255 | } 256 | } 257 | 258 | pub(super) fn round( 259 | input: &KalkValue, 260 | complex_number_type: ComplexNumberType, 261 | ) -> Option { 262 | let (real, imaginary, _) = if let KalkValue::Number(real, imaginary, unit) = input { 263 | (real, imaginary, unit) 264 | } else { 265 | return None; 266 | }; 267 | 268 | let value = match complex_number_type { 269 | ComplexNumberType::Real => real, 270 | ComplexNumberType::Imaginary => imaginary, 271 | }; 272 | let sign = if *value < 0f64 { -1f64 } else { 1f64 }; 273 | let fract = value.clone().abs().fract(); 274 | let integer = value.clone().abs().trunc(); 275 | 276 | // If it's zero something, don't do the rounding as aggressively. 277 | let (limit_floor, limit_ceil) = if integer == 0f64 { 278 | (-8f64, -5f64) 279 | } else { 280 | (-4f64, -6f64) 281 | }; 282 | 283 | if fract.clone().log10() < limit_floor { 284 | // If eg. 0.00xxx 285 | let new_value = integer * sign; 286 | let new_num = match complex_number_type { 287 | ComplexNumberType::Real => { 288 | KalkValue::Number(new_value, imaginary.clone(), input.get_unit().cloned()) 289 | } 290 | ComplexNumberType::Imaginary => { 291 | KalkValue::Number(real.clone(), new_value, input.get_unit().cloned()) 292 | } 293 | }; 294 | 295 | Some(new_num) 296 | } else if (1f64 - fract).log10() < limit_ceil { 297 | // If eg. 0.999 298 | // .abs() this before ceiling to make sure it rounds correctly. The sign is re-added afterwards. 299 | let new_value = value.clone().abs().ceil() * sign; 300 | let new_num = match complex_number_type { 301 | ComplexNumberType::Real => { 302 | KalkValue::Number(new_value, imaginary.clone(), input.get_unit().cloned()) 303 | } 304 | ComplexNumberType::Imaginary => { 305 | KalkValue::Number(real.clone(), new_value, input.get_unit().cloned()) 306 | } 307 | }; 308 | 309 | Some(new_num) 310 | } else { 311 | None 312 | } 313 | } 314 | 315 | pub(super) fn trim_zeroes(input: &str) -> String { 316 | if input.contains('.') { 317 | input 318 | .trim_end_matches('0') 319 | .trim_end_matches('.') 320 | .to_string() 321 | } else { 322 | input.into() 323 | } 324 | } 325 | 326 | #[cfg(test)] 327 | mod tests { 328 | use super::*; 329 | 330 | #[test] 331 | fn test_estimate() { 332 | let in_out = vec![ 333 | (0.99999999, Some(String::from("1"))), 334 | (-0.9999999, Some(String::from("-1"))), 335 | (0.0000000001, Some(String::from("0"))), 336 | (-0.000000001, Some(String::from("0"))), 337 | (1.99999999, Some(String::from("2"))), 338 | (-1.9999999, Some(String::from("-2"))), 339 | (1.000000001, Some(String::from("1"))), 340 | (-1.000001, Some(String::from("-1"))), 341 | (0.5, Some(String::from("1/2"))), 342 | (-0.5, Some(String::from("-1/2"))), 343 | (0.3333333333, Some(String::from("1/3"))), 344 | (1.3333333333, Some(String::from("4/3"))), 345 | (-0.666666666, Some(String::from("-2/3"))), 346 | (-1.666666666, Some(String::from("-5/3"))), 347 | (100.33333333, Some(String::from("100 + 1/3"))), 348 | (-100.6666666, Some(String::from("-100 - 2/3"))), 349 | (0.9932611, None), 350 | (-0.9932611, None), 351 | (-0.00001, None), 352 | (1.9932611, None), 353 | (-1.9932611, None), 354 | (24f64, None), 355 | (-24f64, None), 356 | (1.23456f64, None), 357 | (-1.23456f64, None), 358 | (9999999999f64, None), 359 | (-9999999999f64, None), 360 | (1000000001f64, None), 361 | (-1000000001f64, None), 362 | (0.53f64, None), 363 | (-0.53f64, None), 364 | (-1.51f64, None), 365 | (0.335f64, None), 366 | (-0.335f64, None), 367 | (0.665f64, None), 368 | (-0.665f64, None), 369 | (100f64, None), 370 | (-100f64, None), 371 | (1f64, None), 372 | (0.1f64, None), 373 | (1.23f64, None), 374 | (1.234f64, None), 375 | (1.2345f64, None), 376 | (1.23456f64, None), 377 | (1.234567f64, None), 378 | (1.2345678f64, None), 379 | (1.23456789f64, None), 380 | (-0.1f64, None), 381 | (-1.23f64, None), 382 | (-1.234f64, None), 383 | (-1.2345f64, None), 384 | (-1.23456f64, None), 385 | (-1.234567f64, None), 386 | (-1.2345678f64, None), 387 | (-1.23456789f64, None), 388 | ]; 389 | 390 | for (input, output) in in_out { 391 | let result = KalkValue::from(input).estimate(); 392 | println!("{}", input); 393 | assert_eq!(output, result); 394 | } 395 | } 396 | 397 | #[test] 398 | fn test_equivalent_fraction() { 399 | assert_eq!(equivalent_fraction(0.5f64).unwrap(), "1/2"); 400 | assert_eq!(equivalent_fraction(-0.5f64).unwrap(), "-1/2"); 401 | assert_eq!(equivalent_fraction(1f64 / 3f64).unwrap(), "1/3"); 402 | assert_eq!(equivalent_fraction(4f64 / 3f64).unwrap(), "4/3"); 403 | assert_eq!(equivalent_fraction(7f64 / 3f64).unwrap(), "2 + 1/3"); 404 | assert_eq!(equivalent_fraction(-1f64 / 12f64).unwrap(), "-1/12"); 405 | assert_eq!(equivalent_fraction(-16f64 / -7f64).unwrap(), "2 + 2/7"); 406 | assert!(equivalent_fraction(0.123f64).is_none()); 407 | assert!(equivalent_fraction(1f64).is_none()); 408 | assert!(equivalent_fraction(0.01f64).is_none()); 409 | assert!(equivalent_fraction(-0.9999999f64).is_none()); 410 | assert!(equivalent_fraction(0.9999999f64).is_none()); 411 | assert!(equivalent_fraction(1.9999999f64).is_none()); 412 | } 413 | } 414 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.20" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "ansi_term" 16 | version = "0.12.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 19 | dependencies = [ 20 | "winapi", 21 | ] 22 | 23 | [[package]] 24 | name = "atty" 25 | version = "0.2.14" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 28 | dependencies = [ 29 | "hermit-abi", 30 | "libc", 31 | "winapi", 32 | ] 33 | 34 | [[package]] 35 | name = "az" 36 | version = "1.2.1" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" 39 | 40 | [[package]] 41 | name = "bitflags" 42 | version = "1.3.2" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 45 | 46 | [[package]] 47 | name = "bumpalo" 48 | version = "3.12.0" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" 51 | 52 | [[package]] 53 | name = "cc" 54 | version = "1.0.78" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" 57 | 58 | [[package]] 59 | name = "cfg-if" 60 | version = "1.0.0" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 63 | 64 | [[package]] 65 | name = "console_error_panic_hook" 66 | version = "0.1.7" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" 69 | dependencies = [ 70 | "cfg-if", 71 | "wasm-bindgen", 72 | ] 73 | 74 | [[package]] 75 | name = "dirs" 76 | version = "3.0.2" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309" 79 | dependencies = [ 80 | "dirs-sys", 81 | ] 82 | 83 | [[package]] 84 | name = "dirs-next" 85 | version = "2.0.0" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" 88 | dependencies = [ 89 | "cfg-if", 90 | "dirs-sys-next", 91 | ] 92 | 93 | [[package]] 94 | name = "dirs-sys" 95 | version = "0.3.7" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" 98 | dependencies = [ 99 | "libc", 100 | "redox_users", 101 | "winapi", 102 | ] 103 | 104 | [[package]] 105 | name = "dirs-sys-next" 106 | version = "0.1.2" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" 109 | dependencies = [ 110 | "libc", 111 | "redox_users", 112 | "winapi", 113 | ] 114 | 115 | [[package]] 116 | name = "fs2" 117 | version = "0.4.3" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" 120 | dependencies = [ 121 | "libc", 122 | "winapi", 123 | ] 124 | 125 | [[package]] 126 | name = "getrandom" 127 | version = "0.2.8" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" 130 | dependencies = [ 131 | "cfg-if", 132 | "libc", 133 | "wasi", 134 | ] 135 | 136 | [[package]] 137 | name = "gmp-mpfr-sys" 138 | version = "1.6.2" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "362a6cc3cbe9f41aebe49c03b91aee8fa8fc69d32fb90533f6ed965a882e08e3" 141 | dependencies = [ 142 | "libc", 143 | "windows-sys", 144 | ] 145 | 146 | [[package]] 147 | name = "hermit-abi" 148 | version = "0.1.19" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 151 | dependencies = [ 152 | "libc", 153 | ] 154 | 155 | [[package]] 156 | name = "js-sys" 157 | version = "0.3.60" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" 160 | dependencies = [ 161 | "wasm-bindgen", 162 | ] 163 | 164 | [[package]] 165 | name = "kalk" 166 | version = "3.1.0" 167 | dependencies = [ 168 | "lazy_static", 169 | "regex", 170 | "rug", 171 | "test-case", 172 | "wasm-bindgen", 173 | "wasm-bindgen-test", 174 | ] 175 | 176 | [[package]] 177 | name = "kalker" 178 | version = "2.1.0" 179 | dependencies = [ 180 | "ansi_term", 181 | "atty", 182 | "dirs", 183 | "kalk", 184 | "lazy_static", 185 | "regex", 186 | "rustyline", 187 | "seahorse", 188 | "winres", 189 | ] 190 | 191 | [[package]] 192 | name = "lazy_static" 193 | version = "1.4.0" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 196 | 197 | [[package]] 198 | name = "libc" 199 | version = "0.2.139" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" 202 | 203 | [[package]] 204 | name = "libm" 205 | version = "0.2.8" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" 208 | 209 | [[package]] 210 | name = "log" 211 | version = "0.4.17" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 214 | dependencies = [ 215 | "cfg-if", 216 | ] 217 | 218 | [[package]] 219 | name = "memchr" 220 | version = "2.5.0" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 223 | 224 | [[package]] 225 | name = "nix" 226 | version = "0.19.1" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "b2ccba0cfe4fdf15982d1674c69b1fd80bad427d293849982668dfe454bd61f2" 229 | dependencies = [ 230 | "bitflags", 231 | "cc", 232 | "cfg-if", 233 | "libc", 234 | ] 235 | 236 | [[package]] 237 | name = "once_cell" 238 | version = "1.17.0" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" 241 | 242 | [[package]] 243 | name = "proc-macro2" 244 | version = "1.0.50" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" 247 | dependencies = [ 248 | "unicode-ident", 249 | ] 250 | 251 | [[package]] 252 | name = "quote" 253 | version = "1.0.23" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" 256 | dependencies = [ 257 | "proc-macro2", 258 | ] 259 | 260 | [[package]] 261 | name = "redox_syscall" 262 | version = "0.2.16" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 265 | dependencies = [ 266 | "bitflags", 267 | ] 268 | 269 | [[package]] 270 | name = "redox_users" 271 | version = "0.4.3" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" 274 | dependencies = [ 275 | "getrandom", 276 | "redox_syscall", 277 | "thiserror", 278 | ] 279 | 280 | [[package]] 281 | name = "regex" 282 | version = "1.7.1" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" 285 | dependencies = [ 286 | "aho-corasick", 287 | "memchr", 288 | "regex-syntax", 289 | ] 290 | 291 | [[package]] 292 | name = "regex-syntax" 293 | version = "0.6.28" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" 296 | 297 | [[package]] 298 | name = "rug" 299 | version = "1.24.0" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "76a82fd85950d103ad075f104d10c77d71640830c6a959a418380be380eaf7cd" 302 | dependencies = [ 303 | "az", 304 | "gmp-mpfr-sys", 305 | "libc", 306 | "libm", 307 | ] 308 | 309 | [[package]] 310 | name = "rustyline" 311 | version = "7.1.0" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "8227301bfc717136f0ecbd3d064ba8199e44497a0bdd46bb01ede4387cfd2cec" 314 | dependencies = [ 315 | "bitflags", 316 | "cfg-if", 317 | "dirs-next", 318 | "fs2", 319 | "libc", 320 | "log", 321 | "memchr", 322 | "nix", 323 | "scopeguard", 324 | "unicode-segmentation", 325 | "unicode-width", 326 | "utf8parse", 327 | "winapi", 328 | ] 329 | 330 | [[package]] 331 | name = "scoped-tls" 332 | version = "1.0.1" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" 335 | 336 | [[package]] 337 | name = "scopeguard" 338 | version = "1.1.0" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 341 | 342 | [[package]] 343 | name = "seahorse" 344 | version = "1.1.2" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "8bee1fe454786f1d236a07cb3d3adcbaca96e9ba42d23bc58de059937bb059bd" 347 | 348 | [[package]] 349 | name = "serde" 350 | version = "1.0.152" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" 353 | 354 | [[package]] 355 | name = "syn" 356 | version = "1.0.107" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" 359 | dependencies = [ 360 | "proc-macro2", 361 | "quote", 362 | "unicode-ident", 363 | ] 364 | 365 | [[package]] 366 | name = "test-case" 367 | version = "1.2.3" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "e9e5f048404b43e8ae66dce036163515b6057024cf58c6377be501f250bd3c6a" 370 | dependencies = [ 371 | "cfg-if", 372 | "proc-macro2", 373 | "quote", 374 | "syn", 375 | "version_check", 376 | ] 377 | 378 | [[package]] 379 | name = "thiserror" 380 | version = "1.0.38" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" 383 | dependencies = [ 384 | "thiserror-impl", 385 | ] 386 | 387 | [[package]] 388 | name = "thiserror-impl" 389 | version = "1.0.38" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" 392 | dependencies = [ 393 | "proc-macro2", 394 | "quote", 395 | "syn", 396 | ] 397 | 398 | [[package]] 399 | name = "toml" 400 | version = "0.5.11" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" 403 | dependencies = [ 404 | "serde", 405 | ] 406 | 407 | [[package]] 408 | name = "unicode-ident" 409 | version = "1.0.6" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" 412 | 413 | [[package]] 414 | name = "unicode-segmentation" 415 | version = "1.10.0" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" 418 | 419 | [[package]] 420 | name = "unicode-width" 421 | version = "0.1.10" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 424 | 425 | [[package]] 426 | name = "utf8parse" 427 | version = "0.2.0" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" 430 | 431 | [[package]] 432 | name = "version_check" 433 | version = "0.9.4" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 436 | 437 | [[package]] 438 | name = "wasi" 439 | version = "0.11.0+wasi-snapshot-preview1" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 442 | 443 | [[package]] 444 | name = "wasm-bindgen" 445 | version = "0.2.83" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" 448 | dependencies = [ 449 | "cfg-if", 450 | "wasm-bindgen-macro", 451 | ] 452 | 453 | [[package]] 454 | name = "wasm-bindgen-backend" 455 | version = "0.2.83" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" 458 | dependencies = [ 459 | "bumpalo", 460 | "log", 461 | "once_cell", 462 | "proc-macro2", 463 | "quote", 464 | "syn", 465 | "wasm-bindgen-shared", 466 | ] 467 | 468 | [[package]] 469 | name = "wasm-bindgen-futures" 470 | version = "0.4.33" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" 473 | dependencies = [ 474 | "cfg-if", 475 | "js-sys", 476 | "wasm-bindgen", 477 | "web-sys", 478 | ] 479 | 480 | [[package]] 481 | name = "wasm-bindgen-macro" 482 | version = "0.2.83" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" 485 | dependencies = [ 486 | "quote", 487 | "wasm-bindgen-macro-support", 488 | ] 489 | 490 | [[package]] 491 | name = "wasm-bindgen-macro-support" 492 | version = "0.2.83" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" 495 | dependencies = [ 496 | "proc-macro2", 497 | "quote", 498 | "syn", 499 | "wasm-bindgen-backend", 500 | "wasm-bindgen-shared", 501 | ] 502 | 503 | [[package]] 504 | name = "wasm-bindgen-shared" 505 | version = "0.2.83" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" 508 | 509 | [[package]] 510 | name = "wasm-bindgen-test" 511 | version = "0.3.33" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "09d2fff962180c3fadf677438054b1db62bee4aa32af26a45388af07d1287e1d" 514 | dependencies = [ 515 | "console_error_panic_hook", 516 | "js-sys", 517 | "scoped-tls", 518 | "wasm-bindgen", 519 | "wasm-bindgen-futures", 520 | "wasm-bindgen-test-macro", 521 | ] 522 | 523 | [[package]] 524 | name = "wasm-bindgen-test-macro" 525 | version = "0.3.33" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "4683da3dfc016f704c9f82cf401520c4f1cb3ee440f7f52b3d6ac29506a49ca7" 528 | dependencies = [ 529 | "proc-macro2", 530 | "quote", 531 | ] 532 | 533 | [[package]] 534 | name = "web-sys" 535 | version = "0.3.60" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" 538 | dependencies = [ 539 | "js-sys", 540 | "wasm-bindgen", 541 | ] 542 | 543 | [[package]] 544 | name = "winapi" 545 | version = "0.3.9" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 548 | dependencies = [ 549 | "winapi-i686-pc-windows-gnu", 550 | "winapi-x86_64-pc-windows-gnu", 551 | ] 552 | 553 | [[package]] 554 | name = "winapi-i686-pc-windows-gnu" 555 | version = "0.4.0" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 558 | 559 | [[package]] 560 | name = "winapi-x86_64-pc-windows-gnu" 561 | version = "0.4.0" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 564 | 565 | [[package]] 566 | name = "windows-sys" 567 | version = "0.52.0" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 570 | dependencies = [ 571 | "windows-targets", 572 | ] 573 | 574 | [[package]] 575 | name = "windows-targets" 576 | version = "0.52.4" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" 579 | dependencies = [ 580 | "windows_aarch64_gnullvm", 581 | "windows_aarch64_msvc", 582 | "windows_i686_gnu", 583 | "windows_i686_msvc", 584 | "windows_x86_64_gnu", 585 | "windows_x86_64_gnullvm", 586 | "windows_x86_64_msvc", 587 | ] 588 | 589 | [[package]] 590 | name = "windows_aarch64_gnullvm" 591 | version = "0.52.4" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" 594 | 595 | [[package]] 596 | name = "windows_aarch64_msvc" 597 | version = "0.52.4" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" 600 | 601 | [[package]] 602 | name = "windows_i686_gnu" 603 | version = "0.52.4" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" 606 | 607 | [[package]] 608 | name = "windows_i686_msvc" 609 | version = "0.52.4" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" 612 | 613 | [[package]] 614 | name = "windows_x86_64_gnu" 615 | version = "0.52.4" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" 618 | 619 | [[package]] 620 | name = "windows_x86_64_gnullvm" 621 | version = "0.52.4" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" 624 | 625 | [[package]] 626 | name = "windows_x86_64_msvc" 627 | version = "0.52.4" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" 630 | 631 | [[package]] 632 | name = "winres" 633 | version = "0.1.12" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c" 636 | dependencies = [ 637 | "toml", 638 | ] 639 | --------------------------------------------------------------------------------