KCL: Comparison operators (#4025)
Add `==, !=, >, >=, <, <=` to the language parser, executor and tests. Closes https://github.com/KittyCAD/modeling-app/issues/4020 Currently these comparison operators are associative, allowing users to chain them, e.g. (x <= y <= z). This should not be allowed, will do in a follow-up. See https://github.com/KittyCAD/modeling-app/issues/4155
This commit is contained in:
@ -83520,6 +83520,48 @@
|
||||
"enum": [
|
||||
"^"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Are two numbers equal?",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"=="
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Are two numbers not equal?",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"!="
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Is left greater than right",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
">"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Is left greater than or equal to right",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
">="
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Is left less than right",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"<"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Is left less than or equal to right",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"<="
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -87076,6 +87118,48 @@
|
||||
"enum": [
|
||||
"^"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Are two numbers equal?",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"=="
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Are two numbers not equal?",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"!="
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Is left greater than right",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
">"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Is left greater than or equal to right",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
">="
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Is left less than right",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"<"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Is left less than or equal to right",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"<="
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -90636,6 +90720,48 @@
|
||||
"enum": [
|
||||
"^"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Are two numbers equal?",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"=="
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Are two numbers not equal?",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"!="
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Is left greater than right",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
">"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Is left greater than or equal to right",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
">="
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Is left less than right",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"<"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Is left less than or equal to right",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"<="
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -115048,6 +115174,48 @@
|
||||
"enum": [
|
||||
"^"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Are two numbers equal?",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"=="
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Are two numbers not equal?",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"!="
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Is left greater than right",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
">"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Is left greater than or equal to right",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
">="
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Is left less than right",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"<"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Is left less than or equal to right",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"<="
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -118997,6 +119165,48 @@
|
||||
"enum": [
|
||||
"^"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Are two numbers equal?",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"=="
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Are two numbers not equal?",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"!="
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Is left greater than right",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
">"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Is left greater than or equal to right",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
">="
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Is left less than right",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"<"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Is left less than or equal to right",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"<="
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -122553,6 +122763,48 @@
|
||||
"enum": [
|
||||
"^"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Are two numbers equal?",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"=="
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Are two numbers not equal?",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"!="
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Is left greater than right",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
">"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Is left greater than or equal to right",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
">="
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Is left less than right",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"<"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Is left less than or equal to right",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"<="
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -126107,6 +126359,48 @@
|
||||
"enum": [
|
||||
"^"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Are two numbers equal?",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"=="
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Are two numbers not equal?",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"!="
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Is left greater than right",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
">"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Is left greater than or equal to right",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
">="
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Is left less than right",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"<"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Is left less than or equal to right",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"<="
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@ -82,6 +82,78 @@ Raise a number to a power.
|
||||
|
||||
|
||||
|
||||
----
|
||||
Are two numbers equal?
|
||||
|
||||
**enum:** `==`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Are two numbers not equal?
|
||||
|
||||
**enum:** `!=`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Is left greater than right
|
||||
|
||||
**enum:** `>`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Is left greater than or equal to right
|
||||
|
||||
**enum:** `>=`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Is left less than right
|
||||
|
||||
**enum:** `<`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Is left less than or equal to right
|
||||
|
||||
**enum:** `<=`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
@ -2778,6 +2778,12 @@ impl BinaryExpression {
|
||||
BinaryOperator::Div => (left / right).into(),
|
||||
BinaryOperator::Mod => (left % right).into(),
|
||||
BinaryOperator::Pow => (left.powf(right)).into(),
|
||||
BinaryOperator::Eq => (left == right).into(),
|
||||
BinaryOperator::Neq => (left != right).into(),
|
||||
BinaryOperator::Gt => (left > right).into(),
|
||||
BinaryOperator::Gte => (left >= right).into(),
|
||||
BinaryOperator::Lt => (left < right).into(),
|
||||
BinaryOperator::Lte => (left <= right).into(),
|
||||
};
|
||||
|
||||
Ok(KclValue::UserVal(UserVal {
|
||||
@ -2861,6 +2867,30 @@ pub enum BinaryOperator {
|
||||
#[serde(rename = "^")]
|
||||
#[display("^")]
|
||||
Pow,
|
||||
/// Are two numbers equal?
|
||||
#[serde(rename = "==")]
|
||||
#[display("==")]
|
||||
Eq,
|
||||
/// Are two numbers not equal?
|
||||
#[serde(rename = "!=")]
|
||||
#[display("!=")]
|
||||
Neq,
|
||||
/// Is left greater than right
|
||||
#[serde(rename = ">")]
|
||||
#[display(">")]
|
||||
Gt,
|
||||
/// Is left greater than or equal to right
|
||||
#[serde(rename = ">=")]
|
||||
#[display(">=")]
|
||||
Gte,
|
||||
/// Is left less than right
|
||||
#[serde(rename = "<")]
|
||||
#[display("<")]
|
||||
Lt,
|
||||
/// Is left less than or equal to right
|
||||
#[serde(rename = "<=")]
|
||||
#[display("<=")]
|
||||
Lte,
|
||||
}
|
||||
|
||||
/// Mathematical associativity.
|
||||
@ -2889,6 +2919,12 @@ impl BinaryOperator {
|
||||
BinaryOperator::Div => *b"div",
|
||||
BinaryOperator::Mod => *b"mod",
|
||||
BinaryOperator::Pow => *b"pow",
|
||||
BinaryOperator::Eq => *b"eqq",
|
||||
BinaryOperator::Neq => *b"neq",
|
||||
BinaryOperator::Gt => *b"gtr",
|
||||
BinaryOperator::Gte => *b"gte",
|
||||
BinaryOperator::Lt => *b"ltr",
|
||||
BinaryOperator::Lte => *b"lte",
|
||||
}
|
||||
}
|
||||
|
||||
@ -2899,6 +2935,8 @@ impl BinaryOperator {
|
||||
BinaryOperator::Add | BinaryOperator::Sub => 11,
|
||||
BinaryOperator::Mul | BinaryOperator::Div | BinaryOperator::Mod => 12,
|
||||
BinaryOperator::Pow => 13,
|
||||
Self::Gt | Self::Gte | Self::Lt | Self::Lte => 9,
|
||||
Self::Eq | Self::Neq => 8,
|
||||
}
|
||||
}
|
||||
|
||||
@ -2908,6 +2946,7 @@ impl BinaryOperator {
|
||||
match self {
|
||||
Self::Add | Self::Sub | Self::Mul | Self::Div | Self::Mod => Associativity::Left,
|
||||
Self::Pow => Associativity::Right,
|
||||
Self::Gt | Self::Gte | Self::Lt | Self::Lte | Self::Eq | Self::Neq => Associativity::Left, // I don't know if this is correct
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -303,6 +303,12 @@ fn binary_operator(i: TokenSlice) -> PResult<BinaryOperator> {
|
||||
"*" => BinaryOperator::Mul,
|
||||
"%" => BinaryOperator::Mod,
|
||||
"^" => BinaryOperator::Pow,
|
||||
"==" => BinaryOperator::Eq,
|
||||
"!=" => BinaryOperator::Neq,
|
||||
">" => BinaryOperator::Gt,
|
||||
">=" => BinaryOperator::Gte,
|
||||
"<" => BinaryOperator::Lt,
|
||||
"<=" => BinaryOperator::Lte,
|
||||
_ => {
|
||||
return Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: token.as_source_ranges(),
|
||||
@ -3705,6 +3711,8 @@ const my14 = 4 ^ 2 - 3 ^ 2 * 2
|
||||
5
|
||||
}"#
|
||||
);
|
||||
snapshot_test!(be, "let x = 3 == 3");
|
||||
snapshot_test!(bf, "let x = 3 != 3");
|
||||
snapshot_test!(bg, r#"x = 4"#);
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,65 @@
|
||||
---
|
||||
source: kcl/src/parser/parser_impl.rs
|
||||
expression: actual
|
||||
---
|
||||
{
|
||||
"start": 0,
|
||||
"end": 14,
|
||||
"body": [
|
||||
{
|
||||
"type": "VariableDeclaration",
|
||||
"type": "VariableDeclaration",
|
||||
"start": 0,
|
||||
"end": 14,
|
||||
"declarations": [
|
||||
{
|
||||
"type": "VariableDeclarator",
|
||||
"start": 4,
|
||||
"end": 14,
|
||||
"id": {
|
||||
"type": "Identifier",
|
||||
"start": 4,
|
||||
"end": 5,
|
||||
"name": "x",
|
||||
"digest": null
|
||||
},
|
||||
"init": {
|
||||
"type": "BinaryExpression",
|
||||
"type": "BinaryExpression",
|
||||
"start": 8,
|
||||
"end": 14,
|
||||
"operator": "==",
|
||||
"left": {
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 8,
|
||||
"end": 9,
|
||||
"value": 3,
|
||||
"raw": "3",
|
||||
"digest": null
|
||||
},
|
||||
"right": {
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 13,
|
||||
"end": 14,
|
||||
"value": 3,
|
||||
"raw": "3",
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
}
|
||||
],
|
||||
"kind": "const",
|
||||
"digest": null
|
||||
}
|
||||
],
|
||||
"nonCodeMeta": {
|
||||
"nonCodeNodes": {},
|
||||
"start": [],
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
---
|
||||
source: kcl/src/parser/parser_impl.rs
|
||||
expression: actual
|
||||
---
|
||||
{
|
||||
"start": 0,
|
||||
"end": 14,
|
||||
"body": [
|
||||
{
|
||||
"type": "VariableDeclaration",
|
||||
"type": "VariableDeclaration",
|
||||
"start": 0,
|
||||
"end": 14,
|
||||
"declarations": [
|
||||
{
|
||||
"type": "VariableDeclarator",
|
||||
"start": 4,
|
||||
"end": 14,
|
||||
"id": {
|
||||
"type": "Identifier",
|
||||
"start": 4,
|
||||
"end": 5,
|
||||
"name": "x",
|
||||
"digest": null
|
||||
},
|
||||
"init": {
|
||||
"type": "BinaryExpression",
|
||||
"type": "BinaryExpression",
|
||||
"start": 8,
|
||||
"end": 14,
|
||||
"operator": "!=",
|
||||
"left": {
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 8,
|
||||
"end": 9,
|
||||
"value": 3,
|
||||
"raw": "3",
|
||||
"digest": null
|
||||
},
|
||||
"right": {
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 13,
|
||||
"end": 14,
|
||||
"value": 3,
|
||||
"raw": "3",
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
}
|
||||
],
|
||||
"kind": "const",
|
||||
"digest": null
|
||||
}
|
||||
],
|
||||
"nonCodeMeta": {
|
||||
"nonCodeNodes": {},
|
||||
"start": [],
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
}
|
||||
@ -27,7 +27,7 @@ pub fn token(i: &mut Located<&str>) -> PResult<Token> {
|
||||
'.' => alt((number, double_period, period)),
|
||||
'#' => hash,
|
||||
'$' => dollar,
|
||||
'!' => bang,
|
||||
'!' => alt((operator, bang)),
|
||||
' ' | '\t' | '\n' => whitespace,
|
||||
_ => alt((operator, keyword,type_, word))
|
||||
}
|
||||
@ -90,7 +90,7 @@ fn word(i: &mut Located<&str>) -> PResult<Token> {
|
||||
|
||||
fn operator(i: &mut Located<&str>) -> PResult<Token> {
|
||||
let (value, range) = alt((
|
||||
">=", "<=", "==", "=>", "!= ", "|>", "*", "+", "-", "/", "%", "=", "<", ">", r"\", "|", "^",
|
||||
">=", "<=", "==", "=>", "!=", "|>", "*", "+", "-", "/", "%", "=", "<", ">", r"\", "|", "^",
|
||||
))
|
||||
.with_span()
|
||||
.parse_next(i)?;
|
||||
@ -1522,6 +1522,18 @@ const things = "things"
|
||||
assert_tokens(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_eq() {
|
||||
let actual = lexer("!=").unwrap();
|
||||
let expected = vec![Token {
|
||||
token_type: TokenType::Operator,
|
||||
value: "!=".to_owned(),
|
||||
start: 0,
|
||||
end: 2,
|
||||
}];
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unrecognized_token() {
|
||||
let actual = lexer("12 ; 8").unwrap();
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
assert(3 == 3, "equality")
|
||||
assert(3.0 == 3.0, "equality of floats")
|
||||
assert(3 != 4, "non-equality")
|
||||
assert(3.0 != 4.0, "non-equality of floats")
|
||||
assert(3 < 4, "lt")
|
||||
assert(3 <= 4, "lte but actually lt")
|
||||
assert(4 <= 4, "lte but actually eq")
|
||||
assert(4 > 3, "gt")
|
||||
assert(4 >= 3, "gte but actually gt")
|
||||
assert(3 >= 3, "gte but actually eq")
|
||||
|
||||
assert(0.0 == 0.0, "equality of zero")
|
||||
assert(0.0 == -0.0, "equality of zero and neg zero")
|
||||
@ -0,0 +1 @@
|
||||
assert(3 == 3 == 3, "this should not compile")
|
||||
@ -57,6 +57,7 @@ async fn run_fail(code: &str) -> KclError {
|
||||
|
||||
gen_test!(property_of_object);
|
||||
gen_test!(index_of_array);
|
||||
gen_test!(comparisons);
|
||||
gen_test_fail!(
|
||||
invalid_index_str,
|
||||
"semantic: Only integers >= 0 can be used as the index of an array, but you're using a string"
|
||||
@ -99,4 +100,5 @@ gen_test!(if_else);
|
||||
// if_else_no_expr,
|
||||
// "syntax: blocks inside an if/else expression must end in an expression"
|
||||
// );
|
||||
gen_test_fail!(comparisons_multiple, "syntax: Invalid number: true");
|
||||
gen_test!(add_lots);
|
||||
|
||||
Reference in New Issue
Block a user