diff --git a/src/wasm-lib/kcl/src/abstract_syntax_tree_types.rs b/src/wasm-lib/kcl/src/abstract_syntax_tree_types.rs index 88c21a56c..a82364b34 100644 --- a/src/wasm-lib/kcl/src/abstract_syntax_tree_types.rs +++ b/src/wasm-lib/kcl/src/abstract_syntax_tree_types.rs @@ -1555,6 +1555,38 @@ impl MemberExpression { None } + pub fn get_result_array(&self, memory: &mut ProgramMemory, index: usize) -> Result { + let array = match &self.object { + MemberObject::MemberExpression(member_expr) => member_expr.get_result(memory)?, + MemberObject::Identifier(identifier) => { + let value = memory.get(&identifier.name, identifier.into())?; + value.clone() + } + } + .get_json_value()?; + + if let serde_json::Value::Array(array) = array { + if let Some(value) = array.get(index) { + Ok(MemoryItem::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, memory: &mut ProgramMemory) -> Result { let property_name = match &self.property { LiteralIdentifier::Identifier(identifier) => identifier.name.to_string(), @@ -1563,9 +1595,12 @@ impl MemberExpression { // Parse this as a string. if let serde_json::Value::String(string) = value { string + } else if let serde_json::Value::Number(_) = &value { + // It can also be a number if we are getting a member of an array. + return self.get_result_array(memory, parse_json_number_as_usize(&value, self.into())?); } else { return Err(KclError::Semantic(KclErrorDetails { - message: format!("Expected string literal for property name, found {:?}", value), + message: format!("Expected string literal or number for property name, found {:?}", value), source_ranges: vec![literal.into()], })); } @@ -1767,6 +1802,22 @@ pub fn parse_json_number_as_f64(j: &serde_json::Value, source_range: SourceRange } } +pub fn parse_json_number_as_usize(j: &serde_json::Value, source_range: SourceRange) -> Result { + if let serde_json::Value::Number(n) = &j { + Ok(n.as_i64().ok_or_else(|| { + KclError::Syntax(KclErrorDetails { + source_ranges: vec![source_range], + message: format!("Invalid index: {}", j), + }) + })? as usize) + } else { + Err(KclError::Syntax(KclErrorDetails { + source_ranges: vec![source_range], + message: format!("Invalid index: {}", j), + })) + } +} + pub fn parse_json_value_as_string(j: &serde_json::Value) -> Option { if let serde_json::Value::String(n) = &j { Some(n.clone()) diff --git a/src/wasm-lib/kcl/src/executor.rs b/src/wasm-lib/kcl/src/executor.rs index df329210f..2934d2ed8 100644 --- a/src/wasm-lib/kcl/src/executor.rs +++ b/src/wasm-lib/kcl/src/executor.rs @@ -1114,11 +1114,10 @@ for var in [[3, 6, 10, [0,0]], [1.5, 3, 5, [-10,-10]]] { } #[tokio::test(flavor = "multi_thread")] - #[ignore] // ignore til we get working. async fn test_get_member_of_array_with_function() { let ast = r#"const box = (array) => { let myBox = startSketchAt(array[0]) - |> line([0, array[1], %) + |> line([0, array[1]], %) |> line([array[2], 0], %) |> line([0, -array[1]], %) |> close(%) diff --git a/src/wasm-lib/kcl/src/parser.rs b/src/wasm-lib/kcl/src/parser.rs index c6b7f0ae9..4d0e79c07 100644 --- a/src/wasm-lib/kcl/src/parser.rs +++ b/src/wasm-lib/kcl/src/parser.rs @@ -952,12 +952,18 @@ impl Parser { let opening_brace_token = self.get_token(index)?; let first_element_token = self.next_meaningful_token(index, None)?; // Make sure there is a closing brace. - let _closing_brace = self.find_closing_brace(index, 0, "")?; + let closing_brace_index = self.find_closing_brace(index, 0, "").map_err(|_| { + KclError::Syntax(KclErrorDetails { + source_ranges: vec![opening_brace_token.into()], + message: "missing a closing brace for the array".to_string(), + }) + })?; + let closing_brace_token = self.get_token(closing_brace_index)?; let array_elements = self.make_array_elements(first_element_token.index, Vec::new())?; Ok(ArrayReturn { expression: ArrayExpression { start: opening_brace_token.start, - end: self.get_token(array_elements.last_index)?.end, + end: closing_brace_token.end, elements: array_elements.elements, }, last_index: array_elements.last_index, @@ -1038,17 +1044,6 @@ impl Parser { if let Some(argument_token_token) = argument_token.token { let next_brace_or_comma = self.next_meaningful_token(argument_token.index, None)?; if let Some(next_brace_or_comma_token) = next_brace_or_comma.token { - let is_identifier_or_literal = next_brace_or_comma_token.token_type == TokenType::Comma - || next_brace_or_comma_token.token_type == TokenType::Brace; - if argument_token_token.token_type == TokenType::Brace && argument_token_token.value == "[" { - let array_expression = self.make_array_expression(argument_token.index)?; - let next_comma_or_brace_token_index = - self.next_meaningful_token(array_expression.last_index, None)?.index; - let mut _previous_args = previous_args; - _previous_args.push(Value::ArrayExpression(Box::new(array_expression.expression))); - return self.make_arguments(next_comma_or_brace_token_index, _previous_args); - } - if (argument_token_token.token_type == TokenType::Word) && (next_brace_or_comma_token.token_type == TokenType::Period || (next_brace_or_comma_token.token_type == TokenType::Brace @@ -1062,6 +1057,17 @@ impl Parser { return self.make_arguments(next_comma_or_brace_token_index, _previous_args); } + let is_identifier_or_literal = next_brace_or_comma_token.token_type == TokenType::Comma + || next_brace_or_comma_token.token_type == TokenType::Brace; + if argument_token_token.token_type == TokenType::Brace && argument_token_token.value == "[" { + let array_expression = self.make_array_expression(argument_token.index)?; + let next_comma_or_brace_token_index = + self.next_meaningful_token(array_expression.last_index, None)?.index; + let mut _previous_args = previous_args; + _previous_args.push(Value::ArrayExpression(Box::new(array_expression.expression))); + return self.make_arguments(next_comma_or_brace_token_index, _previous_args); + } + if argument_token_token.token_type == TokenType::Operator && argument_token_token.value == "-" { let value = self.make_value(argument_token.index)?; let next_comma_or_brace_token_index = self.next_meaningful_token(value.last_index, None)?.index; @@ -1185,9 +1191,14 @@ impl Parser { let brace_token = self.next_meaningful_token(index, None)?; let callee = self.make_identifier(index)?; // Make sure there is a closing brace. - let _closing_brace_token = self.find_closing_brace(brace_token.index, 0, "")?; + let closing_brace_index = self.find_closing_brace(brace_token.index, 0, "").map_err(|_| { + KclError::Syntax(KclErrorDetails { + source_ranges: vec![current_token.into()], + message: "missing a closing brace for the function call".to_string(), + }) + })?; + let closing_brace_token = self.get_token(closing_brace_index)?; let args = self.make_arguments(brace_token.index, vec![])?; - let closing_brace_token = self.get_token(args.last_index)?; let function = if let Some(stdlib_fn) = self.stdlib.get(&callee.name) { crate::abstract_syntax_tree_types::Function::StdLib { func: stdlib_fn } } else { @@ -2984,7 +2995,10 @@ z(-[["#, let parser = Parser::new(tokens); let result = parser.ast(); assert!(result.is_err()); - assert!(result.err().unwrap().to_string().contains("unexpected end")); + assert_eq!( + result.err().unwrap().to_string(), + r#"syntax: KclErrorDetails { source_ranges: [SourceRange([1, 2])], message: "missing a closing brace for the function call" }"# + ); } #[test] @@ -2996,7 +3010,10 @@ z(-[["#, let parser = Parser::new(tokens); let result = parser.ast(); assert!(result.is_err()); - assert!(result.err().unwrap().to_string().contains("unexpected end")); + assert_eq!( + result.err().unwrap().to_string(), + r#"syntax: KclErrorDetails { source_ranges: [SourceRange([0, 1])], message: "missing a closing brace for the function call" }"# + ); } #[test]