Compare commits
1 Commits
max/bug-de
...
jtran/y-co
Author | SHA1 | Date | |
---|---|---|---|
b4fb903bd0 |
@ -2880,6 +2880,30 @@ impl BinaryExpression {
|
|||||||
pipe_info: &PipeInfo,
|
pipe_info: &PipeInfo,
|
||||||
ctx: &ExecutorContext,
|
ctx: &ExecutorContext,
|
||||||
) -> Result<MemoryItem, KclError> {
|
) -> Result<MemoryItem, KclError> {
|
||||||
|
// First check if we are doing short-circuiting logical operator.
|
||||||
|
if self.operator == BinaryOperator::LogicalOr {
|
||||||
|
let left_json_value = self.left.get_result(memory, pipe_info, ctx).await?.get_json_value()?;
|
||||||
|
let left = json_to_bool(&left_json_value);
|
||||||
|
if left {
|
||||||
|
// Short-circuit.
|
||||||
|
return Ok(MemoryItem::UserVal(UserVal {
|
||||||
|
value: serde_json::Value::Bool(left),
|
||||||
|
meta: vec![Metadata {
|
||||||
|
source_range: self.into(),
|
||||||
|
}],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let right_json_value = self.right.get_result(memory, pipe_info, ctx).await?.get_json_value()?;
|
||||||
|
let right = json_to_bool(&right_json_value);
|
||||||
|
return Ok(MemoryItem::UserVal(UserVal {
|
||||||
|
value: serde_json::Value::Bool(right),
|
||||||
|
meta: vec![Metadata {
|
||||||
|
source_range: self.into(),
|
||||||
|
}],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
let left_json_value = self.left.get_result(memory, pipe_info, ctx).await?.get_json_value()?;
|
let left_json_value = self.left.get_result(memory, pipe_info, ctx).await?.get_json_value()?;
|
||||||
let right_json_value = self.right.get_result(memory, pipe_info, ctx).await?.get_json_value()?;
|
let right_json_value = self.right.get_result(memory, pipe_info, ctx).await?.get_json_value()?;
|
||||||
|
|
||||||
@ -2909,6 +2933,9 @@ impl BinaryExpression {
|
|||||||
BinaryOperator::Div => (left / right).into(),
|
BinaryOperator::Div => (left / right).into(),
|
||||||
BinaryOperator::Mod => (left % right).into(),
|
BinaryOperator::Mod => (left % right).into(),
|
||||||
BinaryOperator::Pow => (left.powf(right)).into(),
|
BinaryOperator::Pow => (left.powf(right)).into(),
|
||||||
|
BinaryOperator::LogicalOr => {
|
||||||
|
unreachable!("LogicalOr should have been handled above")
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(MemoryItem::UserVal(UserVal {
|
Ok(MemoryItem::UserVal(UserVal {
|
||||||
@ -2950,6 +2977,27 @@ pub fn parse_json_value_as_string(j: &serde_json::Value) -> Option<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn json_to_bool(j: &serde_json::Value) -> bool {
|
||||||
|
match j {
|
||||||
|
JValue::Null => false,
|
||||||
|
JValue::Bool(b) => *b,
|
||||||
|
JValue::Number(n) => {
|
||||||
|
if let Some(n) = n.as_u64() {
|
||||||
|
n != 0
|
||||||
|
} else if let Some(n) = n.as_i64() {
|
||||||
|
n != 0
|
||||||
|
} else if let Some(x) = n.as_f64() {
|
||||||
|
x != 0.0 && !x.is_nan()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JValue::String(s) => !s.is_empty(),
|
||||||
|
JValue::Array(a) => !a.is_empty(),
|
||||||
|
JValue::Object(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, FromStr, Display, Bake)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, FromStr, Display, Bake)]
|
||||||
#[databake(path = kcl_lib::ast::types)]
|
#[databake(path = kcl_lib::ast::types)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
@ -2980,6 +3028,10 @@ pub enum BinaryOperator {
|
|||||||
#[serde(rename = "^")]
|
#[serde(rename = "^")]
|
||||||
#[display("^")]
|
#[display("^")]
|
||||||
Pow,
|
Pow,
|
||||||
|
/// Logical OR.
|
||||||
|
#[serde(rename = "||")]
|
||||||
|
#[display("||")]
|
||||||
|
LogicalOr,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mathematical associativity.
|
/// Mathematical associativity.
|
||||||
@ -3008,6 +3060,7 @@ impl BinaryOperator {
|
|||||||
BinaryOperator::Div => *b"div",
|
BinaryOperator::Div => *b"div",
|
||||||
BinaryOperator::Mod => *b"mod",
|
BinaryOperator::Mod => *b"mod",
|
||||||
BinaryOperator::Pow => *b"pow",
|
BinaryOperator::Pow => *b"pow",
|
||||||
|
BinaryOperator::LogicalOr => *b"lor",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3018,6 +3071,7 @@ impl BinaryOperator {
|
|||||||
BinaryOperator::Add | BinaryOperator::Sub => 11,
|
BinaryOperator::Add | BinaryOperator::Sub => 11,
|
||||||
BinaryOperator::Mul | BinaryOperator::Div | BinaryOperator::Mod => 12,
|
BinaryOperator::Mul | BinaryOperator::Div | BinaryOperator::Mod => 12,
|
||||||
BinaryOperator::Pow => 6,
|
BinaryOperator::Pow => 6,
|
||||||
|
BinaryOperator::LogicalOr => 3,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3025,7 +3079,7 @@ impl BinaryOperator {
|
|||||||
/// Taken from <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_precedence#table>
|
/// Taken from <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_precedence#table>
|
||||||
pub fn associativity(&self) -> Associativity {
|
pub fn associativity(&self) -> Associativity {
|
||||||
match self {
|
match self {
|
||||||
Self::Add | Self::Sub | Self::Mul | Self::Div | Self::Mod => Associativity::Left,
|
Self::Add | Self::Sub | Self::Mul | Self::Div | Self::Mod | Self::LogicalOr => Associativity::Left,
|
||||||
Self::Pow => Associativity::Right,
|
Self::Pow => Associativity::Right,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3089,6 +3143,21 @@ impl UnaryExpression {
|
|||||||
pipe_info: &PipeInfo,
|
pipe_info: &PipeInfo,
|
||||||
ctx: &ExecutorContext,
|
ctx: &ExecutorContext,
|
||||||
) -> Result<MemoryItem, KclError> {
|
) -> Result<MemoryItem, KclError> {
|
||||||
|
if self.operator == UnaryOperator::Not {
|
||||||
|
let value = self
|
||||||
|
.argument
|
||||||
|
.get_result(memory, pipe_info, ctx)
|
||||||
|
.await?
|
||||||
|
.get_json_value()?;
|
||||||
|
let negated = !json_to_bool(&value);
|
||||||
|
return Ok(MemoryItem::UserVal(UserVal {
|
||||||
|
value: serde_json::Value::Bool(negated),
|
||||||
|
meta: vec![Metadata {
|
||||||
|
source_range: self.into(),
|
||||||
|
}],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
let num = parse_json_number_as_f64(
|
let num = parse_json_number_as_f64(
|
||||||
&self
|
&self
|
||||||
.argument
|
.argument
|
||||||
|
@ -2513,6 +2513,57 @@ let shape = layer() |> patternTransform(10, transform, %)"#;
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_execute_ycombinator_is_even() {
|
||||||
|
let ast = r#"
|
||||||
|
// Heavily inspired by: https://raganwald.com/2018/09/10/why-y.html
|
||||||
|
fn why = (f) => {
|
||||||
|
fn inner = (maker) => {
|
||||||
|
fn inner2 = (x) => {
|
||||||
|
return f(maker(maker), x)
|
||||||
|
}
|
||||||
|
return inner2
|
||||||
|
}
|
||||||
|
|
||||||
|
return inner(
|
||||||
|
(maker) => {
|
||||||
|
fn inner2 = (x) => {
|
||||||
|
return f(maker(maker), x)
|
||||||
|
}
|
||||||
|
return inner2
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn innerIsEven = (self, n) => {
|
||||||
|
return !n || !self(n - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isEven = why(innerIsEven)
|
||||||
|
|
||||||
|
const two = isEven(2)
|
||||||
|
const three = isEven(3)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let memory = parse_execute(ast).await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::json!(true),
|
||||||
|
memory
|
||||||
|
.get("two", SourceRange::default())
|
||||||
|
.unwrap()
|
||||||
|
.get_json_value()
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::json!(false),
|
||||||
|
memory
|
||||||
|
.get("three", SourceRange::default())
|
||||||
|
.unwrap()
|
||||||
|
.get_json_value()
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_math_execute_with_functions() {
|
async fn test_math_execute_with_functions() {
|
||||||
let ast = r#"const myVar = 2 + min(100, -1 + legLen(5, 3))"#;
|
let ast = r#"const myVar = 2 + min(100, -1 + legLen(5, 3))"#;
|
||||||
|
@ -299,6 +299,7 @@ fn binary_operator(i: TokenSlice) -> PResult<BinaryOperator> {
|
|||||||
"*" => BinaryOperator::Mul,
|
"*" => BinaryOperator::Mul,
|
||||||
"%" => BinaryOperator::Mod,
|
"%" => BinaryOperator::Mod,
|
||||||
"^" => BinaryOperator::Pow,
|
"^" => BinaryOperator::Pow,
|
||||||
|
"||" => BinaryOperator::LogicalOr,
|
||||||
_ => {
|
_ => {
|
||||||
return Err(KclError::Syntax(KclErrorDetails {
|
return Err(KclError::Syntax(KclErrorDetails {
|
||||||
source_ranges: token.as_source_ranges(),
|
source_ranges: token.as_source_ranges(),
|
||||||
@ -1136,11 +1137,11 @@ fn unary_expression(i: TokenSlice) -> PResult<UnaryExpression> {
|
|||||||
let (operator, op_token) = any
|
let (operator, op_token) = any
|
||||||
.try_map(|token: Token| match token.token_type {
|
.try_map(|token: Token| match token.token_type {
|
||||||
TokenType::Operator if token.value == "-" => Ok((UnaryOperator::Neg, token)),
|
TokenType::Operator if token.value == "-" => Ok((UnaryOperator::Neg, token)),
|
||||||
// TODO: negation. Original parser doesn't support `not` yet.
|
|
||||||
TokenType::Operator => Err(KclError::Syntax(KclErrorDetails {
|
TokenType::Operator => Err(KclError::Syntax(KclErrorDetails {
|
||||||
source_ranges: token.as_source_ranges(),
|
source_ranges: token.as_source_ranges(),
|
||||||
message: format!("{EXPECTED} but found {} which is an operator, but not a unary one (unary operators apply to just a single operand, your operator applies to two or more operands)", token.value.as_str(),),
|
message: format!("{EXPECTED} but found {} which is an operator, but not a unary one (unary operators apply to just a single operand, your operator applies to two or more operands)", token.value.as_str(),),
|
||||||
})),
|
})),
|
||||||
|
TokenType::Bang => Ok((UnaryOperator::Not, token)),
|
||||||
other => Err(KclError::Syntax(KclErrorDetails { source_ranges: token.as_source_ranges(), message: format!("{EXPECTED} but found {} which is {}", token.value.as_str(), other,) })),
|
other => Err(KclError::Syntax(KclErrorDetails { source_ranges: token.as_source_ranges(), message: format!("{EXPECTED} but found {} which is {}", token.value.as_str(), other,) })),
|
||||||
})
|
})
|
||||||
.context(expected("a unary expression, e.g. -x or -3"))
|
.context(expected("a unary expression, e.g. -x or -3"))
|
||||||
|
@ -79,7 +79,7 @@ impl From<ParseError<&[Token], ContextError>> for KclError {
|
|||||||
// See https://github.com/KittyCAD/modeling-app/issues/784
|
// See https://github.com/KittyCAD/modeling-app/issues/784
|
||||||
KclError::Syntax(KclErrorDetails {
|
KclError::Syntax(KclErrorDetails {
|
||||||
source_ranges: bad_token.as_source_ranges(),
|
source_ranges: bad_token.as_source_ranges(),
|
||||||
message: "Unexpected token".to_string(),
|
message: format!("Unexpected token: {}", bad_token.value),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,7 @@ fn word(i: &mut Located<&str>) -> PResult<Token> {
|
|||||||
|
|
||||||
fn operator(i: &mut Located<&str>) -> PResult<Token> {
|
fn operator(i: &mut Located<&str>) -> PResult<Token> {
|
||||||
let (value, range) = alt((
|
let (value, range) = alt((
|
||||||
">=", "<=", "==", "=>", "!= ", "|>", "*", "+", "-", "/", "%", "=", "<", ">", r"\", "|", "^",
|
">=", "<=", "==", "=>", "!= ", "|>", "*", "+", "-", "/", "%", "=", "<", ">", r"\", "||", "|", "^",
|
||||||
))
|
))
|
||||||
.with_span()
|
.with_span()
|
||||||
.parse_next(i)?;
|
.parse_next(i)?;
|
||||||
|
Reference in New Issue
Block a user