Compare commits
1 Commits
kcl-57
...
jtran/y-co
Author | SHA1 | Date | |
---|---|---|---|
b4fb903bd0 |
@ -2880,6 +2880,30 @@ impl BinaryExpression {
|
||||
pipe_info: &PipeInfo,
|
||||
ctx: &ExecutorContext,
|
||||
) -> 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 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::Mod => (left % right).into(),
|
||||
BinaryOperator::Pow => (left.powf(right)).into(),
|
||||
BinaryOperator::LogicalOr => {
|
||||
unreachable!("LogicalOr should have been handled above")
|
||||
}
|
||||
};
|
||||
|
||||
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)]
|
||||
#[databake(path = kcl_lib::ast::types)]
|
||||
#[ts(export)]
|
||||
@ -2980,6 +3028,10 @@ pub enum BinaryOperator {
|
||||
#[serde(rename = "^")]
|
||||
#[display("^")]
|
||||
Pow,
|
||||
/// Logical OR.
|
||||
#[serde(rename = "||")]
|
||||
#[display("||")]
|
||||
LogicalOr,
|
||||
}
|
||||
|
||||
/// Mathematical associativity.
|
||||
@ -3008,6 +3060,7 @@ impl BinaryOperator {
|
||||
BinaryOperator::Div => *b"div",
|
||||
BinaryOperator::Mod => *b"mod",
|
||||
BinaryOperator::Pow => *b"pow",
|
||||
BinaryOperator::LogicalOr => *b"lor",
|
||||
}
|
||||
}
|
||||
|
||||
@ -3018,6 +3071,7 @@ impl BinaryOperator {
|
||||
BinaryOperator::Add | BinaryOperator::Sub => 11,
|
||||
BinaryOperator::Mul | BinaryOperator::Div | BinaryOperator::Mod => 12,
|
||||
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>
|
||||
pub fn associativity(&self) -> Associativity {
|
||||
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,
|
||||
}
|
||||
}
|
||||
@ -3089,6 +3143,21 @@ impl UnaryExpression {
|
||||
pipe_info: &PipeInfo,
|
||||
ctx: &ExecutorContext,
|
||||
) -> 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(
|
||||
&self
|
||||
.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")]
|
||||
async fn test_math_execute_with_functions() {
|
||||
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::Mod,
|
||||
"^" => BinaryOperator::Pow,
|
||||
"||" => BinaryOperator::LogicalOr,
|
||||
_ => {
|
||||
return Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: token.as_source_ranges(),
|
||||
@ -1136,11 +1137,11 @@ fn unary_expression(i: TokenSlice) -> PResult<UnaryExpression> {
|
||||
let (operator, op_token) = any
|
||||
.try_map(|token: Token| match token.token_type {
|
||||
TokenType::Operator if token.value == "-" => Ok((UnaryOperator::Neg, token)),
|
||||
// TODO: negation. Original parser doesn't support `not` yet.
|
||||
TokenType::Operator => Err(KclError::Syntax(KclErrorDetails {
|
||||
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(),),
|
||||
})),
|
||||
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,) })),
|
||||
})
|
||||
.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
|
||||
KclError::Syntax(KclErrorDetails {
|
||||
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> {
|
||||
let (value, range) = alt((
|
||||
">=", "<=", "==", "=>", "!= ", "|>", "*", "+", "-", "/", "%", "=", "<", ">", r"\", "|", "^",
|
||||
">=", "<=", "==", "=>", "!= ", "|>", "*", "+", "-", "/", "%", "=", "<", ">", r"\", "||", "|", "^",
|
||||
))
|
||||
.with_span()
|
||||
.parse_next(i)?;
|
||||
|
Reference in New Issue
Block a user