Compare commits

...

2 Commits

Author SHA1 Message Date
4de0b57ea4 Add array concatenation using the plus operator 2025-07-01 19:58:27 -04:00
fba62dab98 Add parsing arrays and objects as binary operands (#7661)
* Add parsing arrays and objects as binary operands

* Add sim test showing the error message
2025-07-01 23:33:36 +00:00
18 changed files with 701 additions and 4 deletions

View File

@ -864,6 +864,9 @@ impl BinaryPart {
BinaryPart::CallExpressionKw(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, ctx).await,
BinaryPart::ArrayExpression(e) => e.execute(exec_state, ctx).await,
BinaryPart::ArrayRangeExpression(e) => e.execute(exec_state, ctx).await,
BinaryPart::ObjectExpression(e) => e.execute(exec_state, ctx).await,
BinaryPart::IfExpression(e) => e.get_result(exec_state, ctx).await,
BinaryPart::AscribedExpression(e) => e.get_result(exec_state, ctx).await,
}
@ -1083,6 +1086,32 @@ impl Node<MemberExpression> {
}
}
fn concat(left: &[KclValue], left_el_ty: &RuntimeType, right: &[KclValue], right_el_ty: &RuntimeType) -> KclValue {
if left.is_empty() {
return KclValue::HomArray {
value: right.to_vec(),
ty: right_el_ty.clone(),
};
}
if right.is_empty() {
return KclValue::HomArray {
value: left.to_vec(),
ty: left_el_ty.clone(),
};
}
let mut new = left.to_vec();
new.extend_from_slice(right);
// Propagate the element type if we can.
let ty = if right_el_ty.subtype(left_el_ty) {
left_el_ty.clone()
} else if left_el_ty.subtype(right_el_ty) {
right_el_ty.clone()
} else {
RuntimeType::any()
};
KclValue::HomArray { value: new, ty }
}
impl Node<BinaryExpression> {
#[async_recursion]
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
@ -1101,6 +1130,50 @@ impl Node<BinaryExpression> {
meta,
});
}
// Array plus is concatenation.
match (&left_value, &right_value) {
(
KclValue::HomArray {
value: left,
ty: left_el_ty,
..
},
KclValue::HomArray {
value: right,
ty: right_el_ty,
..
},
) => {
return Ok(concat(left, left_el_ty, right, right_el_ty));
}
(
KclValue::HomArray {
value: left,
ty: left_el_ty,
..
},
_,
) => {
// Any single value can be coerced to an array.
let right = vec![right_value.clone()];
let right_el_ty = RuntimeType::any();
return Ok(concat(left, left_el_ty, &right, &right_el_ty));
}
(
_,
KclValue::HomArray {
value: right,
ty: right_el_ty,
..
},
) => {
// Any single value can be coerced to an array.
let left = vec![left_value.clone()];
let left_el_ty = RuntimeType::any();
return Ok(concat(&left, &left_el_ty, right, right_el_ty));
}
_ => {}
}
}
// Then check if we have solids.

View File

