diff --git a/Cargo.lock b/Cargo.lock index df9bc2b..cd4d519 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,6 +65,12 @@ dependencies = [ "constant_time_eq", ] +[[package]] +name = "bumpalo" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" + [[package]] name = "cc" version = "1.0.66" @@ -83,6 +89,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "console_error_panic_hook" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8d976903543e0c48546a91908f21588a680a8c8f984df9a5d69feccb2b2a211" +dependencies = [ + "cfg-if 0.1.10", + "wasm-bindgen", +] + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -151,6 +167,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "js-sys" +version = "0.3.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf3d7383929f7c9c7c2d0fa596f325832df98c3704f2c60553080f7127a58175" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "kalk" version = "1.3.0" @@ -158,8 +183,9 @@ dependencies = [ "lazy_static", "regex", "rug", - "special", "test-case", + "wasm-bindgen", + "wasm-bindgen-test", ] [[package]] @@ -311,6 +337,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + [[package]] name = "scopeguard" version = "1.1.0" @@ -329,15 +361,6 @@ version = "1.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800" -[[package]] -name = "special" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a65e074159b75dcf173a4733ab2188baac24967b5c8ec9ed87ae15fcbc7636" -dependencies = [ - "libc", -] - [[package]] name = "syn" version = "1.0.54" @@ -415,6 +438,106 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +[[package]] +name = "wasm-bindgen" +version = "0.2.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd364751395ca0f68cafb17666eee36b63077fb5ecd972bbcd74c90c4bf736e" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1114f89ab1f4106e5b55e688b828c0ab0ea593a1ea7c094b141b14cbaaec2d62" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fe9756085a84584ee9457a002b7cdfe0bfff169f45d2591d8be1345a6780e35" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6ac8995ead1f084a8dea1e65f194d0973800c7f571f6edd70adf06ecf77084" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a48c72f299d80557c7c62e37e7225369ecc0c963964059509fbafe917c7549" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7811dd7f9398f14cc76efd356f98f03aa30419dea46aa810d71e819fc97158" + +[[package]] +name = "wasm-bindgen-test" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0355fa0c1f9b792a09b6dcb6a8be24d51e71e6d74972f9eb4a44c4c004d24a25" +dependencies = [ + "console_error_panic_hook", + "js-sys", + "scoped-tls", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27e07b46b98024c2ba2f9e83a10c2ef0515f057f2da299c1762a2017de80438b" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "web-sys" +version = "0.3.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222b1ef9334f92a21d3fb53dc3fd80f30836959a90f9274a626d7e06315ba3c3" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/kalk/Cargo.toml b/kalk/Cargo.toml index bc12f8f..13abc1e 100644 --- a/kalk/Cargo.toml +++ b/kalk/Cargo.toml @@ -10,12 +10,18 @@ license = "MIT" keywords = ["math", "calculator", "evaluator"] categories = ["mathematics", "parser-implementations"] +[lib] +crate-type = ["cdylib", "rlib"] + [dependencies] rug = { version = "1.11.0", features = ["float"], optional = true } test-case = "1.0.0" regex = "1" lazy_static = "1.4.0" -special = "0.8.1" +wasm-bindgen = "0.2.69" + +[dev-dependencies] +wasm-bindgen-test = "0.3.19" [features] default = ["rug"] \ No newline at end of file diff --git a/kalk/LICENSE b/kalk/LICENSE new file mode 100644 index 0000000..dce55de --- /dev/null +++ b/kalk/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Oliver Waldemar + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/kalk/src/interpreter.rs b/kalk/src/interpreter.rs index 4622003..3e34200 100644 --- a/kalk/src/interpreter.rs +++ b/kalk/src/interpreter.rs @@ -12,7 +12,9 @@ pub struct Context<'a> { #[cfg(feature = "rug")] precision: u32, sum_n_value: Option, - timeout: Option, + #[cfg(not(target_arch = "wasm32"))] + timeout: Option, + #[cfg(not(target_arch = "wasm32"))] start_time: std::time::SystemTime, } @@ -21,7 +23,7 @@ impl<'a> Context<'a> { symbol_table: &'a mut SymbolTable, angle_unit: &str, #[cfg(feature = "rug")] precision: u32, - timeout: Option, + timeout: Option, ) -> Self { Context { angle_unit: angle_unit.into(), @@ -29,7 +31,9 @@ impl<'a> Context<'a> { #[cfg(feature = "rug")] precision, sum_n_value: None, + #[cfg(not(target_arch = "wasm32"))] timeout: timeout, + #[cfg(not(target_arch = "wasm32"))] start_time: std::time::SystemTime::now(), } } @@ -89,8 +93,9 @@ fn eval_expr_stmt(context: &mut Context, expr: &Expr) -> Result Result { + #[cfg(not(target_arch = "wasm32"))] if let (Ok(elapsed), Some(timeout)) = (context.start_time.elapsed(), context.timeout) { - if elapsed.as_secs() >= timeout as u64 { + if elapsed.as_millis() >= timeout { return Err(CalcError::TimedOut); } } diff --git a/kalk/src/inverter.rs b/kalk/src/inverter.rs index 4c2dd37..26530a9 100644 --- a/kalk/src/inverter.rs +++ b/kalk/src/inverter.rs @@ -406,18 +406,21 @@ fn multiply_into(expr: &Expr, base_expr: &Expr) -> Result { } #[allow(unused_imports, dead_code)] // Getting warnings for some reason +#[cfg(test)] mod tests { use crate::ast::Expr; use crate::lexer::TokenKind::*; use crate::parser::DECL_UNIT; use crate::symbol_table::SymbolTable; use crate::test_helpers::*; + use wasm_bindgen_test::*; fn decl_unit() -> Box { Box::new(Expr::Var(crate::parser::DECL_UNIT.into())) } #[test] + #[wasm_bindgen_test] fn test_binary() { let ladd = binary(decl_unit(), Plus, literal(1f64)); let lsub = binary(decl_unit(), Minus, literal(1f64)); @@ -466,6 +469,7 @@ mod tests { } #[test] + #[wasm_bindgen_test] fn test_unary() { let neg = unary(Minus, decl_unit()); @@ -474,6 +478,7 @@ mod tests { } #[test] + #[wasm_bindgen_test] fn test_fn_call() { let call_with_literal = binary(fn_call("f", vec![*literal(2f64)]), Plus, decl_unit()); let call_with_decl_unit = fn_call("f", vec![*decl_unit()]); @@ -512,6 +517,7 @@ mod tests { } #[test] + #[wasm_bindgen_test] fn test_group() { let group_x = binary( group(binary(decl_unit(), Plus, literal(3f64))), @@ -614,6 +620,7 @@ mod tests { } #[test] + #[wasm_bindgen_test] fn test_multiple_decl_units() { /*let add_two = binary(decl_unit(), Plus, decl_unit()); diff --git a/kalk/src/lexer.rs b/kalk/src/lexer.rs index f5eb05c..2e6c6ce 100644 --- a/kalk/src/lexer.rs +++ b/kalk/src/lexer.rs @@ -221,6 +221,8 @@ fn is_valid_identifier(c: Option<&char>) -> bool { mod tests { use super::*; use test_case::test_case; + use wasm_bindgen_test::*; + wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); fn match_tokens(tokens: Vec, expected: Vec) { let mut expected_iter = expected.iter(); @@ -231,6 +233,7 @@ mod tests { } #[test] + #[wasm_bindgen_test] fn test_token_kinds() { let tokens = Lexer::lex("+-*/%^()|=!,"); let expected = vec![ @@ -253,6 +256,7 @@ mod tests { } #[test] + #[wasm_bindgen_test] fn test_empty() { // test_case macro doesn't seem to work with spaces. let test_cases = vec![" ", " ", "test ", " test "]; diff --git a/kalk/src/parser.rs b/kalk/src/parser.rs index 1cb1328..eecacd7 100644 --- a/kalk/src/parser.rs +++ b/kalk/src/parser.rs @@ -6,17 +6,19 @@ use crate::{ prelude, symbol_table::SymbolTable, }; +use wasm_bindgen::prelude::*; pub const DECL_UNIT: &'static str = ".u"; pub const DEFAULT_ANGLE_UNIT: &'static str = "rad"; /// Struct containing the current state of the parser. It stores user-defined functions and variables. +#[wasm_bindgen] pub struct Context { tokens: Vec, pos: usize, symbol_table: SymbolTable, angle_unit: String, - timeout: Option, + timeout: Option, /// This is true whenever the parser is currently parsing a unit declaration. /// It is necessary to keep track of this in order to know when to find (figure out) units that haven't been defined yet. /// Unit names are instead treated as variables. @@ -55,7 +57,10 @@ impl Context { self } - pub fn set_timeout(mut self, timeout: Option) -> Self { + /// Set the timeout in milliseconds. + /// The calculation will stop after this amount of time has passed. + #[cfg(not(target_arch = "wasm32"))] + pub fn set_timeout(mut self, timeout: Option) -> Self { self.timeout = timeout; self @@ -86,6 +91,31 @@ pub enum CalcError { Unknown, } +impl ToString for CalcError { + fn to_string(&self) -> String { + match err { + IncorrectAmountOfArguments(expected, func, got) => format!( + "Expected {} arguments for function {}, but got {}.", + expected, func, got + ), + InvalidNumberLiteral(x) => format!("Invalid number literal: '{}'.", x), + InvalidOperator => format!("Invalid operator."), + InvalidUnit => format!("Invalid unit."), + TimedOut => format!("Operation took too long."), + VariableReferencesItself => format!("Variable references itself."), + UnexpectedToken(got, expected) => { + format!("Unexpected token: '{:?}', expected '{:?}'.", got, expected) + } + UnableToInvert(msg) => format!("Unable to invert: {}", msg), + UndefinedFn(name) => format!("Undefined function: '{}'.", name), + UndefinedVar(name) => format!("Undefined variable: '{}'.", name), + UnableToParseExpression => format!("Unable to parse expression."), + UnableToSolveEquation => format!("Unable to solve equation."), + Unknown => format!("Unknown error."), + } + } +} + /// Evaluate expressions/declarations and return the answer. /// /// `None` will be returned if the last statement is a declaration. @@ -107,6 +137,19 @@ pub fn eval( interpreter.interpret(statements) } +#[wasm_bindgen] +#[cfg(not(feature = "rug"))] +pub fn simple_eval(input: &str) -> Result { + let mut context = Context::new(); + let result = eval(&mut context, input); + + match result { + Ok(Some(value)) => Ok(value.to_f64().into()), + Ok(None) => Ok(JsValue::NULL), + Err(err) => Err(err.to_string()), + } +} + /// Parse expressions/declarations and return a syntax tree. /// /// `None` will be returned if the last statement is a declaration. @@ -546,6 +589,7 @@ mod tests { use super::*; use crate::lexer::{Token, TokenKind::*}; use crate::test_helpers::*; + use wasm_bindgen_test::*; fn parse_with_context(context: &mut Context, tokens: Vec) -> Result { context.tokens = tokens; @@ -563,6 +607,7 @@ mod tests { } #[test] + #[wasm_bindgen_test] fn test_var() { // x let tokens = vec![token(Identifier, "x"), token(EOF, "")]; @@ -571,6 +616,7 @@ mod tests { } #[test] + #[wasm_bindgen_test] fn test_binary() { // 1+2*(3-4/5) let tokens = vec![ @@ -607,6 +653,7 @@ mod tests { } #[test] + #[wasm_bindgen_test] fn test_pow() { let tokens = vec![ token(Literal, "1"), @@ -640,6 +687,7 @@ mod tests { } #[test] + #[wasm_bindgen_test] fn test_percent() { let tokens = vec![ token(Literal, "1"), @@ -662,6 +710,7 @@ mod tests { } #[test] + #[wasm_bindgen_test] fn test_unit() { let tokens = vec![token(Literal, "1"), token(Identifier, "a")]; @@ -677,6 +726,7 @@ mod tests { } #[test] + #[wasm_bindgen_test] fn test_var_decl() { let tokens = vec![ token(Identifier, "x"), @@ -697,6 +747,7 @@ mod tests { } #[test] + #[wasm_bindgen_test] fn test_fn_decl() { let tokens = vec![ token(Identifier, "f"), @@ -721,6 +772,7 @@ mod tests { } #[test] + #[wasm_bindgen_test] fn test_fn_call() { let tokens = vec![ token(Identifier, "f"), diff --git a/kalk/src/prelude/regular.rs b/kalk/src/prelude/regular.rs index d07b85f..b66b3a6 100644 --- a/kalk/src/prelude/regular.rs +++ b/kalk/src/prelude/regular.rs @@ -96,7 +96,8 @@ fn from_angle_unit(context: &mut interpreter::Context, x: f64, angle_unit: &str) pub mod special_funcs { pub fn factorial(x: f64) -> f64 { - special::Gamma::gamma(x + 1f64) + //special::Gamma::gamma(x + 1f64) + x } } @@ -198,7 +199,8 @@ pub(super) mod funcs { } pub fn gamma(x: f64) -> f64 { - special::Gamma::gamma(x) + //special::Gamma::gamma(x) + x } pub fn hyp(x: f64, y: f64) -> f64 { diff --git a/kalk_cli/LICENSE b/kalk_cli/LICENSE new file mode 100644 index 0000000..dce55de --- /dev/null +++ b/kalk_cli/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Oliver Waldemar + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/kalk_cli/src/output.rs b/kalk_cli/src/output.rs index ced02fb..f338dbd 100644 --- a/kalk_cli/src/output.rs +++ b/kalk_cli/src/output.rs @@ -17,7 +17,7 @@ pub fn eval(parser: &mut parser::Context, input: &str, precision: u32) { println!("{} {}", result_str, result.get_unit()); } Ok(None) => print!(""), - Err(err) => print_calc_err(err), + Err(err) => print_err(&err.to_string()), } } @@ -25,26 +25,3 @@ pub fn print_err(msg: &str) { Red.paint(msg).to_string(); println!("{}", msg); } - -fn print_calc_err(err: CalcError) { - print_err(&match err { - IncorrectAmountOfArguments(expected, func, got) => format!( - "Expected {} arguments for function {}, but got {}.", - expected, func, got - ), - InvalidNumberLiteral(x) => format!("Invalid number literal: '{}'.", x), - InvalidOperator => format!("Invalid operator."), - InvalidUnit => format!("Invalid unit."), - TimedOut => format!("Operation took too long."), - VariableReferencesItself => format!("Variable references itself."), - UnexpectedToken(got, expected) => { - format!("Unexpected token: '{:?}', expected '{:?}'.", got, expected) - } - UnableToInvert(msg) => format!("Unable to invert: {}", msg), - UndefinedFn(name) => format!("Undefined function: '{}'.", name), - UndefinedVar(name) => format!("Undefined variable: '{}'.", name), - UnableToParseExpression => format!("Unable to parse expression."), - UnableToSolveEquation => format!("Unable to solve equation."), - Unknown => format!("Unknown error."), - }); -} diff --git a/kalk_web/.gitignore b/kalk_web/.gitignore new file mode 100644 index 0000000..504afef --- /dev/null +++ b/kalk_web/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +package-lock.json diff --git a/kalk_web/index.html b/kalk_web/index.html new file mode 100644 index 0000000..74994cf --- /dev/null +++ b/kalk_web/index.html @@ -0,0 +1,10 @@ + + + + + kalk + + + + + diff --git a/kalk_web/index.js b/kalk_web/index.js new file mode 100644 index 0000000..d08fd64 --- /dev/null +++ b/kalk_web/index.js @@ -0,0 +1,11 @@ +main(); + +async function main() { + const kalk = await import("kalk-rs"); + + try { + console.log(kalk.simple_eval("5+")); + } catch(err) { + console.log(err); + } +} \ No newline at end of file diff --git a/kalk_web/package.json b/kalk_web/package.json new file mode 100644 index 0000000..436f2fe --- /dev/null +++ b/kalk_web/package.json @@ -0,0 +1,10 @@ +{ + "scripts": { + "serve": "webpack-dev-server" + }, + "devDependencies": { + "webpack": "^4.25.1", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.10" + } +} diff --git a/kalk_web/webpack.config.js b/kalk_web/webpack.config.js new file mode 100644 index 0000000..3e3afe6 --- /dev/null +++ b/kalk_web/webpack.config.js @@ -0,0 +1,9 @@ +const path = require('path'); +module.exports = { + entry: "./index.js", + output: { + path: path.resolve(__dirname, "dist"), + filename: "index.js", + }, + mode: "development" +};