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 |
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 |
--------------------------------------------------------------------------------
/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 | 
3 | 
4 |
5 | A web component built with Svelte that uses the kalk library (WebAssembly).
6 |
7 | 
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 |
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 |
41 |
--------------------------------------------------------------------------------
/mobile/.idea/workspace.xml:
--------------------------------------------------------------------------------
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 | 1614028812239
42 |
43 |
44 | 1614028812239
45 |
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 | 
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 | [](https://crates.io/crates/kalker)  [](https://github.com/PaddiM8/kalker/blob/master/LICENSE) [](https://docs.rs/kalk/latest/kalk/) 
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 |
--------------------------------------------------------------------------------