@ -246,7 +246,7 @@ impl RuntimeType {
}
// Subtype with no coercion, including refining numeric types.
fn subtype(&self, sup: &RuntimeType) -> bool {
pub(super) fn subtype(&self, sup: &RuntimeType) -> bool {
use RuntimeType::*;
match (self, sup) {

View File

@ -149,6 +149,9 @@ impl BinaryPart {
BinaryPart::UnaryExpression(unary_expression) => {
unary_expression.get_hover_value_for_position(pos, code, opts)
}
BinaryPart::ArrayExpression(e) => e.get_hover_value_for_position(pos, code, opts),
BinaryPart::ArrayRangeExpression(e) => e.get_hover_value_for_position(pos, code, opts),
BinaryPart::ObjectExpression(e) => e.get_hover_value_for_position(pos, code, opts),
BinaryPart::IfExpression(e) => e.get_hover_value_for_position(pos, code, opts),
BinaryPart::AscribedExpression(e) => e.expr.get_hover_value_for_position(pos, code, opts),
BinaryPart::MemberExpression(member_expression) => {

View File

@ -161,6 +161,9 @@ impl BinaryPart {
BinaryPart::CallExpressionKw(ce) => ce.compute_digest(),
BinaryPart::UnaryExpression(ue) => ue.compute_digest(),
BinaryPart::MemberExpression(me) => me.compute_digest(),
BinaryPart::ArrayExpression(e) => e.compute_digest(),
BinaryPart::ArrayRangeExpression(e) => e.compute_digest(),
BinaryPart::ObjectExpression(e) => e.compute_digest(),
BinaryPart::IfExpression(e) => e.compute_digest(),
BinaryPart::AscribedExpression(e) => e.compute_digest(),
}

View File

@ -51,6 +51,9 @@ impl BinaryPart {
BinaryPart::CallExpressionKw(call_expression) => call_expression.module_id,
BinaryPart::UnaryExpression(unary_expression) => unary_expression.module_id,
BinaryPart::MemberExpression(member_expression) => member_expression.module_id,
BinaryPart::ArrayExpression(e) => e.module_id,
BinaryPart::ArrayRangeExpression(e) => e.module_id,
BinaryPart::ObjectExpression(e) => e.module_id,
BinaryPart::IfExpression(e) => e.module_id,
BinaryPart::AscribedExpression(e) => e.module_id,
}

View File

@ -1214,6 +1214,9 @@ impl From<&BinaryPart> for Expr {
BinaryPart::CallExpressionKw(call_expression) => Expr::CallExpressionKw(call_expression.clone()),
BinaryPart::UnaryExpression(unary_expression) => Expr::UnaryExpression(unary_expression.clone()),
BinaryPart::MemberExpression(member_expression) => Expr::MemberExpression(member_expression.clone()),
BinaryPart::ArrayExpression(e) => Expr::ArrayExpression(e.clone()),
BinaryPart::ArrayRangeExpression(e) => Expr::ArrayRangeExpression(e.clone()),
BinaryPart::ObjectExpression(e) => Expr::ObjectExpression(e.clone()),
BinaryPart::IfExpression(e) => Expr::IfExpression(e.clone()),
BinaryPart::AscribedExpression(e) => Expr::AscribedExpression(e.clone()),
}
@ -1281,6 +1284,9 @@ pub enum BinaryPart {
CallExpressionKw(BoxNode<CallExpressionKw>),
UnaryExpression(BoxNode<UnaryExpression>),
MemberExpression(BoxNode<MemberExpression>),
ArrayExpression(BoxNode<ArrayExpression>),
ArrayRangeExpression(BoxNode<ArrayRangeExpression>),
ObjectExpression(BoxNode<ObjectExpression>),
IfExpression(BoxNode<IfExpression>),
AscribedExpression(BoxNode<AscribedExpression>),
}
@ -1307,6 +1313,9 @@ impl BinaryPart {
BinaryPart::CallExpressionKw(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::ArrayExpression(e) => e.get_constraint_level(),
BinaryPart::ArrayRangeExpression(e) => e.get_constraint_level(),
BinaryPart::ObjectExpression(e) => e.get_constraint_level(),
BinaryPart::IfExpression(e) => e.get_constraint_level(),
BinaryPart::AscribedExpression(e) => e.expr.get_constraint_level(),
}
@ -1320,6 +1329,9 @@ impl BinaryPart {
BinaryPart::CallExpressionKw(call_expression) => call_expression.replace_value(source_range, new_value),
BinaryPart::UnaryExpression(unary_expression) => unary_expression.replace_value(source_range, new_value),
BinaryPart::MemberExpression(_) => {}
BinaryPart::ArrayExpression(e) => e.replace_value(source_range, new_value),
BinaryPart::ArrayRangeExpression(e) => e.replace_value(source_range, new_value),
BinaryPart::ObjectExpression(e) => e.replace_value(source_range, new_value),
BinaryPart::IfExpression(e) => e.replace_value(source_range, new_value),
BinaryPart::AscribedExpression(e) => e.expr.replace_value(source_range, new_value),
}
@ -1333,6 +1345,9 @@ impl BinaryPart {
BinaryPart::CallExpressionKw(call_expression) => call_expression.start,
BinaryPart::UnaryExpression(unary_expression) => unary_expression.start,
BinaryPart::MemberExpression(member_expression) => member_expression.start,
BinaryPart::ArrayExpression(e) => e.start,
BinaryPart::ArrayRangeExpression(e) => e.start,
BinaryPart::ObjectExpression(e) => e.start,
BinaryPart::IfExpression(e) => e.start,
BinaryPart::AscribedExpression(e) => e.start,
}
@ -1346,6 +1361,9 @@ impl BinaryPart {
BinaryPart::CallExpressionKw(call_expression) => call_expression.end,
BinaryPart::UnaryExpression(unary_expression) => unary_expression.end,
BinaryPart::MemberExpression(member_expression) => member_expression.end,
BinaryPart::ArrayExpression(e) => e.end,
BinaryPart::ArrayRangeExpression(e) => e.end,
BinaryPart::ObjectExpression(e) => e.end,
BinaryPart::IfExpression(e) => e.end,
BinaryPart::AscribedExpression(e) => e.end,
}
@ -1360,6 +1378,9 @@ impl BinaryPart {
BinaryPart::CallExpressionKw(call_expression) => call_expression.rename_identifiers(old_name, new_name),
BinaryPart::UnaryExpression(unary_expression) => unary_expression.rename_identifiers(old_name, new_name),
BinaryPart::MemberExpression(member_expression) => member_expression.rename_identifiers(old_name, new_name),
BinaryPart::ArrayExpression(e) => e.rename_identifiers(old_name, new_name),
BinaryPart::ArrayRangeExpression(e) => e.rename_identifiers(old_name, new_name),
BinaryPart::ObjectExpression(e) => e.rename_identifiers(old_name, new_name),
BinaryPart::IfExpression(if_expression) => if_expression.rename_identifiers(old_name, new_name),
BinaryPart::AscribedExpression(e) => e.expr.rename_identifiers(old_name, new_name),
}

View File

@ -624,9 +624,6 @@ fn operand(i: &mut TokenSlice) -> ModalResult<BinaryPart> {
Expr::FunctionExpression(_)
| Expr::PipeExpression(_)
| Expr::PipeSubstitution(_)
| Expr::ArrayExpression(_)
| Expr::ArrayRangeExpression(_)
| Expr::ObjectExpression(_)
| Expr::LabelledExpression(..) => return Err(CompilationError::fatal(source_range, TODO_783)),
Expr::None(_) => {
return Err(CompilationError::fatal(
@ -652,6 +649,9 @@ fn operand(i: &mut TokenSlice) -> ModalResult<BinaryPart> {
Expr::BinaryExpression(x) => BinaryPart::BinaryExpression(x),
Expr::CallExpressionKw(x) => BinaryPart::CallExpressionKw(x),
Expr::MemberExpression(x) => BinaryPart::MemberExpression(x),
Expr::ArrayExpression(x) => BinaryPart::ArrayExpression(x),
Expr::ArrayRangeExpression(x) => BinaryPart::ArrayRangeExpression(x),
Expr::ObjectExpression(x) => BinaryPart::ObjectExpression(x),
Expr::IfExpression(x) => BinaryPart::IfExpression(x),
Expr::AscribedExpression(x) => BinaryPart::AscribedExpression(x),
};
@ -2115,6 +2115,8 @@ fn possible_operands(i: &mut TokenSlice) -> ModalResult<Expr> {
literal.map(Expr::Literal),
fn_call_kw.map(Box::new).map(Expr::CallExpressionKw),
name.map(Box::new).map(Expr::Name),
array,
object.map(Box::new).map(Expr::ObjectExpression),
binary_expr_in_parens.map(Box::new).map(Expr::BinaryExpression),
unnecessarily_bracketed,
))
@ -3398,6 +3400,27 @@ mod tests {
operand.parse(tokens).unwrap();
}
#[test]
fn parse_binary_operator_on_array() {
let tokens = crate::parsing::token::lex("[0] + 1", ModuleId::default()).unwrap();
let tokens = tokens.as_slice();
binary_expression.parse(tokens).unwrap();
}
#[test]
fn parse_binary_operator_on_object() {
let tokens = crate::parsing::token::lex("{ a = 1 } + 2", ModuleId::default()).unwrap();
let tokens = tokens.as_slice();
binary_expression.parse(tokens).unwrap();
}
#[test]
fn parse_call_array_operator() {
let tokens = crate::parsing::token::lex("f([0] + 1)", ModuleId::default()).unwrap();
let tokens = tokens.as_slice();
fn_call_kw.parse(tokens).unwrap();
}
#[test]
fn weird_program_just_a_pipe() {
let tokens = crate::parsing::token::lex("|", ModuleId::default()).unwrap();

View File

@ -779,6 +779,27 @@ mod add_lots {
super::execute(TEST_NAME, false).await
}
}
mod add_arrays {
const TEST_NAME: &str = "add_arrays";
/// Test parsing KCL.
#[test]
fn parse() {
super::parse(TEST_NAME)
}
/// Test that parsing and unparsing KCL produces the original KCL input.
#[tokio::test(flavor = "multi_thread")]
async fn unparse() {
super::unparse(TEST_NAME).await
}
/// Test that KCL is executed correctly.
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_execute() {
super::execute(TEST_NAME, false).await
}
}
mod argument_error {
//! The argument error points to the problematic argument in the call site,
//! not the function definition that the variable points to.

View File

@ -363,6 +363,9 @@ impl BinaryPart {
BinaryPart::MemberExpression(member_expression) => {
member_expression.recast(options, indentation_level, ctxt)
}
BinaryPart::ArrayExpression(e) => e.recast(options, indentation_level, ctxt),
BinaryPart::ArrayRangeExpression(e) => e.recast(options, indentation_level, ctxt),
BinaryPart::ObjectExpression(e) => e.recast(options, indentation_level, ctxt),
BinaryPart::IfExpression(e) => e.recast(options, indentation_level, ExprContext::Other),
BinaryPart::AscribedExpression(e) => e.recast(options, indentation_level, ExprContext::Other),
}
@ -745,6 +748,9 @@ impl UnaryExpression {
BinaryPart::Literal(_)
| BinaryPart::Name(_)
| BinaryPart::MemberExpression(_)
| BinaryPart::ArrayExpression(_)
| BinaryPart::ArrayRangeExpression(_)
| BinaryPart::ObjectExpression(_)
| BinaryPart::IfExpression(_)
| BinaryPart::AscribedExpression(_)
| BinaryPart::CallExpressionKw(_) => {

View File

@ -220,6 +220,9 @@ impl<'tree> From<&'tree types::BinaryPart> for Node<'tree> {
types::BinaryPart::CallExpressionKw(ce) => ce.as_ref().into(),
types::BinaryPart::UnaryExpression(ue) => ue.as_ref().into(),
types::BinaryPart::MemberExpression(me) => me.as_ref().into(),
types::BinaryPart::ArrayExpression(e) => e.as_ref().into(),
types::BinaryPart::ArrayRangeExpression(e) => e.as_ref().into(),
types::BinaryPart::ObjectExpression(e) => e.as_ref().into(),
types::BinaryPart::IfExpression(e) => e.as_ref().into(),
types::BinaryPart::AscribedExpression(e) => e.as_ref().into(),
}

View File

@ -0,0 +1,18 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Artifact commands add_arrays.kcl
---
{
"rust/kcl-lib/tests/add_arrays/input.kcl": [],
"std::appearance": [],
"std::array": [],
"std::math": [],
"std::prelude": [],
"std::sketch": [],
"std::solid": [],
"std::sweep": [],
"std::transform": [],
"std::turns": [],
"std::types": [],
"std::units": []
}

View File

@ -0,0 +1,6 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Artifact graph flowchart add_arrays.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,3 @@
```mermaid
flowchart LR
```

View File

@ -0,0 +1,270 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Result of parsing add_arrays.kcl
---
{
"Ok": {
"body": [
{
"commentStart": 0,
"declaration": {
"commentStart": 0,
"end": 0,
"id": {
"commentStart": 0,
"end": 0,
"moduleId": 0,
"name": "a",
"start": 0,
"type": "Identifier"
},
"init": {
"commentStart": 0,
"end": 0,
"left": {
"commentStart": 0,
"elements": [
{
"commentStart": 0,
"end": 0,
"moduleId": 0,
"raw": "0",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 0.0,
"suffix": "None"
}
},
{
"commentStart": 0,
"end": 0,
"moduleId": 0,
"raw": "1",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 1.0,
"suffix": "None"
}
}
],
"end": 0,
"moduleId": 0,
"start": 0,
"type": "ArrayExpression",
"type": "ArrayExpression"
},
"moduleId": 0,
"operator": "+",
"right": {
"commentStart": 0,
"elements": [
{
"commentStart": 0,
"end": 0,
"moduleId": 0,
"raw": "2",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 2.0,
"suffix": "None"
}
}
],
"end": 0,
"moduleId": 0,
"start": 0,
"type": "ArrayExpression",
"type": "ArrayExpression"
},
"start": 0,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
"moduleId": 0,
"start": 0,
"type": "VariableDeclarator"
},
"end": 0,
"kind": "const",
"moduleId": 0,
"start": 0,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"commentStart": 0,
"declaration": {
"commentStart": 0,
"end": 0,
"id": {
"commentStart": 0,
"end": 0,
"moduleId": 0,
"name": "b",
"start": 0,
"type": "Identifier"
},
"init": {
"commentStart": 0,
"end": 0,
"left": {
"commentStart": 0,
"elements": [
{
"commentStart": 0,
"end": 0,
"moduleId": 0,
"raw": "0",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 0.0,
"suffix": "None"
}
},
{
"commentStart": 0,
"end": 0,
"moduleId": 0,
"raw": "1",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 1.0,
"suffix": "None"
}
}
],
"end": 0,
"moduleId": 0,
"start": 0,
"type": "ArrayExpression",
"type": "ArrayExpression"
},
"moduleId": 0,
"operator": "+",
"right": {
"commentStart": 0,
"end": 0,
"moduleId": 0,
"raw": "2",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 2.0,
"suffix": "None"
}
},
"start": 0,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
"moduleId": 0,
"start": 0,
"type": "VariableDeclarator"
},
"end": 0,
"kind": "const",
"moduleId": 0,
"start": 0,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"commentStart": 0,
"declaration": {
"commentStart": 0,
"end": 0,
"id": {
"commentStart": 0,
"end": 0,
"moduleId": 0,
"name": "c",
"start": 0,
"type": "Identifier"
},
"init": {
"commentStart": 0,
"end": 0,
"left": {
"commentStart": 0,
"end": 0,
"moduleId": 0,
"raw": "0",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 0.0,
"suffix": "None"
}
},
"moduleId": 0,
"operator": "+",
"right": {
"commentStart": 0,
"elements": [
{
"commentStart": 0,
"end": 0,
"moduleId": 0,
"raw": "1",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 1.0,
"suffix": "None"
}
},
{
"commentStart": 0,
"end": 0,
"moduleId": 0,
"raw": "2",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 2.0,
"suffix": "None"
}
}
],
"end": 0,
"moduleId": 0,
"start": 0,
"type": "ArrayExpression",
"type": "ArrayExpression"
},
"start": 0,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
"moduleId": 0,
"start": 0,
"type": "VariableDeclarator"
},
"end": 0,
"kind": "const",
"moduleId": 0,
"start": 0,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
}
],
"commentStart": 0,
"end": 0,
"moduleId": 0,
"start": 0
}
}

View File

@ -0,0 +1,3 @@
a = [0, 1] + [2]
b = [0, 1] + 2
c = 0 + [1, 2]

View File

@ -0,0 +1,96 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Operations executed add_arrays.kcl
---
{
"rust/kcl-lib/tests/add_arrays/input.kcl": [],
"std::appearance": [],
"std::array": [],
"std::math": [
{
"type": "VariableDeclaration",
"name": "PI",
"value": {
"type": "Number",
"value": 3.141592653589793,
"ty": {
"type": "Unknown"
}
},
"visibility": "export",
"nodePath": {
"steps": []
},
"sourceRange": []
},
{
"type": "VariableDeclaration",
"name": "E",
"value": {
"type": "Number",
"value": 2.718281828459045,
"ty": {
"type": "Known",
"type": "Count"
}
},
"visibility": "export",
"nodePath": {
"steps": []
},
"sourceRange": []
},
{
"type": "VariableDeclaration",
"name": "TAU",
"value": {
"type": "Number",
"value": 6.283185307179586,
"ty": {
"type": "Known",
"type": "Count"
}
},
"visibility": "export",
"nodePath": {
"steps": []
},
"sourceRange": []
}
],
"std::prelude": [
{
"type": "VariableDeclaration",
"name": "START",
"value": {
"type": "String",
"value": "start"
},
"visibility": "export",
"nodePath": {
"steps": []
},
"sourceRange": []
},
{
"type": "VariableDeclaration",
"name": "END",
"value": {
"type": "String",
"value": "end"
},
"visibility": "export",
"nodePath": {
"steps": []
},
"sourceRange": []
}
],
"std::sketch": [],
"std::solid": [],
"std::sweep": [],
"std::transform": [],
"std::turns": [],
"std::types": [],
"std::units": []
}

View File

@ -0,0 +1,138 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Variables in memory after executing add_arrays.kcl
---
{
"a": {
"type": "HomArray",
"value": [
{
"type": "Number",
"value": 0.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
{
"type": "Number",
"value": 1.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
{
"type": "Number",
"value": 2.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
}
]
},
"b": {
"type": "HomArray",
"value": [
{
"type": "Number",
"value": 0.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
{
"type": "Number",
"value": 1.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
{
"type": "Number",
"value": 2.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
}
]
},
"c": {
"type": "HomArray",
"value": [
{
"type": "Number",
"value": 0.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
{
"type": "Number",
"value": 1.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
{
"type": "Number",
"value": 2.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
}
]
}
}

View File

@ -0,0 +1,7 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Result of unparsing add_arrays.kcl
---
a = [0, 1] + [2]
b = [0, 1] + 2
c = 0 + [1, 2]