use crate::Primitive;
pub(crate) fn binary(
operator: &str,
left: Option<Primitive>,
right: Option<Primitive>,
) -> Option<Primitive> {
match operator {
"<" | ">" | "<=" | ">=" | "==" | "!=" | "===" | "!==" => Some(Primitive::Boolean),
"-" | "*" | "/" | "%" | "**" => match (left?, right?) {
(Primitive::Number, Primitive::Number) => Some(Primitive::Number),
(Primitive::BigInt, Primitive::BigInt) => Some(Primitive::BigInt),
_ => None,
},
"+" => plus(left, right),
"??" => match (left?, right?) {
(left, right) if left == right => Some(left),
_ => None,
},
_ => None,
}
}
fn plus(left: Option<Primitive>, right: Option<Primitive>) -> Option<Primitive> {
match (left?, right?) {
(Primitive::String, _) | (_, Primitive::String) => Some(Primitive::String),
(Primitive::Number, Primitive::Number) => Some(Primitive::Number),
(Primitive::BigInt, Primitive::BigInt) => Some(Primitive::BigInt),
_ => None,
}
}
pub(crate) fn unary(operator: &str) -> Option<Primitive> {
match operator {
"typeof" => Some(Primitive::String),
_ => None,
}
}
pub(crate) fn builtin_call(callee: &str) -> Option<Primitive> {
match callee {
"parseFloat" | "parseInt" | "Number" => Some(Primitive::Number),
"String" => Some(Primitive::String),
"BigInt" => Some(Primitive::BigInt),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Primitive;
#[test]
fn comparisons_are_boolean_whatever_their_operands() {
for operator in ["<", ">", "<=", ">=", "==", "!=", "===", "!=="] {
assert_eq!(
binary(operator, None, None),
Some(Primitive::Boolean),
"{operator}"
);
}
}
#[test]
fn the_arithmetic_operators_other_than_plus_are_number_between_numbers() {
for operator in ["-", "*", "/", "%", "**"] {
assert_eq!(
binary(operator, Some(Primitive::Number), Some(Primitive::Number)),
Some(Primitive::Number),
"{operator}"
);
}
}
#[test]
fn the_arithmetic_operators_other_than_plus_are_bigint_between_bigints() {
for operator in ["-", "*", "/", "%", "**"] {
assert_eq!(
binary(operator, Some(Primitive::BigInt), Some(Primitive::BigInt)),
Some(Primitive::BigInt),
"{operator}"
);
}
}
#[test]
fn arithmetic_with_an_unknown_operand_is_unknown() {
for operator in ["-", "*", "/", "%", "**"] {
assert_eq!(binary(operator, None, None), None, "{operator}");
assert_eq!(
binary(operator, None, Some(Primitive::Number)),
None,
"{operator}"
);
assert_eq!(
binary(operator, Some(Primitive::Number), None),
None,
"{operator}"
);
}
}
#[test]
fn arithmetic_refuses_a_mixed_pair() {
for operator in ["-", "*", "/", "%", "**"] {
for (left, right) in [
(Primitive::Number, Primitive::BigInt),
(Primitive::BigInt, Primitive::Number),
(Primitive::String, Primitive::Number),
(Primitive::Boolean, Primitive::Boolean),
] {
assert_eq!(
binary(operator, Some(left), Some(right)),
None,
"{operator}"
);
}
}
}
#[test]
fn plus_is_string_when_either_side_is() {
assert_eq!(
binary("+", Some(Primitive::String), Some(Primitive::Number)),
Some(Primitive::String)
);
assert_eq!(
binary("+", Some(Primitive::Number), Some(Primitive::String)),
Some(Primitive::String)
);
}
#[test]
fn plus_is_number_when_both_sides_are() {
assert_eq!(
binary("+", Some(Primitive::Number), Some(Primitive::Number)),
Some(Primitive::Number)
);
}
#[test]
fn plus_is_bigint_when_both_sides_are() {
assert_eq!(
binary("+", Some(Primitive::BigInt), Some(Primitive::BigInt)),
Some(Primitive::BigInt)
);
}
#[test]
fn plus_refuses_to_mix_number_and_bigint() {
assert_eq!(
binary("+", Some(Primitive::Number), Some(Primitive::BigInt)),
None
);
assert_eq!(
binary("+", Some(Primitive::BigInt), Some(Primitive::Number)),
None
);
}
#[test]
fn plus_with_an_unknown_operand_is_unknown() {
assert_eq!(binary("+", None, Some(Primitive::Number)), None);
assert_eq!(binary("+", Some(Primitive::Number), None), None);
}
#[test]
fn nullish_coalescing_is_that_primitive_when_both_sides_agree() {
assert_eq!(
binary("??", Some(Primitive::Number), Some(Primitive::Number)),
Some(Primitive::Number)
);
assert_eq!(
binary("??", Some(Primitive::BigInt), Some(Primitive::BigInt)),
Some(Primitive::BigInt)
);
assert_eq!(
binary("??", Some(Primitive::String), Some(Primitive::String)),
Some(Primitive::String)
);
}
#[test]
fn nullish_coalescing_refuses_disagreeing_or_unknown_sides() {
assert_eq!(
binary("??", Some(Primitive::Number), Some(Primitive::BigInt)),
None
);
assert_eq!(
binary("??", Some(Primitive::String), Some(Primitive::Number)),
None
);
assert_eq!(binary("??", None, Some(Primitive::Number)), None);
assert_eq!(binary("??", Some(Primitive::Number), None), None);
assert_eq!(binary("??", None, None), None);
}
#[test]
fn an_operator_outside_the_table_is_unknown() {
for operator in ["&&", "||", "&", "|", "^", "<<", ">>", "in", "instanceof"] {
assert_eq!(
binary(operator, Some(Primitive::Number), Some(Primitive::Number)),
None,
"{operator}"
);
}
}
#[test]
fn typeof_is_string_and_the_other_unary_operators_are_not_in_the_table() {
assert_eq!(unary("typeof"), Some(Primitive::String));
assert_eq!(unary("!"), None);
assert_eq!(unary("-"), None);
assert_eq!(unary("void"), None);
}
#[test]
fn the_builtin_conversions_are_their_own_types() {
assert_eq!(builtin_call("parseFloat"), Some(Primitive::Number));
assert_eq!(builtin_call("parseInt"), Some(Primitive::Number));
assert_eq!(builtin_call("Number"), Some(Primitive::Number));
assert_eq!(builtin_call("String"), Some(Primitive::String));
assert_eq!(builtin_call("BigInt"), Some(Primitive::BigInt));
}
#[test]
fn a_function_outside_the_table_is_unknown() {
assert_eq!(builtin_call("Boolean"), None);
assert_eq!(builtin_call("myHelper"), None);
}
}