KCL: If-else expressions (#4022)
Closes https://github.com/KittyCAD/modeling-app/issues/3677 You can review each commit separately, they're neat commits with logical purpose in each. Future enhancements: - https://github.com/KittyCAD/modeling-app/issues/4015 - https://github.com/KittyCAD/modeling-app/issues/4020 - Right now the parser errors are not very good, especially if you forget to put an expression in the end of an if/else block
This commit is contained in:
@ -18,7 +18,11 @@ use tower_lsp::lsp_types::{
|
|||||||
CompletionItem, CompletionItemKind, DocumentSymbol, FoldingRange, FoldingRangeKind, Range as LspRange, SymbolKind,
|
CompletionItem, CompletionItemKind, DocumentSymbol, FoldingRange, FoldingRangeKind, Range as LspRange, SymbolKind,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use crate::ast::types::{literal_value::LiteralValue, none::KclNone};
|
pub use crate::ast::types::{
|
||||||
|
condition::{ElseIf, IfExpression},
|
||||||
|
literal_value::LiteralValue,
|
||||||
|
none::KclNone,
|
||||||
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
docs::StdLibFn,
|
docs::StdLibFn,
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
@ -30,12 +34,14 @@ use crate::{
|
|||||||
std::{kcl_stdlib::KclStdLibFn, FunctionKind},
|
std::{kcl_stdlib::KclStdLibFn, FunctionKind},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod condition;
|
||||||
mod literal_value;
|
mod literal_value;
|
||||||
mod none;
|
mod none;
|
||||||
|
|
||||||
/// Position-independent digest of the AST node.
|
/// Position-independent digest of the AST node.
|
||||||
pub type Digest = [u8; 32];
|
pub type Digest = [u8; 32];
|
||||||
|
|
||||||
|
/// A KCL program top level, or function body.
|
||||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
|
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
|
||||||
#[databake(path = kcl_lib::ast::types)]
|
#[databake(path = kcl_lib::ast::types)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
@ -72,6 +78,7 @@ macro_rules! compute_digest {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
pub(crate) use compute_digest;
|
||||||
|
|
||||||
impl Program {
|
impl Program {
|
||||||
compute_digest!(|slf, hasher| {
|
compute_digest!(|slf, hasher| {
|
||||||
@ -82,6 +89,14 @@ impl Program {
|
|||||||
hasher.update(slf.non_code_meta.compute_digest());
|
hasher.update(slf.non_code_meta.compute_digest());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Is the last body item an expression?
|
||||||
|
pub fn ends_with_expr(&self) -> bool {
|
||||||
|
let Some(ref last) = self.body.last() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
matches!(last, BodyItem::ExpressionStatement(_))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_hover_value_for_position(&self, pos: usize, code: &str) -> Option<Hover> {
|
pub fn get_hover_value_for_position(&self, pos: usize, code: &str) -> Option<Hover> {
|
||||||
// Check if we are in the non code meta.
|
// Check if we are in the non code meta.
|
||||||
if let Some(meta) = self.get_non_code_meta_for_position(pos) {
|
if let Some(meta) = self.get_non_code_meta_for_position(pos) {
|
||||||
@ -501,6 +516,7 @@ pub enum Expr {
|
|||||||
ObjectExpression(Box<ObjectExpression>),
|
ObjectExpression(Box<ObjectExpression>),
|
||||||
MemberExpression(Box<MemberExpression>),
|
MemberExpression(Box<MemberExpression>),
|
||||||
UnaryExpression(Box<UnaryExpression>),
|
UnaryExpression(Box<UnaryExpression>),
|
||||||
|
IfExpression(Box<IfExpression>),
|
||||||
None(KclNone),
|
None(KclNone),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -519,6 +535,7 @@ impl Expr {
|
|||||||
Expr::ObjectExpression(oe) => oe.compute_digest(),
|
Expr::ObjectExpression(oe) => oe.compute_digest(),
|
||||||
Expr::MemberExpression(me) => me.compute_digest(),
|
Expr::MemberExpression(me) => me.compute_digest(),
|
||||||
Expr::UnaryExpression(ue) => ue.compute_digest(),
|
Expr::UnaryExpression(ue) => ue.compute_digest(),
|
||||||
|
Expr::IfExpression(e) => e.compute_digest(),
|
||||||
Expr::None(_) => {
|
Expr::None(_) => {
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
hasher.update(b"Value::None");
|
hasher.update(b"Value::None");
|
||||||
@ -562,6 +579,7 @@ impl Expr {
|
|||||||
Expr::PipeExpression(pipe_exp) => Some(&pipe_exp.non_code_meta),
|
Expr::PipeExpression(pipe_exp) => Some(&pipe_exp.non_code_meta),
|
||||||
Expr::UnaryExpression(_unary_exp) => None,
|
Expr::UnaryExpression(_unary_exp) => None,
|
||||||
Expr::PipeSubstitution(_pipe_substitution) => None,
|
Expr::PipeSubstitution(_pipe_substitution) => None,
|
||||||
|
Expr::IfExpression(_) => None,
|
||||||
Expr::None(_none) => None,
|
Expr::None(_none) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -584,6 +602,7 @@ impl Expr {
|
|||||||
Expr::TagDeclarator(_) => {}
|
Expr::TagDeclarator(_) => {}
|
||||||
Expr::PipeExpression(ref mut pipe_exp) => pipe_exp.replace_value(source_range, new_value),
|
Expr::PipeExpression(ref mut pipe_exp) => pipe_exp.replace_value(source_range, new_value),
|
||||||
Expr::UnaryExpression(ref mut unary_exp) => unary_exp.replace_value(source_range, new_value),
|
Expr::UnaryExpression(ref mut unary_exp) => unary_exp.replace_value(source_range, new_value),
|
||||||
|
Expr::IfExpression(_) => {}
|
||||||
Expr::PipeSubstitution(_) => {}
|
Expr::PipeSubstitution(_) => {}
|
||||||
Expr::None(_) => {}
|
Expr::None(_) => {}
|
||||||
}
|
}
|
||||||
@ -603,6 +622,7 @@ impl Expr {
|
|||||||
Expr::ObjectExpression(object_expression) => object_expression.start(),
|
Expr::ObjectExpression(object_expression) => object_expression.start(),
|
||||||
Expr::MemberExpression(member_expression) => member_expression.start(),
|
Expr::MemberExpression(member_expression) => member_expression.start(),
|
||||||
Expr::UnaryExpression(unary_expression) => unary_expression.start(),
|
Expr::UnaryExpression(unary_expression) => unary_expression.start(),
|
||||||
|
Expr::IfExpression(expr) => expr.start(),
|
||||||
Expr::None(none) => none.start,
|
Expr::None(none) => none.start,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -621,6 +641,7 @@ impl Expr {
|
|||||||
Expr::ObjectExpression(object_expression) => object_expression.end(),
|
Expr::ObjectExpression(object_expression) => object_expression.end(),
|
||||||
Expr::MemberExpression(member_expression) => member_expression.end(),
|
Expr::MemberExpression(member_expression) => member_expression.end(),
|
||||||
Expr::UnaryExpression(unary_expression) => unary_expression.end(),
|
Expr::UnaryExpression(unary_expression) => unary_expression.end(),
|
||||||
|
Expr::IfExpression(expr) => expr.end(),
|
||||||
Expr::None(none) => none.end,
|
Expr::None(none) => none.end,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -639,6 +660,7 @@ impl Expr {
|
|||||||
Expr::ObjectExpression(object_expression) => object_expression.get_hover_value_for_position(pos, code),
|
Expr::ObjectExpression(object_expression) => object_expression.get_hover_value_for_position(pos, code),
|
||||||
Expr::MemberExpression(member_expression) => member_expression.get_hover_value_for_position(pos, code),
|
Expr::MemberExpression(member_expression) => member_expression.get_hover_value_for_position(pos, code),
|
||||||
Expr::UnaryExpression(unary_expression) => unary_expression.get_hover_value_for_position(pos, code),
|
Expr::UnaryExpression(unary_expression) => unary_expression.get_hover_value_for_position(pos, code),
|
||||||
|
Expr::IfExpression(expr) => expr.get_hover_value_for_position(pos, code),
|
||||||
// TODO: LSP hover information for values/types. https://github.com/KittyCAD/modeling-app/issues/1126
|
// TODO: LSP hover information for values/types. https://github.com/KittyCAD/modeling-app/issues/1126
|
||||||
Expr::None(_) => None,
|
Expr::None(_) => None,
|
||||||
Expr::Literal(_) => None,
|
Expr::Literal(_) => None,
|
||||||
@ -670,11 +692,12 @@ impl Expr {
|
|||||||
member_expression.rename_identifiers(old_name, new_name)
|
member_expression.rename_identifiers(old_name, new_name)
|
||||||
}
|
}
|
||||||
Expr::UnaryExpression(ref mut unary_expression) => unary_expression.rename_identifiers(old_name, new_name),
|
Expr::UnaryExpression(ref mut unary_expression) => unary_expression.rename_identifiers(old_name, new_name),
|
||||||
|
Expr::IfExpression(ref mut expr) => expr.rename_identifiers(old_name, new_name),
|
||||||
Expr::None(_) => {}
|
Expr::None(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the constraint level for a value type.
|
/// Get the constraint level for an expression.
|
||||||
pub fn get_constraint_level(&self) -> ConstraintLevel {
|
pub fn get_constraint_level(&self) -> ConstraintLevel {
|
||||||
match self {
|
match self {
|
||||||
Expr::Literal(literal) => literal.get_constraint_level(),
|
Expr::Literal(literal) => literal.get_constraint_level(),
|
||||||
@ -692,6 +715,7 @@ impl Expr {
|
|||||||
Expr::ObjectExpression(object_expression) => object_expression.get_constraint_level(),
|
Expr::ObjectExpression(object_expression) => object_expression.get_constraint_level(),
|
||||||
Expr::MemberExpression(member_expression) => member_expression.get_constraint_level(),
|
Expr::MemberExpression(member_expression) => member_expression.get_constraint_level(),
|
||||||
Expr::UnaryExpression(unary_expression) => unary_expression.get_constraint_level(),
|
Expr::UnaryExpression(unary_expression) => unary_expression.get_constraint_level(),
|
||||||
|
Expr::IfExpression(expr) => expr.get_constraint_level(),
|
||||||
Expr::None(none) => none.get_constraint_level(),
|
Expr::None(none) => none.get_constraint_level(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -720,6 +744,7 @@ pub enum BinaryPart {
|
|||||||
CallExpression(Box<CallExpression>),
|
CallExpression(Box<CallExpression>),
|
||||||
UnaryExpression(Box<UnaryExpression>),
|
UnaryExpression(Box<UnaryExpression>),
|
||||||
MemberExpression(Box<MemberExpression>),
|
MemberExpression(Box<MemberExpression>),
|
||||||
|
IfExpression(Box<IfExpression>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<BinaryPart> for SourceRange {
|
impl From<BinaryPart> for SourceRange {
|
||||||
@ -743,6 +768,7 @@ impl BinaryPart {
|
|||||||
BinaryPart::CallExpression(ce) => ce.compute_digest(),
|
BinaryPart::CallExpression(ce) => ce.compute_digest(),
|
||||||
BinaryPart::UnaryExpression(ue) => ue.compute_digest(),
|
BinaryPart::UnaryExpression(ue) => ue.compute_digest(),
|
||||||
BinaryPart::MemberExpression(me) => me.compute_digest(),
|
BinaryPart::MemberExpression(me) => me.compute_digest(),
|
||||||
|
BinaryPart::IfExpression(e) => e.compute_digest(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -755,6 +781,7 @@ impl BinaryPart {
|
|||||||
BinaryPart::CallExpression(call_expression) => call_expression.get_constraint_level(),
|
BinaryPart::CallExpression(call_expression) => call_expression.get_constraint_level(),
|
||||||
BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_constraint_level(),
|
BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_constraint_level(),
|
||||||
BinaryPart::MemberExpression(member_expression) => member_expression.get_constraint_level(),
|
BinaryPart::MemberExpression(member_expression) => member_expression.get_constraint_level(),
|
||||||
|
BinaryPart::IfExpression(e) => e.get_constraint_level(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -772,6 +799,7 @@ impl BinaryPart {
|
|||||||
unary_expression.replace_value(source_range, new_value)
|
unary_expression.replace_value(source_range, new_value)
|
||||||
}
|
}
|
||||||
BinaryPart::MemberExpression(_) => {}
|
BinaryPart::MemberExpression(_) => {}
|
||||||
|
BinaryPart::IfExpression(e) => e.replace_value(source_range, new_value),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -783,6 +811,7 @@ impl BinaryPart {
|
|||||||
BinaryPart::CallExpression(call_expression) => call_expression.start(),
|
BinaryPart::CallExpression(call_expression) => call_expression.start(),
|
||||||
BinaryPart::UnaryExpression(unary_expression) => unary_expression.start(),
|
BinaryPart::UnaryExpression(unary_expression) => unary_expression.start(),
|
||||||
BinaryPart::MemberExpression(member_expression) => member_expression.start(),
|
BinaryPart::MemberExpression(member_expression) => member_expression.start(),
|
||||||
|
BinaryPart::IfExpression(e) => e.start(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -794,6 +823,7 @@ impl BinaryPart {
|
|||||||
BinaryPart::CallExpression(call_expression) => call_expression.end(),
|
BinaryPart::CallExpression(call_expression) => call_expression.end(),
|
||||||
BinaryPart::UnaryExpression(unary_expression) => unary_expression.end(),
|
BinaryPart::UnaryExpression(unary_expression) => unary_expression.end(),
|
||||||
BinaryPart::MemberExpression(member_expression) => member_expression.end(),
|
BinaryPart::MemberExpression(member_expression) => member_expression.end(),
|
||||||
|
BinaryPart::IfExpression(e) => e.end(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -809,6 +839,7 @@ impl BinaryPart {
|
|||||||
BinaryPart::CallExpression(call_expression) => call_expression.execute(exec_state, ctx).await,
|
BinaryPart::CallExpression(call_expression) => call_expression.execute(exec_state, ctx).await,
|
||||||
BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, ctx).await,
|
BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, ctx).await,
|
||||||
BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state),
|
BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state),
|
||||||
|
BinaryPart::IfExpression(e) => e.get_result(exec_state, ctx).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -822,6 +853,7 @@ impl BinaryPart {
|
|||||||
}
|
}
|
||||||
BinaryPart::CallExpression(call_expression) => call_expression.get_hover_value_for_position(pos, code),
|
BinaryPart::CallExpression(call_expression) => call_expression.get_hover_value_for_position(pos, code),
|
||||||
BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_hover_value_for_position(pos, code),
|
BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_hover_value_for_position(pos, code),
|
||||||
|
BinaryPart::IfExpression(e) => e.get_hover_value_for_position(pos, code),
|
||||||
BinaryPart::MemberExpression(member_expression) => {
|
BinaryPart::MemberExpression(member_expression) => {
|
||||||
member_expression.get_hover_value_for_position(pos, code)
|
member_expression.get_hover_value_for_position(pos, code)
|
||||||
}
|
}
|
||||||
@ -845,6 +877,7 @@ impl BinaryPart {
|
|||||||
BinaryPart::MemberExpression(ref mut member_expression) => {
|
BinaryPart::MemberExpression(ref mut member_expression) => {
|
||||||
member_expression.rename_identifiers(old_name, new_name)
|
member_expression.rename_identifiers(old_name, new_name)
|
||||||
}
|
}
|
||||||
|
BinaryPart::IfExpression(ref mut if_expression) => if_expression.rename_identifiers(old_name, new_name),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1331,7 +1364,7 @@ impl CallExpression {
|
|||||||
};
|
};
|
||||||
|
|
||||||
match exec_result {
|
match exec_result {
|
||||||
Ok(()) => {}
|
Ok(_) => {}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
// We need to override the source ranges so we don't get the embedded kcl
|
// We need to override the source ranges so we don't get the embedded kcl
|
||||||
// function from the stdlib.
|
// function from the stdlib.
|
||||||
@ -3149,6 +3182,7 @@ async fn inner_execute_pipe_body(
|
|||||||
| Expr::ObjectExpression(_)
|
| Expr::ObjectExpression(_)
|
||||||
| Expr::MemberExpression(_)
|
| Expr::MemberExpression(_)
|
||||||
| Expr::UnaryExpression(_)
|
| Expr::UnaryExpression(_)
|
||||||
|
| Expr::IfExpression(_)
|
||||||
| Expr::None(_) => {}
|
| Expr::None(_) => {}
|
||||||
};
|
};
|
||||||
let metadata = Metadata {
|
let metadata = Metadata {
|
||||||
|
215
src/wasm-lib/kcl/src/ast/types/condition.rs
Normal file
215
src/wasm-lib/kcl/src/ast/types/condition.rs
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
use crate::errors::KclError;
|
||||||
|
use crate::executor::BodyType;
|
||||||
|
use crate::executor::ExecState;
|
||||||
|
use crate::executor::ExecutorContext;
|
||||||
|
use crate::executor::KclValue;
|
||||||
|
use crate::executor::Metadata;
|
||||||
|
use crate::executor::SourceRange;
|
||||||
|
use crate::executor::StatementKind;
|
||||||
|
|
||||||
|
use super::compute_digest;
|
||||||
|
use super::impl_value_meta;
|
||||||
|
use super::ConstraintLevel;
|
||||||
|
use super::Hover;
|
||||||
|
use super::{Digest, Expr};
|
||||||
|
use databake::*;
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sha2::{Digest as DigestTrait, Sha256};
|
||||||
|
|
||||||
|
// TODO: This should be its own type, similar to Program,
|
||||||
|
// but guaranteed to have an Expression as its final item.
|
||||||
|
// https://github.com/KittyCAD/modeling-app/issues/4015
|
||||||
|
type IfBlock = crate::ast::types::Program;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
|
||||||
|
#[databake(path = kcl_lib::ast::types)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub struct IfExpression {
|
||||||
|
pub start: usize,
|
||||||
|
pub end: usize,
|
||||||
|
pub cond: Box<Expr>,
|
||||||
|
pub then_val: Box<IfBlock>,
|
||||||
|
pub else_ifs: Vec<ElseIf>,
|
||||||
|
pub final_else: Box<IfBlock>,
|
||||||
|
|
||||||
|
pub digest: Option<Digest>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
|
||||||
|
#[databake(path = kcl_lib::ast::types)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub struct ElseIf {
|
||||||
|
pub start: usize,
|
||||||
|
pub end: usize,
|
||||||
|
pub cond: Expr,
|
||||||
|
pub then_val: Box<IfBlock>,
|
||||||
|
|
||||||
|
pub digest: Option<Digest>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Source code metadata
|
||||||
|
|
||||||
|
impl_value_meta!(IfExpression);
|
||||||
|
impl_value_meta!(ElseIf);
|
||||||
|
|
||||||
|
impl IfExpression {
|
||||||
|
compute_digest!(|slf, hasher| {
|
||||||
|
hasher.update(slf.cond.compute_digest());
|
||||||
|
hasher.update(slf.then_val.compute_digest());
|
||||||
|
for else_if in &mut slf.else_ifs {
|
||||||
|
hasher.update(else_if.compute_digest());
|
||||||
|
}
|
||||||
|
hasher.update(slf.final_else.compute_digest());
|
||||||
|
});
|
||||||
|
fn source_ranges(&self) -> Vec<SourceRange> {
|
||||||
|
vec![SourceRange::from(self)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<IfExpression> for Metadata {
|
||||||
|
fn from(value: IfExpression) -> Self {
|
||||||
|
Self {
|
||||||
|
source_range: value.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ElseIf> for Metadata {
|
||||||
|
fn from(value: ElseIf) -> Self {
|
||||||
|
Self {
|
||||||
|
source_range: value.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<&IfExpression> for Metadata {
|
||||||
|
fn from(value: &IfExpression) -> Self {
|
||||||
|
Self {
|
||||||
|
source_range: value.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&ElseIf> for Metadata {
|
||||||
|
fn from(value: &ElseIf) -> Self {
|
||||||
|
Self {
|
||||||
|
source_range: value.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ElseIf {
|
||||||
|
compute_digest!(|slf, hasher| {
|
||||||
|
hasher.update(slf.cond.compute_digest());
|
||||||
|
hasher.update(slf.then_val.compute_digest());
|
||||||
|
});
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn source_ranges(&self) -> Vec<SourceRange> {
|
||||||
|
vec![SourceRange([self.start, self.end])]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execution
|
||||||
|
|
||||||
|
impl IfExpression {
|
||||||
|
#[async_recursion::async_recursion]
|
||||||
|
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
||||||
|
// Check the `if` branch.
|
||||||
|
let cond = ctx
|
||||||
|
.execute_expr(&self.cond, exec_state, &Metadata::from(self), StatementKind::Expression)
|
||||||
|
.await?
|
||||||
|
.get_bool()?;
|
||||||
|
if cond {
|
||||||
|
let block_result = ctx.inner_execute(&self.then_val, exec_state, BodyType::Block).await?;
|
||||||
|
// Block must end in an expression, so this has to be Some.
|
||||||
|
// Enforced by the parser.
|
||||||
|
// See https://github.com/KittyCAD/modeling-app/issues/4015
|
||||||
|
return Ok(block_result.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check any `else if` branches.
|
||||||
|
for else_if in &self.else_ifs {
|
||||||
|
let cond = ctx
|
||||||
|
.execute_expr(
|
||||||
|
&else_if.cond,
|
||||||
|
exec_state,
|
||||||
|
&Metadata::from(self),
|
||||||
|
StatementKind::Expression,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.get_bool()?;
|
||||||
|
if cond {
|
||||||
|
let block_result = ctx
|
||||||
|
.inner_execute(&else_if.then_val, exec_state, BodyType::Block)
|
||||||
|
.await?;
|
||||||
|
// Block must end in an expression, so this has to be Some.
|
||||||
|
// Enforced by the parser.
|
||||||
|
// See https://github.com/KittyCAD/modeling-app/issues/4015
|
||||||
|
return Ok(block_result.unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the final `else` branch.
|
||||||
|
ctx.inner_execute(&self.final_else, exec_state, BodyType::Block)
|
||||||
|
.await
|
||||||
|
.map(|expr| expr.unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDE support and refactors
|
||||||
|
|
||||||
|
impl IfExpression {
|
||||||
|
pub fn get_hover_value_for_position(&self, pos: usize, code: &str) -> Option<Hover> {
|
||||||
|
self.cond
|
||||||
|
.get_hover_value_for_position(pos, code)
|
||||||
|
.or_else(|| self.then_val.get_hover_value_for_position(pos, code))
|
||||||
|
.or_else(|| {
|
||||||
|
self.else_ifs
|
||||||
|
.iter()
|
||||||
|
.find_map(|else_if| else_if.get_hover_value_for_position(pos, code))
|
||||||
|
})
|
||||||
|
.or_else(|| self.final_else.get_hover_value_for_position(pos, code))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rename all identifiers that have the old name to the new given name.
|
||||||
|
pub fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
|
||||||
|
self.cond.rename_identifiers(old_name, new_name);
|
||||||
|
self.then_val.rename_identifiers(old_name, new_name);
|
||||||
|
for else_if in &mut self.else_ifs {
|
||||||
|
else_if.rename_identifiers(old_name, new_name);
|
||||||
|
}
|
||||||
|
self.final_else.rename_identifiers(old_name, new_name);
|
||||||
|
}
|
||||||
|
/// Get the constraint level.
|
||||||
|
pub fn get_constraint_level(&self) -> ConstraintLevel {
|
||||||
|
ConstraintLevel::Full {
|
||||||
|
source_ranges: self.source_ranges(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn replace_value(&mut self, source_range: SourceRange, new_value: Expr) {
|
||||||
|
self.cond.replace_value(source_range, new_value.clone());
|
||||||
|
for else_if in &mut self.else_ifs {
|
||||||
|
else_if.cond.replace_value(source_range, new_value.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ElseIf {
|
||||||
|
fn get_hover_value_for_position(&self, pos: usize, code: &str) -> Option<Hover> {
|
||||||
|
self.cond
|
||||||
|
.get_hover_value_for_position(pos, code)
|
||||||
|
.or_else(|| self.then_val.get_hover_value_for_position(pos, code))
|
||||||
|
}
|
||||||
|
/// Rename all identifiers that have the old name to the new given name.
|
||||||
|
fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
|
||||||
|
self.cond.rename_identifiers(old_name, new_name);
|
||||||
|
self.then_val.rename_identifiers(old_name, new_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Linting
|
||||||
|
|
||||||
|
impl IfExpression {}
|
||||||
|
impl ElseIf {}
|
@ -776,6 +776,25 @@ impl From<KclValue> for Vec<SourceRange> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<&KclValue> for Vec<SourceRange> {
|
||||||
|
fn from(item: &KclValue) -> Self {
|
||||||
|
match item {
|
||||||
|
KclValue::UserVal(u) => u.meta.iter().map(|m| m.source_range).collect(),
|
||||||
|
KclValue::TagDeclarator(ref t) => vec![t.into()],
|
||||||
|
KclValue::TagIdentifier(t) => t.meta.iter().map(|m| m.source_range).collect(),
|
||||||
|
KclValue::Solid(e) => e.meta.iter().map(|m| m.source_range).collect(),
|
||||||
|
KclValue::Solids { value } => value
|
||||||
|
.iter()
|
||||||
|
.flat_map(|eg| eg.meta.iter().map(|m| m.source_range))
|
||||||
|
.collect(),
|
||||||
|
KclValue::ImportedGeometry(i) => i.meta.iter().map(|m| m.source_range).collect(),
|
||||||
|
KclValue::Function { meta, .. } => meta.iter().map(|m| m.source_range).collect(),
|
||||||
|
KclValue::Plane(p) => p.meta.iter().map(|m| m.source_range).collect(),
|
||||||
|
KclValue::Face(f) => f.meta.iter().map(|m| m.source_range).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl KclValue {
|
impl KclValue {
|
||||||
pub fn get_json_value(&self) -> Result<serde_json::Value, KclError> {
|
pub fn get_json_value(&self) -> Result<serde_json::Value, KclError> {
|
||||||
if let KclValue::UserVal(user_val) = self {
|
if let KclValue::UserVal(user_val) = self {
|
||||||
@ -906,6 +925,23 @@ impl KclValue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If this KCL value is a bool, retrieve it.
|
||||||
|
pub fn get_bool(&self) -> Result<bool, KclError> {
|
||||||
|
let Self::UserVal(uv) = self else {
|
||||||
|
return Err(KclError::Type(KclErrorDetails {
|
||||||
|
source_ranges: self.into(),
|
||||||
|
message: format!("Expected bool, found {}", self.human_friendly_type()),
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
let JValue::Bool(b) = uv.value else {
|
||||||
|
return Err(KclError::Type(KclErrorDetails {
|
||||||
|
source_ranges: self.into(),
|
||||||
|
message: format!("Expected bool, found {}", human_friendly_type(&uv.value)),
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
Ok(b)
|
||||||
|
}
|
||||||
|
|
||||||
/// If this memory item is a function, call it with the given arguments, return its val as Ok.
|
/// If this memory item is a function, call it with the given arguments, return its val as Ok.
|
||||||
/// If it's not a function, return Err.
|
/// If it's not a function, return Err.
|
||||||
pub async fn call_fn(
|
pub async fn call_fn(
|
||||||
@ -1844,20 +1880,22 @@ impl ExecutorContext {
|
|||||||
program: &crate::ast::types::Program,
|
program: &crate::ast::types::Program,
|
||||||
exec_state: &mut ExecState,
|
exec_state: &mut ExecState,
|
||||||
body_type: BodyType,
|
body_type: BodyType,
|
||||||
) -> Result<(), KclError> {
|
) -> Result<Option<KclValue>, KclError> {
|
||||||
|
let mut last_expr = None;
|
||||||
// Iterate over the body of the program.
|
// Iterate over the body of the program.
|
||||||
for statement in &program.body {
|
for statement in &program.body {
|
||||||
match statement {
|
match statement {
|
||||||
BodyItem::ExpressionStatement(expression_statement) => {
|
BodyItem::ExpressionStatement(expression_statement) => {
|
||||||
let metadata = Metadata::from(expression_statement);
|
let metadata = Metadata::from(expression_statement);
|
||||||
// Discard return value.
|
last_expr = Some(
|
||||||
self.execute_expr(
|
self.execute_expr(
|
||||||
&expression_statement.expression,
|
&expression_statement.expression,
|
||||||
exec_state,
|
exec_state,
|
||||||
&metadata,
|
&metadata,
|
||||||
StatementKind::Expression,
|
StatementKind::Expression,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
BodyItem::VariableDeclaration(variable_declaration) => {
|
BodyItem::VariableDeclaration(variable_declaration) => {
|
||||||
for declaration in &variable_declaration.declarations {
|
for declaration in &variable_declaration.declarations {
|
||||||
@ -1875,6 +1913,7 @@ impl ExecutorContext {
|
|||||||
.await?;
|
.await?;
|
||||||
exec_state.memory.add(&var_name, memory_item, source_range)?;
|
exec_state.memory.add(&var_name, memory_item, source_range)?;
|
||||||
}
|
}
|
||||||
|
last_expr = None;
|
||||||
}
|
}
|
||||||
BodyItem::ReturnStatement(return_statement) => {
|
BodyItem::ReturnStatement(return_statement) => {
|
||||||
let metadata = Metadata::from(return_statement);
|
let metadata = Metadata::from(return_statement);
|
||||||
@ -1887,6 +1926,7 @@ impl ExecutorContext {
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
exec_state.memory.return_ = Some(value);
|
exec_state.memory.return_ = Some(value);
|
||||||
|
last_expr = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1903,7 +1943,7 @@ impl ExecutorContext {
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(last_expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn execute_expr<'a>(
|
pub async fn execute_expr<'a>(
|
||||||
@ -1960,6 +2000,7 @@ impl ExecutorContext {
|
|||||||
Expr::ObjectExpression(object_expression) => object_expression.execute(exec_state, self).await?,
|
Expr::ObjectExpression(object_expression) => object_expression.execute(exec_state, self).await?,
|
||||||
Expr::MemberExpression(member_expression) => member_expression.get_result(exec_state)?,
|
Expr::MemberExpression(member_expression) => member_expression.get_result(exec_state)?,
|
||||||
Expr::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, self).await?,
|
Expr::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, self).await?,
|
||||||
|
Expr::IfExpression(expr) => expr.get_result(exec_state, self).await?,
|
||||||
};
|
};
|
||||||
Ok(item)
|
Ok(item)
|
||||||
}
|
}
|
||||||
@ -2088,7 +2129,7 @@ pub(crate) async fn call_user_defined_function(
|
|||||||
(result, fn_memory)
|
(result, fn_memory)
|
||||||
};
|
};
|
||||||
|
|
||||||
result.map(|()| fn_memory.return_)
|
result.map(|_| fn_memory.return_)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum StatementKind<'a> {
|
pub enum StatementKind<'a> {
|
||||||
|
@ -10,11 +10,11 @@ use winnow::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::types::{
|
ast::types::{
|
||||||
ArrayExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression, CommentStyle, Expr,
|
ArrayExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression, CommentStyle, ElseIf,
|
||||||
ExpressionStatement, FnArgPrimitive, FnArgType, FunctionExpression, Identifier, Literal, LiteralIdentifier,
|
Expr, ExpressionStatement, FnArgPrimitive, FnArgType, FunctionExpression, Identifier, IfExpression, Literal,
|
||||||
LiteralValue, MemberExpression, MemberObject, NonCodeMeta, NonCodeNode, NonCodeValue, ObjectExpression,
|
LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, NonCodeMeta, NonCodeNode, NonCodeValue,
|
||||||
ObjectProperty, Parameter, PipeExpression, PipeSubstitution, Program, ReturnStatement, TagDeclarator,
|
ObjectExpression, ObjectProperty, Parameter, PipeExpression, PipeSubstitution, Program, ReturnStatement,
|
||||||
UnaryExpression, UnaryOperator, VariableDeclaration, VariableDeclarator, VariableKind,
|
TagDeclarator, UnaryExpression, UnaryOperator, VariableDeclaration, VariableDeclarator, VariableKind,
|
||||||
},
|
},
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
executor::SourceRange,
|
executor::SourceRange,
|
||||||
@ -359,6 +359,7 @@ fn operand(i: TokenSlice) -> PResult<BinaryPart> {
|
|||||||
Expr::BinaryExpression(x) => BinaryPart::BinaryExpression(x),
|
Expr::BinaryExpression(x) => BinaryPart::BinaryExpression(x),
|
||||||
Expr::CallExpression(x) => BinaryPart::CallExpression(x),
|
Expr::CallExpression(x) => BinaryPart::CallExpression(x),
|
||||||
Expr::MemberExpression(x) => BinaryPart::MemberExpression(x),
|
Expr::MemberExpression(x) => BinaryPart::MemberExpression(x),
|
||||||
|
Expr::IfExpression(x) => BinaryPart::IfExpression(x),
|
||||||
};
|
};
|
||||||
Ok(expr)
|
Ok(expr)
|
||||||
})
|
})
|
||||||
@ -670,6 +671,119 @@ fn pipe_sub(i: TokenSlice) -> PResult<PipeSubstitution> {
|
|||||||
.parse_next(i)
|
.parse_next(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn else_if(i: TokenSlice) -> PResult<ElseIf> {
|
||||||
|
let start = any
|
||||||
|
.try_map(|token: Token| {
|
||||||
|
if matches!(token.token_type, TokenType::Keyword) && token.value == "else" {
|
||||||
|
Ok(token.start)
|
||||||
|
} else {
|
||||||
|
Err(KclError::Syntax(KclErrorDetails {
|
||||||
|
source_ranges: token.as_source_ranges(),
|
||||||
|
message: format!("{} is not 'else'", token.value.as_str()),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.context(expected("the 'else' keyword"))
|
||||||
|
.parse_next(i)?;
|
||||||
|
ignore_whitespace(i);
|
||||||
|
let _if = any
|
||||||
|
.try_map(|token: Token| {
|
||||||
|
if matches!(token.token_type, TokenType::Keyword) && token.value == "if" {
|
||||||
|
Ok(token.start)
|
||||||
|
} else {
|
||||||
|
Err(KclError::Syntax(KclErrorDetails {
|
||||||
|
source_ranges: token.as_source_ranges(),
|
||||||
|
message: format!("{} is not 'if'", token.value.as_str()),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.context(expected("the 'if' keyword"))
|
||||||
|
.parse_next(i)?;
|
||||||
|
ignore_whitespace(i);
|
||||||
|
let cond = expression(i)?;
|
||||||
|
ignore_whitespace(i);
|
||||||
|
let _ = open_brace(i)?;
|
||||||
|
let then_val = program
|
||||||
|
.verify(|block| block.ends_with_expr())
|
||||||
|
.parse_next(i)
|
||||||
|
.map(Box::new)?;
|
||||||
|
ignore_whitespace(i);
|
||||||
|
let end = close_brace(i)?.end;
|
||||||
|
ignore_whitespace(i);
|
||||||
|
Ok(ElseIf {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
cond,
|
||||||
|
then_val,
|
||||||
|
digest: Default::default(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn if_expr(i: TokenSlice) -> PResult<IfExpression> {
|
||||||
|
let start = any
|
||||||
|
.try_map(|token: Token| {
|
||||||
|
if matches!(token.token_type, TokenType::Keyword) && token.value == "if" {
|
||||||
|
Ok(token.start)
|
||||||
|
} else {
|
||||||
|
Err(KclError::Syntax(KclErrorDetails {
|
||||||
|
source_ranges: token.as_source_ranges(),
|
||||||
|
message: format!("{} is not 'if'", token.value.as_str()),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.context(expected("the 'if' keyword"))
|
||||||
|
.parse_next(i)?;
|
||||||
|
let _ = whitespace(i)?;
|
||||||
|
let cond = expression(i).map(Box::new)?;
|
||||||
|
let _ = whitespace(i)?;
|
||||||
|
let _ = open_brace(i)?;
|
||||||
|
ignore_whitespace(i);
|
||||||
|
let then_val = program
|
||||||
|
.verify(|block| block.ends_with_expr())
|
||||||
|
.parse_next(i)
|
||||||
|
.map_err(|e| e.cut())
|
||||||
|
.map(Box::new)?;
|
||||||
|
ignore_whitespace(i);
|
||||||
|
let _ = close_brace(i)?;
|
||||||
|
ignore_whitespace(i);
|
||||||
|
let else_ifs = repeat(0.., else_if).parse_next(i)?;
|
||||||
|
|
||||||
|
ignore_whitespace(i);
|
||||||
|
let _ = any
|
||||||
|
.try_map(|token: Token| {
|
||||||
|
if matches!(token.token_type, TokenType::Keyword) && token.value == "else" {
|
||||||
|
Ok(token.start)
|
||||||
|
} else {
|
||||||
|
Err(KclError::Syntax(KclErrorDetails {
|
||||||
|
source_ranges: token.as_source_ranges(),
|
||||||
|
message: format!("{} is not 'else'", token.value.as_str()),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.context(expected("the 'else' keyword"))
|
||||||
|
.parse_next(i)?;
|
||||||
|
ignore_whitespace(i);
|
||||||
|
let _ = open_brace(i)?;
|
||||||
|
ignore_whitespace(i);
|
||||||
|
|
||||||
|
let final_else = program
|
||||||
|
.verify(|block| block.ends_with_expr())
|
||||||
|
.parse_next(i)
|
||||||
|
.map_err(|e| e.cut())
|
||||||
|
.map(Box::new)?;
|
||||||
|
ignore_whitespace(i);
|
||||||
|
let end = close_brace(i)?.end;
|
||||||
|
Ok(IfExpression {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
cond,
|
||||||
|
then_val,
|
||||||
|
else_ifs,
|
||||||
|
final_else,
|
||||||
|
digest: Default::default(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Looks like
|
// Looks like
|
||||||
// (arg0, arg1) => {
|
// (arg0, arg1) => {
|
||||||
// const x = arg0 + arg1;
|
// const x = arg0 + arg1;
|
||||||
@ -1069,6 +1183,7 @@ fn expr_allowed_in_pipe_expr(i: TokenSlice) -> PResult<Expr> {
|
|||||||
object.map(Box::new).map(Expr::ObjectExpression),
|
object.map(Box::new).map(Expr::ObjectExpression),
|
||||||
pipe_sub.map(Box::new).map(Expr::PipeSubstitution),
|
pipe_sub.map(Box::new).map(Expr::PipeSubstitution),
|
||||||
function_expression.map(Box::new).map(Expr::FunctionExpression),
|
function_expression.map(Box::new).map(Expr::FunctionExpression),
|
||||||
|
if_expr.map(Box::new).map(Expr::IfExpression),
|
||||||
unnecessarily_bracketed,
|
unnecessarily_bracketed,
|
||||||
))
|
))
|
||||||
.context(expected("a KCL expression (but not a pipe expression)"))
|
.context(expected("a KCL expression (but not a pipe expression)"))
|
||||||
@ -3147,6 +3262,42 @@ e
|
|||||||
let _arr = array_elem_by_elem(&mut sl).unwrap();
|
let _arr = array_elem_by_elem(&mut sl).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic_if_else() {
|
||||||
|
let some_program_string = "if true {
|
||||||
|
3
|
||||||
|
} else {
|
||||||
|
4
|
||||||
|
}";
|
||||||
|
let tokens = crate::token::lexer(some_program_string).unwrap();
|
||||||
|
let mut sl: &[Token] = &tokens;
|
||||||
|
let _res = if_expr(&mut sl).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic_else_if() {
|
||||||
|
let some_program_string = "else if true {
|
||||||
|
4
|
||||||
|
}";
|
||||||
|
let tokens = crate::token::lexer(some_program_string).unwrap();
|
||||||
|
let mut sl: &[Token] = &tokens;
|
||||||
|
let _res = else_if(&mut sl).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic_if_else_if() {
|
||||||
|
let some_program_string = "if true {
|
||||||
|
3
|
||||||
|
} else if true {
|
||||||
|
4
|
||||||
|
} else {
|
||||||
|
5
|
||||||
|
}";
|
||||||
|
let tokens = crate::token::lexer(some_program_string).unwrap();
|
||||||
|
let mut sl: &[Token] = &tokens;
|
||||||
|
let _res = if_expr(&mut sl).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_keyword_ok_in_fn_args_return() {
|
fn test_keyword_ok_in_fn_args_return() {
|
||||||
let some_program_string = r#"fn thing = (param) => {
|
let some_program_string = r#"fn thing = (param) => {
|
||||||
@ -3511,6 +3662,24 @@ const sketch001 = startSketchOn('XY')
|
|||||||
const my14 = 4 ^ 2 - 3 ^ 2 * 2
|
const my14 = 4 ^ 2 - 3 ^ 2 * 2
|
||||||
"#
|
"#
|
||||||
);
|
);
|
||||||
|
snapshot_test!(
|
||||||
|
bc,
|
||||||
|
r#"const x = if true {
|
||||||
|
3
|
||||||
|
} else {
|
||||||
|
4
|
||||||
|
}"#
|
||||||
|
);
|
||||||
|
snapshot_test!(
|
||||||
|
bd,
|
||||||
|
r#"const x = if true {
|
||||||
|
3
|
||||||
|
} else if func(radius) {
|
||||||
|
4
|
||||||
|
} else {
|
||||||
|
5
|
||||||
|
}"#
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
|
@ -0,0 +1,112 @@
|
|||||||
|
---
|
||||||
|
source: kcl/src/parser/parser_impl.rs
|
||||||
|
expression: actual
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"start": 0,
|
||||||
|
"end": 74,
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"start": 0,
|
||||||
|
"end": 74,
|
||||||
|
"declarations": [
|
||||||
|
{
|
||||||
|
"type": "VariableDeclarator",
|
||||||
|
"start": 6,
|
||||||
|
"end": 74,
|
||||||
|
"id": {
|
||||||
|
"type": "Identifier",
|
||||||
|
"start": 6,
|
||||||
|
"end": 7,
|
||||||
|
"name": "x",
|
||||||
|
"digest": null
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"type": "IfExpression",
|
||||||
|
"type": "IfExpression",
|
||||||
|
"start": 10,
|
||||||
|
"end": 74,
|
||||||
|
"cond": {
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"start": 13,
|
||||||
|
"end": 17,
|
||||||
|
"value": true,
|
||||||
|
"raw": "true",
|
||||||
|
"digest": null
|
||||||
|
},
|
||||||
|
"then_val": {
|
||||||
|
"start": 32,
|
||||||
|
"end": 42,
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "ExpressionStatement",
|
||||||
|
"type": "ExpressionStatement",
|
||||||
|
"start": 32,
|
||||||
|
"end": 33,
|
||||||
|
"expression": {
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"start": 32,
|
||||||
|
"end": 33,
|
||||||
|
"value": 3,
|
||||||
|
"raw": "3",
|
||||||
|
"digest": null
|
||||||
|
},
|
||||||
|
"digest": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nonCodeMeta": {
|
||||||
|
"nonCodeNodes": {},
|
||||||
|
"start": [],
|
||||||
|
"digest": null
|
||||||
|
},
|
||||||
|
"digest": null
|
||||||
|
},
|
||||||
|
"else_ifs": [],
|
||||||
|
"final_else": {
|
||||||
|
"start": 63,
|
||||||
|
"end": 73,
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "ExpressionStatement",
|
||||||
|
"type": "ExpressionStatement",
|
||||||
|
"start": 63,
|
||||||
|
"end": 64,
|
||||||
|
"expression": {
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"start": 63,
|
||||||
|
"end": 64,
|
||||||
|
"value": 4,
|
||||||
|
"raw": "4",
|
||||||
|
"digest": null
|
||||||
|
},
|
||||||
|
"digest": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nonCodeMeta": {
|
||||||
|
"nonCodeNodes": {},
|
||||||
|
"start": [],
|
||||||
|
"digest": null
|
||||||
|
},
|
||||||
|
"digest": null
|
||||||
|
},
|
||||||
|
"digest": null
|
||||||
|
},
|
||||||
|
"digest": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"kind": "const",
|
||||||
|
"digest": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nonCodeMeta": {
|
||||||
|
"nonCodeNodes": {},
|
||||||
|
"start": [],
|
||||||
|
"digest": null
|
||||||
|
},
|
||||||
|
"digest": null
|
||||||
|
}
|
@ -0,0 +1,172 @@
|
|||||||
|
---
|
||||||
|
source: kcl/src/parser/parser_impl.rs
|
||||||
|
expression: actual
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"start": 0,
|
||||||
|
"end": 121,
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"start": 0,
|
||||||
|
"end": 121,
|
||||||
|
"declarations": [
|
||||||
|
{
|
||||||
|
"type": "VariableDeclarator",
|
||||||
|
"start": 6,
|
||||||
|
"end": 121,
|
||||||
|
"id": {
|
||||||
|
"type": "Identifier",
|
||||||
|
"start": 6,
|
||||||
|
"end": 7,
|
||||||
|
"name": "x",
|
||||||
|
"digest": null
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"type": "IfExpression",
|
||||||
|
"type": "IfExpression",
|
||||||
|
"start": 10,
|
||||||
|
"end": 121,
|
||||||
|
"cond": {
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"start": 13,
|
||||||
|
"end": 17,
|
||||||
|
"value": true,
|
||||||
|
"raw": "true",
|
||||||
|
"digest": null
|
||||||
|
},
|
||||||
|
"then_val": {
|
||||||
|
"start": 32,
|
||||||
|
"end": 42,
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "ExpressionStatement",
|
||||||
|
"type": "ExpressionStatement",
|
||||||
|
"start": 32,
|
||||||
|
"end": 33,
|
||||||
|
"expression": {
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"start": 32,
|
||||||
|
"end": 33,
|
||||||
|
"value": 3,
|
||||||
|
"raw": "3",
|
||||||
|
"digest": null
|
||||||
|
},
|
||||||
|
"digest": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nonCodeMeta": {
|
||||||
|
"nonCodeNodes": {},
|
||||||
|
"start": [],
|
||||||
|
"digest": null
|
||||||
|
},
|
||||||
|
"digest": null
|
||||||
|
},
|
||||||
|
"else_ifs": [
|
||||||
|
{
|
||||||
|
"type": "ElseIf",
|
||||||
|
"start": 44,
|
||||||
|
"end": 90,
|
||||||
|
"cond": {
|
||||||
|
"type": "CallExpression",
|
||||||
|
"type": "CallExpression",
|
||||||
|
"start": 52,
|
||||||
|
"end": 64,
|
||||||
|
"callee": {
|
||||||
|
"type": "Identifier",
|
||||||
|
"start": 52,
|
||||||
|
"end": 56,
|
||||||
|
"name": "func",
|
||||||
|
"digest": null
|
||||||
|
},
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"type": "Identifier",
|
||||||
|
"type": "Identifier",
|
||||||
|
"start": 57,
|
||||||
|
"end": 63,
|
||||||
|
"name": "radius",
|
||||||
|
"digest": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"optional": false,
|
||||||
|
"digest": null
|
||||||
|
},
|
||||||
|
"then_val": {
|
||||||
|
"start": 65,
|
||||||
|
"end": 89,
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "ExpressionStatement",
|
||||||
|
"type": "ExpressionStatement",
|
||||||
|
"start": 79,
|
||||||
|
"end": 80,
|
||||||
|
"expression": {
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"start": 79,
|
||||||
|
"end": 80,
|
||||||
|
"value": 4,
|
||||||
|
"raw": "4",
|
||||||
|
"digest": null
|
||||||
|
},
|
||||||
|
"digest": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nonCodeMeta": {
|
||||||
|
"nonCodeNodes": {},
|
||||||
|
"start": [],
|
||||||
|
"digest": null
|
||||||
|
},
|
||||||
|
"digest": null
|
||||||
|
},
|
||||||
|
"digest": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final_else": {
|
||||||
|
"start": 110,
|
||||||
|
"end": 120,
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "ExpressionStatement",
|
||||||
|
"type": "ExpressionStatement",
|
||||||
|
"start": 110,
|
||||||
|
"end": 111,
|
||||||
|
"expression": {
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"start": 110,
|
||||||
|
"end": 111,
|
||||||
|
"value": 5,
|
||||||
|
"raw": "5",
|
||||||
|
"digest": null
|
||||||
|
},
|
||||||
|
"digest": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nonCodeMeta": {
|
||||||
|
"nonCodeNodes": {},
|
||||||
|
"start": [],
|
||||||
|
"digest": null
|
||||||
|
},
|
||||||
|
"digest": null
|
||||||
|
},
|
||||||
|
"digest": null
|
||||||
|
},
|
||||||
|
"digest": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"kind": "const",
|
||||||
|
"digest": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nonCodeMeta": {
|
||||||
|
"nonCodeNodes": {},
|
||||||
|
"start": [],
|
||||||
|
"digest": null
|
||||||
|
},
|
||||||
|
"digest": null
|
||||||
|
}
|
@ -3,8 +3,8 @@ use std::fmt::Write;
|
|||||||
use crate::{
|
use crate::{
|
||||||
ast::types::{
|
ast::types::{
|
||||||
ArrayExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression, Expr, FormatOptions,
|
ArrayExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression, Expr, FormatOptions,
|
||||||
FunctionExpression, Literal, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, NonCodeValue,
|
FunctionExpression, IfExpression, Literal, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject,
|
||||||
ObjectExpression, PipeExpression, Program, TagDeclarator, UnaryExpression, VariableDeclaration,
|
NonCodeValue, ObjectExpression, PipeExpression, Program, TagDeclarator, UnaryExpression, VariableDeclaration,
|
||||||
},
|
},
|
||||||
parser::PIPE_OPERATOR,
|
parser::PIPE_OPERATOR,
|
||||||
};
|
};
|
||||||
@ -121,6 +121,7 @@ impl Expr {
|
|||||||
Expr::TagDeclarator(tag) => tag.recast(),
|
Expr::TagDeclarator(tag) => tag.recast(),
|
||||||
Expr::PipeExpression(pipe_exp) => pipe_exp.recast(options, indentation_level),
|
Expr::PipeExpression(pipe_exp) => pipe_exp.recast(options, indentation_level),
|
||||||
Expr::UnaryExpression(unary_exp) => unary_exp.recast(options),
|
Expr::UnaryExpression(unary_exp) => unary_exp.recast(options),
|
||||||
|
Expr::IfExpression(e) => e.recast(options, indentation_level, is_in_pipe),
|
||||||
Expr::PipeSubstitution(_) => crate::parser::PIPE_SUBSTITUTION_OPERATOR.to_string(),
|
Expr::PipeSubstitution(_) => crate::parser::PIPE_SUBSTITUTION_OPERATOR.to_string(),
|
||||||
Expr::None(_) => {
|
Expr::None(_) => {
|
||||||
unimplemented!("there is no literal None, see https://github.com/KittyCAD/modeling-app/issues/1115")
|
unimplemented!("there is no literal None, see https://github.com/KittyCAD/modeling-app/issues/1115")
|
||||||
@ -138,6 +139,7 @@ impl BinaryPart {
|
|||||||
BinaryPart::CallExpression(call_expression) => call_expression.recast(options, indentation_level, false),
|
BinaryPart::CallExpression(call_expression) => call_expression.recast(options, indentation_level, false),
|
||||||
BinaryPart::UnaryExpression(unary_expression) => unary_expression.recast(options),
|
BinaryPart::UnaryExpression(unary_expression) => unary_expression.recast(options),
|
||||||
BinaryPart::MemberExpression(member_expression) => member_expression.recast(),
|
BinaryPart::MemberExpression(member_expression) => member_expression.recast(),
|
||||||
|
BinaryPart::IfExpression(e) => e.recast(options, indentation_level, false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -403,6 +405,7 @@ impl UnaryExpression {
|
|||||||
BinaryPart::Literal(_)
|
BinaryPart::Literal(_)
|
||||||
| BinaryPart::Identifier(_)
|
| BinaryPart::Identifier(_)
|
||||||
| BinaryPart::MemberExpression(_)
|
| BinaryPart::MemberExpression(_)
|
||||||
|
| BinaryPart::IfExpression(_)
|
||||||
| BinaryPart::CallExpression(_) => {
|
| BinaryPart::CallExpression(_) => {
|
||||||
format!("{}{}", &self.operator, self.argument.recast(options, 0))
|
format!("{}{}", &self.operator, self.argument.recast(options, 0))
|
||||||
}
|
}
|
||||||
@ -413,6 +416,32 @@ impl UnaryExpression {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl IfExpression {
|
||||||
|
fn recast(&self, options: &FormatOptions, indentation_level: usize, is_in_pipe: bool) -> String {
|
||||||
|
// We can calculate how many lines this will take, so let's do it and avoid growing the vec.
|
||||||
|
// Total lines = starting lines, else-if lines, ending lines.
|
||||||
|
let n = 2 + (self.else_ifs.len() * 2) + 3;
|
||||||
|
let mut lines = Vec::with_capacity(n);
|
||||||
|
|
||||||
|
let cond = self.cond.recast(options, indentation_level, is_in_pipe);
|
||||||
|
lines.push((0, format!("if {cond} {{")));
|
||||||
|
lines.push((1, self.then_val.recast(options, indentation_level + 1)));
|
||||||
|
for else_if in &self.else_ifs {
|
||||||
|
let cond = else_if.cond.recast(options, indentation_level, is_in_pipe);
|
||||||
|
lines.push((0, format!("}} else if {cond} {{")));
|
||||||
|
lines.push((1, else_if.then_val.recast(options, indentation_level + 1)));
|
||||||
|
}
|
||||||
|
lines.push((0, "} else {".to_owned()));
|
||||||
|
lines.push((1, self.final_else.recast(options, indentation_level + 1)));
|
||||||
|
lines.push((0, "}".to_owned()));
|
||||||
|
lines
|
||||||
|
.into_iter()
|
||||||
|
.map(|(ind, line)| format!("{}{}", options.get_indentation(indentation_level + ind), line.trim()))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PipeExpression {
|
impl PipeExpression {
|
||||||
fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
|
fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
|
||||||
let pipe = self
|
let pipe = self
|
||||||
@ -477,6 +506,38 @@ mod tests {
|
|||||||
|
|
||||||
use crate::ast::types::FormatOptions;
|
use crate::ast::types::FormatOptions;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_recast_if_else_if_same() {
|
||||||
|
let input = r#"let b = if false {
|
||||||
|
3
|
||||||
|
} else if true {
|
||||||
|
4
|
||||||
|
} else {
|
||||||
|
5
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
let tokens = crate::token::lexer(input).unwrap();
|
||||||
|
let parser = crate::parser::Parser::new(tokens);
|
||||||
|
let program = parser.ast().unwrap();
|
||||||
|
let output = program.recast(&Default::default(), 0);
|
||||||
|
assert_eq!(output, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_recast_if_same() {
|
||||||
|
let input = r#"let b = if false {
|
||||||
|
3
|
||||||
|
} else {
|
||||||
|
5
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
let tokens = crate::token::lexer(input).unwrap();
|
||||||
|
let parser = crate::parser::Parser::new(tokens);
|
||||||
|
let program = parser.ast().unwrap();
|
||||||
|
let output = program.recast(&Default::default(), 0);
|
||||||
|
assert_eq!(output, input);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_recast_bug_fn_in_fn() {
|
fn test_recast_bug_fn_in_fn() {
|
||||||
let some_program_string = r#"// Start point (top left)
|
let some_program_string = r#"// Start point (top left)
|
||||||
|
@ -27,6 +27,7 @@ pub enum Node<'a> {
|
|||||||
ObjectExpression(&'a types::ObjectExpression),
|
ObjectExpression(&'a types::ObjectExpression),
|
||||||
MemberExpression(&'a types::MemberExpression),
|
MemberExpression(&'a types::MemberExpression),
|
||||||
UnaryExpression(&'a types::UnaryExpression),
|
UnaryExpression(&'a types::UnaryExpression),
|
||||||
|
IfExpression(&'a types::IfExpression),
|
||||||
|
|
||||||
Parameter(&'a types::Parameter),
|
Parameter(&'a types::Parameter),
|
||||||
|
|
||||||
@ -59,6 +60,7 @@ impl From<&Node<'_>> for SourceRange {
|
|||||||
Node::Parameter(p) => SourceRange([p.identifier.start(), p.identifier.end()]),
|
Node::Parameter(p) => SourceRange([p.identifier.start(), p.identifier.end()]),
|
||||||
Node::ObjectProperty(o) => SourceRange([o.start(), o.end()]),
|
Node::ObjectProperty(o) => SourceRange([o.start(), o.end()]),
|
||||||
Node::MemberObject(m) => SourceRange([m.start(), m.end()]),
|
Node::MemberObject(m) => SourceRange([m.start(), m.end()]),
|
||||||
|
Node::IfExpression(m) => SourceRange([m.start(), m.end()]),
|
||||||
Node::LiteralIdentifier(l) => SourceRange([l.start(), l.end()]),
|
Node::LiteralIdentifier(l) => SourceRange([l.start(), l.end()]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -94,4 +96,5 @@ impl_from!(Node, UnaryExpression);
|
|||||||
impl_from!(Node, Parameter);
|
impl_from!(Node, Parameter);
|
||||||
impl_from!(Node, ObjectProperty);
|
impl_from!(Node, ObjectProperty);
|
||||||
impl_from!(Node, MemberObject);
|
impl_from!(Node, MemberObject);
|
||||||
|
impl_from!(Node, IfExpression);
|
||||||
impl_from!(Node, LiteralIdentifier);
|
impl_from!(Node, LiteralIdentifier);
|
||||||
|
@ -2,7 +2,7 @@ use anyhow::Result;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::types::{
|
ast::types::{
|
||||||
BinaryPart, BodyItem, Expr, LiteralIdentifier, MemberExpression, MemberObject, ObjectExpression,
|
BinaryPart, BodyItem, Expr, IfExpression, LiteralIdentifier, MemberExpression, MemberObject, ObjectExpression,
|
||||||
ObjectProperty, Parameter, Program, UnaryExpression, VariableDeclarator,
|
ObjectProperty, Parameter, Program, UnaryExpression, VariableDeclarator,
|
||||||
},
|
},
|
||||||
walk::Node,
|
walk::Node,
|
||||||
@ -105,9 +105,11 @@ where
|
|||||||
BinaryPart::CallExpression(ce) => f.walk(ce.as_ref().into()),
|
BinaryPart::CallExpression(ce) => f.walk(ce.as_ref().into()),
|
||||||
BinaryPart::UnaryExpression(ue) => walk_unary_expression(ue, f),
|
BinaryPart::UnaryExpression(ue) => walk_unary_expression(ue, f),
|
||||||
BinaryPart::MemberExpression(me) => walk_member_expression(me, f),
|
BinaryPart::MemberExpression(me) => walk_member_expression(me, f),
|
||||||
|
BinaryPart::IfExpression(e) => walk_if_expression(e, f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Rename this to walk_expr
|
||||||
fn walk_value<'a, WalkT>(node: &'a Expr, f: &WalkT) -> Result<bool>
|
fn walk_value<'a, WalkT>(node: &'a Expr, f: &WalkT) -> Result<bool>
|
||||||
where
|
where
|
||||||
WalkT: Walker<'a>,
|
WalkT: Walker<'a>,
|
||||||
@ -184,6 +186,7 @@ where
|
|||||||
Expr::ObjectExpression(oe) => walk_object_expression(oe, f),
|
Expr::ObjectExpression(oe) => walk_object_expression(oe, f),
|
||||||
Expr::MemberExpression(me) => walk_member_expression(me, f),
|
Expr::MemberExpression(me) => walk_member_expression(me, f),
|
||||||
Expr::UnaryExpression(ue) => walk_unary_expression(ue, f),
|
Expr::UnaryExpression(ue) => walk_unary_expression(ue, f),
|
||||||
|
Expr::IfExpression(e) => walk_if_expression(e, f),
|
||||||
Expr::None(_) => Ok(true),
|
Expr::None(_) => Ok(true),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -216,6 +219,33 @@ where
|
|||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Walk through an [IfExpression].
|
||||||
|
fn walk_if_expression<'a, WalkT>(node: &'a IfExpression, f: &WalkT) -> Result<bool>
|
||||||
|
where
|
||||||
|
WalkT: Walker<'a>,
|
||||||
|
{
|
||||||
|
if !f.walk(node.into())? {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
if !walk_value(&node.cond, f)? {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
for else_if in &node.else_ifs {
|
||||||
|
if !walk_value(&else_if.cond, f)? {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
if !walk(&else_if.then_val, f)? {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let final_else = &(*node.final_else);
|
||||||
|
if !f.walk(final_else.into())? {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
/// walk through an [UnaryExpression].
|
/// walk through an [UnaryExpression].
|
||||||
fn walk_unary_expression<'a, WalkT>(node: &'a UnaryExpression, f: &WalkT) -> Result<bool>
|
fn walk_unary_expression<'a, WalkT>(node: &'a UnaryExpression, f: &WalkT) -> Result<bool>
|
||||||
where
|
where
|
||||||
|
28
src/wasm-lib/tests/executor/inputs/no_visuals/if_else.kcl
Normal file
28
src/wasm-lib/tests/executor/inputs/no_visuals/if_else.kcl
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// This tests evaluating if-else expressions.
|
||||||
|
|
||||||
|
let a = if true {
|
||||||
|
3
|
||||||
|
} else if true {
|
||||||
|
4
|
||||||
|
} else {
|
||||||
|
5
|
||||||
|
}
|
||||||
|
assertEqual(a, 3, 0.001, "the 'if' branch gets returned")
|
||||||
|
|
||||||
|
let b = if false {
|
||||||
|
3
|
||||||
|
} else if true {
|
||||||
|
4
|
||||||
|
} else {
|
||||||
|
5
|
||||||
|
}
|
||||||
|
assertEqual(b, 4, 0.001, "the 'else if' branch gets returned")
|
||||||
|
|
||||||
|
let c = if false {
|
||||||
|
3
|
||||||
|
} else if false {
|
||||||
|
4
|
||||||
|
} else {
|
||||||
|
5
|
||||||
|
}
|
||||||
|
assertEqual(c, 5, 0.001, "the 'else' branch gets returned")
|
@ -0,0 +1,5 @@
|
|||||||
|
let x = if true {
|
||||||
|
let y = 1
|
||||||
|
} else {
|
||||||
|
let z = 1
|
||||||
|
}
|
@ -90,4 +90,9 @@ gen_test_fail!(
|
|||||||
"semantic: cannot use % outside a pipe expression"
|
"semantic: cannot use % outside a pipe expression"
|
||||||
);
|
);
|
||||||
gen_test!(sketch_in_object);
|
gen_test!(sketch_in_object);
|
||||||
|
gen_test!(if_else);
|
||||||
|
// gen_test_fail!(
|
||||||
|
// if_else_no_expr,
|
||||||
|
// "syntax: blocks inside an if/else expression must end in an expression"
|
||||||
|
// );
|
||||||
gen_test!(add_lots);
|
gen_test!(add_lots);
|
||||||
|
Reference in New Issue
Block a user