Merge pull request #347 from schungx/master

Use radians for trig and ** for exponentiation.
This commit is contained in:
Stephen Chung 2021-02-10 12:55:58 +08:00 committed by GitHub
commit bd633a2540
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 102 additions and 66 deletions

View File

@ -13,7 +13,12 @@ Bug fixes
Breaking changes Breaking changes
---------------- ----------------
* In order to be consistent with other scripting languages:
* the power/exponentiation operator is changed from `~` to `**`; `~` is now a reserved symbol
* the power/exponentiation operator now binds to the right
* trigonometry functions now take radians and return radians instead of degrees
* `Dynamic::into_shared` is no longer available under `no_closure`. It used to panic. * `Dynamic::into_shared` is no longer available under `no_closure`. It used to panic.
* `Token::is_operator` is renamed to `Token::is_symbol`.
Enhancements Enhancements
------------ ------------
@ -55,6 +60,7 @@ Enhancements
* `ahash` is used to hash function call parameters. This should yield speed improvements. * `ahash` is used to hash function call parameters. This should yield speed improvements.
* `Dynamic` and `ImmutableString` now implement `serde::Serialize` and `serde::Deserialize`. * `Dynamic` and `ImmutableString` now implement `serde::Serialize` and `serde::Deserialize`.
* `NativeCallContext` has a new field containing the name of the function called, useful when the same Rust function is registered under multiple names in Rhai. * `NativeCallContext` has a new field containing the name of the function called, useful when the same Rust function is registered under multiple names in Rhai.
* New functions `PI()` and `E()` to return mathematical constants, and `to_radians` and `to_degrees` to convert between radians and degrees.
Version 0.19.10 Version 0.19.10

View File

