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,
|
||||
};
|
||||
|
||||
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::{
|
||||
docs::StdLibFn,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
@ -30,12 +34,14 @@ use crate::{
|
||||
std::{kcl_stdlib::KclStdLibFn, FunctionKind},
|
||||
};
|
||||
|
||||
mod condition;
|
||||
mod literal_value;
|
||||
mod none;
|
||||
|
||||
/// Position-independent digest of the AST node.
|
||||
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)]
|
||||
#[databake(path = kcl_lib::ast::types)]
|
||||
#[ts(export)]
|
||||
@ -72,6 +78,7 @@ macro_rules! compute_digest {
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use compute_digest;
|
||||
|
||||
impl Program {
|
||||
compute_digest!(|slf, hasher| {
|
||||
@ -82,6 +89,14 @@ impl Program {
|
||||
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> {
|
||||
// Check if we are in the non code meta.
|
||||
if let Some(meta) = self.get_non_code_meta_for_position(pos) {
|
||||
@ -501,6 +516,7 @@ pub enum Expr {
|
||||
ObjectExpression(Box<ObjectExpression>),
|
||||
MemberExpression(Box<MemberExpression>),
|
||||
UnaryExpression(Box<UnaryExpression>),
|
||||
IfExpression(Box<IfExpression>),
|
||||
None(KclNone),
|
||||
}
|
||||
|
||||
@ -519,6 +535,7 @@ impl Expr {
|
||||
Expr::ObjectExpression(oe) => oe.compute_digest(),
|
||||
Expr::MemberExpression(me) => me.compute_digest(),
|
||||
Expr::UnaryExpression(ue) => ue.compute_digest(),
|
||||
Expr::IfExpression(e) => e.compute_digest(),
|
||||
Expr::None(_) => {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(b"Value::None");
|
||||
@ -562,6 +579,7 @@ impl Expr {
|
||||
Expr::PipeExpression(pipe_exp) => Some(&pipe_exp.non_code_meta),
|
||||
Expr::UnaryExpression(_unary_exp) => None,
|
||||
Expr::PipeSubstitution(_pipe_substitution) => None,
|
||||
Expr::IfExpression(_) => None,
|
||||
Expr::None(_none) => None,
|
||||
}
|
||||
}
|
||||
@ -584,6 +602,7 @@ impl Expr {
|
||||
Expr::TagDeclarator(_) => {}
|
||||
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::IfExpression(_) => {}
|
||||
Expr::PipeSubstitution(_) => {}
|
||||
Expr::None(_) => {}
|
||||
}
|
||||
@ -603,6 +622,7 @@ impl Expr {
|
||||
Expr::ObjectExpression(object_expression) => object_expression.start(),
|
||||
Expr::MemberExpression(member_expression) => member_expression.start(),
|
||||
Expr::UnaryExpression(unary_expression) => unary_expression.start(),
|
||||
Expr::IfExpression(expr) => expr.start(),
|
||||
Expr::None(none) => none.start,
|
||||
}
|
||||
}
|
||||
@ -621,6 +641,7 @@ impl Expr {
|
||||
Expr::ObjectExpression(object_expression) => object_expression.end(),
|
||||
Expr::MemberExpression(member_expression) => member_expression.end(),
|
||||
Expr::UnaryExpression(unary_expression) => unary_expression.end(),
|
||||
Expr::IfExpression(expr) => expr.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::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::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
|
||||
Expr::None(_) => None,
|
||||
Expr::Literal(_) => None,
|
||||
@ -670,11 +692,12 @@ impl Expr {
|
||||
member_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(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the constraint level for a value type.
|
||||
/// Get the constraint level for an expression.
|
||||
pub fn get_constraint_level(&self) -> ConstraintLevel {
|
||||
match self {
|
||||
Expr::Literal(literal) => literal.get_constraint_level(),
|
||||
@ -692,6 +715,7 @@ impl Expr {
|
||||
Expr::ObjectExpression(object_expression) => object_expression.get_constraint_level(),
|
||||
Expr::MemberExpression(member_expression) => member_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(),
|
||||
}
|
||||
}
|
||||
@ -720,6 +744,7 @@ pub enum BinaryPart {
|
||||
CallExpression(Box<CallExpression>),
|
||||
UnaryExpression(Box<UnaryExpression>),
|
||||
MemberExpression(Box<MemberExpression>),
|
||||
IfExpression(Box<IfExpression>),
|
||||
}
|
||||
|
||||
impl From<BinaryPart> for SourceRange {
|
||||
@ -743,6 +768,7 @@ impl BinaryPart {
|
||||
BinaryPart::CallExpression(ce) => ce.compute_digest(),
|
||||
BinaryPart::UnaryExpression(ue) => ue.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::UnaryExpression(unary_expression) => unary_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)
|
||||
}
|
||||
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::UnaryExpression(unary_expression) => unary_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::UnaryExpression(unary_expression) => unary_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::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, ctx).await,
|
||||
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::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) => {
|
||||
member_expression.get_hover_value_for_position(pos, code)
|
||||
}
|
||||
@ -845,6 +877,7 @@ impl BinaryPart {
|
||||
BinaryPart::MemberExpression(ref mut member_expression) => {
|
||||
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 {
|
||||
Ok(()) => {}
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
// We need to override the source ranges so we don't get the embedded kcl
|
||||
// function from the stdlib.
|
||||
@ -3149,6 +3182,7 @@ async fn inner_execute_pipe_body(
|
||||
| Expr::ObjectExpression(_)
|
||||
| Expr::MemberExpression(_)
|
||||
| Expr::UnaryExpression(_)
|
||||
| Expr::IfExpression(_)
|
||||
| Expr::None(_) => {}
|
||||
};
|
||||
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 {
|
||||
pub fn get_json_value(&self) -> Result<serde_json::Value, KclError> {
|
||||
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 it's not a function, return Err.
|
||||
pub async fn call_fn(
|
||||
@ -1844,20 +1880,22 @@ impl ExecutorContext {
|
||||
program: &crate::ast::types::Program,
|
||||
exec_state: &mut ExecState,
|
||||
body_type: BodyType,
|
||||
) -> Result<(), KclError> {
|
||||
) -> Result<Option<KclValue>, KclError> {
|
||||
let mut last_expr = None;
|
||||
// Iterate over the body of the program.
|
||||
for statement in &program.body {
|
||||
match statement {
|
||||
BodyItem::ExpressionStatement(expression_statement) => {
|
||||
let metadata = Metadata::from(expression_statement);
|
||||
// Discard return value.
|
||||
last_expr = Some(
|
||||
self.execute_expr(
|
||||
&expression_statement.expression,
|
||||
exec_state,
|
||||
&metadata,
|
||||
StatementKind::Expression,
|
||||
)
|
||||
.await?;
|
||||
.await?,
|
||||
);
|
||||
}
|
||||
BodyItem::VariableDeclaration(variable_declaration) => {
|
||||
for declaration in &variable_declaration.declarations {
|
||||
@ -1875,6 +1913,7 @@ impl ExecutorContext {
|
||||
.await?;
|
||||
exec_state.memory.add(&var_name, memory_item, source_range)?;
|
||||
}
|
||||
last_expr = None;
|
||||
}
|
||||
BodyItem::ReturnStatement(return_statement) => {
|
||||
let metadata = Metadata::from(return_statement);
|
||||
@ -1887,6 +1926,7 @@ impl ExecutorContext {
|
||||
)
|
||||
.await?;
|
||||
exec_state.memory.return_ = Some(value);
|
||||
last_expr = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1903,7 +1943,7 @@ impl ExecutorContext {
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(last_expr)
|
||||
}
|
||||
|
||||
pub async fn execute_expr<'a>(
|
||||
@ -1960,6 +2000,7 @@ impl ExecutorContext {
|
||||
Expr::ObjectExpression(object_expression) => object_expression.execute(exec_state, self).await?,
|
||||
Expr::MemberExpression(member_expression) => member_expression.get_result(exec_state)?,
|
||||
Expr::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, self).await?,
|
||||
Expr::IfExpression(expr) => expr.get_result(exec_state, self).await?,
|
||||
};
|
||||
Ok(item)
|
||||
}
|
||||
@ -2088,7 +2129,7 @@ pub(crate) async fn call_user_defined_function(
|
||||
(result, fn_memory)
|
||||
};
|
||||
|
||||
result.map(|()| fn_memory.return_)
|
||||
result.map(|_| fn_memory.return_)
|
||||
}
|
||||
|
||||
pub enum StatementKind<'a> {
|
||||
|
@ -10,11 +10,11 @@ use winnow::{
|
||||
|
||||
use crate::{
|
||||
ast::types::{
|
||||
ArrayExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression, CommentStyle, Expr,
|
||||
ExpressionStatement, FnArgPrimitive, FnArgType, FunctionExpression, Identifier, Literal, LiteralIdentifier,
|
||||
LiteralValue, MemberExpression, MemberObject, NonCodeMeta, NonCodeNode, NonCodeValue, ObjectExpression,
|
||||
ObjectProperty, Parameter, PipeExpression, PipeSubstitution, Program, ReturnStatement, TagDeclarator,
|
||||
UnaryExpression, UnaryOperator, VariableDeclaration, VariableDeclarator, VariableKind,
|
||||
ArrayExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression, CommentStyle, ElseIf,
|
||||
Expr, ExpressionStatement, FnArgPrimitive, FnArgType, FunctionExpression, Identifier, IfExpression, Literal,
|
||||
LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, NonCodeMeta, NonCodeNode, NonCodeValue,
|
||||
ObjectExpression, ObjectProperty, Parameter, PipeExpression, PipeSubstitution, Program, ReturnStatement,
|
||||
TagDeclarator, UnaryExpression, UnaryOperator, VariableDeclaration, VariableDeclarator, VariableKind,
|
||||
},
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::SourceRange,
|
||||
@ -359,6 +359,7 @@ fn operand(i: TokenSlice) -> PResult<BinaryPart> {
|
||||
Expr::BinaryExpression(x) => BinaryPart::BinaryExpression(x),
|
||||
Expr::CallExpression(x) => BinaryPart::CallExpression(x),
|
||||
Expr::MemberExpression(x) => BinaryPart::MemberExpression(x),
|
||||
Expr::IfExpression(x) => BinaryPart::IfExpression(x),
|
||||
};
|
||||
Ok(expr)
|
||||
})
|
||||
@ -670,6 +671,119 @@ fn pipe_sub(i: TokenSlice) -> PResult<PipeSubstitution> {
|
||||
.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
|
||||
// (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),
|
||||
pipe_sub.map(Box::new).map(Expr::PipeSubstitution),
|
||||
function_expression.map(Box::new).map(Expr::FunctionExpression),
|
||||
if_expr.map(Box::new).map(Expr::IfExpression),
|
||||
unnecessarily_bracketed,
|
||||
))
|
||||
.context(expected("a KCL expression (but not a pipe expression)"))
|
||||
@ -3147,6 +3262,42 @@ e
|
||||
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]
|
||||
fn test_keyword_ok_in_fn_args_return() {
|
||||
let some_program_string = r#"fn thing = (param) => {
|
||||
@ -3511,6 +3662,24 @@ const sketch001 = startSketchOn('XY')
|
||||
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)]
|
||||
|
@ -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::{
|
||||
ast::types::{
|
||||
ArrayExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression, Expr, FormatOptions,
|
||||
FunctionExpression, Literal, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, NonCodeValue,
|
||||
ObjectExpression, PipeExpression, Program, TagDeclarator, UnaryExpression, VariableDeclaration,
|
||||
FunctionExpression, IfExpression, Literal, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject,
|
||||
NonCodeValue, ObjectExpression, PipeExpression, Program, TagDeclarator, UnaryExpression, VariableDeclaration,
|
||||
},
|
||||
parser::PIPE_OPERATOR,
|
||||
};
|
||||
@ -121,6 +121,7 @@ impl Expr {
|
||||
Expr::TagDeclarator(tag) => tag.recast(),
|
||||
Expr::PipeExpression(pipe_exp) => pipe_exp.recast(options, indentation_level),
|
||||
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::None(_) => {
|
||||
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::UnaryExpression(unary_expression) => unary_expression.recast(options),
|
||||
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::Identifier(_)
|
||||
| BinaryPart::MemberExpression(_)
|
||||
| BinaryPart::IfExpression(_)
|
||||
| BinaryPart::CallExpression(_) => {
|
||||
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 {
|
||||
fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
|
||||
let pipe = self
|
||||
@ -477,6 +506,38 @@ mod tests {
|
||||
|
||||
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]
|
||||
fn test_recast_bug_fn_in_fn() {
|
||||
let some_program_string = r#"// Start point (top left)
|
||||
|
@ -27,6 +27,7 @@ pub enum Node<'a> {
|
||||
ObjectExpression(&'a types::ObjectExpression),
|
||||
MemberExpression(&'a types::MemberExpression),
|
||||
UnaryExpression(&'a types::UnaryExpression),
|
||||
IfExpression(&'a types::IfExpression),
|
||||
|
||||
Parameter(&'a types::Parameter),
|
||||
|
||||
@ -59,6 +60,7 @@ impl From<&Node<'_>> for SourceRange {
|
||||
Node::Parameter(p) => SourceRange([p.identifier.start(), p.identifier.end()]),
|
||||
Node::ObjectProperty(o) => SourceRange([o.start(), o.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()]),
|
||||
}
|
||||
}
|
||||
@ -94,4 +96,5 @@ impl_from!(Node, UnaryExpression);
|
||||
impl_from!(Node, Parameter);
|
||||
impl_from!(Node, ObjectProperty);
|
||||
impl_from!(Node, MemberObject);
|
||||
impl_from!(Node, IfExpression);
|
||||
impl_from!(Node, LiteralIdentifier);
|
||||
|
@ -2,7 +2,7 @@ use anyhow::Result;
|
||||
|
||||
use crate::{
|
||||
ast::types::{
|
||||
BinaryPart, BodyItem, Expr, LiteralIdentifier, MemberExpression, MemberObject, ObjectExpression,
|
||||
BinaryPart, BodyItem, Expr, IfExpression, LiteralIdentifier, MemberExpression, MemberObject, ObjectExpression,
|
||||
ObjectProperty, Parameter, Program, UnaryExpression, VariableDeclarator,
|
||||
},
|
||||
walk::Node,
|
||||
@ -105,9 +105,11 @@ where
|
||||
BinaryPart::CallExpression(ce) => f.walk(ce.as_ref().into()),
|
||||
BinaryPart::UnaryExpression(ue) => walk_unary_expression(ue, 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>
|
||||
where
|
||||
WalkT: Walker<'a>,
|
||||
@ -184,6 +186,7 @@ where
|
||||
Expr::ObjectExpression(oe) => walk_object_expression(oe, f),
|
||||
Expr::MemberExpression(me) => walk_member_expression(me, f),
|
||||
Expr::UnaryExpression(ue) => walk_unary_expression(ue, f),
|
||||
Expr::IfExpression(e) => walk_if_expression(e, f),
|
||||
Expr::None(_) => Ok(true),
|
||||
}
|
||||
}
|
||||
@ -216,6 +219,33 @@ where
|
||||
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].
|
||||
fn walk_unary_expression<'a, WalkT>(node: &'a UnaryExpression, f: &WalkT) -> Result<bool>
|
||||
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"
|
||||
);
|
||||
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);
|
||||
|
Reference in New Issue
Block a user