KCL refactor: Move execution into its own file (#4174)
types.rs is really, really big. I've moved executing AST nodes into their own file.
This commit is contained in:
@ -12,12 +12,13 @@ use databake::*;
|
|||||||
use parse_display::{Display, FromStr};
|
use parse_display::{Display, FromStr};
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::{Map, Value as JValue};
|
use serde_json::Value as JValue;
|
||||||
use sha2::{Digest as DigestTrait, Sha256};
|
use sha2::{Digest as DigestTrait, Sha256};
|
||||||
use tower_lsp::lsp_types::{
|
use tower_lsp::lsp_types::{
|
||||||
CompletionItem, CompletionItemKind, DocumentSymbol, FoldingRange, FoldingRangeKind, Range as LspRange, SymbolKind,
|
CompletionItem, CompletionItemKind, DocumentSymbol, FoldingRange, FoldingRangeKind, Range as LspRange, SymbolKind,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use self::execute::execute_pipe_body;
|
||||||
pub use crate::ast::types::{
|
pub use crate::ast::types::{
|
||||||
condition::{ElseIf, IfExpression},
|
condition::{ElseIf, IfExpression},
|
||||||
literal_value::LiteralValue,
|
literal_value::LiteralValue,
|
||||||
@ -25,16 +26,14 @@ pub use crate::ast::types::{
|
|||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
docs::StdLibFn,
|
docs::StdLibFn,
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::KclError,
|
||||||
executor::{
|
executor::{ExecState, ExecutorContext, KclValue, Metadata, SourceRange, TagIdentifier, UserVal},
|
||||||
BodyType, ExecState, ExecutorContext, KclValue, Metadata, Sketch, SourceRange, StatementKind, TagEngineInfo,
|
|
||||||
TagIdentifier, UserVal,
|
|
||||||
},
|
|
||||||
parser::PIPE_OPERATOR,
|
parser::PIPE_OPERATOR,
|
||||||
std::{kcl_stdlib::KclStdLibFn, FunctionKind},
|
std::kcl_stdlib::KclStdLibFn,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod condition;
|
mod condition;
|
||||||
|
pub(crate) mod execute;
|
||||||
mod literal_value;
|
mod literal_value;
|
||||||
mod none;
|
mod none;
|
||||||
|
|
||||||
@ -836,22 +835,6 @@ impl BinaryPart {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_recursion::async_recursion]
|
|
||||||
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
|
||||||
match self {
|
|
||||||
BinaryPart::Literal(literal) => Ok(literal.into()),
|
|
||||||
BinaryPart::Identifier(identifier) => {
|
|
||||||
let value = exec_state.memory.get(&identifier.name, identifier.into())?;
|
|
||||||
Ok(value.clone())
|
|
||||||
}
|
|
||||||
BinaryPart::BinaryExpression(binary_expression) => binary_expression.get_result(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::MemberExpression(member_expression) => member_expression.get_result(exec_state),
|
|
||||||
BinaryPart::IfExpression(e) => e.get_result(exec_state, ctx).await,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a hover value that includes the given character position.
|
/// Returns a hover value that includes the given character position.
|
||||||
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> {
|
||||||
match self {
|
match self {
|
||||||
@ -1220,209 +1203,6 @@ impl CallExpression {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_recursion::async_recursion]
|
|
||||||
pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
|
||||||
let fn_name = &self.callee.name;
|
|
||||||
|
|
||||||
let mut fn_args: Vec<KclValue> = Vec::with_capacity(self.arguments.len());
|
|
||||||
|
|
||||||
for arg in &self.arguments {
|
|
||||||
let metadata = Metadata {
|
|
||||||
source_range: SourceRange::from(arg),
|
|
||||||
};
|
|
||||||
let result = ctx
|
|
||||||
.execute_expr(arg, exec_state, &metadata, StatementKind::Expression)
|
|
||||||
.await?;
|
|
||||||
fn_args.push(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
match ctx.stdlib.get_either(&self.callee.name) {
|
|
||||||
FunctionKind::Core(func) => {
|
|
||||||
// Attempt to call the function.
|
|
||||||
let args = crate::std::Args::new(fn_args, self.into(), ctx.clone());
|
|
||||||
let mut result = func.std_lib_fn()(exec_state, args).await?;
|
|
||||||
|
|
||||||
// If the return result is a sketch or solid, we want to update the
|
|
||||||
// memory for the tags of the group.
|
|
||||||
// TODO: This could probably be done in a better way, but as of now this was my only idea
|
|
||||||
// and it works.
|
|
||||||
match result {
|
|
||||||
KclValue::UserVal(ref mut uval) => {
|
|
||||||
uval.mutate(|sketch: &mut Sketch| {
|
|
||||||
for (_, tag) in sketch.tags.iter() {
|
|
||||||
exec_state.memory.update_tag(&tag.value, tag.clone())?;
|
|
||||||
}
|
|
||||||
Ok::<_, KclError>(())
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
KclValue::Solid(ref mut solid) => {
|
|
||||||
for value in &solid.value {
|
|
||||||
if let Some(tag) = value.get_tag() {
|
|
||||||
// Get the past tag and update it.
|
|
||||||
let mut t = if let Some(t) = solid.sketch.tags.get(&tag.name) {
|
|
||||||
t.clone()
|
|
||||||
} else {
|
|
||||||
// It's probably a fillet or a chamfer.
|
|
||||||
// Initialize it.
|
|
||||||
TagIdentifier {
|
|
||||||
value: tag.name.clone(),
|
|
||||||
info: Some(TagEngineInfo {
|
|
||||||
id: value.get_id(),
|
|
||||||
surface: Some(value.clone()),
|
|
||||||
path: None,
|
|
||||||
sketch: solid.id,
|
|
||||||
}),
|
|
||||||
meta: vec![Metadata {
|
|
||||||
source_range: tag.clone().into(),
|
|
||||||
}],
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(ref info) = t.info else {
|
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
|
||||||
message: format!("Tag {} does not have path info", tag.name),
|
|
||||||
source_ranges: vec![tag.into()],
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut info = info.clone();
|
|
||||||
info.surface = Some(value.clone());
|
|
||||||
info.sketch = solid.id;
|
|
||||||
t.info = Some(info);
|
|
||||||
|
|
||||||
exec_state.memory.update_tag(&tag.name, t.clone())?;
|
|
||||||
|
|
||||||
// update the sketch tags.
|
|
||||||
solid.sketch.tags.insert(tag.name.clone(), t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the stale sketch in memory and update it.
|
|
||||||
if let Some(current_env) = exec_state
|
|
||||||
.memory
|
|
||||||
.environments
|
|
||||||
.get_mut(exec_state.memory.current_env.index())
|
|
||||||
{
|
|
||||||
current_env.update_sketch_tags(&solid.sketch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
FunctionKind::Std(func) => {
|
|
||||||
let function_expression = func.function();
|
|
||||||
let (required_params, optional_params) =
|
|
||||||
function_expression.required_and_optional_params().map_err(|e| {
|
|
||||||
KclError::Semantic(KclErrorDetails {
|
|
||||||
message: format!("Error getting parts of function: {}", e),
|
|
||||||
source_ranges: vec![self.into()],
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
if fn_args.len() < required_params.len() || fn_args.len() > function_expression.params.len() {
|
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
|
||||||
message: format!(
|
|
||||||
"this function expected {} arguments, got {}",
|
|
||||||
required_params.len(),
|
|
||||||
fn_args.len(),
|
|
||||||
),
|
|
||||||
source_ranges: vec![self.into()],
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the arguments to the memory.
|
|
||||||
let mut fn_memory = exec_state.memory.clone();
|
|
||||||
for (index, param) in required_params.iter().enumerate() {
|
|
||||||
fn_memory.add(
|
|
||||||
¶m.identifier.name,
|
|
||||||
fn_args.get(index).unwrap().clone(),
|
|
||||||
param.identifier.clone().into(),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
// Add the optional arguments to the memory.
|
|
||||||
for (index, param) in optional_params.iter().enumerate() {
|
|
||||||
if let Some(arg) = fn_args.get(index + required_params.len()) {
|
|
||||||
fn_memory.add(¶m.identifier.name, arg.clone(), param.identifier.clone().into())?;
|
|
||||||
} else {
|
|
||||||
fn_memory.add(
|
|
||||||
¶m.identifier.name,
|
|
||||||
KclValue::UserVal(UserVal {
|
|
||||||
value: serde_json::value::Value::Null,
|
|
||||||
meta: Default::default(),
|
|
||||||
}),
|
|
||||||
param.identifier.clone().into(),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let fn_dynamic_state = exec_state.dynamic_state.clone();
|
|
||||||
// TODO: Shouldn't we merge program memory into fn_dynamic_state
|
|
||||||
// here?
|
|
||||||
|
|
||||||
// Call the stdlib function
|
|
||||||
let p = &func.function().body;
|
|
||||||
|
|
||||||
let (exec_result, fn_memory) = {
|
|
||||||
let previous_memory = std::mem::replace(&mut exec_state.memory, fn_memory);
|
|
||||||
let previous_dynamic_state = std::mem::replace(&mut exec_state.dynamic_state, fn_dynamic_state);
|
|
||||||
let result = ctx.inner_execute(p, exec_state, BodyType::Block).await;
|
|
||||||
exec_state.dynamic_state = previous_dynamic_state;
|
|
||||||
let fn_memory = std::mem::replace(&mut exec_state.memory, previous_memory);
|
|
||||||
(result, fn_memory)
|
|
||||||
};
|
|
||||||
|
|
||||||
match exec_result {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(err) => {
|
|
||||||
// We need to override the source ranges so we don't get the embedded kcl
|
|
||||||
// function from the stdlib.
|
|
||||||
return Err(err.override_source_ranges(vec![self.into()]));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let out = fn_memory.return_;
|
|
||||||
let result = out.ok_or_else(|| {
|
|
||||||
KclError::UndefinedValue(KclErrorDetails {
|
|
||||||
message: format!("Result of stdlib function {} is undefined", fn_name),
|
|
||||||
source_ranges: vec![self.into()],
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
FunctionKind::UserDefined => {
|
|
||||||
let source_range = SourceRange::from(self);
|
|
||||||
// Clone the function so that we can use a mutable reference to
|
|
||||||
// exec_state.
|
|
||||||
let func = exec_state.memory.get(fn_name, source_range)?.clone();
|
|
||||||
let fn_dynamic_state = exec_state.dynamic_state.merge(&exec_state.memory);
|
|
||||||
|
|
||||||
let return_value = {
|
|
||||||
let previous_dynamic_state = std::mem::replace(&mut exec_state.dynamic_state, fn_dynamic_state);
|
|
||||||
let result = func.call_fn(fn_args, exec_state, ctx.clone()).await.map_err(|e| {
|
|
||||||
// Add the call expression to the source ranges.
|
|
||||||
e.add_source_ranges(vec![source_range])
|
|
||||||
});
|
|
||||||
exec_state.dynamic_state = previous_dynamic_state;
|
|
||||||
result?
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = return_value.ok_or_else(move || {
|
|
||||||
let mut source_ranges: Vec<SourceRange> = vec![source_range];
|
|
||||||
// We want to send the source range of the original function.
|
|
||||||
if let KclValue::Function { meta, .. } = func {
|
|
||||||
source_ranges = meta.iter().map(|m| m.source_range).collect();
|
|
||||||
};
|
|
||||||
KclError::UndefinedValue(KclErrorDetails {
|
|
||||||
message: format!("Result of user-defined function {} is undefined", fn_name),
|
|
||||||
source_ranges,
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a hover value that includes the given character position.
|
/// Returns a hover value that includes the given character position.
|
||||||
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> {
|
||||||
let callee_source_range: SourceRange = self.callee.clone().into();
|
let callee_source_range: SourceRange = self.callee.clone().into();
|
||||||
@ -2015,20 +1795,6 @@ impl TagDeclarator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn execute(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
|
|
||||||
let memory_item = KclValue::TagIdentifier(Box::new(TagIdentifier {
|
|
||||||
value: self.name.clone(),
|
|
||||||
info: None,
|
|
||||||
meta: vec![Metadata {
|
|
||||||
source_range: self.into(),
|
|
||||||
}],
|
|
||||||
}));
|
|
||||||
|
|
||||||
exec_state.memory.add(&self.name, memory_item.clone(), self.into())?;
|
|
||||||
|
|
||||||
Ok(self.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_lsp_symbols(&self, code: &str) -> Vec<DocumentSymbol> {
|
pub fn get_lsp_symbols(&self, code: &str) -> Vec<DocumentSymbol> {
|
||||||
let source_range: SourceRange = self.into();
|
let source_range: SourceRange = self.into();
|
||||||
|
|
||||||
@ -2160,29 +1926,6 @@ impl ArrayExpression {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_recursion::async_recursion]
|
|
||||||
pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
|
||||||
let mut results = Vec::with_capacity(self.elements.len());
|
|
||||||
|
|
||||||
for element in &self.elements {
|
|
||||||
let metadata = Metadata::from(element);
|
|
||||||
// TODO: Carry statement kind here so that we know if we're
|
|
||||||
// inside a variable declaration.
|
|
||||||
let value = ctx
|
|
||||||
.execute_expr(element, exec_state, &metadata, StatementKind::Expression)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
results.push(value.get_json_value()?);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(KclValue::UserVal(UserVal {
|
|
||||||
value: results.into(),
|
|
||||||
meta: vec![Metadata {
|
|
||||||
source_range: self.into(),
|
|
||||||
}],
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Rename all identifiers that have the old name to the new given name.
|
/// Rename all identifiers that have the old name to the new given name.
|
||||||
fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
|
fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
|
||||||
for element in &mut self.elements {
|
for element in &mut self.elements {
|
||||||
@ -2256,42 +1999,6 @@ impl ArrayRangeExpression {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_recursion::async_recursion]
|
|
||||||
pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
|
||||||
let metadata = Metadata::from(&*self.start_element);
|
|
||||||
let start = ctx
|
|
||||||
.execute_expr(&self.start_element, exec_state, &metadata, StatementKind::Expression)
|
|
||||||
.await?
|
|
||||||
.get_json_value()?;
|
|
||||||
let start = parse_json_number_as_u64(&start, (&*self.start_element).into())?;
|
|
||||||
let metadata = Metadata::from(&*self.end_element);
|
|
||||||
let end = ctx
|
|
||||||
.execute_expr(&self.end_element, exec_state, &metadata, StatementKind::Expression)
|
|
||||||
.await?
|
|
||||||
.get_json_value()?;
|
|
||||||
let end = parse_json_number_as_u64(&end, (&*self.end_element).into())?;
|
|
||||||
|
|
||||||
if end < start {
|
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
|
||||||
source_ranges: vec![self.into()],
|
|
||||||
message: format!("Range start is greater than range end: {start} .. {end}"),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
let range: Vec<_> = if self.end_inclusive {
|
|
||||||
(start..=end).map(JValue::from).collect()
|
|
||||||
} else {
|
|
||||||
(start..end).map(JValue::from).collect()
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(KclValue::UserVal(UserVal {
|
|
||||||
value: range.into(),
|
|
||||||
meta: vec![Metadata {
|
|
||||||
source_range: self.into(),
|
|
||||||
}],
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Rename all identifiers that have the old name to the new given name.
|
/// Rename all identifiers that have the old name to the new given name.
|
||||||
fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
|
fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
|
||||||
self.start_element.rename_identifiers(old_name, new_name);
|
self.start_element.rename_identifiers(old_name, new_name);
|
||||||
@ -2364,26 +2071,6 @@ impl ObjectExpression {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_recursion::async_recursion]
|
|
||||||
pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
|
||||||
let mut object = Map::new();
|
|
||||||
for property in &self.properties {
|
|
||||||
let metadata = Metadata::from(&property.value);
|
|
||||||
let result = ctx
|
|
||||||
.execute_expr(&property.value, exec_state, &metadata, StatementKind::Expression)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
object.insert(property.key.name.clone(), result.get_json_value()?);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(KclValue::UserVal(UserVal {
|
|
||||||
value: object.into(),
|
|
||||||
meta: vec![Metadata {
|
|
||||||
source_range: self.into(),
|
|
||||||
}],
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Rename all identifiers that have the old name to the new given name.
|
/// Rename all identifiers that have the old name to the new given name.
|
||||||
fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
|
fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
|
||||||
for property in &mut self.properties {
|
for property in &mut self.properties {
|
||||||
@ -2583,194 +2270,6 @@ impl MemberExpression {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_result_array(&self, exec_state: &mut ExecState, index: usize) -> Result<KclValue, KclError> {
|
|
||||||
let array = match &self.object {
|
|
||||||
MemberObject::MemberExpression(member_expr) => member_expr.get_result(exec_state)?,
|
|
||||||
MemberObject::Identifier(identifier) => {
|
|
||||||
let value = exec_state.memory.get(&identifier.name, identifier.into())?;
|
|
||||||
value.clone()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let array_json = array.get_json_value()?;
|
|
||||||
|
|
||||||
if let serde_json::Value::Array(array) = array_json {
|
|
||||||
if let Some(value) = array.get(index) {
|
|
||||||
Ok(KclValue::UserVal(UserVal {
|
|
||||||
value: value.clone(),
|
|
||||||
meta: vec![Metadata {
|
|
||||||
source_range: self.into(),
|
|
||||||
}],
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
Err(KclError::UndefinedValue(KclErrorDetails {
|
|
||||||
message: format!("index {} not found in array", index),
|
|
||||||
source_ranges: vec![self.clone().into()],
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(KclError::Semantic(KclErrorDetails {
|
|
||||||
message: format!("MemberExpression array is not an array: {:?}", array),
|
|
||||||
source_ranges: vec![self.clone().into()],
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_result(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum Property {
|
|
||||||
Number(usize),
|
|
||||||
String(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Property {
|
|
||||||
fn type_name(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
Property::Number(_) => "number",
|
|
||||||
Property::String(_) => "string",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let property_src: SourceRange = self.property.clone().into();
|
|
||||||
let property_sr = vec![property_src];
|
|
||||||
|
|
||||||
let property: Property = match self.property.clone() {
|
|
||||||
LiteralIdentifier::Identifier(identifier) => {
|
|
||||||
let name = identifier.name;
|
|
||||||
if !self.computed {
|
|
||||||
// Treat the property as a literal
|
|
||||||
Property::String(name.to_string())
|
|
||||||
} else {
|
|
||||||
// Actually evaluate memory to compute the property.
|
|
||||||
let prop = exec_state.memory.get(&name, property_src)?;
|
|
||||||
let KclValue::UserVal(prop) = prop else {
|
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
|
||||||
source_ranges: property_sr,
|
|
||||||
message: format!(
|
|
||||||
"{name} is not a valid property/index, you can only use a string or int (>= 0) here",
|
|
||||||
),
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
match prop.value {
|
|
||||||
JValue::Number(ref num) => {
|
|
||||||
num
|
|
||||||
.as_u64()
|
|
||||||
.and_then(|x| usize::try_from(x).ok())
|
|
||||||
.map(Property::Number)
|
|
||||||
.ok_or_else(|| {
|
|
||||||
KclError::Semantic(KclErrorDetails {
|
|
||||||
source_ranges: property_sr,
|
|
||||||
message: format!(
|
|
||||||
"{name}'s value is not a valid property/index, you can only use a string or int (>= 0) here",
|
|
||||||
),
|
|
||||||
})
|
|
||||||
})?
|
|
||||||
}
|
|
||||||
JValue::String(ref x) => Property::String(x.to_owned()),
|
|
||||||
_ => {
|
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
|
||||||
source_ranges: property_sr,
|
|
||||||
message: format!(
|
|
||||||
"{name} is not a valid property/index, you can only use a string to get the property of an object, or an int (>= 0) to get an item in an array",
|
|
||||||
),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LiteralIdentifier::Literal(literal) => {
|
|
||||||
let value = literal.value.clone();
|
|
||||||
match value {
|
|
||||||
LiteralValue::IInteger(x) => {
|
|
||||||
if let Ok(x) = u64::try_from(x) {
|
|
||||||
Property::Number(x.try_into().unwrap())
|
|
||||||
} else {
|
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
|
||||||
source_ranges: property_sr,
|
|
||||||
message: format!("{x} is not a valid index, indices must be whole numbers >= 0"),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LiteralValue::String(s) => Property::String(s),
|
|
||||||
_ => {
|
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
|
||||||
source_ranges: vec![self.into()],
|
|
||||||
message: "Only strings or ints (>= 0) can be properties/indexes".to_owned(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let object = match &self.object {
|
|
||||||
// TODO: Don't use recursion here, use a loop.
|
|
||||||
MemberObject::MemberExpression(member_expr) => member_expr.get_result(exec_state)?,
|
|
||||||
MemberObject::Identifier(identifier) => {
|
|
||||||
let value = exec_state.memory.get(&identifier.name, identifier.into())?;
|
|
||||||
value.clone()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let object_json = object.get_json_value()?;
|
|
||||||
|
|
||||||
// Check the property and object match -- e.g. ints for arrays, strs for objects.
|
|
||||||
match (object_json, property) {
|
|
||||||
(JValue::Object(map), Property::String(property)) => {
|
|
||||||
if let Some(value) = map.get(&property) {
|
|
||||||
Ok(KclValue::UserVal(UserVal {
|
|
||||||
value: value.clone(),
|
|
||||||
meta: vec![Metadata {
|
|
||||||
source_range: self.into(),
|
|
||||||
}],
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
Err(KclError::UndefinedValue(KclErrorDetails {
|
|
||||||
message: format!("Property '{property}' not found in object"),
|
|
||||||
source_ranges: vec![self.clone().into()],
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(JValue::Object(_), p) => Err(KclError::Semantic(KclErrorDetails {
|
|
||||||
message: format!(
|
|
||||||
"Only strings can be used as the property of an object, but you're using a {}",
|
|
||||||
p.type_name()
|
|
||||||
),
|
|
||||||
source_ranges: vec![self.clone().into()],
|
|
||||||
})),
|
|
||||||
(JValue::Array(arr), Property::Number(index)) => {
|
|
||||||
let value_of_arr: Option<&JValue> = arr.get(index);
|
|
||||||
if let Some(value) = value_of_arr {
|
|
||||||
Ok(KclValue::UserVal(UserVal {
|
|
||||||
value: value.clone(),
|
|
||||||
meta: vec![Metadata {
|
|
||||||
source_range: self.into(),
|
|
||||||
}],
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
Err(KclError::UndefinedValue(KclErrorDetails {
|
|
||||||
message: format!("The array doesn't have any item at index {index}"),
|
|
||||||
source_ranges: vec![self.clone().into()],
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(JValue::Array(_), p) => Err(KclError::Semantic(KclErrorDetails {
|
|
||||||
message: format!(
|
|
||||||
"Only integers >= 0 can be used as the index of an array, but you're using a {}",
|
|
||||||
p.type_name()
|
|
||||||
),
|
|
||||||
source_ranges: vec![self.clone().into()],
|
|
||||||
})),
|
|
||||||
(being_indexed, _) => {
|
|
||||||
let t = human_friendly_type(&being_indexed);
|
|
||||||
Err(KclError::Semantic(KclErrorDetails {
|
|
||||||
message: format!("Only arrays and objects can be indexed, but you're trying to index a {t}"),
|
|
||||||
source_ranges: vec![self.clone().into()],
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Rename all identifiers that have the old name to the new given name.
|
/// Rename all identifiers that have the old name to the new given name.
|
||||||
fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
|
fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
|
||||||
match &mut self.object {
|
match &mut self.object {
|
||||||
@ -2864,53 +2363,6 @@ impl BinaryExpression {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_recursion::async_recursion]
|
|
||||||
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
|
||||||
let left_json_value = self.left.get_result(exec_state, ctx).await?.get_json_value()?;
|
|
||||||
let right_json_value = self.right.get_result(exec_state, ctx).await?.get_json_value()?;
|
|
||||||
|
|
||||||
// First check if we are doing string concatenation.
|
|
||||||
if self.operator == BinaryOperator::Add {
|
|
||||||
if let (Some(left), Some(right)) = (
|
|
||||||
parse_json_value_as_string(&left_json_value),
|
|
||||||
parse_json_value_as_string(&right_json_value),
|
|
||||||
) {
|
|
||||||
let value = serde_json::Value::String(format!("{}{}", left, right));
|
|
||||||
return Ok(KclValue::UserVal(UserVal {
|
|
||||||
value,
|
|
||||||
meta: vec![Metadata {
|
|
||||||
source_range: self.into(),
|
|
||||||
}],
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let left = parse_json_number_as_f64(&left_json_value, self.left.clone().into())?;
|
|
||||||
let right = parse_json_number_as_f64(&right_json_value, self.right.clone().into())?;
|
|
||||||
|
|
||||||
let value: serde_json::Value = match self.operator {
|
|
||||||
BinaryOperator::Add => (left + right).into(),
|
|
||||||
BinaryOperator::Sub => (left - right).into(),
|
|
||||||
BinaryOperator::Mul => (left * right).into(),
|
|
||||||
BinaryOperator::Div => (left / right).into(),
|
|
||||||
BinaryOperator::Mod => (left % right).into(),
|
|
||||||
BinaryOperator::Pow => (left.powf(right)).into(),
|
|
||||||
BinaryOperator::Eq => (left == right).into(),
|
|
||||||
BinaryOperator::Neq => (left != right).into(),
|
|
||||||
BinaryOperator::Gt => (left > right).into(),
|
|
||||||
BinaryOperator::Gte => (left >= right).into(),
|
|
||||||
BinaryOperator::Lt => (left < right).into(),
|
|
||||||
BinaryOperator::Lte => (left <= right).into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(KclValue::UserVal(UserVal {
|
|
||||||
value,
|
|
||||||
meta: vec![Metadata {
|
|
||||||
source_range: self.into(),
|
|
||||||
}],
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Rename all identifiers that have the old name to the new given name.
|
/// Rename all identifiers that have the old name to the new given name.
|
||||||
fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
|
fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
|
||||||
self.left.rename_identifiers(old_name, new_name);
|
self.left.rename_identifiers(old_name, new_name);
|
||||||
@ -2918,58 +2370,6 @@ impl BinaryExpression {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_json_number_as_f64(j: &serde_json::Value, source_range: SourceRange) -> Result<f64, KclError> {
|
|
||||||
if let serde_json::Value::Number(n) = &j {
|
|
||||||
n.as_f64().ok_or_else(|| {
|
|
||||||
KclError::Syntax(KclErrorDetails {
|
|
||||||
source_ranges: vec![source_range],
|
|
||||||
message: format!("Invalid number: {}", j),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Err(KclError::Syntax(KclErrorDetails {
|
|
||||||
source_ranges: vec![source_range],
|
|
||||||
message: format!("Invalid number: {}", j),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_json_number_as_u64(j: &serde_json::Value, source_range: SourceRange) -> Result<u64, KclError> {
|
|
||||||
if let serde_json::Value::Number(n) = &j {
|
|
||||||
n.as_u64().ok_or_else(|| {
|
|
||||||
KclError::Syntax(KclErrorDetails {
|
|
||||||
source_ranges: vec![source_range],
|
|
||||||
message: format!("Invalid integer: {}", j),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Err(KclError::Syntax(KclErrorDetails {
|
|
||||||
source_ranges: vec![source_range],
|
|
||||||
message: format!("Invalid integer: {}", j),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_json_value_as_string(j: &serde_json::Value) -> Option<String> {
|
|
||||||
if let serde_json::Value::String(n) = &j {
|
|
||||||
Some(n.clone())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// JSON value as bool. If it isn't a bool, returns None.
|
|
||||||
pub fn json_as_bool(j: &serde_json::Value) -> Option<bool> {
|
|
||||||
match j {
|
|
||||||
JValue::Null => None,
|
|
||||||
JValue::Bool(b) => Some(*b),
|
|
||||||
JValue::Number(_) => None,
|
|
||||||
JValue::String(_) => None,
|
|
||||||
JValue::Array(_) => None,
|
|
||||||
JValue::Object(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, FromStr, Display, Bake)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, FromStr, Display, Bake)]
|
||||||
#[databake(path = kcl_lib::ast::types)]
|
#[databake(path = kcl_lib::ast::types)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
@ -3122,36 +2522,6 @@ impl UnaryExpression {
|
|||||||
self.argument.get_constraint_level()
|
self.argument.get_constraint_level()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
|
||||||
if self.operator == UnaryOperator::Not {
|
|
||||||
let value = self.argument.get_result(exec_state, ctx).await?.get_json_value()?;
|
|
||||||
let Some(bool_value) = json_as_bool(&value) else {
|
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
|
||||||
message: format!("Cannot apply unary operator ! to non-boolean value: {}", value),
|
|
||||||
source_ranges: vec![self.into()],
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
let negated = !bool_value;
|
|
||||||
return Ok(KclValue::UserVal(UserVal {
|
|
||||||
value: serde_json::Value::Bool(negated),
|
|
||||||
meta: vec![Metadata {
|
|
||||||
source_range: self.into(),
|
|
||||||
}],
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
let num = parse_json_number_as_f64(
|
|
||||||
&self.argument.get_result(exec_state, ctx).await?.get_json_value()?,
|
|
||||||
self.into(),
|
|
||||||
)?;
|
|
||||||
Ok(KclValue::UserVal(UserVal {
|
|
||||||
value: (-(num)).into(),
|
|
||||||
meta: vec![Metadata {
|
|
||||||
source_range: self.into(),
|
|
||||||
}],
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a hover value that includes the given character position.
|
/// Returns a hover value that includes the given character position.
|
||||||
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> {
|
||||||
let argument_source_range: SourceRange = self.argument.clone().into();
|
let argument_source_range: SourceRange = self.argument.clone().into();
|
||||||
@ -3282,85 +2652,6 @@ impl PipeExpression {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute_pipe_body(
|
|
||||||
exec_state: &mut ExecState,
|
|
||||||
body: &[Expr],
|
|
||||||
source_range: SourceRange,
|
|
||||||
ctx: &ExecutorContext,
|
|
||||||
) -> Result<KclValue, KclError> {
|
|
||||||
let Some((first, body)) = body.split_first() else {
|
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
|
||||||
message: "Pipe expressions cannot be empty".to_owned(),
|
|
||||||
source_ranges: vec![source_range],
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
// Evaluate the first element in the pipeline.
|
|
||||||
// They use the pipe_value from some AST node above this, so that if pipe expression is nested in a larger pipe expression,
|
|
||||||
// they use the % from the parent. After all, this pipe expression hasn't been executed yet, so it doesn't have any % value
|
|
||||||
// of its own.
|
|
||||||
let meta = Metadata {
|
|
||||||
source_range: SourceRange([first.start(), first.end()]),
|
|
||||||
};
|
|
||||||
let output = ctx
|
|
||||||
.execute_expr(first, exec_state, &meta, StatementKind::Expression)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Now that we've evaluated the first child expression in the pipeline, following child expressions
|
|
||||||
// should use the previous child expression for %.
|
|
||||||
// This means there's no more need for the previous pipe_value from the parent AST node above this one.
|
|
||||||
let previous_pipe_value = std::mem::replace(&mut exec_state.pipe_value, Some(output));
|
|
||||||
// Evaluate remaining elements.
|
|
||||||
let result = inner_execute_pipe_body(exec_state, body, ctx).await;
|
|
||||||
// Restore the previous pipe value.
|
|
||||||
exec_state.pipe_value = previous_pipe_value;
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Execute the tail of a pipe expression. exec_state.pipe_value must be set by
|
|
||||||
/// the caller.
|
|
||||||
#[async_recursion]
|
|
||||||
async fn inner_execute_pipe_body(
|
|
||||||
exec_state: &mut ExecState,
|
|
||||||
body: &[Expr],
|
|
||||||
ctx: &ExecutorContext,
|
|
||||||
) -> Result<KclValue, KclError> {
|
|
||||||
for expression in body {
|
|
||||||
match expression {
|
|
||||||
Expr::TagDeclarator(_) => {
|
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
|
||||||
message: format!("This cannot be in a PipeExpression: {:?}", expression),
|
|
||||||
source_ranges: vec![expression.into()],
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
Expr::Literal(_)
|
|
||||||
| Expr::Identifier(_)
|
|
||||||
| Expr::BinaryExpression(_)
|
|
||||||
| Expr::FunctionExpression(_)
|
|
||||||
| Expr::CallExpression(_)
|
|
||||||
| Expr::PipeExpression(_)
|
|
||||||
| Expr::PipeSubstitution(_)
|
|
||||||
| Expr::ArrayExpression(_)
|
|
||||||
| Expr::ArrayRangeExpression(_)
|
|
||||||
| Expr::ObjectExpression(_)
|
|
||||||
| Expr::MemberExpression(_)
|
|
||||||
| Expr::UnaryExpression(_)
|
|
||||||
| Expr::IfExpression(_)
|
|
||||||
| Expr::None(_) => {}
|
|
||||||
};
|
|
||||||
let metadata = Metadata {
|
|
||||||
source_range: SourceRange([expression.start(), expression.end()]),
|
|
||||||
};
|
|
||||||
let output = ctx
|
|
||||||
.execute_expr(expression, exec_state, &metadata, StatementKind::Expression)
|
|
||||||
.await?;
|
|
||||||
exec_state.pipe_value = Some(output);
|
|
||||||
}
|
|
||||||
// Safe to unwrap here, because pipe_value always has something pushed in when the `match first` executes.
|
|
||||||
let final_output = exec_state.pipe_value.take().unwrap();
|
|
||||||
Ok(final_output)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, Bake, FromStr, Display)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, Bake, FromStr, Display)]
|
||||||
#[databake(path = kcl_lib::ast::types)]
|
#[databake(path = kcl_lib::ast::types)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
|
|||||||
740
src/wasm-lib/kcl/src/ast/types/execute.rs
Normal file
740
src/wasm-lib/kcl/src/ast/types/execute.rs
Normal file
@ -0,0 +1,740 @@
|
|||||||
|
use super::{
|
||||||
|
human_friendly_type, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart,
|
||||||
|
CallExpression, Expr, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, ObjectExpression,
|
||||||
|
TagDeclarator, UnaryExpression, UnaryOperator,
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
errors::{KclError, KclErrorDetails},
|
||||||
|
executor::{
|
||||||
|
BodyType, ExecState, ExecutorContext, KclValue, Metadata, Sketch, SourceRange, StatementKind, TagEngineInfo,
|
||||||
|
TagIdentifier, UserVal,
|
||||||
|
},
|
||||||
|
std::FunctionKind,
|
||||||
|
};
|
||||||
|
use async_recursion::async_recursion;
|
||||||
|
use serde_json::Value as JValue;
|
||||||
|
|
||||||
|
impl BinaryPart {
|
||||||
|
#[async_recursion]
|
||||||
|
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
||||||
|
match self {
|
||||||
|
BinaryPart::Literal(literal) => Ok(literal.into()),
|
||||||
|
BinaryPart::Identifier(identifier) => {
|
||||||
|
let value = exec_state.memory.get(&identifier.name, identifier.into())?;
|
||||||
|
Ok(value.clone())
|
||||||
|
}
|
||||||
|
BinaryPart::BinaryExpression(binary_expression) => binary_expression.get_result(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::MemberExpression(member_expression) => member_expression.get_result(exec_state),
|
||||||
|
BinaryPart::IfExpression(e) => e.get_result(exec_state, ctx).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MemberExpression {
|
||||||
|
pub fn get_result_array(&self, exec_state: &mut ExecState, index: usize) -> Result<KclValue, KclError> {
|
||||||
|
let array = match &self.object {
|
||||||
|
MemberObject::MemberExpression(member_expr) => member_expr.get_result(exec_state)?,
|
||||||
|
MemberObject::Identifier(identifier) => {
|
||||||
|
let value = exec_state.memory.get(&identifier.name, identifier.into())?;
|
||||||
|
value.clone()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let array_json = array.get_json_value()?;
|
||||||
|
|
||||||
|
if let serde_json::Value::Array(array) = array_json {
|
||||||
|
if let Some(value) = array.get(index) {
|
||||||
|
Ok(KclValue::UserVal(UserVal {
|
||||||
|
value: value.clone(),
|
||||||
|
meta: vec![Metadata {
|
||||||
|
source_range: self.into(),
|
||||||
|
}],
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Err(KclError::UndefinedValue(KclErrorDetails {
|
||||||
|
message: format!("index {} not found in array", index),
|
||||||
|
source_ranges: vec![self.clone().into()],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message: format!("MemberExpression array is not an array: {:?}", array),
|
||||||
|
source_ranges: vec![self.clone().into()],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn get_result(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Property {
|
||||||
|
Number(usize),
|
||||||
|
String(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Property {
|
||||||
|
fn type_name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Property::Number(_) => "number",
|
||||||
|
Property::String(_) => "string",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let property_src: SourceRange = self.property.clone().into();
|
||||||
|
let property_sr = vec![property_src];
|
||||||
|
|
||||||
|
let property: Property = match self.property.clone() {
|
||||||
|
LiteralIdentifier::Identifier(identifier) => {
|
||||||
|
let name = identifier.name;
|
||||||
|
if !self.computed {
|
||||||
|
// Treat the property as a literal
|
||||||
|
Property::String(name.to_string())
|
||||||
|
} else {
|
||||||
|
// Actually evaluate memory to compute the property.
|
||||||
|
let prop = exec_state.memory.get(&name, property_src)?;
|
||||||
|
let KclValue::UserVal(prop) = prop else {
|
||||||
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
source_ranges: property_sr,
|
||||||
|
message: format!(
|
||||||
|
"{name} is not a valid property/index, you can only use a string or int (>= 0) here",
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
match prop.value {
|
||||||
|
JValue::Number(ref num) => {
|
||||||
|
num
|
||||||
|
.as_u64()
|
||||||
|
.and_then(|x| usize::try_from(x).ok())
|
||||||
|
.map(Property::Number)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
KclError::Semantic(KclErrorDetails {
|
||||||
|
source_ranges: property_sr,
|
||||||
|
message: format!(
|
||||||
|
"{name}'s value is not a valid property/index, you can only use a string or int (>= 0) here",
|
||||||
|
),
|
||||||
|
})
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
JValue::String(ref x) => Property::String(x.to_owned()),
|
||||||
|
_ => {
|
||||||
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
source_ranges: property_sr,
|
||||||
|
message: format!(
|
||||||
|
"{name} is not a valid property/index, you can only use a string to get the property of an object, or an int (>= 0) to get an item in an array",
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LiteralIdentifier::Literal(literal) => {
|
||||||
|
let value = literal.value.clone();
|
||||||
|
match value {
|
||||||
|
LiteralValue::IInteger(x) => {
|
||||||
|
if let Ok(x) = u64::try_from(x) {
|
||||||
|
Property::Number(x.try_into().unwrap())
|
||||||
|
} else {
|
||||||
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
source_ranges: property_sr,
|
||||||
|
message: format!("{x} is not a valid index, indices must be whole numbers >= 0"),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LiteralValue::String(s) => Property::String(s),
|
||||||
|
_ => {
|
||||||
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
source_ranges: vec![self.into()],
|
||||||
|
message: "Only strings or ints (>= 0) can be properties/indexes".to_owned(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let object = match &self.object {
|
||||||
|
// TODO: Don't use recursion here, use a loop.
|
||||||
|
MemberObject::MemberExpression(member_expr) => member_expr.get_result(exec_state)?,
|
||||||
|
MemberObject::Identifier(identifier) => {
|
||||||
|
let value = exec_state.memory.get(&identifier.name, identifier.into())?;
|
||||||
|
value.clone()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let object_json = object.get_json_value()?;
|
||||||
|
|
||||||
|
// Check the property and object match -- e.g. ints for arrays, strs for objects.
|
||||||
|
match (object_json, property) {
|
||||||
|
(JValue::Object(map), Property::String(property)) => {
|
||||||
|
if let Some(value) = map.get(&property) {
|
||||||
|
Ok(KclValue::UserVal(UserVal {
|
||||||
|
value: value.clone(),
|
||||||
|
meta: vec![Metadata {
|
||||||
|
source_range: self.into(),
|
||||||
|
}],
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Err(KclError::UndefinedValue(KclErrorDetails {
|
||||||
|
message: format!("Property '{property}' not found in object"),
|
||||||
|
source_ranges: vec![self.clone().into()],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(JValue::Object(_), p) => Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message: format!(
|
||||||
|
"Only strings can be used as the property of an object, but you're using a {}",
|
||||||
|
p.type_name()
|
||||||
|
),
|
||||||
|
source_ranges: vec![self.clone().into()],
|
||||||
|
})),
|
||||||
|
(JValue::Array(arr), Property::Number(index)) => {
|
||||||
|
let value_of_arr: Option<&JValue> = arr.get(index);
|
||||||
|
if let Some(value) = value_of_arr {
|
||||||
|
Ok(KclValue::UserVal(UserVal {
|
||||||
|
value: value.clone(),
|
||||||
|
meta: vec![Metadata {
|
||||||
|
source_range: self.into(),
|
||||||
|
}],
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Err(KclError::UndefinedValue(KclErrorDetails {
|
||||||
|
message: format!("The array doesn't have any item at index {index}"),
|
||||||
|
source_ranges: vec![self.clone().into()],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(JValue::Array(_), p) => Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message: format!(
|
||||||
|
"Only integers >= 0 can be used as the index of an array, but you're using a {}",
|
||||||
|
p.type_name()
|
||||||
|
),
|
||||||
|
source_ranges: vec![self.clone().into()],
|
||||||
|
})),
|
||||||
|
(being_indexed, _) => {
|
||||||
|
let t = human_friendly_type(&being_indexed);
|
||||||
|
Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message: format!("Only arrays and objects can be indexed, but you're trying to index a {t}"),
|
||||||
|
source_ranges: vec![self.clone().into()],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BinaryExpression {
|
||||||
|
#[async_recursion]
|
||||||
|
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
||||||
|
let left_json_value = self.left.get_result(exec_state, ctx).await?.get_json_value()?;
|
||||||
|
let right_json_value = self.right.get_result(exec_state, ctx).await?.get_json_value()?;
|
||||||
|
|
||||||
|
// First check if we are doing string concatenation.
|
||||||
|
if self.operator == BinaryOperator::Add {
|
||||||
|
if let (Some(left), Some(right)) = (
|
||||||
|
parse_json_value_as_string(&left_json_value),
|
||||||
|
parse_json_value_as_string(&right_json_value),
|
||||||
|
) {
|
||||||
|
let value = serde_json::Value::String(format!("{}{}", left, right));
|
||||||
|
return Ok(KclValue::UserVal(UserVal {
|
||||||
|
value,
|
||||||
|
meta: vec![Metadata {
|
||||||
|
source_range: self.into(),
|
||||||
|
}],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let left = parse_json_number_as_f64(&left_json_value, self.left.clone().into())?;
|
||||||
|
let right = parse_json_number_as_f64(&right_json_value, self.right.clone().into())?;
|
||||||
|
|
||||||
|
let value: serde_json::Value = match self.operator {
|
||||||
|
BinaryOperator::Add => (left + right).into(),
|
||||||
|
BinaryOperator::Sub => (left - right).into(),
|
||||||
|
BinaryOperator::Mul => (left * right).into(),
|
||||||
|
BinaryOperator::Div => (left / right).into(),
|
||||||
|
BinaryOperator::Mod => (left % right).into(),
|
||||||
|
BinaryOperator::Pow => (left.powf(right)).into(),
|
||||||
|
BinaryOperator::Eq => (left == right).into(),
|
||||||
|
BinaryOperator::Neq => (left != right).into(),
|
||||||
|
BinaryOperator::Gt => (left > right).into(),
|
||||||
|
BinaryOperator::Gte => (left >= right).into(),
|
||||||
|
BinaryOperator::Lt => (left < right).into(),
|
||||||
|
BinaryOperator::Lte => (left <= right).into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(KclValue::UserVal(UserVal {
|
||||||
|
value,
|
||||||
|
meta: vec![Metadata {
|
||||||
|
source_range: self.into(),
|
||||||
|
}],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UnaryExpression {
|
||||||
|
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
||||||
|
if self.operator == UnaryOperator::Not {
|
||||||
|
let value = self.argument.get_result(exec_state, ctx).await?.get_json_value()?;
|
||||||
|
let Some(bool_value) = json_as_bool(&value) else {
|
||||||
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message: format!("Cannot apply unary operator ! to non-boolean value: {}", value),
|
||||||
|
source_ranges: vec![self.into()],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
let negated = !bool_value;
|
||||||
|
return Ok(KclValue::UserVal(UserVal {
|
||||||
|
value: serde_json::Value::Bool(negated),
|
||||||
|
meta: vec![Metadata {
|
||||||
|
source_range: self.into(),
|
||||||
|
}],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let num = parse_json_number_as_f64(
|
||||||
|
&self.argument.get_result(exec_state, ctx).await?.get_json_value()?,
|
||||||
|
self.into(),
|
||||||
|
)?;
|
||||||
|
Ok(KclValue::UserVal(UserVal {
|
||||||
|
value: (-(num)).into(),
|
||||||
|
meta: vec![Metadata {
|
||||||
|
source_range: self.into(),
|
||||||
|
}],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn execute_pipe_body(
|
||||||
|
exec_state: &mut ExecState,
|
||||||
|
body: &[Expr],
|
||||||
|
source_range: SourceRange,
|
||||||
|
ctx: &ExecutorContext,
|
||||||
|
) -> Result<KclValue, KclError> {
|
||||||
|
let Some((first, body)) = body.split_first() else {
|
||||||
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message: "Pipe expressions cannot be empty".to_owned(),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
// Evaluate the first element in the pipeline.
|
||||||
|
// They use the pipe_value from some AST node above this, so that if pipe expression is nested in a larger pipe expression,
|
||||||
|
// they use the % from the parent. After all, this pipe expression hasn't been executed yet, so it doesn't have any % value
|
||||||
|
// of its own.
|
||||||
|
let meta = Metadata {
|
||||||
|
source_range: SourceRange([first.start(), first.end()]),
|
||||||
|
};
|
||||||
|
let output = ctx
|
||||||
|
.execute_expr(first, exec_state, &meta, StatementKind::Expression)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Now that we've evaluated the first child expression in the pipeline, following child expressions
|
||||||
|
// should use the previous child expression for %.
|
||||||
|
// This means there's no more need for the previous pipe_value from the parent AST node above this one.
|
||||||
|
let previous_pipe_value = std::mem::replace(&mut exec_state.pipe_value, Some(output));
|
||||||
|
// Evaluate remaining elements.
|
||||||
|
let result = inner_execute_pipe_body(exec_state, body, ctx).await;
|
||||||
|
// Restore the previous pipe value.
|
||||||
|
exec_state.pipe_value = previous_pipe_value;
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute the tail of a pipe expression. exec_state.pipe_value must be set by
|
||||||
|
/// the caller.
|
||||||
|
#[async_recursion]
|
||||||
|
async fn inner_execute_pipe_body(
|
||||||
|
exec_state: &mut ExecState,
|
||||||
|
body: &[Expr],
|
||||||
|
ctx: &ExecutorContext,
|
||||||
|
) -> Result<KclValue, KclError> {
|
||||||
|
for expression in body {
|
||||||
|
match expression {
|
||||||
|
Expr::TagDeclarator(_) => {
|
||||||
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message: format!("This cannot be in a PipeExpression: {:?}", expression),
|
||||||
|
source_ranges: vec![expression.into()],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
Expr::Literal(_)
|
||||||
|
| Expr::Identifier(_)
|
||||||
|
| Expr::BinaryExpression(_)
|
||||||
|
| Expr::FunctionExpression(_)
|
||||||
|
| Expr::CallExpression(_)
|
||||||
|
| Expr::PipeExpression(_)
|
||||||
|
| Expr::PipeSubstitution(_)
|
||||||
|
| Expr::ArrayExpression(_)
|
||||||
|
| Expr::ArrayRangeExpression(_)
|
||||||
|
| Expr::ObjectExpression(_)
|
||||||
|
| Expr::MemberExpression(_)
|
||||||
|
| Expr::UnaryExpression(_)
|
||||||
|
| Expr::IfExpression(_)
|
||||||
|
| Expr::None(_) => {}
|
||||||
|
};
|
||||||
|
let metadata = Metadata {
|
||||||
|
source_range: SourceRange([expression.start(), expression.end()]),
|
||||||
|
};
|
||||||
|
let output = ctx
|
||||||
|
.execute_expr(expression, exec_state, &metadata, StatementKind::Expression)
|
||||||
|
.await?;
|
||||||
|
exec_state.pipe_value = Some(output);
|
||||||
|
}
|
||||||
|
// Safe to unwrap here, because pipe_value always has something pushed in when the `match first` executes.
|
||||||
|
let final_output = exec_state.pipe_value.take().unwrap();
|
||||||
|
Ok(final_output)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CallExpression {
|
||||||
|
#[async_recursion]
|
||||||
|
pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
||||||
|
let fn_name = &self.callee.name;
|
||||||
|
|
||||||
|
let mut fn_args: Vec<KclValue> = Vec::with_capacity(self.arguments.len());
|
||||||
|
|
||||||
|
for arg in &self.arguments {
|
||||||
|
let metadata = Metadata {
|
||||||
|
source_range: SourceRange::from(arg),
|
||||||
|
};
|
||||||
|
let result = ctx
|
||||||
|
.execute_expr(arg, exec_state, &metadata, StatementKind::Expression)
|
||||||
|
.await?;
|
||||||
|
fn_args.push(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
match ctx.stdlib.get_either(&self.callee.name) {
|
||||||
|
FunctionKind::Core(func) => {
|
||||||
|
// Attempt to call the function.
|
||||||
|
let args = crate::std::Args::new(fn_args, self.into(), ctx.clone());
|
||||||
|
let mut result = func.std_lib_fn()(exec_state, args).await?;
|
||||||
|
|
||||||
|
// If the return result is a sketch or solid, we want to update the
|
||||||
|
// memory for the tags of the group.
|
||||||
|
// TODO: This could probably be done in a better way, but as of now this was my only idea
|
||||||
|
// and it works.
|
||||||
|
match result {
|
||||||
|
KclValue::UserVal(ref mut uval) => {
|
||||||
|
uval.mutate(|sketch: &mut Sketch| {
|
||||||
|
for (_, tag) in sketch.tags.iter() {
|
||||||
|
exec_state.memory.update_tag(&tag.value, tag.clone())?;
|
||||||
|
}
|
||||||
|
Ok::<_, KclError>(())
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
KclValue::Solid(ref mut solid) => {
|
||||||
|
for value in &solid.value {
|
||||||
|
if let Some(tag) = value.get_tag() {
|
||||||
|
// Get the past tag and update it.
|
||||||
|
let mut t = if let Some(t) = solid.sketch.tags.get(&tag.name) {
|
||||||
|
t.clone()
|
||||||
|
} else {
|
||||||
|
// It's probably a fillet or a chamfer.
|
||||||
|
// Initialize it.
|
||||||
|
TagIdentifier {
|
||||||
|
value: tag.name.clone(),
|
||||||
|
info: Some(TagEngineInfo {
|
||||||
|
id: value.get_id(),
|
||||||
|
surface: Some(value.clone()),
|
||||||
|
path: None,
|
||||||
|
sketch: solid.id,
|
||||||
|
}),
|
||||||
|
meta: vec![Metadata {
|
||||||
|
source_range: tag.clone().into(),
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(ref info) = t.info else {
|
||||||
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message: format!("Tag {} does not have path info", tag.name),
|
||||||
|
source_ranges: vec![tag.into()],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut info = info.clone();
|
||||||
|
info.surface = Some(value.clone());
|
||||||
|
info.sketch = solid.id;
|
||||||
|
t.info = Some(info);
|
||||||
|
|
||||||
|
exec_state.memory.update_tag(&tag.name, t.clone())?;
|
||||||
|
|
||||||
|
// update the sketch tags.
|
||||||
|
solid.sketch.tags.insert(tag.name.clone(), t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the stale sketch in memory and update it.
|
||||||
|
if let Some(current_env) = exec_state
|
||||||
|
.memory
|
||||||
|
.environments
|
||||||
|
.get_mut(exec_state.memory.current_env.index())
|
||||||
|
{
|
||||||
|
current_env.update_sketch_tags(&solid.sketch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
FunctionKind::Std(func) => {
|
||||||
|
let function_expression = func.function();
|
||||||
|
let (required_params, optional_params) =
|
||||||
|
function_expression.required_and_optional_params().map_err(|e| {
|
||||||
|
KclError::Semantic(KclErrorDetails {
|
||||||
|
message: format!("Error getting parts of function: {}", e),
|
||||||
|
source_ranges: vec![self.into()],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
if fn_args.len() < required_params.len() || fn_args.len() > function_expression.params.len() {
|
||||||
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message: format!(
|
||||||
|
"this function expected {} arguments, got {}",
|
||||||
|
required_params.len(),
|
||||||
|
fn_args.len(),
|
||||||
|
),
|
||||||
|
source_ranges: vec![self.into()],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the arguments to the memory.
|
||||||
|
let mut fn_memory = exec_state.memory.clone();
|
||||||
|
for (index, param) in required_params.iter().enumerate() {
|
||||||
|
fn_memory.add(
|
||||||
|
¶m.identifier.name,
|
||||||
|
fn_args.get(index).unwrap().clone(),
|
||||||
|
param.identifier.clone().into(),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
// Add the optional arguments to the memory.
|
||||||
|
for (index, param) in optional_params.iter().enumerate() {
|
||||||
|
if let Some(arg) = fn_args.get(index + required_params.len()) {
|
||||||
|
fn_memory.add(¶m.identifier.name, arg.clone(), param.identifier.clone().into())?;
|
||||||
|
} else {
|
||||||
|
fn_memory.add(
|
||||||
|
¶m.identifier.name,
|
||||||
|
KclValue::UserVal(UserVal {
|
||||||
|
value: serde_json::value::Value::Null,
|
||||||
|
meta: Default::default(),
|
||||||
|
}),
|
||||||
|
param.identifier.clone().into(),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let fn_dynamic_state = exec_state.dynamic_state.clone();
|
||||||
|
// TODO: Shouldn't we merge program memory into fn_dynamic_state
|
||||||
|
// here?
|
||||||
|
|
||||||
|
// Call the stdlib function
|
||||||
|
let p = &func.function().body;
|
||||||
|
|
||||||
|
let (exec_result, fn_memory) = {
|
||||||
|
let previous_memory = std::mem::replace(&mut exec_state.memory, fn_memory);
|
||||||
|
let previous_dynamic_state = std::mem::replace(&mut exec_state.dynamic_state, fn_dynamic_state);
|
||||||
|
let result = ctx.inner_execute(p, exec_state, BodyType::Block).await;
|
||||||
|
exec_state.dynamic_state = previous_dynamic_state;
|
||||||
|
let fn_memory = std::mem::replace(&mut exec_state.memory, previous_memory);
|
||||||
|
(result, fn_memory)
|
||||||
|
};
|
||||||
|
|
||||||
|
match exec_result {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(err) => {
|
||||||
|
// We need to override the source ranges so we don't get the embedded kcl
|
||||||
|
// function from the stdlib.
|
||||||
|
return Err(err.override_source_ranges(vec![self.into()]));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let out = fn_memory.return_;
|
||||||
|
let result = out.ok_or_else(|| {
|
||||||
|
KclError::UndefinedValue(KclErrorDetails {
|
||||||
|
message: format!("Result of stdlib function {} is undefined", fn_name),
|
||||||
|
source_ranges: vec![self.into()],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
FunctionKind::UserDefined => {
|
||||||
|
let source_range = SourceRange::from(self);
|
||||||
|
// Clone the function so that we can use a mutable reference to
|
||||||
|
// exec_state.
|
||||||
|
let func = exec_state.memory.get(fn_name, source_range)?.clone();
|
||||||
|
let fn_dynamic_state = exec_state.dynamic_state.merge(&exec_state.memory);
|
||||||
|
|
||||||
|
let return_value = {
|
||||||
|
let previous_dynamic_state = std::mem::replace(&mut exec_state.dynamic_state, fn_dynamic_state);
|
||||||
|
let result = func.call_fn(fn_args, exec_state, ctx.clone()).await.map_err(|e| {
|
||||||
|
// Add the call expression to the source ranges.
|
||||||
|
e.add_source_ranges(vec![source_range])
|
||||||
|
});
|
||||||
|
exec_state.dynamic_state = previous_dynamic_state;
|
||||||
|
result?
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = return_value.ok_or_else(move || {
|
||||||
|
let mut source_ranges: Vec<SourceRange> = vec![source_range];
|
||||||
|
// We want to send the source range of the original function.
|
||||||
|
if let KclValue::Function { meta, .. } = func {
|
||||||
|
source_ranges = meta.iter().map(|m| m.source_range).collect();
|
||||||
|
};
|
||||||
|
KclError::UndefinedValue(KclErrorDetails {
|
||||||
|
message: format!("Result of user-defined function {} is undefined", fn_name),
|
||||||
|
source_ranges,
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TagDeclarator {
|
||||||
|
pub async fn execute(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
|
||||||
|
let memory_item = KclValue::TagIdentifier(Box::new(TagIdentifier {
|
||||||
|
value: self.name.clone(),
|
||||||
|
info: None,
|
||||||
|
meta: vec![Metadata {
|
||||||
|
source_range: self.into(),
|
||||||
|
}],
|
||||||
|
}));
|
||||||
|
|
||||||
|
exec_state.memory.add(&self.name, memory_item.clone(), self.into())?;
|
||||||
|
|
||||||
|
Ok(self.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArrayExpression {
|
||||||
|
#[async_recursion]
|
||||||
|
pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
||||||
|
let mut results = Vec::with_capacity(self.elements.len());
|
||||||
|
|
||||||
|
for element in &self.elements {
|
||||||
|
let metadata = Metadata::from(element);
|
||||||
|
// TODO: Carry statement kind here so that we know if we're
|
||||||
|
// inside a variable declaration.
|
||||||
|
let value = ctx
|
||||||
|
.execute_expr(element, exec_state, &metadata, StatementKind::Expression)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
results.push(value.get_json_value()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(KclValue::UserVal(UserVal {
|
||||||
|
value: results.into(),
|
||||||
|
meta: vec![Metadata {
|
||||||
|
source_range: self.into(),
|
||||||
|
}],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArrayRangeExpression {
|
||||||
|
#[async_recursion]
|
||||||
|
pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
||||||
|
let metadata = Metadata::from(&*self.start_element);
|
||||||
|
let start = ctx
|
||||||
|
.execute_expr(&self.start_element, exec_state, &metadata, StatementKind::Expression)
|
||||||
|
.await?
|
||||||
|
.get_json_value()?;
|
||||||
|
let start = parse_json_number_as_u64(&start, (&*self.start_element).into())?;
|
||||||
|
let metadata = Metadata::from(&*self.end_element);
|
||||||
|
let end = ctx
|
||||||
|
.execute_expr(&self.end_element, exec_state, &metadata, StatementKind::Expression)
|
||||||
|
.await?
|
||||||
|
.get_json_value()?;
|
||||||
|
let end = parse_json_number_as_u64(&end, (&*self.end_element).into())?;
|
||||||
|
|
||||||
|
if end < start {
|
||||||
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
source_ranges: vec![self.into()],
|
||||||
|
message: format!("Range start is greater than range end: {start} .. {end}"),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let range: Vec<_> = if self.end_inclusive {
|
||||||
|
(start..=end).map(JValue::from).collect()
|
||||||
|
} else {
|
||||||
|
(start..end).map(JValue::from).collect()
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(KclValue::UserVal(UserVal {
|
||||||
|
value: range.into(),
|
||||||
|
meta: vec![Metadata {
|
||||||
|
source_range: self.into(),
|
||||||
|
}],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectExpression {
|
||||||
|
#[async_recursion]
|
||||||
|
pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
||||||
|
let mut object = serde_json::Map::new();
|
||||||
|
for property in &self.properties {
|
||||||
|
let metadata = Metadata::from(&property.value);
|
||||||
|
let result = ctx
|
||||||
|
.execute_expr(&property.value, exec_state, &metadata, StatementKind::Expression)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
object.insert(property.key.name.clone(), result.get_json_value()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(KclValue::UserVal(UserVal {
|
||||||
|
value: object.into(),
|
||||||
|
meta: vec![Metadata {
|
||||||
|
source_range: self.into(),
|
||||||
|
}],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_json_number_as_u64(j: &serde_json::Value, source_range: SourceRange) -> Result<u64, KclError> {
|
||||||
|
if let serde_json::Value::Number(n) = &j {
|
||||||
|
n.as_u64().ok_or_else(|| {
|
||||||
|
KclError::Syntax(KclErrorDetails {
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
message: format!("Invalid integer: {}", j),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(KclError::Syntax(KclErrorDetails {
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
message: format!("Invalid integer: {}", j),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_json_number_as_f64(j: &serde_json::Value, source_range: SourceRange) -> Result<f64, KclError> {
|
||||||
|
if let serde_json::Value::Number(n) = &j {
|
||||||
|
n.as_f64().ok_or_else(|| {
|
||||||
|
KclError::Syntax(KclErrorDetails {
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
message: format!("Invalid number: {}", j),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(KclError::Syntax(KclErrorDetails {
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
message: format!("Invalid number: {}", j),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_json_value_as_string(j: &serde_json::Value) -> Option<String> {
|
||||||
|
if let serde_json::Value::String(n) = &j {
|
||||||
|
Some(n.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// JSON value as bool. If it isn't a bool, returns None.
|
||||||
|
pub fn json_as_bool(j: &serde_json::Value) -> Option<bool> {
|
||||||
|
match j {
|
||||||
|
JValue::Null => None,
|
||||||
|
JValue::Bool(b) => Some(*b),
|
||||||
|
JValue::Number(_) => None,
|
||||||
|
JValue::String(_) => None,
|
||||||
|
JValue::Array(_) => None,
|
||||||
|
JValue::Object(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,7 +7,7 @@ use serde::de::DeserializeOwned;
|
|||||||
use serde_json::Value as JValue;
|
use serde_json::Value as JValue;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::types::{parse_json_number_as_f64, TagDeclarator},
|
ast::types::{execute::parse_json_number_as_f64, TagDeclarator},
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
executor::{
|
executor::{
|
||||||
ExecState, ExecutorContext, ExtrudeSurface, KclValue, Metadata, Sketch, SketchSet, SketchSurface, Solid,
|
ExecState, ExecutorContext, ExtrudeSurface, KclValue, Metadata, Sketch, SketchSet, SketchSurface, Solid,
|
||||||
|
|||||||
Reference in New Issue
Block a user