@ -288,8 +288,8 @@ impl Engine {
return Err(format!("'{}' is a reserved keyword", keyword).into()); return Err(format!("'{}' is a reserved keyword", keyword).into());
} }
} }
// Active standard operators cannot be made custom // Active standard symbols cannot be made custom
Some(token) if token.is_operator() => { Some(token) if token.is_symbol() => {
if !self.disabled_symbols.contains(token.syntax().as_ref()) { if !self.disabled_symbols.contains(token.syntax().as_ref()) {
return Err(format!("'{}' is a reserved operator", keyword).into()); return Err(format!("'{}' is a reserved operator", keyword).into());
} }

View File

@ -1367,7 +1367,7 @@ pub fn run_builtin_binary_op(
"*" => return multiply(x, y).map(Some), "*" => return multiply(x, y).map(Some),
"/" => return divide(x, y).map(Some), "/" => return divide(x, y).map(Some),
"%" => return modulo(x, y).map(Some), "%" => return modulo(x, y).map(Some),
"~" => return power(x, y).map(Some), "**" => return power(x, y).map(Some),
">>" => return shift_right(x, y).map(Some), ">>" => return shift_right(x, y).map(Some),
"<<" => return shift_left(x, y).map(Some), "<<" => return shift_left(x, y).map(Some),
_ => (), _ => (),
@ -1379,7 +1379,7 @@ pub fn run_builtin_binary_op(
"*" => return Ok(Some((x * y).into())), "*" => return Ok(Some((x * y).into())),
"/" => return Ok(Some((x / y).into())), "/" => return Ok(Some((x / y).into())),
"%" => return Ok(Some((x % y).into())), "%" => return Ok(Some((x % y).into())),
"~" => return Ok(Some(x.pow(y as u32).into())), "**" => return Ok(Some(x.pow(y as u32).into())),
">>" => return Ok(Some((x >> y).into())), ">>" => return Ok(Some((x >> y).into())),
"<<" => return Ok(Some((x << y).into())), "<<" => return Ok(Some((x << y).into())),
_ => (), _ => (),
@ -1457,7 +1457,7 @@ pub fn run_builtin_binary_op(
"*" => return Ok(Some((x * y).into())), "*" => return Ok(Some((x * y).into())),
"/" => return Ok(Some((x / y).into())), "/" => return Ok(Some((x / y).into())),
"%" => return Ok(Some((x % y).into())), "%" => return Ok(Some((x % y).into())),
"~" => return Ok(Some(x.powf(y).into())), "**" => return Ok(Some(x.powf(y).into())),
"==" => return Ok(Some((x == y).into())), "==" => return Ok(Some((x == y).into())),
"!=" => return Ok(Some((x != y).into())), "!=" => return Ok(Some((x != y).into())),
">" => return Ok(Some((x > y).into())), ">" => return Ok(Some((x > y).into())),

View File

@ -68,7 +68,7 @@ macro_rules! gen_arithmetic_functions {
Ok(Dynamic::from(x % y)) Ok(Dynamic::from(x % y))
} }
} }
#[rhai_fn(name = "~", return_raw)] #[rhai_fn(name = "**", return_raw)]
pub fn power(x: INT, y: INT) -> Result<Dynamic, Box<EvalAltResult>> { pub fn power(x: INT, y: INT) -> Result<Dynamic, Box<EvalAltResult>> {
if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "unchecked")) {
if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) { if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) {
@ -246,7 +246,7 @@ mod f32_functions {
pub fn modulo(x: f32, y: f32) -> f32 { pub fn modulo(x: f32, y: f32) -> f32 {
x % y x % y
} }
#[rhai_fn(name = "~", return_raw)] #[rhai_fn(name = "**", return_raw)]
pub fn pow_f_f(x: f32, y: f32) -> Result<Dynamic, Box<EvalAltResult>> { pub fn pow_f_f(x: f32, y: f32) -> Result<Dynamic, Box<EvalAltResult>> {
Ok(Dynamic::from(x.powf(y))) Ok(Dynamic::from(x.powf(y)))
} }
@ -271,7 +271,7 @@ mod f32_functions {
1 1
} }
} }
#[rhai_fn(name = "~", return_raw)] #[rhai_fn(name = "**", return_raw)]
pub fn pow_f_i(x: f32, y: INT) -> Result<Dynamic, Box<EvalAltResult>> { pub fn pow_f_i(x: f32, y: INT) -> Result<Dynamic, Box<EvalAltResult>> {
if cfg!(not(feature = "unchecked")) && y > (i32::MAX as INT) { if cfg!(not(feature = "unchecked")) && y > (i32::MAX as INT) {
Err(make_err(format!( Err(make_err(format!(
@ -309,7 +309,7 @@ mod f64_functions {
pub fn modulo(x: f64, y: f64) -> f64 { pub fn modulo(x: f64, y: f64) -> f64 {
x % y x % y
} }
#[rhai_fn(name = "~", return_raw)] #[rhai_fn(name = "**", return_raw)]
pub fn pow_f_f(x: f64, y: f64) -> Result<Dynamic, Box<EvalAltResult>> { pub fn pow_f_f(x: f64, y: f64) -> Result<Dynamic, Box<EvalAltResult>> {
Ok(Dynamic::from(x.powf(y))) Ok(Dynamic::from(x.powf(y)))
} }
@ -334,7 +334,7 @@ mod f64_functions {
1 1
} }
} }
#[rhai_fn(name = "~", return_raw)] #[rhai_fn(name = "**", return_raw)]
pub fn pow_f_i(x: FLOAT, y: INT) -> Result<Dynamic, Box<EvalAltResult>> { pub fn pow_f_i(x: FLOAT, y: INT) -> Result<Dynamic, Box<EvalAltResult>> {
if cfg!(not(feature = "unchecked")) && y > (i32::MAX as INT) { if cfg!(not(feature = "unchecked")) && y > (i32::MAX as INT) {
Err(make_err(format!( Err(make_err(format!(

View File

@ -112,40 +112,40 @@ mod trig_functions {
use crate::FLOAT; use crate::FLOAT;
pub fn sin(x: FLOAT) -> FLOAT { pub fn sin(x: FLOAT) -> FLOAT {
x.to_radians().sin() x.sin()
} }
pub fn cos(x: FLOAT) -> FLOAT { pub fn cos(x: FLOAT) -> FLOAT {
x.to_radians().cos() x.cos()
} }
pub fn tan(x: FLOAT) -> FLOAT { pub fn tan(x: FLOAT) -> FLOAT {
x.to_radians().tan() x.tan()
} }
pub fn sinh(x: FLOAT) -> FLOAT { pub fn sinh(x: FLOAT) -> FLOAT {
x.to_radians().sinh() x.sinh()
} }
pub fn cosh(x: FLOAT) -> FLOAT { pub fn cosh(x: FLOAT) -> FLOAT {
x.to_radians().cosh() x.cosh()
} }
pub fn tanh(x: FLOAT) -> FLOAT { pub fn tanh(x: FLOAT) -> FLOAT {
x.to_radians().tanh() x.tanh()
} }
pub fn asin(x: FLOAT) -> FLOAT { pub fn asin(x: FLOAT) -> FLOAT {
x.asin().to_degrees() x.asin()
} }
pub fn acos(x: FLOAT) -> FLOAT { pub fn acos(x: FLOAT) -> FLOAT {
x.acos().to_degrees() x.acos()
} }
pub fn atan(x: FLOAT) -> FLOAT { pub fn atan(x: FLOAT) -> FLOAT {
x.atan().to_degrees() x.atan()
} }
pub fn asinh(x: FLOAT) -> FLOAT { pub fn asinh(x: FLOAT) -> FLOAT {
x.asinh().to_degrees() x.asinh()
} }
pub fn acosh(x: FLOAT) -> FLOAT { pub fn acosh(x: FLOAT) -> FLOAT {
x.acosh().to_degrees() x.acosh()
} }
pub fn atanh(x: FLOAT) -> FLOAT { pub fn atanh(x: FLOAT) -> FLOAT {
x.atanh().to_degrees() x.atanh()
} }
} }
@ -154,6 +154,26 @@ mod trig_functions {
mod float_functions { mod float_functions {
use crate::FLOAT; use crate::FLOAT;
#[rhai_fn(name = "E")]
pub fn e() -> FLOAT {
#[cfg(not(feature = "f32_float"))]
return crate::stdlib::f64::consts::E;
#[cfg(feature = "f32_float")]
return crate::stdlib::f32::consts::E;
}
#[rhai_fn(name = "PI")]
pub fn pi() -> FLOAT {
#[cfg(not(feature = "f32_float"))]
return crate::stdlib::f64::consts::PI;
#[cfg(feature = "f32_float")]
return crate::stdlib::f32::consts::PI;
}
pub fn to_radians(x: FLOAT) -> FLOAT {
x.to_radians()
}
pub fn to_degrees(x: FLOAT) -> FLOAT {
x.to_degrees()
}
pub fn sqrt(x: FLOAT) -> FLOAT { pub fn sqrt(x: FLOAT) -> FLOAT {
x.sqrt() x.sqrt()
} }

View File

@ -194,7 +194,7 @@ pub enum Token {
Divide, Divide,
/// `%` /// `%`
Modulo, Modulo,
/// `~` /// `**`
PowerOf, PowerOf,
/// `<<` /// `<<`
LeftShift, LeftShift,
@ -305,7 +305,7 @@ pub enum Token {
XOrAssign, XOrAssign,
/// `%=` /// `%=`
ModuloAssign, ModuloAssign,
/// `~=` /// `**=`
PowerOfAssign, PowerOfAssign,
/// `private` /// `private`
/// ///
@ -422,8 +422,8 @@ impl Token {
XOr => "^", XOr => "^",
Modulo => "%", Modulo => "%",
ModuloAssign => "%=", ModuloAssign => "%=",
PowerOf => "~", PowerOf => "**",
PowerOfAssign => "~=", PowerOfAssign => "**=",
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
Fn => "fn", Fn => "fn",
@ -511,8 +511,8 @@ impl Token {
"^" => XOr, "^" => XOr,
"%" => Modulo, "%" => Modulo,
"%=" => ModuloAssign, "%=" => ModuloAssign,
"~" => PowerOf, "**" => PowerOf,
"~=" => PowerOfAssign, "**=" => PowerOfAssign,
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
"fn" => Fn, "fn" => Fn,
@ -532,7 +532,7 @@ impl Token {
#[cfg(feature = "no_module")] #[cfg(feature = "no_module")]
"import" | "export" | "as" => Reserved(syntax.into()), "import" | "export" | "as" => Reserved(syntax.into()),
"===" | "!==" | "->" | "<-" | ":=" | "**" | "::<" | "(*" | "*)" | "#" | "public" "===" | "!==" | "->" | "<-" | ":=" | "~" | "::<" | "(*" | "*)" | "#" | "public"
| "new" | "use" | "module" | "package" | "var" | "static" | "begin" | "end" | "new" | "use" | "module" | "package" | "var" | "static" | "begin" | "end"
| "shared" | "with" | "each" | "then" | "goto" | "unless" | "exit" | "match" | "shared" | "with" | "each" | "then" | "goto" | "unless" | "exit" | "match"
| "case" | "default" | "void" | "null" | "nil" | "spawn" | "thread" | "go" | "sync" | "case" | "default" | "void" | "null" | "nil" | "spawn" | "thread" | "go" | "sync"
@ -599,6 +599,8 @@ impl Token {
DivideAssign | DivideAssign |
LeftShiftAssign | LeftShiftAssign |
RightShiftAssign | RightShiftAssign |
PowerOf |
PowerOfAssign |
AndAssign | AndAssign |
OrAssign | OrAssign |
XOrAssign | XOrAssign |
@ -609,9 +611,7 @@ impl Token {
ModuloAssign | ModuloAssign |
Return | Return |
Throw | Throw |
PowerOf | In => true,
In |
PowerOfAssign => true,
_ => false, _ => false,
} }
@ -623,9 +623,9 @@ impl Token {
match self { match self {
// Assignments are not considered expressions - set to zero // Assignments are not considered expressions - set to zero
Equals | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | LeftShiftAssign Equals | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | PowerOfAssign
| RightShiftAssign | AndAssign | OrAssign | XOrAssign | ModuloAssign | LeftShiftAssign | RightShiftAssign | AndAssign | OrAssign | XOrAssign
| PowerOfAssign => 0, | ModuloAssign => 0,
Or | XOr | Pipe => 30, Or | XOr | Pipe => 30,
@ -657,19 +657,22 @@ impl Token {
match self { match self {
// Assignments bind to the right // Assignments bind to the right
Equals | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | LeftShiftAssign Equals | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | PowerOfAssign
| RightShiftAssign | AndAssign | OrAssign | XOrAssign | ModuloAssign | LeftShiftAssign | RightShiftAssign | AndAssign | OrAssign | XOrAssign
| PowerOfAssign => true, | ModuloAssign => true,
// Property access binds to the right // Property access binds to the right
Period => true, Period => true,
// Exponentiation binds to the right
PowerOf => true,
_ => false, _ => false,
} }
} }
/// Is this token an operator? /// Is this token a standard symbol used in the language?
pub fn is_operator(&self) -> bool { pub fn is_symbol(&self) -> bool {
use Token::*; use Token::*;
match self { match self {
@ -1284,10 +1287,6 @@ fn get_next_token_inner(
('-', _) if !state.non_unary => return Some((Token::UnaryMinus, start_pos)), ('-', _) if !state.non_unary => return Some((Token::UnaryMinus, start_pos)),
('-', _) => return Some((Token::Minus, start_pos)), ('-', _) => return Some((Token::Minus, start_pos)),
('*', '*') => {
eat_next(stream, pos);
return Some((Token::Reserved("**".into()), start_pos));
}
('*', ')') => { ('*', ')') => {
eat_next(stream, pos); eat_next(stream, pos);
return Some((Token::Reserved("*)".into()), start_pos)); return Some((Token::Reserved("*)".into()), start_pos));
@ -1296,6 +1295,19 @@ fn get_next_token_inner(
eat_next(stream, pos); eat_next(stream, pos);
return Some((Token::MultiplyAssign, start_pos)); return Some((Token::MultiplyAssign, start_pos));
} }
('*', '*') => {
eat_next(stream, pos);
return Some((
if stream.peek_next() == Some('=') {
eat_next(stream, pos);
Token::PowerOfAssign
} else {
Token::PowerOf
},
start_pos,
));
}
('*', _) => return Some((Token::Multiply, start_pos)), ('*', _) => return Some((Token::Multiply, start_pos)),
// Comments // Comments
@ -1490,18 +1502,14 @@ fn get_next_token_inner(
} }
('^', _) => return Some((Token::XOr, start_pos)), ('^', _) => return Some((Token::XOr, start_pos)),
('~', _) => return Some((Token::Reserved("~".into()), start_pos)),
('%', '=') => { ('%', '=') => {
eat_next(stream, pos); eat_next(stream, pos);
return Some((Token::ModuloAssign, start_pos)); return Some((Token::ModuloAssign, start_pos));
} }
('%', _) => return Some((Token::Modulo, start_pos)), ('%', _) => return Some((Token::Modulo, start_pos)),
('~', '=') => {
eat_next(stream, pos);
return Some((Token::PowerOfAssign, start_pos));
}
('~', _) => return Some((Token::PowerOf, start_pos)),
('@', _) => return Some((Token::Reserved("@".into()), start_pos)), ('@', _) => return Some((Token::Reserved("@".into()), start_pos)),
('$', _) => return Some((Token::Reserved("$".into()), start_pos)), ('$', _) => return Some((Token::Reserved("$".into()), start_pos)),

View File

@ -14,10 +14,10 @@ fn test_math() -> Result<(), Box<EvalAltResult>> {
assert_eq!(engine.eval::<INT>("3 % 2")?, 1); assert_eq!(engine.eval::<INT>("3 % 2")?, 1);
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
assert!((engine.eval::<FLOAT>("sin(30.0)")? - 0.5).abs() < 0.001); assert!((engine.eval::<FLOAT>("sin(PI()/6.0)")? - 0.5).abs() < 0.001);
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
assert!(engine.eval::<FLOAT>("cos(90.0)")?.abs() < 0.001); assert!(engine.eval::<FLOAT>("cos(PI()/2.0)")?.abs() < 0.001);
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
assert_eq!( assert_eq!(

View File

@ -10,18 +10,20 @@ const EPSILON: FLOAT = 0.000_001;
fn test_power_of() -> Result<(), Box<EvalAltResult>> { fn test_power_of() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("2 ~ 3")?, 8); assert_eq!(engine.eval::<INT>("2 ** 3")?, 8);
assert_eq!(engine.eval::<INT>("(-2 ~ 3)")?, -8); assert_eq!(engine.eval::<INT>("(-2 ** 3)")?, -8);
assert_eq!(engine.eval::<INT>("2 ** 3 ** 2")?, 512);
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
{ {
assert!( assert!(
(engine.eval::<FLOAT>("2.2 ~ 3.3")? - 13.489_468_760_533_386 as FLOAT).abs() <= EPSILON (engine.eval::<FLOAT>("2.2 ** 3.3")? - 13.489_468_760_533_386 as FLOAT).abs()
<= EPSILON
); );
assert!((engine.eval::<FLOAT>("2.0~-2.0")? - 0.25 as FLOAT).abs() < EPSILON); assert!((engine.eval::<FLOAT>("2.0**-2.0")? - 0.25 as FLOAT).abs() < EPSILON);
assert!((engine.eval::<FLOAT>("(-2.0~-2.0)")? - 0.25 as FLOAT).abs() < EPSILON); assert!((engine.eval::<FLOAT>("(-2.0**-2.0)")? - 0.25 as FLOAT).abs() < EPSILON);
assert!((engine.eval::<FLOAT>("(-2.0~-2)")? - 0.25 as FLOAT).abs() < EPSILON); assert!((engine.eval::<FLOAT>("(-2.0**-2)")? - 0.25 as FLOAT).abs() < EPSILON);
assert_eq!(engine.eval::<INT>("4~3")?, 64); assert_eq!(engine.eval::<INT>("4**3")?, 64);
} }
Ok(()) Ok(())
@ -31,26 +33,26 @@ fn test_power_of() -> Result<(), Box<EvalAltResult>> {
fn test_power_of_equals() -> Result<(), Box<EvalAltResult>> { fn test_power_of_equals() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("let x = 2; x ~= 3; x")?, 8); assert_eq!(engine.eval::<INT>("let x = 2; x **= 3; x")?, 8);
assert_eq!(engine.eval::<INT>("let x = -2; x ~= 3; x")?, -8); assert_eq!(engine.eval::<INT>("let x = -2; x **= 3; x")?, -8);
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
{ {
assert!( assert!(
(engine.eval::<FLOAT>("let x = 2.2; x ~= 3.3; x")? - 13.489_468_760_533_386 as FLOAT) (engine.eval::<FLOAT>("let x = 2.2; x **= 3.3; x")? - 13.489_468_760_533_386 as FLOAT)
.abs() .abs()
<= EPSILON <= EPSILON
); );
assert!( assert!(
(engine.eval::<FLOAT>("let x = 2.0; x ~= -2.0; x")? - 0.25 as FLOAT).abs() < EPSILON (engine.eval::<FLOAT>("let x = 2.0; x **= -2.0; x")? - 0.25 as FLOAT).abs() < EPSILON
); );
assert!( assert!(
(engine.eval::<FLOAT>("let x = -2.0; x ~= -2.0; x")? - 0.25 as FLOAT).abs() < EPSILON (engine.eval::<FLOAT>("let x = -2.0; x **= -2.0; x")? - 0.25 as FLOAT).abs() < EPSILON
); );
assert!( assert!(
(engine.eval::<FLOAT>("let x = -2.0; x ~= -2; x")? - 0.25 as FLOAT).abs() < EPSILON (engine.eval::<FLOAT>("let x = -2.0; x **= -2; x")? - 0.25 as FLOAT).abs() < EPSILON
); );
assert_eq!(engine.eval::<INT>("let x =4; x ~= 3; x")?, 64); assert_eq!(engine.eval::<INT>("let x =4; x **= 3; x")?, 64);
} }
Ok(()) Ok(())