Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3431,10 +3431,12 @@ pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) ->
TaggedValue::String({
let mut expression = x.value.trim().to_string();

if ["+", "-", "*", "/", "^", "%"].iter().any(|&infix| infix == expression) {
if ["+", "-", "*", "/", "^"].iter().any(|&infix| infix == expression) {
expression = format!("A {expression} B");
} else if expression == "^" {
expression = String::from("A^B");
Comment thread
Keavon marked this conversation as resolved.
} else if expression == "%" || expression == "mod" {
expression = String::from("mod(a, b)");
}

expression
Expand Down
1 change: 0 additions & 1 deletion libraries/math-parser/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ pub enum BinaryOp {
Div,
/// Logical OR over operands that must each be exactly 0 or 1, returning 0 or 1.
Or,
Modulo,
Pow,
Leq,
Lt,
Expand Down
9 changes: 9 additions & 0 deletions libraries/math-parser/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,15 @@ pub fn builtin_function(name: &str) -> Option<BuiltinFunction> {
_ => None,
},

"mod" => |values| match values {
[Value::Number(Number::Real(x)), Value::Number(Number::Real(modulus))] => {
// Floored, so a truncated remainder with the opposite sign from the modulus moves over by one modulus
let remainder = x % modulus;
Some(Value::from_f64(if remainder != 0. && (remainder < 0.) != (*modulus < 0.) { remainder + modulus } else { remainder }))
}
_ => None,
},

"gcd" => |values| {
let reduced = real_operands(values)?
.into_iter()
Expand Down
7 changes: 4 additions & 3 deletions libraries/math-parser/src/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ pub enum Token<'src> {
Comma,
Plus,
Minus,
Modulo,
/// Reserved for percentages, so the parser never matches it and its error points a C-style remainder to `mod(a, b)`.
Percent,
Star,
Slash,
Caret,
Expand Down Expand Up @@ -59,7 +60,7 @@ impl<'src> fmt::Display for Token<'src> {
Token::Comma => f.write_str(","),
Token::Plus => f.write_str("+"),
Token::Minus => f.write_str("-"),
Token::Modulo => f.write_str("%"),
Token::Percent => f.write_str("%"),
Token::Star => f.write_str("*"),
Token::Slash => f.write_str("/"),
Token::Caret => f.write_str("^"),
Expand Down Expand Up @@ -375,7 +376,7 @@ impl<'a> Lexer<'a> {
'+' => Plus,
'-' => Minus,
'*' => Star,
'%' => Modulo,
'%' => Percent,
'/' => Slash,
'^' => Caret,
'≠' => Neq,
Expand Down
23 changes: 18 additions & 5 deletions libraries/math-parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ mod tests {
}
}

#[test]
fn percent_is_not_the_remainder() {
// `%` is reserved for percentages, so a C-style remainder is a parse error naming the function that computes it
for input in ["5 % 3", "x % 2", "%", "50%"] {
let error = evaluate(input).unwrap_err().to_string();
assert!(error.contains("`mod(a, b)`"), "`{input}` gave the error `{error}`");
}
}

#[test]
fn not_sign_is_prefix_only() {
// `¬` spells only the prefix logical not, so it must not stand in for `!` in its postfix factorial role
Expand Down Expand Up @@ -136,6 +145,7 @@ mod tests {
"sin(inf)",
"gcd(inf, 6)",
"gcd(10000000000000000000, 2)",
"mod(5, 0)",
"(-1)!",
"2.5!",
"i!",
Expand Down Expand Up @@ -359,10 +369,13 @@ mod tests {
infix_subtraction: "5 - 3" => 2.,
infix_multiplication: "4 * 4" => 16.,
infix_division: "8/2" => 4.,
modulo_pos_pos: "3.2 % 2" => 1.2,
modulo_pos_neg: "3.2 % -2" => 1.2,
modulo_neg_neg: "(-3.2) % -2" => -1.2,
modulo_neg_pos: "(-3.2) % 2" => -1.2,
modulo_pos_pos: "mod(3.2, 2)" => 1.2,
modulo_pos_neg: "mod(3.2, -2)" => -0.8,
modulo_neg_neg: "mod(-3.2, -2)" => -1.2,
modulo_neg_pos: "mod(-3.2, 2)" => 0.8,
modulo_neg_multiple: "mod(-4, 2)" => 0.,
modulo_integer_wrap: "mod(-7, 3)" => 2.,
modulo_angle_wrap: "mod(-pi/2, tau)" => 1.5 * std::f64::consts::PI,
exp_pos_pos: "3.2 ^ 2" => 256. / 25.,
exp_pos_neg: "3.2 ^ -2" => 25. / 256.,
exp_neg_neg: "-3.2 ^ -2" => -25. / 256.,
Expand Down Expand Up @@ -686,7 +699,7 @@ mod tests {
magnitude_difference_spaced: "|-2| - |-3|" => -1.,
magnitude_difference_unspaced: "|-2|-|-3|" => -1.,
magnitude_quotient: "|-2| / |-4|" => 0.5,
magnitude_modulo: "|-7| % |-4|" => 3.,
magnitude_modulo: "mod(|-7|, |-4|)" => 3.,
magnitude_factorial_inside: "|3!|" => 6.,
magnitude_factorial_then_sum: "|-3|! + 1" => 7.,
magnitude_less_than_spaced: "|-3| < |-5|" => 1.,
Expand Down
12 changes: 10 additions & 2 deletions libraries/math-parser/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,15 @@ impl Node {

match parser::<Lexer, extra::Err<Rich<Token, Span>>>().parse(Lexer::new(src)).into_result() {
Ok(ast) => Ok(ast),
Err(parse_errs) => Err(ParseError(parse_errs.into_iter().map(|e| format!("{e} at {}", e.span())).collect())),
Err(parse_errs) => Err(ParseError(
parse_errs
.into_iter()
.map(|e| match e.found() {
Some(Token::Percent) => format!("`%` is reserved for percentages, so the remainder is written `mod(a, b)`, at {}", e.span()),
_ => format!("{e} at {}", e.span()),
})
.collect(),
)),
}
}
}
Expand Down Expand Up @@ -92,7 +100,7 @@ where
let atom = choice((constant, if_expr, call_or_var, parens, magnitude)).labelled("atom");

let add_op = choice((just(Token::Plus).to(BinaryOp::Add), just(Token::Minus).to(BinaryOp::Sub)));
let mul_op = choice((just(Token::Star).to(BinaryOp::Mul), just(Token::Slash).to(BinaryOp::Div), just(Token::Modulo).to(BinaryOp::Modulo)));
let mul_op = choice((just(Token::Star).to(BinaryOp::Mul), just(Token::Slash).to(BinaryOp::Div)));
Comment thread
Keavon marked this conversation as resolved.
let pow_op = just(Token::Caret).to(BinaryOp::Pow);
let unary_op = choice((
just(Token::Minus).to(UnaryOp::Neg),
Expand Down
2 changes: 0 additions & 2 deletions libraries/math-parser/src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,6 @@ impl Number {
BinaryOp::Sub => lhs - rhs,
BinaryOp::Mul => lhs * rhs,
BinaryOp::Div => lhs / rhs,
BinaryOp::Modulo => lhs % rhs,
BinaryOp::Pow => {
// A negative base under a fractional exponent has no real power, so it climbs to the principal complex one
let power = lhs.powf(rhs);
Expand All @@ -180,7 +179,6 @@ impl Number {
BinaryOp::Sub => lhs - rhs,
BinaryOp::Mul => lhs * rhs,
BinaryOp::Div => lhs / rhs,
BinaryOp::Modulo => lhs % rhs,
BinaryOp::Pow => lhs.powc(rhs),
BinaryOp::Leq | BinaryOp::Lt | BinaryOp::Geq | BinaryOp::Gt => {
return None;
Expand Down
Loading