Divorce JSON and KCL (#4436)

Removes JSON from the KCL object model. Closes https://github.com/KittyCAD/modeling-app/issues/1130 -- it was filed on Nov 27 last year. Hopefully I close it before its one year anniversary.

Changes:

- Removed the UserVal variant from `enum KclValue`. That variant held JSON data.
- Replaced it with several new variants like Number, String, Array (of KCL values), Object (where keys are String and values are KCL values)
- Added a dedicated Sketch variant to KclValue. We used to have a variant like this, but I removed it as an experimental approach to fix this issue. Eventually I decided to undo it and use the approach of this PR instead.
- Removed the `impl_from_arg_via_json` macro, which implemented conversion from KclValue to Rust types by matching the KclValue to its UserVal variant, grabbing the JSON, then deserializing that into the desired Rust type. 
- Instead, replaced it with manual conversion from KclValue to Rust types, using some convenience macros like `field!`
This commit is contained in:
Adam Chalmers
2024-11-14 17:27:19 -06:00
committed by GitHub
parent b798f7da03
commit a0493cb332
47 changed files with 8532 additions and 2498 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

16
docs/kcl/types/KclNone.md Normal file
View File

@ -0,0 +1,16 @@
---
title: "KclNone"
excerpt: "KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application)."
layout: manual
---
KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application).
**Type:** `object`

View File

@ -23,8 +23,110 @@ Any KCL value.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `UserVal`| | No | | `type` |enum: `Uuid`| | No |
| `value` |``| | No | | `value` |`string`| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Bool`| | No |
| `value` |`boolean`| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Number`| | No |
| `value` |`number`| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Int`| | No |
| `value` |`integer`| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `String`| | No |
| `value` |`string`| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Array`| | No |
| `value` |`[` [`KclValue`](/docs/kcl/types/KclValue) `]`| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Object`| | No |
| `value` |`object`| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
@ -111,6 +213,38 @@ A face.
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`Sketch`](/docs/kcl/types/Sketch)| | No |
| `value` |[`Sketch`](/docs/kcl/types/Sketch)| Any KCL value. | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Sketches`| | No |
| `value` |`[` [`Sketch`](/docs/kcl/types/Sketch) `]`| | No |
---- ----
An solid is a collection of extrude surfaces. An solid is a collection of extrude surfaces.
@ -190,6 +324,23 @@ Data for an imported geometry.
---- ----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`KclNone`](/docs/kcl/types/KclNone)| | No |
| `value` |[`KclNone`](/docs/kcl/types/KclNone)| Any KCL value. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
----

View File

@ -145,7 +145,7 @@ export function useCalc({
const _programMem: ProgramMemory = ProgramMemory.empty() const _programMem: ProgramMemory = ProgramMemory.empty()
for (const { key, value } of availableVarInfo.variables) { for (const { key, value } of availableVarInfo.variables) {
const error = _programMem.set(key, { const error = _programMem.set(key, {
type: 'UserVal', type: 'String',
value, value,
__meta: [], __meta: [],
}) })

View File

@ -89,9 +89,9 @@ export const processMemory = (programMemory: ProgramMemory) => {
const processedMemory: any = {} const processedMemory: any = {}
for (const [key, val] of programMemory?.visibleEntries()) { for (const [key, val] of programMemory?.visibleEntries()) {
if ( if (
(val.type === 'UserVal' && val.value.type === 'Sketch') || val.type === 'Sketch' ||
// @ts-ignore // @ts-ignore
(val.type !== 'Function' && val.type !== 'UserVal') val.type !== 'Function'
) { ) {
const sg = sketchFromKclValue(val, key) const sg = sketchFromKclValue(val, key)
if (val.type === 'Solid') { if (val.type === 'Solid') {
@ -110,8 +110,6 @@ export const processMemory = (programMemory: ProgramMemory) => {
processedMemory[key] = `__function(${(val as any)?.expression?.params processedMemory[key] = `__function(${(val as any)?.expression?.params
?.map?.(({ identifier }: any) => identifier?.name || '') ?.map?.(({ identifier }: any) => identifier?.name || '')
.join(', ')})__` .join(', ')})__`
} else {
processedMemory[key] = val.value
} }
} }
return processedMemory return processedMemory

View File

@ -18,8 +18,7 @@ const mySketch001 = startSketchOn('XY')
// @ts-ignore // @ts-ignore
const sketch001 = execState.memory.get('mySketch001') const sketch001 = execState.memory.get('mySketch001')
expect(sketch001).toEqual({ expect(sketch001).toEqual({
type: 'UserVal', type: 'Sketch',
__meta: [{ sourceRange: [46, 71, 0] }],
value: { value: {
type: 'Sketch', type: 'Sketch',
on: expect.any(Object), on: expect.any(Object),

View File

@ -58,7 +58,13 @@ const newVar = myVar + 1`
` `
const mem = await exe(code) const mem = await exe(code)
// geo is three js buffer geometry and is very bloated to have in tests // geo is three js buffer geometry and is very bloated to have in tests
const minusGeo = mem.get('mySketch')?.value?.paths const sk = mem.get('mySketch')
expect(sk?.type).toEqual('Sketch')
if (sk?.type !== 'Sketch') {
return
}
const minusGeo = sk?.value?.paths
expect(minusGeo).toEqual([ expect(minusGeo).toEqual([
{ {
type: 'ToPoint', type: 'ToPoint',
@ -150,7 +156,7 @@ const newVar = myVar + 1`
].join('\n') ].join('\n')
const mem = await exe(code) const mem = await exe(code)
expect(mem.get('mySk1')).toEqual({ expect(mem.get('mySk1')).toEqual({
type: 'UserVal', type: 'Sketch',
value: { value: {
type: 'Sketch', type: 'Sketch',
on: expect.any(Object), on: expect.any(Object),
@ -215,7 +221,6 @@ const newVar = myVar + 1`
id: expect.any(String), id: expect.any(String),
__meta: [{ sourceRange: [39, 63, 0] }], __meta: [{ sourceRange: [39, 63, 0] }],
}, },
__meta: [{ sourceRange: [39, 63, 0] }],
}) })
}) })
it('execute array expression', async () => { it('execute array expression', async () => {
@ -225,7 +230,7 @@ const newVar = myVar + 1`
const mem = await exe(code) const mem = await exe(code)
// TODO path to node is probably wrong here, zero indexes are not correct // TODO path to node is probably wrong here, zero indexes are not correct
expect(mem.get('three')).toEqual({ expect(mem.get('three')).toEqual({
type: 'UserVal', type: 'Int',
value: 3, value: 3,
__meta: [ __meta: [
{ {
@ -234,8 +239,17 @@ const newVar = myVar + 1`
], ],
}) })
expect(mem.get('yo')).toEqual({ expect(mem.get('yo')).toEqual({
type: 'UserVal', type: 'Array',
value: [1, '2', 3, 9], value: [
{ type: 'Int', value: 1, __meta: [{ sourceRange: [28, 29, 0] }] },
{ type: 'String', value: '2', __meta: [{ sourceRange: [31, 34, 0] }] },
{ type: 'Int', value: 3, __meta: [{ sourceRange: [14, 15, 0] }] },
{
type: 'Number',
value: 9,
__meta: [{ sourceRange: [43, 44, 0] }, { sourceRange: [47, 48, 0] }],
},
],
__meta: [ __meta: [
{ {
sourceRange: [27, 49, 0], sourceRange: [27, 49, 0],
@ -253,8 +267,25 @@ const newVar = myVar + 1`
].join('\n') ].join('\n')
const mem = await exe(code) const mem = await exe(code)
expect(mem.get('yo')).toEqual({ expect(mem.get('yo')).toEqual({
type: 'UserVal', type: 'Object',
value: { aStr: 'str', anum: 2, identifier: 3, binExp: 9 }, value: {
aStr: {
type: 'String',
value: 'str',
__meta: [{ sourceRange: [34, 39, 0] }],
},
anum: { type: 'Int', value: 2, __meta: [{ sourceRange: [47, 48, 0] }] },
identifier: {
type: 'Int',
value: 3,
__meta: [{ sourceRange: [14, 15, 0] }],
},
binExp: {
type: 'Number',
value: 9,
__meta: [{ sourceRange: [77, 78, 0] }, { sourceRange: [81, 82, 0] }],
},
},
__meta: [ __meta: [
{ {
sourceRange: [27, 83, 0], sourceRange: [27, 83, 0],
@ -268,11 +299,11 @@ const newVar = myVar + 1`
) )
const mem = await exe(code) const mem = await exe(code)
expect(mem.get('myVar')).toEqual({ expect(mem.get('myVar')).toEqual({
type: 'UserVal', type: 'String',
value: '123', value: '123',
__meta: [ __meta: [
{ {
sourceRange: [41, 50, 0], sourceRange: [19, 24, 0],
}, },
], ],
}) })
@ -356,7 +387,26 @@ describe('testing math operators', () => {
it('with unaryExpression in ArrayExpression', async () => { it('with unaryExpression in ArrayExpression', async () => {
const code = 'const myVar = [1,-legLen(5, 4)]' const code = 'const myVar = [1,-legLen(5, 4)]'
const mem = await exe(code) const mem = await exe(code)
expect(mem.get('myVar')?.value).toEqual([1, -3]) expect(mem.get('myVar')?.value).toEqual([
{
__meta: [
{
sourceRange: [15, 16, 0],
},
],
type: 'Int',
value: 1,
},
{
__meta: [
{
sourceRange: [17, 30, 0],
},
],
type: 'Number',
value: -3,
},
])
}) })
it('with unaryExpression in ArrayExpression in CallExpression, checking nothing funny happens when used in a sketch', async () => { it('with unaryExpression in ArrayExpression in CallExpression, checking nothing funny happens when used in a sketch', async () => {
const code = [ const code = [

View File

@ -55,18 +55,13 @@ describe('Test KCL Samples from public Github repository', () => {
}) })
// Run through all of the files in the manifest json. This will allow us to be automatically updated // Run through all of the files in the manifest json. This will allow us to be automatically updated
// with the latest changes in github. We won't be hard coding the filenames // with the latest changes in github. We won't be hard coding the filenames
it( files.forEach((file: KclSampleFile) => {
'should run through all the files', it(`should parse ${file.filename} without errors`, async () => {
async () => { const code = await getKclSampleCodeFromGithub(file.filename)
for (let i = 0; i < files.length; i++) { const parsed = parse(code)
const file: KclSampleFile = files[i] assert(!(parsed instanceof Error))
const code = await getKclSampleCodeFromGithub(file.filename) }, 1000)
const parsed = parse(code) })
assert(!(parsed instanceof Error))
}
},
files.length * 1000
)
}) })
describe('when performing enginelessExecutor', () => { describe('when performing enginelessExecutor', () => {

View File

@ -336,7 +336,7 @@ export class ProgramMemory {
*/ */
hasSketchOrSolid(): boolean { hasSketchOrSolid(): boolean {
for (const node of this.visibleEntries().values()) { for (const node of this.visibleEntries().values()) {
if (node.type === 'Solid' || node.value?.type === 'Sketch') { if (node.type === 'Solid' || node.type === 'Sketch') {
return true return true
} }
} }

View File

@ -91,7 +91,7 @@ export function useCalculateKclExpression({
const _programMem: ProgramMemory = ProgramMemory.empty() const _programMem: ProgramMemory = ProgramMemory.empty()
for (const { key, value } of availableVarInfo.variables) { for (const { key, value } of availableVarInfo.variables) {
const error = _programMem.set(key, { const error = _programMem.set(key, {
type: 'UserVal', type: 'String',
value, value,
__meta: [], __meta: [],
}) })

View File

@ -27,7 +27,7 @@ pub use crate::ast::types::{
use crate::{ use crate::{
docs::StdLibFn, docs::StdLibFn,
errors::KclError, errors::KclError,
executor::{ExecState, ExecutorContext, KclValue, Metadata, SourceRange, TagIdentifier, UserVal}, executor::{ExecState, ExecutorContext, KclValue, Metadata, SourceRange, TagIdentifier},
parser::PIPE_OPERATOR, parser::PIPE_OPERATOR,
std::kcl_stdlib::KclStdLibFn, std::kcl_stdlib::KclStdLibFn,
}; };
@ -59,6 +59,14 @@ pub struct Node<T> {
pub module_id: ModuleId, pub module_id: ModuleId,
} }
impl<T> Node<T> {
pub fn metadata(&self) -> Metadata {
Metadata {
source_range: SourceRange([self.start, self.end, self.module_id.0 as usize]),
}
}
}
impl<T: JsonSchema> schemars::JsonSchema for Node<T> { impl<T: JsonSchema> schemars::JsonSchema for Node<T> {
fn schema_name() -> String { fn schema_name() -> String {
T::schema_name() T::schema_name()
@ -1708,34 +1716,26 @@ impl Literal {
impl From<Node<Literal>> for KclValue { impl From<Node<Literal>> for KclValue {
fn from(literal: Node<Literal>) -> Self { fn from(literal: Node<Literal>) -> Self {
KclValue::UserVal(UserVal { let meta = vec![literal.metadata()];
value: JValue::from(literal.value.clone()), match literal.inner.value {
meta: vec![Metadata { LiteralValue::IInteger(value) => KclValue::Int { value, meta },
source_range: literal.into(), LiteralValue::Fractional(value) => KclValue::Number { value, meta },
}], LiteralValue::String(value) => KclValue::String { value, meta },
}) LiteralValue::Bool(value) => KclValue::Bool { value, meta },
}
} }
} }
impl From<&Node<Literal>> for KclValue { impl From<&Node<Literal>> for KclValue {
fn from(literal: &Node<Literal>) -> Self { fn from(literal: &Node<Literal>) -> Self {
KclValue::UserVal(UserVal { Self::from(literal.to_owned())
value: JValue::from(literal.value.clone()),
meta: vec![Metadata {
source_range: literal.into(),
}],
})
} }
} }
impl From<&BoxNode<Literal>> for KclValue { impl From<&BoxNode<Literal>> for KclValue {
fn from(literal: &BoxNode<Literal>) -> Self { fn from(literal: &BoxNode<Literal>) -> Self {
KclValue::UserVal(UserVal { let b: &Node<Literal> = literal;
value: JValue::from(literal.value.clone()), Self::from(b)
meta: vec![Metadata {
source_range: literal.into(),
}],
})
} }
} }
@ -3010,17 +3010,6 @@ impl ConstraintLevels {
} }
} }
pub(crate) fn human_friendly_type(j: &JValue) -> &'static str {
match j {
JValue::Null => "null",
JValue::Bool(_) => "boolean (true/false value)",
JValue::Number(_) => "number",
JValue::String(_) => "string (text)",
JValue::Array(_) => "array (list)",
JValue::Object(_) => "object",
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;

View File

@ -1,18 +1,21 @@
use std::collections::HashMap;
use super::{ use super::{
human_friendly_type, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, CallExpression, Expr,
CallExpression, Expr, IfExpression, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node, IfExpression, KclNone, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node, ObjectExpression,
ObjectExpression, TagDeclarator, UnaryExpression, UnaryOperator, TagDeclarator, UnaryExpression, UnaryOperator,
}; };
use crate::{ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
executor::{ executor::{
BodyType, ExecState, ExecutorContext, KclValue, Metadata, Sketch, SourceRange, StatementKind, TagEngineInfo, BodyType, ExecState, ExecutorContext, KclValue, Metadata, SourceRange, StatementKind, TagEngineInfo,
TagIdentifier, UserVal, TagIdentifier,
}, },
std::FunctionKind, std::FunctionKind,
}; };
use async_recursion::async_recursion; use async_recursion::async_recursion;
use serde_json::Value as JValue;
const FLOAT_TO_INT_MAX_DELTA: f64 = 0.01;
impl BinaryPart { impl BinaryPart {
#[async_recursion] #[async_recursion]
@ -42,26 +45,19 @@ impl Node<MemberExpression> {
} }
}; };
let array_json = array.get_json_value()?; let KclValue::Array { value: array, meta: _ } = array else {
return Err(KclError::Semantic(KclErrorDetails {
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), message: format!("MemberExpression array is not an array: {:?}", array),
source_ranges: vec![self.clone().into()], source_ranges: vec![self.clone().into()],
}));
};
if let Some(value) = array.get(index) {
Ok(value.to_owned())
} else {
Err(KclError::UndefinedValue(KclErrorDetails {
message: format!("index {} not found in array", index),
source_ranges: vec![self.clone().into()],
})) }))
} }
} }
@ -77,18 +73,11 @@ impl Node<MemberExpression> {
} }
}; };
let object_json = object.get_json_value()?;
// Check the property and object match -- e.g. ints for arrays, strs for objects. // Check the property and object match -- e.g. ints for arrays, strs for objects.
match (object_json, property) { match (object, property) {
(JValue::Object(map), Property::String(property)) => { (KclValue::Object { value: map, meta: _ }, Property::String(property)) => {
if let Some(value) = map.get(&property) { if let Some(value) = map.get(&property) {
Ok(KclValue::UserVal(UserVal { Ok(value.to_owned())
value: value.clone(),
meta: vec![Metadata {
source_range: self.into(),
}],
}))
} else { } else {
Err(KclError::UndefinedValue(KclErrorDetails { Err(KclError::UndefinedValue(KclErrorDetails {
message: format!("Property '{property}' not found in object"), message: format!("Property '{property}' not found in object"),
@ -96,22 +85,20 @@ impl Node<MemberExpression> {
})) }))
} }
} }
(JValue::Object(_), p) => Err(KclError::Semantic(KclErrorDetails { (KclValue::Object { .. }, p) => {
message: format!( let t = p.type_name();
"Only strings can be used as the property of an object, but you're using a {}", let article = article_for(t);
p.type_name() Err(KclError::Semantic(KclErrorDetails {
), message: format!(
source_ranges: vec![self.clone().into()], "Only strings can be used as the property of an object, but you're using {article} {t}",
})), ),
(JValue::Array(arr), Property::Number(index)) => { source_ranges: vec![self.clone().into()],
let value_of_arr: Option<&JValue> = arr.get(index); }))
}
(KclValue::Array { value: arr, meta: _ }, Property::Number(index)) => {
let value_of_arr = arr.get(index);
if let Some(value) = value_of_arr { if let Some(value) = value_of_arr {
Ok(KclValue::UserVal(UserVal { Ok(value.to_owned())
value: value.clone(),
meta: vec![Metadata {
source_range: self.into(),
}],
}))
} else { } else {
Err(KclError::UndefinedValue(KclErrorDetails { Err(KclError::UndefinedValue(KclErrorDetails {
message: format!("The array doesn't have any item at index {index}"), message: format!("The array doesn't have any item at index {index}"),
@ -119,17 +106,36 @@ impl Node<MemberExpression> {
})) }))
} }
} }
(JValue::Array(_), p) => Err(KclError::Semantic(KclErrorDetails { (KclValue::Array { .. }, p) => {
message: format!( let t = p.type_name();
"Only integers >= 0 can be used as the index of an array, but you're using a {}", let article = article_for(t);
p.type_name()
),
source_ranges: vec![self.clone().into()],
})),
(being_indexed, _) => {
let t = human_friendly_type(&being_indexed);
Err(KclError::Semantic(KclErrorDetails { Err(KclError::Semantic(KclErrorDetails {
message: format!("Only arrays and objects can be indexed, but you're trying to index a {t}"), message: format!(
"Only integers >= 0 can be used as the index of an array, but you're using {article} {t}",
),
source_ranges: vec![self.clone().into()],
}))
}
(KclValue::Solid(solid), Property::String(prop)) if prop == "sketch" => Ok(KclValue::Sketch {
value: Box::new(solid.sketch),
}),
(KclValue::Sketch { value: sk }, Property::String(prop)) if prop == "tags" => Ok(KclValue::Object {
meta: vec![Metadata {
source_range: SourceRange::from(self.clone()),
}],
value: sk
.tags
.iter()
.map(|(k, tag)| (k.to_owned(), KclValue::TagIdentifier(Box::new(tag.to_owned()))))
.collect(),
}),
(being_indexed, _) => {
let t = being_indexed.human_friendly_type();
let article = article_for(t);
Err(KclError::Semantic(KclErrorDetails {
message: format!(
"Only arrays and objects can be indexed, but you're trying to index {article} {t}"
),
source_ranges: vec![self.clone().into()], source_ranges: vec![self.clone().into()],
})) }))
} }
@ -140,81 +146,134 @@ impl Node<MemberExpression> {
impl Node<BinaryExpression> { impl Node<BinaryExpression> {
#[async_recursion] #[async_recursion]
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> { 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 left_value = self.left.get_result(exec_state, ctx).await?;
let right_json_value = self.right.get_result(exec_state, ctx).await?.get_json_value()?; let right_value = self.right.get_result(exec_state, ctx).await?;
let mut meta = left_value.metadata();
meta.extend(right_value.metadata());
// First check if we are doing string concatenation. // First check if we are doing string concatenation.
if self.operator == BinaryOperator::Add { if self.operator == BinaryOperator::Add {
if let (Some(left), Some(right)) = ( if let (KclValue::String { value: left, meta: _ }, KclValue::String { value: right, meta: _ }) =
parse_json_value_as_string(&left_json_value), (&left_value, &right_value)
parse_json_value_as_string(&right_json_value), {
) { return Ok(KclValue::String {
let value = serde_json::Value::String(format!("{}{}", left, right)); value: format!("{}{}", left, right),
return Ok(KclValue::UserVal(UserVal { meta,
value, });
meta: vec![Metadata {
source_range: self.into(),
}],
}));
} }
} }
let left = parse_json_number_as_f64(&left_json_value, self.left.clone().into())?; let left = parse_number_as_f64(&left_value, self.left.clone().into())?;
let right = parse_json_number_as_f64(&right_json_value, self.right.clone().into())?; let right = parse_number_as_f64(&right_value, self.right.clone().into())?;
let value: serde_json::Value = match self.operator { let value = match self.operator {
BinaryOperator::Add => (left + right).into(), BinaryOperator::Add => KclValue::Number {
BinaryOperator::Sub => (left - right).into(), value: left + right,
BinaryOperator::Mul => (left * right).into(), meta,
BinaryOperator::Div => (left / right).into(), },
BinaryOperator::Mod => (left % right).into(), BinaryOperator::Sub => KclValue::Number {
BinaryOperator::Pow => (left.powf(right)).into(), value: left - right,
BinaryOperator::Eq => (left == right).into(), meta,
BinaryOperator::Neq => (left != right).into(), },
BinaryOperator::Gt => (left > right).into(), BinaryOperator::Mul => KclValue::Number {
BinaryOperator::Gte => (left >= right).into(), value: left * right,
BinaryOperator::Lt => (left < right).into(), meta,
BinaryOperator::Lte => (left <= right).into(), },
BinaryOperator::Div => KclValue::Number {
value: left / right,
meta,
},
BinaryOperator::Mod => KclValue::Number {
value: left % right,
meta,
},
BinaryOperator::Pow => KclValue::Number {
value: left.powf(right),
meta,
},
BinaryOperator::Neq => KclValue::Bool {
value: left != right,
meta,
},
BinaryOperator::Gt => KclValue::Bool {
value: left > right,
meta,
},
BinaryOperator::Gte => KclValue::Bool {
value: left >= right,
meta,
},
BinaryOperator::Lt => KclValue::Bool {
value: left < right,
meta,
},
BinaryOperator::Lte => KclValue::Bool {
value: left <= right,
meta,
},
BinaryOperator::Eq => KclValue::Bool {
value: left == right,
meta,
},
}; };
Ok(KclValue::UserVal(UserVal { Ok(value)
value,
meta: vec![Metadata {
source_range: self.into(),
}],
}))
} }
} }
impl Node<UnaryExpression> { impl Node<UnaryExpression> {
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> { pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
if self.operator == UnaryOperator::Not { if self.operator == UnaryOperator::Not {
let value = self.argument.get_result(exec_state, ctx).await?.get_json_value()?; let value = self.argument.get_result(exec_state, ctx).await?;
let Some(bool_value) = json_as_bool(&value) else { let KclValue::Bool {
value: bool_value,
meta: _,
} = value
else {
return Err(KclError::Semantic(KclErrorDetails { return Err(KclError::Semantic(KclErrorDetails {
message: format!("Cannot apply unary operator ! to non-boolean value: {}", value), message: format!(
"Cannot apply unary operator ! to non-boolean value: {}",
value.human_friendly_type()
),
source_ranges: vec![self.into()], source_ranges: vec![self.into()],
})); }));
}; };
let negated = !bool_value; let meta = vec![Metadata {
return Ok(KclValue::UserVal(UserVal { source_range: self.into(),
value: serde_json::Value::Bool(negated), }];
meta: vec![Metadata { let negated = KclValue::Bool {
source_range: self.into(), value: !bool_value,
}], meta,
})); };
return Ok(negated);
} }
let num = parse_json_number_as_f64( let value = &self.argument.get_result(exec_state, ctx).await?;
&self.argument.get_result(exec_state, ctx).await?.get_json_value()?, match value {
self.into(), KclValue::Number { value, meta: _ } => {
)?; let meta = vec![Metadata {
Ok(KclValue::UserVal(UserVal { source_range: self.into(),
value: (-(num)).into(), }];
meta: vec![Metadata { Ok(KclValue::Number { value: -value, meta })
source_range: self.into(), }
}], KclValue::Int { value, meta: _ } => {
})) let meta = vec![Metadata {
source_range: self.into(),
}];
Ok(KclValue::Number {
value: (-value) as f64,
meta,
})
}
_ => Err(KclError::Semantic(KclErrorDetails {
message: format!(
"You can only negate numbers, but this is a {}",
value.human_friendly_type()
),
source_ranges: vec![self.into()],
})),
}
} }
} }
@ -325,13 +384,10 @@ impl Node<CallExpression> {
// TODO: This could probably be done in a better way, but as of now this was my only idea // TODO: This could probably be done in a better way, but as of now this was my only idea
// and it works. // and it works.
match result { match result {
KclValue::UserVal(ref mut uval) => { KclValue::Sketch { value: ref mut sketch } => {
uval.mutate(|sketch: &mut Sketch| { for (_, tag) in sketch.tags.iter() {
for (_, tag) in sketch.tags.iter() { exec_state.memory.update_tag(&tag.value, tag.clone())?;
exec_state.memory.update_tag(&tag.value, tag.clone())?; }
}
Ok::<_, KclError>(())
})?;
} }
KclValue::Solid(ref mut solid) => { KclValue::Solid(ref mut solid) => {
for value in &solid.value { for value in &solid.value {
@ -425,10 +481,10 @@ impl Node<CallExpression> {
} else { } else {
fn_memory.add( fn_memory.add(
&param.identifier.name, &param.identifier.name,
KclValue::UserVal(UserVal { KclValue::KclNone {
value: serde_json::value::Value::Null, value: KclNone::new(),
meta: Default::default(), meta: vec![self.into()],
}), },
param.identifier.clone().into(), param.identifier.clone().into(),
)?; )?;
} }
@ -531,15 +587,13 @@ impl Node<ArrayExpression> {
.execute_expr(element, exec_state, &metadata, StatementKind::Expression) .execute_expr(element, exec_state, &metadata, StatementKind::Expression)
.await?; .await?;
results.push(value.get_json_value()?); results.push(value);
} }
Ok(KclValue::UserVal(UserVal { Ok(KclValue::Array {
value: results.into(), value: results,
meta: vec![Metadata { meta: vec![self.into()],
source_range: self.into(), })
}],
}))
} }
} }
@ -549,15 +603,19 @@ impl Node<ArrayRangeExpression> {
let metadata = Metadata::from(&self.start_element); let metadata = Metadata::from(&self.start_element);
let start = ctx let start = ctx
.execute_expr(&self.start_element, exec_state, &metadata, StatementKind::Expression) .execute_expr(&self.start_element, exec_state, &metadata, StatementKind::Expression)
.await? .await?;
.get_json_value()?; let start = start.as_int().ok_or(KclError::Semantic(KclErrorDetails {
let start = parse_json_number_as_i64(&start, (&self.start_element).into())?; source_ranges: vec![self.into()],
message: format!("Expected int but found {}", start.human_friendly_type()),
}))?;
let metadata = Metadata::from(&self.end_element); let metadata = Metadata::from(&self.end_element);
let end = ctx let end = ctx
.execute_expr(&self.end_element, exec_state, &metadata, StatementKind::Expression) .execute_expr(&self.end_element, exec_state, &metadata, StatementKind::Expression)
.await? .await?;
.get_json_value()?; let end = end.as_int().ok_or(KclError::Semantic(KclErrorDetails {
let end = parse_json_number_as_i64(&end, (&self.end_element).into())?; source_ranges: vec![self.into()],
message: format!("Expected int but found {}", end.human_friendly_type()),
}))?;
if end < start { if end < start {
return Err(KclError::Semantic(KclErrorDetails { return Err(KclError::Semantic(KclErrorDetails {
@ -567,94 +625,76 @@ impl Node<ArrayRangeExpression> {
} }
let range: Vec<_> = if self.end_inclusive { let range: Vec<_> = if self.end_inclusive {
(start..=end).map(JValue::from).collect() (start..=end).collect()
} else { } else {
(start..end).map(JValue::from).collect() (start..end).collect()
}; };
Ok(KclValue::UserVal(UserVal { let meta = vec![Metadata {
value: range.into(), source_range: self.into(),
meta: vec![Metadata { }];
source_range: self.into(), Ok(KclValue::Array {
}], value: range
})) .into_iter()
.map(|num| KclValue::Int {
value: num,
meta: meta.clone(),
})
.collect(),
meta,
})
} }
} }
impl Node<ObjectExpression> { impl Node<ObjectExpression> {
#[async_recursion] #[async_recursion]
pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> { pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
let mut object = serde_json::Map::new(); let mut object = HashMap::with_capacity(self.properties.len());
for property in &self.properties { for property in &self.properties {
let metadata = Metadata::from(&property.value); let metadata = Metadata::from(&property.value);
let result = ctx let result = ctx
.execute_expr(&property.value, exec_state, &metadata, StatementKind::Expression) .execute_expr(&property.value, exec_state, &metadata, StatementKind::Expression)
.await?; .await?;
object.insert(property.key.name.clone(), result.get_json_value()?); object.insert(property.key.name.clone(), result);
} }
Ok(KclValue::UserVal(UserVal { Ok(KclValue::Object {
value: object.into(), value: object,
meta: vec![Metadata { meta: vec![Metadata {
source_range: self.into(), source_range: self.into(),
}], }],
}))
}
}
fn parse_json_number_as_i64(j: &serde_json::Value, source_range: SourceRange) -> Result<i64, KclError> {
if let serde_json::Value::Number(n) = &j {
n.as_i64().ok_or_else(|| {
KclError::Syntax(KclErrorDetails {
source_ranges: vec![source_range],
message: format!("Invalid integer: {}", j),
})
}) })
}
}
fn article_for(s: &str) -> &'static str {
if s.starts_with(['a', 'e', 'i', 'o', 'u']) {
"an"
} else { } else {
Err(KclError::Syntax(KclErrorDetails { "a"
}
}
pub fn parse_number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<f64, KclError> {
if let KclValue::Number { value: n, .. } = &v {
Ok(*n)
} else if let KclValue::Int { value: n, .. } = &v {
Ok(*n as f64)
} else {
let actual_type = v.human_friendly_type();
let article = if actual_type.starts_with(['a', 'e', 'i', 'o', 'u']) {
"an"
} else {
"a"
};
Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![source_range], source_ranges: vec![source_range],
message: format!("Invalid integer: {}", j), message: format!("Expected a number, but found {article} {actual_type}",),
})) }))
} }
} }
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,
}
}
impl Node<IfExpression> { impl Node<IfExpression> {
#[async_recursion] #[async_recursion]
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> { pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
@ -724,15 +764,7 @@ impl Property {
} else { } else {
// Actually evaluate memory to compute the property. // Actually evaluate memory to compute the property.
let prop = exec_state.memory.get(name, property_src)?; let prop = exec_state.memory.get(name, property_src)?;
let KclValue::UserVal(prop) = prop else { jvalue_to_prop(prop, property_sr, name)
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",
),
}));
};
jvalue_to_prop(&prop.value, property_sr, name)
} }
} }
LiteralIdentifier::Literal(literal) => { LiteralIdentifier::Literal(literal) => {
@ -759,35 +791,37 @@ impl Property {
} }
} }
fn jvalue_to_prop(value: &JValue, property_sr: Vec<SourceRange>, name: &str) -> Result<Property, KclError> { fn jvalue_to_prop(value: &KclValue, property_sr: Vec<SourceRange>, name: &str) -> Result<Property, KclError> {
let make_err = |message: String| { let make_err = |message: String| {
Err::<Property, _>(KclError::Semantic(KclErrorDetails { Err::<Property, _>(KclError::Semantic(KclErrorDetails {
source_ranges: property_sr, source_ranges: property_sr,
message, message,
})) }))
}; };
const MUST_BE_POSINT: &str = "indices must be whole positive numbers";
const TRY_INT: &str = "try using the int() function to make this a whole number";
match value { match value {
JValue::Number(ref num) => { KclValue::Int { value:num, meta: _ } => {
let maybe_uint = num.as_u64().and_then(|x| usize::try_from(x).ok()); let maybe_int: Result<usize, _> = (*num).try_into();
if let Some(uint) = maybe_uint { if let Ok(uint) = maybe_int {
Ok(Property::Number(uint)) Ok(Property::Number(uint))
} else if let Some(iint) = num.as_i64() { }
make_err(format!("'{iint}' is not a valid index, {MUST_BE_POSINT}")) else {
} else if let Some(fnum) = num.as_f64() { make_err(format!("'{num}' is negative, so you can't index an array with it"))
if fnum < 0.0 {
make_err(format!("'{fnum}' is not a valid index, {MUST_BE_POSINT}"))
} else if fnum.fract() == 0.0 {
make_err(format!("'{fnum:.1}' is stored as a fractional number but indices must be whole numbers, {TRY_INT}"))
} else {
make_err(format!("'{fnum}' is not a valid index, {MUST_BE_POSINT}, {TRY_INT}"))
}
} else {
make_err(format!("'{num}' is not a valid index, {MUST_BE_POSINT}"))
} }
} }
JValue::String(ref x) => Ok(Property::String(x.to_owned())), KclValue::Number{value: num, meta:_} => {
let num = *num;
if num < 0.0 {
return make_err(format!("'{num}' is negative, so you can't index an array with it"))
}
let nearest_int = num.round();
let delta = num-nearest_int;
if delta < FLOAT_TO_INT_MAX_DELTA {
Ok(Property::Number(nearest_int as usize))
} else {
make_err(format!("'{num}' is not an integer, so you can't index an array with it"))
}
}
KclValue::String{value: x, meta:_} => Ok(Property::String(x.to_owned())),
_ => { _ => {
make_err(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")) make_err(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"))
} }

View File

@ -4,10 +4,7 @@ use databake::*;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{ast::types::ConstraintLevel, executor::KclValue};
ast::types::ConstraintLevel,
executor::{KclValue, UserVal},
};
use super::Node; use super::Node;
@ -16,7 +13,7 @@ const KCL_NONE_ID: &str = "KCL_NONE_ID";
/// KCL value for an optional parameter which was not given an argument. /// KCL value for an optional parameter which was not given an argument.
/// (remember, parameters are in the function declaration, /// (remember, parameters are in the function declaration,
/// arguments are in the function call/application). /// arguments are in the function call/application).
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake, Default)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake, Default, Copy)]
#[databake(path = kcl_lib::ast::types)] #[databake(path = kcl_lib::ast::types)]
#[ts(export)] #[ts(export)]
#[serde(tag = "type")] #[serde(tag = "type")]
@ -58,19 +55,12 @@ where
} }
} }
impl From<&KclNone> for UserVal {
fn from(none: &KclNone) -> Self {
UserVal {
value: serde_json::to_value(none).expect("can always serialize a None"),
meta: Default::default(),
}
}
}
impl From<&KclNone> for KclValue { impl From<&KclNone> for KclValue {
fn from(none: &KclNone) -> Self { fn from(none: &KclNone) -> Self {
let val = UserVal::from(none); KclValue::KclNone {
KclValue::UserVal(val) value: *none,
meta: Default::default(),
}
} }
} }

View File

@ -19,7 +19,6 @@ use kittycad_modeling_cmds::length_unit::LengthUnit;
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::Value as JValue;
use tower_lsp::lsp_types::{Position as LspPosition, Range as LspRange}; use tower_lsp::lsp_types::{Position as LspPosition, Range as LspRange};
type Point2D = kcmc::shared::Point2d<f64>; type Point2D = kcmc::shared::Point2d<f64>;
@ -27,8 +26,8 @@ type Point3D = kcmc::shared::Point3d<f64>;
use crate::{ use crate::{
ast::types::{ ast::types::{
human_friendly_type, BodyItem, Expr, FunctionExpression, ItemVisibility, KclNone, ModuleId, Node, NodeRef, BodyItem, Expr, FunctionExpression, ItemVisibility, KclNone, ModuleId, Node, NodeRef, Program, TagDeclarator,
Program, TagDeclarator, TagNode, TagNode,
}, },
engine::{EngineManager, ExecutionKind}, engine::{EngineManager, ExecutionKind},
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
@ -201,33 +200,18 @@ impl Environment {
Self { Self {
// Prelude // Prelude
bindings: HashMap::from([ bindings: HashMap::from([
( ("ZERO".to_string(), KclValue::from_number(0.0, Default::default())),
"ZERO".to_string(),
KclValue::UserVal(UserVal {
value: serde_json::Value::Number(serde_json::value::Number::from(0)),
meta: Default::default(),
}),
),
( (
"QUARTER_TURN".to_string(), "QUARTER_TURN".to_string(),
KclValue::UserVal(UserVal { KclValue::from_number(90.0, Default::default()),
value: serde_json::Value::Number(serde_json::value::Number::from(90)),
meta: Default::default(),
}),
), ),
( (
"HALF_TURN".to_string(), "HALF_TURN".to_string(),
KclValue::UserVal(UserVal { KclValue::from_number(180.0, Default::default()),
value: serde_json::Value::Number(serde_json::value::Number::from(180)),
meta: Default::default(),
}),
), ),
( (
"THREE_QUARTER_TURN".to_string(), "THREE_QUARTER_TURN".to_string(),
KclValue::UserVal(UserVal { KclValue::from_number(270.0, Default::default()),
value: serde_json::Value::Number(serde_json::value::Number::from(270)),
meta: Default::default(),
}),
), ),
]), ]),
parent: None, parent: None,
@ -264,22 +248,15 @@ impl Environment {
} }
for (_, val) in self.bindings.iter_mut() { for (_, val) in self.bindings.iter_mut() {
let KclValue::UserVal(v) = val else { continue }; let KclValue::Sketch { value } = val else { continue };
let meta = v.meta.clone(); let mut sketch = value.to_owned();
let maybe_sg: Result<Sketch, _> = serde_json::from_value(v.value.clone());
let Ok(mut sketch) = maybe_sg else {
continue;
};
if sketch.original_id == sg.original_id { if sketch.original_id == sg.original_id {
for tag in sg.tags.iter() { for tag in sg.tags.iter() {
sketch.tags.insert(tag.0.clone(), tag.1.clone()); sketch.tags.insert(tag.0.clone(), tag.1.clone());
} }
} }
*val = KclValue::UserVal(UserVal { *val = KclValue::Sketch { value: sketch };
meta,
value: serde_json::to_value(sketch).expect("can always turn Sketch into JSON"),
});
} }
} }
} }
@ -360,12 +337,52 @@ impl IdGenerator {
#[ts(export)] #[ts(export)]
#[serde(tag = "type")] #[serde(tag = "type")]
pub enum KclValue { pub enum KclValue {
UserVal(UserVal), Uuid {
value: ::uuid::Uuid,
#[serde(rename = "__meta")]
meta: Vec<Metadata>,
},
Bool {
value: bool,
#[serde(rename = "__meta")]
meta: Vec<Metadata>,
},
Number {
value: f64,
#[serde(rename = "__meta")]
meta: Vec<Metadata>,
},
Int {
value: i64,
#[serde(rename = "__meta")]
meta: Vec<Metadata>,
},
String {
value: String,
#[serde(rename = "__meta")]
meta: Vec<Metadata>,
},
Array {
value: Vec<KclValue>,
#[serde(rename = "__meta")]
meta: Vec<Metadata>,
},
Object {
value: HashMap<String, KclValue>,
#[serde(rename = "__meta")]
meta: Vec<Metadata>,
},
TagIdentifier(Box<TagIdentifier>), TagIdentifier(Box<TagIdentifier>),
TagDeclarator(crate::ast::types::BoxNode<TagDeclarator>), TagDeclarator(crate::ast::types::BoxNode<TagDeclarator>),
Plane(Box<Plane>), Plane(Box<Plane>),
Face(Box<Face>), Face(Box<Face>),
Sketch {
value: Box<Sketch>,
},
Sketches {
value: Vec<Box<Sketch>>,
},
Solid(Box<Solid>), Solid(Box<Solid>),
Solids { Solids {
value: Vec<Box<Solid>>, value: Vec<Box<Solid>>,
@ -380,31 +397,55 @@ pub enum KclValue {
#[serde(rename = "__meta")] #[serde(rename = "__meta")]
meta: Vec<Metadata>, meta: Vec<Metadata>,
}, },
KclNone {
value: KclNone,
#[serde(rename = "__meta")]
meta: Vec<Metadata>,
},
} }
impl KclValue { impl KclValue {
pub(crate) fn new_user_val<T: Serialize>(meta: Vec<Metadata>, val: T) -> Self { pub(crate) fn metadata(&self) -> Vec<Metadata> {
Self::UserVal(UserVal::new(meta, val)) match self {
KclValue::Uuid { value: _, meta } => meta.clone(),
KclValue::Bool { value: _, meta } => meta.clone(),
KclValue::Number { value: _, meta } => meta.clone(),
KclValue::Int { value: _, meta } => meta.clone(),
KclValue::String { value: _, meta } => meta.clone(),
KclValue::Array { value: _, meta } => meta.clone(),
KclValue::Object { value: _, meta } => meta.clone(),
KclValue::TagIdentifier(x) => x.meta.clone(),
KclValue::TagDeclarator(x) => vec![x.metadata()],
KclValue::Plane(x) => x.meta.clone(),
KclValue::Face(x) => x.meta.clone(),
KclValue::Sketch { value } => value.meta.clone(),
KclValue::Sketches { value } => value.iter().flat_map(|sketch| &sketch.meta).copied().collect(),
KclValue::Solid(x) => x.meta.clone(),
KclValue::Solids { value } => value.iter().flat_map(|sketch| &sketch.meta).copied().collect(),
KclValue::ImportedGeometry(x) => x.meta.clone(),
KclValue::Function { meta, .. } => meta.clone(),
KclValue::KclNone { meta, .. } => meta.clone(),
}
} }
pub(crate) fn get_solid_set(&self) -> Result<SolidSet> { pub(crate) fn get_solid_set(&self) -> Result<SolidSet> {
match self { match self {
KclValue::Solid(e) => Ok(SolidSet::Solid(e.clone())), KclValue::Solid(e) => Ok(SolidSet::Solid(e.clone())),
KclValue::Solids { value } => Ok(SolidSet::Solids(value.clone())), KclValue::Solids { value } => Ok(SolidSet::Solids(value.clone())),
KclValue::UserVal(value) => { KclValue::Array { value, .. } => {
let value = value.value.clone(); let solids: Vec<_> = value
match value { .iter()
JValue::Null | JValue::Bool(_) | JValue::Number(_) | JValue::String(_) => Err(anyhow::anyhow!( .enumerate()
"Failed to deserialize solid set from JSON {}", .map(|(i, v)| {
human_friendly_type(&value) v.as_solid().map(|v| v.to_owned()).map(Box::new).ok_or_else(|| {
)), anyhow::anyhow!(
JValue::Array(_) => serde_json::from_value::<Vec<Box<Solid>>>(value) "expected this array to only contain solids, but element {i} was actually {}",
.map(SolidSet::from) v.human_friendly_type()
.map_err(|e| anyhow::anyhow!("Failed to deserialize array of solids from JSON: {}", e)), )
JValue::Object(_) => serde_json::from_value::<Box<Solid>>(value) })
.map(SolidSet::from) })
.map_err(|e| anyhow::anyhow!("Failed to deserialize solid from JSON: {}", e)), .collect::<Result<_, _>>()?;
} Ok(SolidSet::Solids(solids))
} }
_ => anyhow::bail!("Not a solid or solids: {:?}", self), _ => anyhow::bail!("Not a solid or solids: {:?}", self),
} }
@ -414,43 +455,44 @@ impl KclValue {
/// on for program logic. /// on for program logic.
pub(crate) fn human_friendly_type(&self) -> &'static str { pub(crate) fn human_friendly_type(&self) -> &'static str {
match self { match self {
KclValue::UserVal(u) => human_friendly_type(&u.value), KclValue::Uuid { .. } => "Unique ID (uuid)",
KclValue::TagDeclarator(_) => "TagDeclarator", KclValue::TagDeclarator(_) => "TagDeclarator",
KclValue::TagIdentifier(_) => "TagIdentifier", KclValue::TagIdentifier(_) => "TagIdentifier",
KclValue::Solid(_) => "Solid", KclValue::Solid(_) => "Solid",
KclValue::Solids { .. } => "Solids", KclValue::Solids { .. } => "Solids",
KclValue::Sketch { .. } => "Sketch",
KclValue::Sketches { .. } => "Sketches",
KclValue::ImportedGeometry(_) => "ImportedGeometry", KclValue::ImportedGeometry(_) => "ImportedGeometry",
KclValue::Function { .. } => "Function", KclValue::Function { .. } => "Function",
KclValue::Plane(_) => "Plane", KclValue::Plane(_) => "Plane",
KclValue::Face(_) => "Face", KclValue::Face(_) => "Face",
KclValue::Bool { .. } => "boolean (true/false value)",
KclValue::Number { .. } => "number",
KclValue::Int { .. } => "integer",
KclValue::String { .. } => "string (text)",
KclValue::Array { .. } => "array (list)",
KclValue::Object { .. } => "object",
KclValue::KclNone { .. } => "None",
} }
} }
pub(crate) fn is_function(&self) -> bool { pub(crate) fn is_function(&self) -> bool {
match self { matches!(self, KclValue::Function { .. })
KclValue::UserVal(..)
| KclValue::TagIdentifier(..)
| KclValue::TagDeclarator(..)
| KclValue::Plane(..)
| KclValue::Face(..)
| KclValue::Solid(..)
| KclValue::Solids { .. }
| KclValue::ImportedGeometry(..) => false,
KclValue::Function { .. } => true,
}
} }
} }
impl From<SketchSet> for KclValue { impl From<SketchSet> for KclValue {
fn from(sg: SketchSet) -> Self { fn from(sg: SketchSet) -> Self {
KclValue::UserVal(UserVal::new(sg.meta(), sg)) match sg {
SketchSet::Sketch(value) => KclValue::Sketch { value },
SketchSet::Sketches(value) => KclValue::Sketches { value },
}
} }
} }
impl From<Vec<Box<Sketch>>> for KclValue { impl From<Vec<Box<Sketch>>> for KclValue {
fn from(sg: Vec<Box<Sketch>>) -> Self { fn from(sg: Vec<Box<Sketch>>) -> Self {
let meta = sg.iter().flat_map(|sg| sg.meta.clone()).collect(); KclValue::Sketches { value: sg }
KclValue::UserVal(UserVal::new(meta, sg))
} }
} }
@ -815,52 +857,6 @@ pub enum PlaneType {
Custom, Custom,
} }
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type", rename_all = "camelCase")]
pub struct UserVal {
#[ts(type = "any")]
pub value: serde_json::Value,
#[serde(rename = "__meta")]
pub meta: Vec<Metadata>,
}
impl UserVal {
pub fn new<T: serde::Serialize>(meta: Vec<Metadata>, val: T) -> Self {
Self {
meta,
value: serde_json::to_value(val).expect("all KCL values should be compatible with JSON"),
}
}
/// If the UserVal matches the type `T`, return it.
pub fn get<T: serde::de::DeserializeOwned>(&self) -> Option<(T, Vec<Metadata>)> {
let meta = self.meta.clone();
// TODO: This clone might cause performance problems, it'll happen a lot.
let res: Result<T, _> = serde_json::from_value(self.value.clone());
if let Ok(t) = res {
Some((t, meta))
} else {
None
}
}
/// If the UserVal matches the type `T`, then mutate it via the given closure.
/// If the closure returns Err, the mutation won't be applied.
pub fn mutate<T, F, E>(&mut self, mutate: F) -> Result<(), E>
where
T: serde::de::DeserializeOwned + Serialize,
F: FnOnce(&mut T) -> Result<(), E>,
{
let Some((mut val, meta)) = self.get::<T>() else {
return Ok(());
};
mutate(&mut val)?;
*self = Self::new(meta, val);
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
#[serde(tag = "type", rename_all = "camelCase")] #[serde(tag = "type", rename_all = "camelCase")]
@ -922,108 +918,177 @@ pub type MemoryFunction =
impl From<KclValue> for Vec<SourceRange> { impl From<KclValue> for Vec<SourceRange> {
fn from(item: KclValue) -> Self { fn from(item: KclValue) -> Self {
match item { match item {
KclValue::UserVal(u) => u.meta.iter().map(|m| m.source_range).collect(), KclValue::TagDeclarator(t) => vec![SourceRange([t.start, t.end, t.module_id.0 as usize])],
KclValue::TagDeclarator(t) => vec![(&t).into()], KclValue::TagIdentifier(t) => to_vec_sr(&t.meta),
KclValue::TagIdentifier(t) => t.meta.iter().map(|m| m.source_range).collect(), KclValue::Solid(e) => to_vec_sr(&e.meta),
KclValue::Solid(e) => e.meta.iter().map(|m| m.source_range).collect(), KclValue::Solids { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(),
KclValue::Solids { value } => value KclValue::Sketch { value } => to_vec_sr(&value.meta),
.iter() KclValue::Sketches { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(),
.flat_map(|eg| eg.meta.iter().map(|m| m.source_range)) KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta),
.collect(), KclValue::Function { meta, .. } => to_vec_sr(&meta),
KclValue::ImportedGeometry(i) => i.meta.iter().map(|m| m.source_range).collect(), KclValue::Plane(p) => to_vec_sr(&p.meta),
KclValue::Function { meta, .. } => meta.iter().map(|m| m.source_range).collect(), KclValue::Face(f) => to_vec_sr(&f.meta),
KclValue::Plane(p) => p.meta.iter().map(|m| m.source_range).collect(), KclValue::Bool { meta, .. } => to_vec_sr(&meta),
KclValue::Face(f) => f.meta.iter().map(|m| m.source_range).collect(), KclValue::Number { meta, .. } => to_vec_sr(&meta),
KclValue::Int { meta, .. } => to_vec_sr(&meta),
KclValue::String { meta, .. } => to_vec_sr(&meta),
KclValue::Array { meta, .. } => to_vec_sr(&meta),
KclValue::Object { meta, .. } => to_vec_sr(&meta),
KclValue::Uuid { meta, .. } => to_vec_sr(&meta),
KclValue::KclNone { meta, .. } => to_vec_sr(&meta),
} }
} }
} }
fn to_vec_sr(meta: &[Metadata]) -> Vec<SourceRange> {
meta.iter().map(|m| m.source_range).collect()
}
impl From<&KclValue> for Vec<SourceRange> { impl From<&KclValue> for Vec<SourceRange> {
fn from(item: &KclValue) -> Self { fn from(item: &KclValue) -> Self {
match item { match item {
KclValue::UserVal(u) => u.meta.iter().map(|m| m.source_range).collect(), KclValue::TagDeclarator(t) => vec![SourceRange([t.start, t.end, t.module_id.0 as usize])],
KclValue::TagDeclarator(ref t) => vec![t.into()], KclValue::TagIdentifier(t) => to_vec_sr(&t.meta),
KclValue::TagIdentifier(t) => t.meta.iter().map(|m| m.source_range).collect(), KclValue::Solid(e) => to_vec_sr(&e.meta),
KclValue::Solid(e) => e.meta.iter().map(|m| m.source_range).collect(), KclValue::Solids { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(),
KclValue::Solids { value } => value KclValue::Sketch { value } => to_vec_sr(&value.meta),
.iter() KclValue::Sketches { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(),
.flat_map(|eg| eg.meta.iter().map(|m| m.source_range)) KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta),
.collect(), KclValue::Function { meta, .. } => to_vec_sr(meta),
KclValue::ImportedGeometry(i) => i.meta.iter().map(|m| m.source_range).collect(), KclValue::Plane(p) => to_vec_sr(&p.meta),
KclValue::Function { meta, .. } => meta.iter().map(|m| m.source_range).collect(), KclValue::Face(f) => to_vec_sr(&f.meta),
KclValue::Plane(p) => p.meta.iter().map(|m| m.source_range).collect(), KclValue::Bool { meta, .. } => to_vec_sr(meta),
KclValue::Face(f) => f.meta.iter().map(|m| m.source_range).collect(), KclValue::Number { meta, .. } => to_vec_sr(meta),
KclValue::Int { meta, .. } => to_vec_sr(meta),
KclValue::String { meta, .. } => to_vec_sr(meta),
KclValue::Uuid { meta, .. } => to_vec_sr(meta),
KclValue::Array { meta, .. } => to_vec_sr(meta),
KclValue::Object { meta, .. } => to_vec_sr(meta),
KclValue::KclNone { meta, .. } => to_vec_sr(meta),
} }
} }
} }
impl KclValue { impl KclValue {
pub fn get_json_value(&self) -> Result<serde_json::Value, KclError> { /// Put the number into a KCL value.
if let KclValue::UserVal(user_val) = self { pub fn from_number(f: f64, meta: Vec<Metadata>) -> Self {
Ok(user_val.value.clone()) Self::Number { value: f, meta }
} else { }
serde_json::to_value(self).map_err(|err| {
KclError::Semantic(KclErrorDetails { /// Put the point into a KCL value.
message: format!("Cannot convert memory item to json value: {:?}", err), pub fn from_point2d(p: [f64; 2], meta: Vec<Metadata>) -> Self {
source_ranges: self.clone().into(), Self::Array {
}) value: vec![
}) Self::Number {
value: p[0],
meta: meta.clone(),
},
Self::Number {
value: p[1],
meta: meta.clone(),
},
],
meta,
} }
} }
/// Get a JSON value and deserialize it into some concrete type. pub(crate) fn as_usize(&self) -> Option<usize> {
pub fn get_json<T: serde::de::DeserializeOwned>(&self) -> Result<T, KclError> { match self {
let json = self.get_json_value()?; KclValue::Int { value, .. } => Some(*value as usize),
_ => None,
serde_json::from_value(json).map_err(|e| {
KclError::Type(KclErrorDetails {
message: format!("Failed to deserialize struct from JSON: {}", e),
source_ranges: self.clone().into(),
})
})
}
/// Get a JSON value and deserialize it into some concrete type.
/// If it's a KCL None, return None. Otherwise return Some.
pub fn get_json_opt<T: serde::de::DeserializeOwned>(&self) -> Result<Option<T>, KclError> {
let json = self.get_json_value()?;
if let JValue::Object(ref o) = json {
if let Some(JValue::String(s)) = o.get("type") {
if s == "KclNone" {
return Ok(None);
}
}
} }
serde_json::from_value(json)
.map_err(|e| {
KclError::Type(KclErrorDetails {
message: format!("Failed to deserialize struct from JSON: {}", e),
source_ranges: self.clone().into(),
})
})
.map(Some)
} }
pub fn as_user_val(&self) -> Option<&UserVal> { pub fn as_int(&self) -> Option<i64> {
if let KclValue::UserVal(x) = self { if let KclValue::Int { value, meta: _ } = &self {
Some(x) Some(*value)
} else { } else {
None None
} }
} }
/// If this value is of type u32, return it. pub fn as_object(&self) -> Option<&HashMap<String, KclValue>> {
if let KclValue::Object { value, meta: _ } = &self {
Some(value)
} else {
None
}
}
pub fn as_str(&self) -> Option<&str> {
if let KclValue::String { value, meta: _ } = &self {
Some(value)
} else {
None
}
}
pub fn as_array(&self) -> Option<&[KclValue]> {
if let KclValue::Array { value, meta: _ } = &self {
Some(value)
} else {
None
}
}
pub fn as_point2d(&self) -> Option<[f64; 2]> {
let arr = self.as_array()?;
if arr.len() != 2 {
return None;
}
let x = arr[0].as_f64()?;
let y = arr[1].as_f64()?;
Some([x, y])
}
pub fn as_uuid(&self) -> Option<uuid::Uuid> {
if let KclValue::Uuid { value, meta: _ } = &self {
Some(*value)
} else {
None
}
}
pub fn as_solid(&self) -> Option<&Solid> {
if let KclValue::Solid(value) = &self {
Some(value)
} else {
None
}
}
pub fn as_f64(&self) -> Option<f64> {
if let KclValue::Number { value, meta: _ } = &self {
Some(*value)
} else if let KclValue::Int { value, meta: _ } = &self {
Some(*value as f64)
} else {
None
}
}
pub fn as_bool(&self) -> Option<bool> {
if let KclValue::Bool { value, meta: _ } = &self {
Some(*value)
} else {
None
}
}
/// If this value fits in a u32, return it.
pub fn get_u32(&self, source_ranges: Vec<SourceRange>) -> Result<u32, KclError> { pub fn get_u32(&self, source_ranges: Vec<SourceRange>) -> Result<u32, KclError> {
let err = KclError::Semantic(KclErrorDetails { let u = self.as_int().and_then(|n| u64::try_from(n).ok()).ok_or_else(|| {
message: "Expected an integer >= 0".to_owned(), KclError::Semantic(KclErrorDetails {
source_ranges, message: "Expected an integer >= 0".to_owned(),
}); source_ranges: source_ranges.clone(),
self.as_user_val() })
.and_then(|uv| uv.value.as_number()) })?;
.and_then(|n| n.as_u64()) u32::try_from(u).map_err(|_| {
.and_then(|n| u32::try_from(n).ok()) KclError::Semantic(KclErrorDetails {
.ok_or(err) message: "Number was too big".to_owned(),
source_ranges,
})
})
} }
/// If this value is of type function, return it. /// If this value is of type function, return it.
@ -1048,16 +1113,6 @@ impl KclValue {
pub fn get_tag_identifier(&self) -> Result<TagIdentifier, KclError> { pub fn get_tag_identifier(&self) -> Result<TagIdentifier, KclError> {
match self { match self {
KclValue::TagIdentifier(t) => Ok(*t.clone()), KclValue::TagIdentifier(t) => Ok(*t.clone()),
KclValue::UserVal(_) => {
if let Some(identifier) = self.get_json_opt::<TagIdentifier>()? {
Ok(identifier)
} else {
Err(KclError::Semantic(KclErrorDetails {
message: format!("Not a tag identifier: {:?}", self),
source_ranges: self.clone().into(),
}))
}
}
_ => Err(KclError::Semantic(KclErrorDetails { _ => Err(KclError::Semantic(KclErrorDetails {
message: format!("Not a tag identifier: {:?}", self), message: format!("Not a tag identifier: {:?}", self),
source_ranges: self.clone().into(), source_ranges: self.clone().into(),
@ -1089,19 +1144,13 @@ impl KclValue {
/// If this KCL value is a bool, retrieve it. /// If this KCL value is a bool, retrieve it.
pub fn get_bool(&self) -> Result<bool, KclError> { pub fn get_bool(&self) -> Result<bool, KclError> {
let Self::UserVal(uv) = self else { let Self::Bool { value: b, .. } = self else {
return Err(KclError::Type(KclErrorDetails { return Err(KclError::Type(KclErrorDetails {
source_ranges: self.into(), source_ranges: self.into(),
message: format!("Expected bool, found {}", self.human_friendly_type()), message: format!("Expected bool, found {}", self.human_friendly_type()),
})); }));
}; };
let JValue::Bool(b) = uv.value else { Ok(*b)
return Err(KclError::Type(KclErrorDetails {
source_ranges: self.into(),
message: format!("Expected bool, found {}", human_friendly_type(&uv.value)),
}));
};
Ok(b)
} }
/// If this memory item is a function, call it with the given arguments, return its val as Ok. /// If this memory item is a function, call it with the given arguments, return its val as Ok.
@ -1555,7 +1604,7 @@ impl From<Point3d> for kittycad_modeling_cmds::shared::Point3d<LengthUnit> {
} }
/// Metadata. /// Metadata.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq, Copy)]
#[ts(export)] #[ts(export)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Metadata { pub struct Metadata {
@ -1563,6 +1612,12 @@ pub struct Metadata {
pub source_range: SourceRange, pub source_range: SourceRange,
} }
impl From<Metadata> for Vec<SourceRange> {
fn from(meta: Metadata) -> Self {
vec![meta.source_range]
}
}
impl From<SourceRange> for Metadata { impl From<SourceRange> for Metadata {
fn from(source_range: SourceRange) -> Self { fn from(source_range: SourceRange) -> Self {
Self { source_range } Self { source_range }
@ -2655,74 +2710,8 @@ mod tests {
} }
/// Convenience function to get a JSON value from memory and unwrap. /// Convenience function to get a JSON value from memory and unwrap.
fn mem_get_json(memory: &ProgramMemory, name: &str) -> serde_json::Value { fn mem_get_json(memory: &ProgramMemory, name: &str) -> KclValue {
memory memory.get(name, SourceRange::default()).unwrap().to_owned()
.get(name, SourceRange::default())
.unwrap()
.get_json_value()
.unwrap()
}
#[tokio::test(flavor = "multi_thread")]
async fn test_execute_assign_two_variables() {
let ast = r#"const myVar = 5
const newVar = myVar + 1"#;
let memory = parse_execute(ast).await.unwrap();
assert_eq!(
serde_json::json!(5),
memory
.get("myVar", SourceRange::default())
.unwrap()
.get_json_value()
.unwrap()
);
assert_eq!(
serde_json::json!(6.0),
memory
.get("newVar", SourceRange::default())
.unwrap()
.get_json_value()
.unwrap()
);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_execute_angled_line_that_intersects() {
let ast_fn = |offset: &str| -> String {
format!(
r#"const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([2, 2], %, $yo)
|> lineTo([3, 1], %)
|> angledLineThatIntersects({{
angle: 180,
intersectTag: yo,
offset: {},
}}, %, $yo2)
const intersect = segEndX(yo2)"#,
offset
)
};
let memory = parse_execute(&ast_fn("-1")).await.unwrap();
assert_eq!(
serde_json::json!(1.0 + 2.0f64.sqrt()),
memory
.get("intersect", SourceRange::default())
.unwrap()
.get_json_value()
.unwrap()
);
let memory = parse_execute(&ast_fn("0")).await.unwrap();
assert_eq!(
serde_json::json!(1.0000000000000002),
memory
.get("intersect", SourceRange::default())
.unwrap()
.get_json_value()
.unwrap()
);
} }
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
@ -3120,200 +3109,41 @@ let shape = layer() |> patternTransform(10, transform, %)
); );
} }
#[tokio::test(flavor = "multi_thread")] // ADAM: Move some of these into simulation tests.
async fn test_execute_function_with_parameter_redefined_outside() {
let ast = r#"
fn myIdentity = (x) => {
return x
}
const x = 33
const two = myIdentity(2)"#;
let memory = parse_execute(ast).await.unwrap();
assert_eq!(
serde_json::json!(2),
memory
.get("two", SourceRange::default())
.unwrap()
.get_json_value()
.unwrap()
);
assert_eq!(
serde_json::json!(33),
memory
.get("x", SourceRange::default())
.unwrap()
.get_json_value()
.unwrap()
);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_execute_function_referencing_variable_in_parent_scope() {
let ast = r#"
const x = 22
const y = 3
fn add = (x) => {
return x + y
}
const answer = add(2)"#;
let memory = parse_execute(ast).await.unwrap();
assert_eq!(
serde_json::json!(5.0),
memory
.get("answer", SourceRange::default())
.unwrap()
.get_json_value()
.unwrap()
);
assert_eq!(
serde_json::json!(22),
memory
.get("x", SourceRange::default())
.unwrap()
.get_json_value()
.unwrap()
);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_execute_function_redefining_variable_in_parent_scope() {
let ast = r#"
const x = 1
fn foo = () => {
const x = 2
return x
}
const answer = foo()"#;
let memory = parse_execute(ast).await.unwrap();
assert_eq!(
serde_json::json!(2),
memory
.get("answer", SourceRange::default())
.unwrap()
.get_json_value()
.unwrap()
);
assert_eq!(
serde_json::json!(1),
memory
.get("x", SourceRange::default())
.unwrap()
.get_json_value()
.unwrap()
);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_execute_pattern_transform_function_redefining_variable_in_parent_scope() {
let ast = r#"
const scale = 100
fn transform = (replicaId) => {
// Redefine same variable as in parent scope.
const scale = 2
return {
translate: [0, 0, replicaId * 10],
scale: [scale, 1, 0],
}
}
fn layer = () => {
return startSketchOn("XY")
|> circle({ center: [0, 0], radius: 1 }, %, $tag1)
|> extrude(10, %)
}
// The 10 layers are replicas of each other, with a transform applied to each.
let shape = layer() |> patternTransform(10, transform, %)"#;
let memory = parse_execute(ast).await.unwrap();
// TODO: Assert that scale 2 was used.
assert_eq!(
serde_json::json!(100),
memory
.get("scale", SourceRange::default())
.unwrap()
.get_json_value()
.unwrap()
);
}
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_math_execute_with_functions() { async fn test_math_execute_with_functions() {
let ast = r#"const myVar = 2 + min(100, -1 + legLen(5, 3))"#; let ast = r#"const myVar = 2 + min(100, -1 + legLen(5, 3))"#;
let memory = parse_execute(ast).await.unwrap(); let memory = parse_execute(ast).await.unwrap();
assert_eq!( assert_eq!(5.0, mem_get_json(&memory, "myVar").as_f64().unwrap());
serde_json::json!(5.0),
memory
.get("myVar", SourceRange::default())
.unwrap()
.get_json_value()
.unwrap()
);
} }
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_math_execute() { async fn test_math_execute() {
let ast = r#"const myVar = 1 + 2 * (3 - 4) / -5 + 6"#; let ast = r#"const myVar = 1 + 2 * (3 - 4) / -5 + 6"#;
let memory = parse_execute(ast).await.unwrap(); let memory = parse_execute(ast).await.unwrap();
assert_eq!( assert_eq!(7.4, mem_get_json(&memory, "myVar").as_f64().unwrap());
serde_json::json!(7.4),
memory
.get("myVar", SourceRange::default())
.unwrap()
.get_json_value()
.unwrap()
);
} }
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_math_execute_start_negative() { async fn test_math_execute_start_negative() {
let ast = r#"const myVar = -5 + 6"#; let ast = r#"const myVar = -5 + 6"#;
let memory = parse_execute(ast).await.unwrap(); let memory = parse_execute(ast).await.unwrap();
assert_eq!( assert_eq!(1.0, mem_get_json(&memory, "myVar").as_f64().unwrap());
serde_json::json!(1.0),
memory
.get("myVar", SourceRange::default())
.unwrap()
.get_json_value()
.unwrap()
);
} }
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_math_execute_with_pi() { async fn test_math_execute_with_pi() {
let ast = r#"const myVar = pi() * 2"#; let ast = r#"const myVar = pi() * 2"#;
let memory = parse_execute(ast).await.unwrap(); let memory = parse_execute(ast).await.unwrap();
assert_eq!( assert_eq!(std::f64::consts::TAU, mem_get_json(&memory, "myVar").as_f64().unwrap());
serde_json::json!(std::f64::consts::TAU),
memory
.get("myVar", SourceRange::default())
.unwrap()
.get_json_value()
.unwrap()
);
} }
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_math_define_decimal_without_leading_zero() { async fn test_math_define_decimal_without_leading_zero() {
let ast = r#"let thing = .4 + 7"#; let ast = r#"let thing = .4 + 7"#;
let memory = parse_execute(ast).await.unwrap(); let memory = parse_execute(ast).await.unwrap();
assert_eq!( assert_eq!(7.4, mem_get_json(&memory, "thing").as_f64().unwrap());
serde_json::json!(7.4),
memory
.get("thing", SourceRange::default())
.unwrap()
.get_json_value()
.unwrap()
);
} }
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
@ -3353,10 +3183,10 @@ fn check = (x) => {
check(false) check(false)
"#; "#;
let mem = parse_execute(ast).await.unwrap(); let mem = parse_execute(ast).await.unwrap();
assert_eq!(serde_json::json!(false), mem_get_json(&mem, "notTrue")); assert_eq!(false, mem_get_json(&mem, "notTrue").as_bool().unwrap());
assert_eq!(serde_json::json!(true), mem_get_json(&mem, "notFalse")); assert_eq!(true, mem_get_json(&mem, "notFalse").as_bool().unwrap());
assert_eq!(serde_json::json!(true), mem_get_json(&mem, "c")); assert_eq!(true, mem_get_json(&mem, "c").as_bool().unwrap());
assert_eq!(serde_json::json!(false), mem_get_json(&mem, "d")); assert_eq!(false, mem_get_json(&mem, "d").as_bool().unwrap());
} }
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
@ -3369,7 +3199,7 @@ let notNull = !myNull
assert_eq!( assert_eq!(
parse_execute(code1).await.unwrap_err().downcast::<KclError>().unwrap(), parse_execute(code1).await.unwrap_err().downcast::<KclError>().unwrap(),
KclError::Semantic(KclErrorDetails { KclError::Semantic(KclErrorDetails {
message: "Cannot apply unary operator ! to non-boolean value: null".to_owned(), message: "Cannot apply unary operator ! to non-boolean value: number".to_owned(),
source_ranges: vec![SourceRange([56, 63, 0])], source_ranges: vec![SourceRange([56, 63, 0])],
}) })
); );
@ -3378,7 +3208,7 @@ let notNull = !myNull
assert_eq!( assert_eq!(
parse_execute(code2).await.unwrap_err().downcast::<KclError>().unwrap(), parse_execute(code2).await.unwrap_err().downcast::<KclError>().unwrap(),
KclError::Semantic(KclErrorDetails { KclError::Semantic(KclErrorDetails {
message: "Cannot apply unary operator ! to non-boolean value: 0".to_owned(), message: "Cannot apply unary operator ! to non-boolean value: integer".to_owned(),
source_ranges: vec![SourceRange([14, 16, 0])], source_ranges: vec![SourceRange([14, 16, 0])],
}) })
); );
@ -3389,7 +3219,7 @@ let notEmptyString = !""
assert_eq!( assert_eq!(
parse_execute(code3).await.unwrap_err().downcast::<KclError>().unwrap(), parse_execute(code3).await.unwrap_err().downcast::<KclError>().unwrap(),
KclError::Semantic(KclErrorDetails { KclError::Semantic(KclErrorDetails {
message: "Cannot apply unary operator ! to non-boolean value: \"\"".to_owned(), message: "Cannot apply unary operator ! to non-boolean value: string (text)".to_owned(),
source_ranges: vec![SourceRange([22, 25, 0])], source_ranges: vec![SourceRange([22, 25, 0])],
}) })
); );
@ -3401,7 +3231,7 @@ let notMember = !obj.a
assert_eq!( assert_eq!(
parse_execute(code4).await.unwrap_err().downcast::<KclError>().unwrap(), parse_execute(code4).await.unwrap_err().downcast::<KclError>().unwrap(),
KclError::Semantic(KclErrorDetails { KclError::Semantic(KclErrorDetails {
message: "Cannot apply unary operator ! to non-boolean value: 1".to_owned(), message: "Cannot apply unary operator ! to non-boolean value: integer".to_owned(),
source_ranges: vec![SourceRange([36, 42, 0])], source_ranges: vec![SourceRange([36, 42, 0])],
}) })
); );
@ -3412,7 +3242,7 @@ let notArray = !a";
assert_eq!( assert_eq!(
parse_execute(code5).await.unwrap_err().downcast::<KclError>().unwrap(), parse_execute(code5).await.unwrap_err().downcast::<KclError>().unwrap(),
KclError::Semantic(KclErrorDetails { KclError::Semantic(KclErrorDetails {
message: "Cannot apply unary operator ! to non-boolean value: []".to_owned(), message: "Cannot apply unary operator ! to non-boolean value: array (list)".to_owned(),
source_ranges: vec![SourceRange([27, 29, 0])], source_ranges: vec![SourceRange([27, 29, 0])],
}) })
); );
@ -3423,7 +3253,7 @@ let notObject = !x";
assert_eq!( assert_eq!(
parse_execute(code6).await.unwrap_err().downcast::<KclError>().unwrap(), parse_execute(code6).await.unwrap_err().downcast::<KclError>().unwrap(),
KclError::Semantic(KclErrorDetails { KclError::Semantic(KclErrorDetails {
message: "Cannot apply unary operator ! to non-boolean value: {}".to_owned(), message: "Cannot apply unary operator ! to non-boolean value: object".to_owned(),
source_ranges: vec![SourceRange([28, 30, 0])], source_ranges: vec![SourceRange([28, 30, 0])],
}) })
); );
@ -3451,7 +3281,7 @@ let notTagDeclarator = !myTagDeclarator";
assert!( assert!(
tag_declarator_err tag_declarator_err
.message() .message()
.starts_with("Cannot apply unary operator ! to non-boolean value: {\"type\":\"TagDeclarator\","), .starts_with("Cannot apply unary operator ! to non-boolean value: TagDeclarator"),
"Actual error: {:?}", "Actual error: {:?}",
tag_declarator_err tag_declarator_err
); );
@ -3465,7 +3295,7 @@ let notTagIdentifier = !myTag";
assert!( assert!(
tag_identifier_err tag_identifier_err
.message() .message()
.starts_with("Cannot apply unary operator ! to non-boolean value: {\"type\":\"TagIdentifier\","), .starts_with("Cannot apply unary operator ! to non-boolean value: TagIdentifier"),
"Actual error: {:?}", "Actual error: {:?}",
tag_identifier_err tag_identifier_err
); );
@ -3603,10 +3433,10 @@ let w = f() + f()
fn test_assign_args_to_params() { fn test_assign_args_to_params() {
// Set up a little framework for this test. // Set up a little framework for this test.
fn mem(number: usize) -> KclValue { fn mem(number: usize) -> KclValue {
KclValue::UserVal(UserVal { KclValue::Int {
value: number.into(), value: number as i64,
meta: Default::default(), meta: Default::default(),
}) }
} }
fn ident(s: &'static str) -> Node<Identifier> { fn ident(s: &'static str) -> Node<Identifier> {
Node::no_src(Identifier { Node::no_src(Identifier {

File diff suppressed because it is too large Load Diff

View File

@ -1,47 +1,25 @@
use derive_docs::stdlib; use derive_docs::stdlib;
use serde_json::Value as JValue;
use super::{args::FromArgs, Args, FnAsArg}; use super::{args::FromArgs, Args, FnAsArg};
use crate::{ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
executor::{ExecState, KclValue, SourceRange, UserVal}, executor::{ExecState, KclValue, SourceRange},
function_param::FunctionParam, function_param::FunctionParam,
}; };
/// Apply a function to each element of an array. /// Apply a function to each element of an array.
pub async fn map(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn map(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (array, f): (Vec<JValue>, FnAsArg<'_>) = FromArgs::from_args(&args, 0)?; let (array, f): (Vec<KclValue>, FnAsArg<'_>) = FromArgs::from_args(&args, 0)?;
let array: Vec<KclValue> = array let meta = vec![args.source_range.into()];
.into_iter()
.map(|jval| {
KclValue::UserVal(UserVal {
value: jval,
meta: vec![args.source_range.into()],
})
})
.collect();
let map_fn = FunctionParam { let map_fn = FunctionParam {
inner: f.func, inner: f.func,
fn_expr: f.expr, fn_expr: f.expr,
meta: vec![args.source_range.into()], meta: meta.clone(),
ctx: args.ctx.clone(), ctx: args.ctx.clone(),
memory: *f.memory, memory: *f.memory,
}; };
let new_array = inner_map(array, map_fn, exec_state, &args).await?; let new_array = inner_map(array, map_fn, exec_state, &args).await?;
let unwrapped = new_array Ok(KclValue::Array { value: new_array, meta })
.clone()
.into_iter()
.map(|k| match k {
KclValue::UserVal(user_val) => Ok(user_val.value),
_ => Err(()),
})
.collect::<Result<Vec<_>, _>>();
if let Ok(unwrapped) = unwrapped {
let uv = UserVal::new(vec![args.source_range.into()], unwrapped);
return Ok(KclValue::UserVal(uv));
}
let uv = UserVal::new(vec![args.source_range.into()], new_array);
Ok(KclValue::UserVal(uv))
} }
/// Apply a function to every element of a list. /// Apply a function to every element of a list.
@ -110,16 +88,7 @@ async fn call_map_closure<'a>(
/// For each item in an array, update a value. /// For each item in an array, update a value.
pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (array, start, f): (Vec<JValue>, KclValue, FnAsArg<'_>) = FromArgs::from_args(&args, 0)?; let (array, start, f): (Vec<KclValue>, KclValue, FnAsArg<'_>) = FromArgs::from_args(&args, 0)?;
let array: Vec<KclValue> = array
.into_iter()
.map(|jval| {
KclValue::UserVal(UserVal {
value: jval,
meta: vec![args.source_range.into()],
})
})
.collect();
let reduce_fn = FunctionParam { let reduce_fn = FunctionParam {
inner: f.func, inner: f.func,
fn_expr: f.expr, fn_expr: f.expr,
@ -206,50 +175,26 @@ async fn call_reduce_closure<'a>(
#[stdlib { #[stdlib {
name = "push", name = "push",
}] }]
async fn inner_push(array: Vec<KclValue>, elem: KclValue, args: &Args) -> Result<KclValue, KclError> { async fn inner_push(mut array: Vec<KclValue>, elem: KclValue, args: &Args) -> Result<KclValue, KclError> {
// Unwrap the KclValues to JValues for manipulation // Unwrap the KclValues to JValues for manipulation
let mut unwrapped_array = array array.push(elem);
.into_iter() Ok(KclValue::Array {
.map(|k| match k { value: array,
KclValue::UserVal(user_val) => Ok(user_val.value), meta: vec![args.source_range.into()],
_ => Err(KclError::Semantic(KclErrorDetails { })
message: "Expected a UserVal in array".to_string(),
source_ranges: vec![args.source_range],
})),
})
.collect::<Result<Vec<_>, _>>()?;
// Unwrap the element
let unwrapped_elem = match elem {
KclValue::UserVal(user_val) => user_val.value,
_ => {
return Err(KclError::Semantic(KclErrorDetails {
message: "Expected a UserVal as element".to_string(),
source_ranges: vec![args.source_range],
}));
}
};
// Append the element to the array
unwrapped_array.push(unwrapped_elem);
// Wrap the new array into a UserVal with the source range metadata
let uv = UserVal::new(vec![args.source_range.into()], unwrapped_array);
// Return the new array wrapped as a KclValue::UserVal
Ok(KclValue::UserVal(uv))
} }
pub async fn push(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn push(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
// Extract the array and the element from the arguments // Extract the array and the element from the arguments
let (array_jvalues, elem): (Vec<JValue>, KclValue) = FromArgs::from_args(&args, 0)?; let (val, elem): (KclValue, KclValue) = FromArgs::from_args(&args, 0)?;
// Convert the array of JValue into Vec<KclValue> let meta = vec![args.source_range];
let array: Vec<KclValue> = array_jvalues let KclValue::Array { value: array, meta: _ } = val else {
.into_iter() let actual_type = val.human_friendly_type();
.map(|jval| KclValue::UserVal(UserVal::new(vec![args.source_range.into()], jval))) return Err(KclError::Semantic(KclErrorDetails {
.collect(); source_ranges: meta,
message: format!("You can't push to a value of type {actual_type}, only an array"),
// Call the inner_push function }));
};
inner_push(array, elem, &args).await inner_push(array, elem, &args).await
} }

View File

@ -24,7 +24,7 @@ async fn _assert(value: bool, message: &str, args: &Args) -> Result<(), KclError
pub async fn assert(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn assert(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (data, description): (bool, String) = args.get_data()?; let (data, description): (bool, String) = args.get_data()?;
inner_assert(data, &description, &args).await?; inner_assert(data, &description, &args).await?;
Ok(args.make_null_user_val()) Ok(args.make_user_val_from_f64(0.0)) // TODO: Add a new Void enum for fns that don't return anything.
} }
/// Check a value at runtime, and raise an error if the argument provided /// Check a value at runtime, and raise an error if the argument provided
@ -44,7 +44,7 @@ async fn inner_assert(data: bool, message: &str, args: &Args) -> Result<(), KclE
pub async fn assert_lt(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn assert_lt(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (left, right, description): (f64, f64, String) = args.get_data()?; let (left, right, description): (f64, f64, String) = args.get_data()?;
inner_assert_lt(left, right, &description, &args).await?; inner_assert_lt(left, right, &description, &args).await?;
Ok(args.make_null_user_val()) Ok(args.make_user_val_from_f64(0.0)) // TODO: Add a new Void enum for fns that don't return anything.
} }
/// Check that a numerical value is less than to another at runtime, /// Check that a numerical value is less than to another at runtime,
@ -63,7 +63,7 @@ async fn inner_assert_lt(left: f64, right: f64, message: &str, args: &Args) -> R
pub async fn assert_gt(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn assert_gt(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (left, right, description): (f64, f64, String) = args.get_data()?; let (left, right, description): (f64, f64, String) = args.get_data()?;
inner_assert_gt(left, right, &description, &args).await?; inner_assert_gt(left, right, &description, &args).await?;
Ok(args.make_null_user_val()) Ok(args.make_user_val_from_f64(0.0)) // TODO: Add a new Void enum for fns that don't return anything.
} }
/// Check that a numerical value equals another at runtime, /// Check that a numerical value equals another at runtime,
@ -96,7 +96,7 @@ async fn inner_assert_equal(left: f64, right: f64, epsilon: f64, message: &str,
pub async fn assert_equal(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn assert_equal(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (left, right, epsilon, description): (f64, f64, f64, String) = args.get_data()?; let (left, right, epsilon, description): (f64, f64, f64, String) = args.get_data()?;
inner_assert_equal(left, right, epsilon, &description, &args).await?; inner_assert_equal(left, right, epsilon, &description, &args).await?;
Ok(args.make_null_user_val()) Ok(args.make_user_val_from_f64(0.0)) // TODO: Add a new Void enum for fns that don't return anything.
} }
/// Check that a numerical value is greater than another at runtime, /// Check that a numerical value is greater than another at runtime,
@ -115,7 +115,7 @@ async fn inner_assert_gt(left: f64, right: f64, message: &str, args: &Args) -> R
pub async fn assert_lte(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn assert_lte(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (left, right, description): (f64, f64, String) = args.get_data()?; let (left, right, description): (f64, f64, String) = args.get_data()?;
inner_assert_lte(left, right, &description, &args).await?; inner_assert_lte(left, right, &description, &args).await?;
Ok(args.make_null_user_val()) Ok(args.make_user_val_from_f64(0.0)) // TODO: Add a new Void enum for fns that don't return anything.
} }
/// Check that a numerical value is less than or equal to another at runtime, /// Check that a numerical value is less than or equal to another at runtime,
@ -135,7 +135,7 @@ async fn inner_assert_lte(left: f64, right: f64, message: &str, args: &Args) ->
pub async fn assert_gte(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn assert_gte(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (left, right, description): (f64, f64, String) = args.get_data()?; let (left, right, description): (f64, f64, String) = args.get_data()?;
inner_assert_gte(left, right, &description, &args).await?; inner_assert_gte(left, right, &description, &args).await?;
Ok(args.make_null_user_val()) Ok(args.make_user_val_from_f64(0.0)) // TODO: Add a new Void enum for fns that don't return anything.
} }
/// Check that a numerical value is greater than or equal to another at runtime, /// Check that a numerical value is greater than or equal to another at runtime,

View File

@ -233,7 +233,7 @@ pub(crate) async fn do_post_extrude(
tag: path.get_base().tag.clone(), tag: path.get_base().tag.clone(),
geo_meta: GeoMeta { geo_meta: GeoMeta {
id: path.get_base().geo_meta.id, id: path.get_base().geo_meta.id,
metadata: path.get_base().geo_meta.metadata.clone(), metadata: path.get_base().geo_meta.metadata,
}, },
}); });
Some(extrude_surface) Some(extrude_surface)
@ -244,7 +244,7 @@ pub(crate) async fn do_post_extrude(
tag: path.get_base().tag.clone(), tag: path.get_base().tag.clone(),
geo_meta: GeoMeta { geo_meta: GeoMeta {
id: path.get_base().geo_meta.id, id: path.get_base().geo_meta.id,
metadata: path.get_base().geo_meta.metadata.clone(), metadata: path.get_base().geo_meta.metadata,
}, },
}); });
Some(extrude_surface) Some(extrude_surface)
@ -259,7 +259,7 @@ pub(crate) async fn do_post_extrude(
tag: path.get_base().tag.clone(), tag: path.get_base().tag.clone(),
geo_meta: GeoMeta { geo_meta: GeoMeta {
id: path.get_base().geo_meta.id, id: path.get_base().geo_meta.id,
metadata: path.get_base().geo_meta.metadata.clone(), metadata: path.get_base().geo_meta.metadata,
}, },
}); });
Some(extrude_surface) Some(extrude_surface)

View File

@ -14,7 +14,7 @@ use uuid::Uuid;
use crate::{ use crate::{
ast::types::TagNode, ast::types::TagNode,
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
executor::{EdgeCut, ExecState, ExtrudeSurface, FilletSurface, GeoMeta, KclValue, Solid, TagIdentifier, UserVal}, executor::{EdgeCut, ExecState, ExtrudeSurface, FilletSurface, GeoMeta, KclValue, Solid, TagIdentifier},
settings::types::UnitLength, settings::types::UnitLength,
std::Args, std::Args,
}; };
@ -186,15 +186,10 @@ pub async fn get_opposite_edge(exec_state: &mut ExecState, args: Args) -> Result
let tag: TagIdentifier = args.get_data()?; let tag: TagIdentifier = args.get_data()?;
let edge = inner_get_opposite_edge(tag, exec_state, args.clone()).await?; let edge = inner_get_opposite_edge(tag, exec_state, args.clone()).await?;
Ok(KclValue::UserVal(UserVal { Ok(KclValue::Uuid {
value: serde_json::to_value(edge).map_err(|e| { value: edge,
KclError::Type(KclErrorDetails {
message: format!("Failed to convert Uuid to json: {}", e),
source_ranges: vec![args.source_range],
})
})?,
meta: vec![args.source_range.into()], meta: vec![args.source_range.into()],
})) })
} }
/// Get the opposite edge to the edge given. /// Get the opposite edge to the edge given.
@ -264,15 +259,10 @@ pub async fn get_next_adjacent_edge(exec_state: &mut ExecState, args: Args) -> R
let tag: TagIdentifier = args.get_data()?; let tag: TagIdentifier = args.get_data()?;
let edge = inner_get_next_adjacent_edge(tag, exec_state, args.clone()).await?; let edge = inner_get_next_adjacent_edge(tag, exec_state, args.clone()).await?;
Ok(KclValue::UserVal(UserVal { Ok(KclValue::Uuid {
value: serde_json::to_value(edge).map_err(|e| { value: edge,
KclError::Type(KclErrorDetails {
message: format!("Failed to convert Uuid to json: {}", e),
source_ranges: vec![args.source_range],
})
})?,
meta: vec![args.source_range.into()], meta: vec![args.source_range.into()],
})) })
} }
/// Get the next adjacent edge to the edge given. /// Get the next adjacent edge to the edge given.
@ -354,15 +344,10 @@ pub async fn get_previous_adjacent_edge(exec_state: &mut ExecState, args: Args)
let tag: TagIdentifier = args.get_data()?; let tag: TagIdentifier = args.get_data()?;
let edge = inner_get_previous_adjacent_edge(tag, exec_state, args.clone()).await?; let edge = inner_get_previous_adjacent_edge(tag, exec_state, args.clone()).await?;
Ok(KclValue::UserVal(UserVal { Ok(KclValue::Uuid {
value: serde_json::to_value(edge).map_err(|e| { value: edge,
KclError::Type(KclErrorDetails {
message: format!("Failed to convert Uuid to json: {}", e),
source_ranges: vec![args.source_range],
})
})?,
meta: vec![args.source_range.into()], meta: vec![args.source_range.into()],
})) })
} }
/// Get the previous adjacent edge to the edge given. /// Get the previous adjacent edge to the edge given.

View File

@ -40,7 +40,7 @@ pub async fn cos(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
let num = args.get_number()?; let num = args.get_number()?;
let result = inner_cos(num)?; let result = inner_cos(num)?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Compute the cosine of a number (in radians). /// Compute the cosine of a number (in radians).
@ -70,7 +70,7 @@ pub async fn sin(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
let num = args.get_number()?; let num = args.get_number()?;
let result = inner_sin(num)?; let result = inner_sin(num)?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Compute the sine of a number (in radians). /// Compute the sine of a number (in radians).
@ -100,7 +100,7 @@ pub async fn tan(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
let num = args.get_number()?; let num = args.get_number()?;
let result = inner_tan(num)?; let result = inner_tan(num)?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Compute the tangent of a number (in radians). /// Compute the tangent of a number (in radians).
@ -129,7 +129,7 @@ fn inner_tan(num: f64) -> Result<f64, KclError> {
pub async fn pi(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn pi(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let result = inner_pi()?; let result = inner_pi()?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Return the value of `pi`. Archimedes constant (π). /// Return the value of `pi`. Archimedes constant (π).
@ -155,7 +155,7 @@ pub async fn sqrt(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
let num = args.get_number()?; let num = args.get_number()?;
let result = inner_sqrt(num)?; let result = inner_sqrt(num)?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Compute the square root of a number. /// Compute the square root of a number.
@ -185,7 +185,7 @@ pub async fn abs(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
let num = args.get_number()?; let num = args.get_number()?;
let result = inner_abs(num)?; let result = inner_abs(num)?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Compute the absolute value of a number. /// Compute the absolute value of a number.
@ -222,7 +222,7 @@ pub async fn floor(_exec_state: &mut ExecState, args: Args) -> Result<KclValue,
let num = args.get_number()?; let num = args.get_number()?;
let result = inner_floor(num)?; let result = inner_floor(num)?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Compute the largest integer less than or equal to a number. /// Compute the largest integer less than or equal to a number.
@ -250,7 +250,7 @@ pub async fn ceil(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
let num = args.get_number()?; let num = args.get_number()?;
let result = inner_ceil(num)?; let result = inner_ceil(num)?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Compute the smallest integer greater than or equal to a number. /// Compute the smallest integer greater than or equal to a number.
@ -278,7 +278,7 @@ pub async fn min(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
let nums = args.get_number_array()?; let nums = args.get_number_array()?;
let result = inner_min(nums); let result = inner_min(nums);
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Compute the minimum of the given arguments. /// Compute the minimum of the given arguments.
@ -315,7 +315,7 @@ pub async fn max(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
let nums = args.get_number_array()?; let nums = args.get_number_array()?;
let result = inner_max(nums); let result = inner_max(nums);
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Compute the maximum of the given arguments. /// Compute the maximum of the given arguments.
@ -366,7 +366,7 @@ pub async fn pow(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
let result = inner_pow(nums[0], nums[1])?; let result = inner_pow(nums[0], nums[1])?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Compute the number to a power. /// Compute the number to a power.
@ -396,7 +396,7 @@ pub async fn acos(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
let num = args.get_number()?; let num = args.get_number()?;
let result = inner_acos(num)?; let result = inner_acos(num)?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Compute the arccosine of a number (in radians). /// Compute the arccosine of a number (in radians).
@ -427,7 +427,7 @@ pub async fn asin(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
let num = args.get_number()?; let num = args.get_number()?;
let result = inner_asin(num)?; let result = inner_asin(num)?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Compute the arcsine of a number (in radians). /// Compute the arcsine of a number (in radians).
@ -457,7 +457,7 @@ pub async fn atan(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
let num = args.get_number()?; let num = args.get_number()?;
let result = inner_atan(num)?; let result = inner_atan(num)?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Compute the arctangent of a number (in radians). /// Compute the arctangent of a number (in radians).
@ -504,7 +504,7 @@ pub async fn log(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
} }
let result = inner_log(nums[0], nums[1])?; let result = inner_log(nums[0], nums[1])?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Compute the logarithm of the number with respect to an arbitrary base. /// Compute the logarithm of the number with respect to an arbitrary base.
@ -536,7 +536,7 @@ pub async fn log2(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
let num = args.get_number()?; let num = args.get_number()?;
let result = inner_log2(num)?; let result = inner_log2(num)?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Compute the base 2 logarithm of the number. /// Compute the base 2 logarithm of the number.
@ -564,7 +564,7 @@ pub async fn log10(_exec_state: &mut ExecState, args: Args) -> Result<KclValue,
let num = args.get_number()?; let num = args.get_number()?;
let result = inner_log10(num)?; let result = inner_log10(num)?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Compute the base 10 logarithm of the number. /// Compute the base 10 logarithm of the number.
@ -592,7 +592,7 @@ pub async fn ln(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
let num = args.get_number()?; let num = args.get_number()?;
let result = inner_ln(num)?; let result = inner_ln(num)?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Compute the natural logarithm of the number. /// Compute the natural logarithm of the number.
@ -619,7 +619,7 @@ fn inner_ln(num: f64) -> Result<f64, KclError> {
pub async fn e(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn e(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let result = inner_e()?; let result = inner_e()?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Return the value of Eulers number `e`. /// Return the value of Eulers number `e`.
@ -648,7 +648,7 @@ fn inner_e() -> Result<f64, KclError> {
pub async fn tau(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn tau(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let result = inner_tau()?; let result = inner_tau()?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Return the value of `tau`. The full circle constant (τ). Equal to 2π. /// Return the value of `tau`. The full circle constant (τ). Equal to 2π.
@ -678,7 +678,7 @@ pub async fn to_radians(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
let num = args.get_number()?; let num = args.get_number()?;
let result = inner_to_radians(num)?; let result = inner_to_radians(num)?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Converts a number from degrees to radians. /// Converts a number from degrees to radians.
@ -708,7 +708,7 @@ pub async fn to_degrees(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
let num = args.get_number()?; let num = args.get_number()?;
let result = inner_to_degrees(num)?; let result = inner_to_degrees(num)?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Converts a number from radians to degrees. /// Converts a number from radians to degrees.

View File

@ -244,7 +244,7 @@ pub enum FunctionKind {
pub async fn leg_length(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn leg_length(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (hypotenuse, leg) = args.get_hypotenuse_leg()?; let (hypotenuse, leg) = args.get_hypotenuse_leg()?;
let result = inner_leg_length(hypotenuse, leg); let result = inner_leg_length(hypotenuse, leg);
args.make_user_val_from_f64(result) Ok(KclValue::from_number(result, vec![args.into()]))
} }
/// Compute the length of the given leg. /// Compute the length of the given leg.
@ -264,7 +264,7 @@ fn inner_leg_length(hypotenuse: f64, leg: f64) -> f64 {
pub async fn leg_angle_x(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn leg_angle_x(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (hypotenuse, leg) = args.get_hypotenuse_leg()?; let (hypotenuse, leg) = args.get_hypotenuse_leg()?;
let result = inner_leg_angle_x(hypotenuse, leg); let result = inner_leg_angle_x(hypotenuse, leg);
args.make_user_val_from_f64(result) Ok(KclValue::from_number(result, vec![args.into()]))
} }
/// Compute the angle of the given leg for x. /// Compute the angle of the given leg for x.
@ -284,7 +284,7 @@ fn inner_leg_angle_x(hypotenuse: f64, leg: f64) -> f64 {
pub async fn leg_angle_y(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn leg_angle_y(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (hypotenuse, leg) = args.get_hypotenuse_leg()?; let (hypotenuse, leg) = args.get_hypotenuse_leg()?;
let result = inner_leg_angle_y(hypotenuse, leg); let result = inner_leg_angle_y(hypotenuse, leg);
args.make_user_val_from_f64(result) Ok(KclValue::from_number(result, vec![args.into()]))
} }
/// Compute the angle of the given leg for y. /// Compute the angle of the given leg for y.

View File

@ -14,13 +14,10 @@ use kittycad_modeling_cmds::{
}; };
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value as JValue;
use crate::{ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
executor::{ executor::{ExecState, Geometries, Geometry, KclValue, Point3d, Sketch, SketchSet, Solid, SolidSet, SourceRange},
ExecState, Geometries, Geometry, KclValue, Point3d, Sketch, SketchSet, Solid, SolidSet, SourceRange, UserVal,
},
function_param::FunctionParam, function_param::FunctionParam,
std::{types::Uint, Args}, std::{types::Uint, Args},
}; };
@ -361,10 +358,10 @@ async fn make_transform<'a>(
exec_state: &mut ExecState, exec_state: &mut ExecState,
) -> Result<Transform, KclError> { ) -> Result<Transform, KclError> {
// Call the transform fn for this repetition. // Call the transform fn for this repetition.
let repetition_num = KclValue::UserVal(UserVal { let repetition_num = KclValue::Int {
value: JValue::Number(i.into()), value: i.into(),
meta: vec![source_range.into()], meta: vec![source_range.into()],
}); };
let transform_fn_args = vec![repetition_num]; let transform_fn_args = vec![repetition_num];
let transform_fn_return = transform_function.call(exec_state, transform_fn_args).await?; let transform_fn_return = transform_function.call(exec_state, transform_fn_args).await?;
@ -376,7 +373,7 @@ async fn make_transform<'a>(
source_ranges: source_ranges.clone(), source_ranges: source_ranges.clone(),
}) })
})?; })?;
let KclValue::UserVal(transform) = transform_fn_return else { let KclValue::Object { value: transform, meta } = transform_fn_return else {
return Err(KclError::Semantic(KclErrorDetails { return Err(KclError::Semantic(KclErrorDetails {
message: "Transform function must return a transform object".to_string(), message: "Transform function must return a transform object".to_string(),
source_ranges: source_ranges.clone(), source_ranges: source_ranges.clone(),
@ -384,9 +381,9 @@ async fn make_transform<'a>(
}; };
// Apply defaults to the transform. // Apply defaults to the transform.
let replicate = match transform.value.get("replicate") { let replicate = match transform.get("replicate") {
Some(JValue::Bool(true)) => true, Some(KclValue::Bool { value: true, .. }) => true,
Some(JValue::Bool(false)) => false, Some(KclValue::Bool { value: false, .. }) => false,
Some(_) => { Some(_) => {
return Err(KclError::Semantic(KclErrorDetails { return Err(KclError::Semantic(KclErrorDetails {
message: "The 'replicate' key must be a bool".to_string(), message: "The 'replicate' key must be a bool".to_string(),
@ -395,38 +392,43 @@ async fn make_transform<'a>(
} }
None => true, None => true,
}; };
let scale = match transform.value.get("scale") { let scale = match transform.get("scale") {
Some(x) => array_to_point3d(x, source_ranges.clone())?, Some(x) => array_to_point3d(x, source_ranges.clone())?,
None => Point3d { x: 1.0, y: 1.0, z: 1.0 }, None => Point3d { x: 1.0, y: 1.0, z: 1.0 },
}; };
let translate = match transform.value.get("translate") { let translate = match transform.get("translate") {
Some(x) => array_to_point3d(x, source_ranges.clone())?, Some(x) => array_to_point3d(x, source_ranges.clone())?,
None => Point3d { x: 0.0, y: 0.0, z: 0.0 }, None => Point3d { x: 0.0, y: 0.0, z: 0.0 },
}; };
let mut rotation = Rotation::default(); let mut rotation = Rotation::default();
if let Some(rot) = transform.value.get("rotation") { if let Some(rot) = transform.get("rotation") {
let KclValue::Object { value: rot, meta: _ } = rot else {
return Err(KclError::Semantic(KclErrorDetails {
message: "The 'rotation' key must be an object (with optional fields 'angle', 'axis' and 'origin')"
.to_string(),
source_ranges: source_ranges.clone(),
}));
};
if let Some(axis) = rot.get("axis") { if let Some(axis) = rot.get("axis") {
rotation.axis = array_to_point3d(axis, source_ranges.clone())?.into(); rotation.axis = array_to_point3d(axis, source_ranges.clone())?.into();
} }
if let Some(angle) = rot.get("angle") { if let Some(angle) = rot.get("angle") {
match angle { match angle {
JValue::Number(number) => { KclValue::Number { value: number, meta: _ } => {
if let Some(number) = number.as_f64() { rotation.angle = Angle::from_degrees(*number);
rotation.angle = Angle::from_degrees(number);
}
} }
_ => { _ => {
return Err(KclError::Semantic(KclErrorDetails { return Err(KclError::Semantic(KclErrorDetails {
message: "The 'rotation.angle' key must be a number (of degrees)".to_string(), message: "The 'rotation.angle' key must be a number (of degrees)".to_string(),
source_ranges: source_ranges.clone(), source_ranges: meta.iter().map(|m| m.source_range).collect(),
})); }));
} }
} }
} }
if let Some(origin) = rot.get("origin") { if let Some(origin) = rot.get("origin") {
rotation.origin = match origin { rotation.origin = match origin {
JValue::String(s) if s == "local" => OriginType::Local, KclValue::String { value: s, meta: _ } if s == "local" => OriginType::Local,
JValue::String(s) if s == "global" => OriginType::Global, KclValue::String { value: s, meta: _ } if s == "global" => OriginType::Global,
other => { other => {
let origin = array_to_point3d(other, source_ranges.clone())?.into(); let origin = array_to_point3d(other, source_ranges.clone())?.into();
OriginType::Custom { origin } OriginType::Custom { origin }
@ -443,8 +445,8 @@ async fn make_transform<'a>(
Ok(t) Ok(t)
} }
fn array_to_point3d(json: &JValue, source_ranges: Vec<SourceRange>) -> Result<Point3d, KclError> { fn array_to_point3d(val: &KclValue, source_ranges: Vec<SourceRange>) -> Result<Point3d, KclError> {
let JValue::Array(arr) = json else { let KclValue::Array { value: arr, meta } = val else {
return Err(KclError::Semantic(KclErrorDetails { return Err(KclError::Semantic(KclErrorDetails {
message: "Expected an array of 3 numbers (i.e. a 3D point)".to_string(), message: "Expected an array of 3 numbers (i.e. a 3D point)".to_string(),
source_ranges, source_ranges,
@ -457,17 +459,21 @@ fn array_to_point3d(json: &JValue, source_ranges: Vec<SourceRange>) -> Result<Po
source_ranges, source_ranges,
})); }));
}; };
// Gets an f64 from a JSON value, returns Option. // Gets an f64 from a KCL value.
let f = |j: &JValue| j.as_number().and_then(|num| num.as_f64()).map(|x| x.to_owned()); let f = |k: &KclValue, component: char| {
let err = |component| { use super::args::FromKclValue;
KclError::Semantic(KclErrorDetails { if let Some(value) = f64::from_mem_item(k) {
message: format!("{component} component of this point was not a number"), Ok(value)
source_ranges: source_ranges.clone(), } else {
}) Err(KclError::Semantic(KclErrorDetails {
message: format!("{component} component of this point was not a number"),
source_ranges: meta.iter().map(|m| m.source_range).collect(),
}))
}
}; };
let x = f(&arr[0]).ok_or_else(|| err("X"))?; let x = f(&arr[0], 'x')?;
let y = f(&arr[1]).ok_or_else(|| err("Y"))?; let y = f(&arr[1], 'y')?;
let z = f(&arr[2]).ok_or_else(|| err("Z"))?; let z = f(&arr[2], 'z')?;
Ok(Point3d { x, y, z }) Ok(Point3d { x, y, z })
} }
@ -477,8 +483,22 @@ mod tests {
#[test] #[test]
fn test_array_to_point3d() { fn test_array_to_point3d() {
let input = serde_json::json! { let input = KclValue::Array {
[1.1, 2.2, 3.3] value: vec![
KclValue::Number {
value: 1.1,
meta: Default::default(),
},
KclValue::Number {
value: 2.2,
meta: Default::default(),
},
KclValue::Number {
value: 3.3,
meta: Default::default(),
},
],
meta: Default::default(),
}; };
let expected = Point3d { x: 1.1, y: 2.2, z: 3.3 }; let expected = Point3d { x: 1.1, y: 2.2, z: 3.3 };
let actual = array_to_point3d(&input, Vec::new()); let actual = array_to_point3d(&input, Vec::new());

View File

@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
errors::KclError, errors::KclError,
executor::{ExecState, KclValue, Metadata, Plane, UserVal}, executor::{ExecState, KclValue, Plane},
std::{sketch::PlaneData, Args}, std::{sketch::PlaneData, Args},
}; };
@ -50,15 +50,9 @@ impl From<StandardPlane> for PlaneData {
/// Offset a plane by a distance along its normal. /// Offset a plane by a distance along its normal.
pub async fn offset_plane(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn offset_plane(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (std_plane, offset): (StandardPlane, f64) = args.get_data_and_float()?; let (std_plane, offset): (StandardPlane, f64) = args.get_data_and_float()?;
let plane_data = inner_offset_plane(std_plane, offset, exec_state).await?;
let plane = inner_offset_plane(std_plane, offset, exec_state).await?; let plane = Plane::from_plane_data(plane_data, exec_state);
Ok(KclValue::Plane(Box::new(plane)))
Ok(KclValue::UserVal(UserVal::new(
vec![Metadata {
source_range: args.source_range,
}],
plane,
)))
} }
/// Offset a plane by a distance along its normal. /// Offset a plane by a distance along its normal.
@ -129,6 +123,20 @@ pub async fn offset_plane(exec_state: &mut ExecState, args: Args) -> Result<KclV
/// ///
/// loft([squareSketch, circleSketch]) /// loft([squareSketch, circleSketch])
/// ``` /// ```
/// ```no_run
/// // A circle on the XY plane
/// startSketchOn("XY")
/// |> startProfileAt([0, 0], %)
/// |> circle({radius: 10, center: [0, 0]}, %)
///
/// // Triangle on the plane 4 units above
/// startSketchOn(offsetPlane("XY", 4))
/// |> startProfileAt([0, 0], %)
/// |> line([10, 0], %)
/// |> line([0, 10], %)
/// |> close(%)
/// ```
#[stdlib { #[stdlib {
name = "offsetPlane", name = "offsetPlane",
}] }]

View File

@ -60,7 +60,7 @@ pub async fn segment_end_x(exec_state: &mut ExecState, args: Args) -> Result<Kcl
let tag: TagIdentifier = args.get_data()?; let tag: TagIdentifier = args.get_data()?;
let result = inner_segment_end_x(&tag, exec_state, args.clone())?; let result = inner_segment_end_x(&tag, exec_state, args.clone())?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Compute the ending point of the provided line segment along the 'x' axis. /// Compute the ending point of the provided line segment along the 'x' axis.
@ -96,7 +96,7 @@ pub async fn segment_end_y(exec_state: &mut ExecState, args: Args) -> Result<Kcl
let tag: TagIdentifier = args.get_data()?; let tag: TagIdentifier = args.get_data()?;
let result = inner_segment_end_y(&tag, exec_state, args.clone())?; let result = inner_segment_end_y(&tag, exec_state, args.clone())?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Compute the ending point of the provided line segment along the 'y' axis. /// Compute the ending point of the provided line segment along the 'y' axis.
@ -179,7 +179,7 @@ pub async fn segment_start_x(exec_state: &mut ExecState, args: Args) -> Result<K
let tag: TagIdentifier = args.get_data()?; let tag: TagIdentifier = args.get_data()?;
let result = inner_segment_start_x(&tag, exec_state, args.clone())?; let result = inner_segment_start_x(&tag, exec_state, args.clone())?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Compute the starting point of the provided line segment along the 'x' axis. /// Compute the starting point of the provided line segment along the 'x' axis.
@ -215,7 +215,7 @@ pub async fn segment_start_y(exec_state: &mut ExecState, args: Args) -> Result<K
let tag: TagIdentifier = args.get_data()?; let tag: TagIdentifier = args.get_data()?;
let result = inner_segment_start_y(&tag, exec_state, args.clone())?; let result = inner_segment_start_y(&tag, exec_state, args.clone())?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Compute the starting point of the provided line segment along the 'y' axis. /// Compute the starting point of the provided line segment along the 'y' axis.
@ -251,7 +251,7 @@ pub async fn last_segment_x(_exec_state: &mut ExecState, args: Args) -> Result<K
let sketch = args.get_sketch()?; let sketch = args.get_sketch()?;
let result = inner_last_segment_x(sketch, args.clone())?; let result = inner_last_segment_x(sketch, args.clone())?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Extract the 'x' axis value of the last line segment in the provided 2-d /// Extract the 'x' axis value of the last line segment in the provided 2-d
@ -291,7 +291,7 @@ pub async fn last_segment_y(_exec_state: &mut ExecState, args: Args) -> Result<K
let sketch = args.get_sketch()?; let sketch = args.get_sketch()?;
let result = inner_last_segment_y(sketch, args.clone())?; let result = inner_last_segment_y(sketch, args.clone())?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Extract the 'y' axis value of the last line segment in the provided 2-d /// Extract the 'y' axis value of the last line segment in the provided 2-d
@ -330,7 +330,7 @@ fn inner_last_segment_y(sketch: Sketch, args: Args) -> Result<f64, KclError> {
pub async fn segment_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn segment_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let tag: TagIdentifier = args.get_data()?; let tag: TagIdentifier = args.get_data()?;
let result = inner_segment_length(&tag, exec_state, args.clone())?; let result = inner_segment_length(&tag, exec_state, args.clone())?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Compute the length of the provided line segment. /// Compute the length of the provided line segment.
@ -376,7 +376,7 @@ pub async fn segment_angle(exec_state: &mut ExecState, args: Args) -> Result<Kcl
let tag: TagIdentifier = args.get_data()?; let tag: TagIdentifier = args.get_data()?;
let result = inner_segment_angle(&tag, exec_state, args.clone())?; let result = inner_segment_angle(&tag, exec_state, args.clone())?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Compute the angle (in degrees) of the provided line segment. /// Compute the angle (in degrees) of the provided line segment.
@ -415,7 +415,7 @@ fn inner_segment_angle(tag: &TagIdentifier, exec_state: &mut ExecState, args: Ar
pub async fn angle_to_match_length_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn angle_to_match_length_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (tag, to, sketch) = args.get_tag_to_number_sketch()?; let (tag, to, sketch) = args.get_tag_to_number_sketch()?;
let result = inner_angle_to_match_length_x(&tag, to, sketch, exec_state, args.clone())?; let result = inner_angle_to_match_length_x(&tag, to, sketch, exec_state, args.clone())?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Returns the angle to match the given length for x. /// Returns the angle to match the given length for x.
@ -478,7 +478,7 @@ fn inner_angle_to_match_length_x(
pub async fn angle_to_match_length_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn angle_to_match_length_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (tag, to, sketch) = args.get_tag_to_number_sketch()?; let (tag, to, sketch) = args.get_tag_to_number_sketch()?;
let result = inner_angle_to_match_length_y(&tag, to, sketch, exec_state, args.clone())?; let result = inner_angle_to_match_length_y(&tag, to, sketch, exec_state, args.clone())?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Returns the angle to match the given length for y. /// Returns the angle to match the given length for y.

View File

@ -48,7 +48,9 @@ pub async fn circle(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
args.get_circle_args()?; args.get_circle_args()?;
let sketch = inner_circle(data, sketch_surface_or_group, tag, exec_state, args).await?; let sketch = inner_circle(data, sketch_surface_or_group, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(sketch.meta.clone(), sketch)) Ok(KclValue::Sketch {
value: Box::new(sketch),
})
} }
/// Construct a 2-dimensional circle, of the specified radius, centered at /// Construct a 2-dimensional circle, of the specified radius, centered at
@ -166,7 +168,7 @@ pub struct PolygonData {
pub center: [f64; 2], pub center: [f64; 2],
/// The type of the polygon (inscribed or circumscribed) /// The type of the polygon (inscribed or circumscribed)
#[serde(skip)] #[serde(skip)]
polygon_type: PolygonType, pub polygon_type: PolygonType,
/// Whether the polygon is inscribed (true) or circumscribed (false) about a circle with the specified radius /// Whether the polygon is inscribed (true) or circumscribed (false) about a circle with the specified radius
#[serde(default = "default_inscribed")] #[serde(default = "default_inscribed")]
pub inscribed: bool, pub inscribed: bool,
@ -182,7 +184,9 @@ pub async fn polygon(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
args.get_polygon_args()?; args.get_polygon_args()?;
let sketch = inner_polygon(data, sketch_surface_or_group, tag, exec_state, args).await?; let sketch = inner_polygon(data, sketch_surface_or_group, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(sketch.meta.clone(), sketch)) Ok(KclValue::Sketch {
value: Box::new(sketch),
})
} }
/// Create a regular polygon with the specified number of sides that is either inscribed or circumscribed around a circle of the specified radius. /// Create a regular polygon with the specified number of sides that is either inscribed or circumscribed around a circle of the specified radius.

View File

@ -17,7 +17,7 @@ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
executor::{ executor::{
BasePath, ExecState, Face, GeoMeta, KclValue, Path, Plane, Point2d, Point3d, Sketch, SketchSet, SketchSurface, BasePath, ExecState, Face, GeoMeta, KclValue, Path, Plane, Point2d, Point3d, Sketch, SketchSet, SketchSurface,
Solid, TagEngineInfo, TagIdentifier, UserVal, Solid, TagEngineInfo, TagIdentifier,
}, },
std::{ std::{
utils::{ utils::{
@ -97,7 +97,9 @@ pub async fn line_to(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
let (to, sketch, tag): ([f64; 2], Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?; let (to, sketch, tag): ([f64; 2], Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?;
let new_sketch = inner_line_to(to, sketch, tag, exec_state, args).await?; let new_sketch = inner_line_to(to, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::Sketch {
value: Box::new(new_sketch),
})
} }
/// Draw a line from the current origin to some absolute (x, y) point. /// Draw a line from the current origin to some absolute (x, y) point.
@ -164,7 +166,9 @@ pub async fn x_line_to(exec_state: &mut ExecState, args: Args) -> Result<KclValu
let (to, sketch, tag): (f64, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?; let (to, sketch, tag): (f64, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?;
let new_sketch = inner_x_line_to(to, sketch, tag, exec_state, args).await?; let new_sketch = inner_x_line_to(to, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::Sketch {
value: Box::new(new_sketch),
})
} }
/// Draw a line parallel to the X axis, that ends at the given X. /// Draw a line parallel to the X axis, that ends at the given X.
@ -212,7 +216,9 @@ pub async fn y_line_to(exec_state: &mut ExecState, args: Args) -> Result<KclValu
let (to, sketch, tag): (f64, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?; let (to, sketch, tag): (f64, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?;
let new_sketch = inner_y_line_to(to, sketch, tag, exec_state, args).await?; let new_sketch = inner_y_line_to(to, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::Sketch {
value: Box::new(new_sketch),
})
} }
/// Draw a line parallel to the Y axis, that ends at the given Y. /// Draw a line parallel to the Y axis, that ends at the given Y.
@ -252,7 +258,9 @@ pub async fn line(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
let (delta, sketch, tag): ([f64; 2], Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?; let (delta, sketch, tag): ([f64; 2], Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?;
let new_sketch = inner_line(delta, sketch, tag, exec_state, args).await?; let new_sketch = inner_line(delta, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::Sketch {
value: Box::new(new_sketch),
})
} }
/// Draw a line relative to the current origin to a specified (x, y) away /// Draw a line relative to the current origin to a specified (x, y) away
@ -333,7 +341,9 @@ pub async fn x_line(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
let (length, sketch, tag): (f64, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?; let (length, sketch, tag): (f64, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?;
let new_sketch = inner_x_line(length, sketch, tag, exec_state, args).await?; let new_sketch = inner_x_line(length, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::Sketch {
value: Box::new(new_sketch),
})
} }
/// Draw a line relative to the current origin to a specified distance away /// Draw a line relative to the current origin to a specified distance away
@ -376,7 +386,9 @@ pub async fn y_line(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
let (length, sketch, tag): (f64, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?; let (length, sketch, tag): (f64, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?;
let new_sketch = inner_y_line(length, sketch, tag, exec_state, args).await?; let new_sketch = inner_y_line(length, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::Sketch {
value: Box::new(new_sketch),
})
} }
/// Draw a line relative to the current origin to a specified distance away /// Draw a line relative to the current origin to a specified distance away
@ -430,7 +442,9 @@ pub async fn angled_line(exec_state: &mut ExecState, args: Args) -> Result<KclVa
let (data, sketch, tag): (AngledLineData, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?; let (data, sketch, tag): (AngledLineData, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?;
let new_sketch = inner_angled_line(data, sketch, tag, exec_state, args).await?; let new_sketch = inner_angled_line(data, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::Sketch {
value: Box::new(new_sketch),
})
} }
/// Draw a line segment relative to the current origin using the polar /// Draw a line segment relative to the current origin using the polar
@ -515,7 +529,9 @@ pub async fn angled_line_of_x_length(exec_state: &mut ExecState, args: Args) ->
let (data, sketch, tag): (AngledLineData, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?; let (data, sketch, tag): (AngledLineData, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?;
let new_sketch = inner_angled_line_of_x_length(data, sketch, tag, exec_state, args).await?; let new_sketch = inner_angled_line_of_x_length(data, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::Sketch {
value: Box::new(new_sketch),
})
} }
/// Create a line segment from the current 2-dimensional sketch origin /// Create a line segment from the current 2-dimensional sketch origin
@ -573,9 +589,9 @@ async fn inner_angled_line_of_x_length(
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct AngledLineToData { pub struct AngledLineToData {
/// The angle of the line. /// The angle of the line.
angle: f64, pub angle: f64,
/// The point to draw to. /// The point to draw to.
to: f64, pub to: f64,
} }
/// Draw an angled line to a given x coordinate. /// Draw an angled line to a given x coordinate.
@ -583,7 +599,9 @@ pub async fn angled_line_to_x(exec_state: &mut ExecState, args: Args) -> Result<
let (data, sketch, tag): (AngledLineToData, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?; let (data, sketch, tag): (AngledLineToData, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?;
let new_sketch = inner_angled_line_to_x(data, sketch, tag, exec_state, args).await?; let new_sketch = inner_angled_line_to_x(data, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::Sketch {
value: Box::new(new_sketch),
})
} }
/// Create a line segment from the current 2-dimensional sketch origin /// Create a line segment from the current 2-dimensional sketch origin
@ -641,7 +659,9 @@ pub async fn angled_line_of_y_length(exec_state: &mut ExecState, args: Args) ->
let new_sketch = inner_angled_line_of_y_length(data, sketch, tag, exec_state, args).await?; let new_sketch = inner_angled_line_of_y_length(data, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::Sketch {
value: Box::new(new_sketch),
})
} }
/// Create a line segment from the current 2-dimensional sketch origin /// Create a line segment from the current 2-dimensional sketch origin
@ -700,7 +720,9 @@ pub async fn angled_line_to_y(exec_state: &mut ExecState, args: Args) -> Result<
let (data, sketch, tag): (AngledLineToData, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?; let (data, sketch, tag): (AngledLineToData, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?;
let new_sketch = inner_angled_line_to_y(data, sketch, tag, exec_state, args).await?; let new_sketch = inner_angled_line_to_y(data, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::Sketch {
value: Box::new(new_sketch),
})
} }
/// Create a line segment from the current 2-dimensional sketch origin /// Create a line segment from the current 2-dimensional sketch origin
@ -771,7 +793,9 @@ pub async fn angled_line_that_intersects(exec_state: &mut ExecState, args: Args)
let (data, sketch, tag): (AngledLineThatIntersectsData, Sketch, Option<TagNode>) = let (data, sketch, tag): (AngledLineThatIntersectsData, Sketch, Option<TagNode>) =
args.get_data_and_sketch_and_tag()?; args.get_data_and_sketch_and_tag()?;
let new_sketch = inner_angled_line_that_intersects(data, sketch, tag, exec_state, args).await?; let new_sketch = inner_angled_line_that_intersects(data, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::Sketch {
value: Box::new(new_sketch),
})
} }
/// Draw an angled line from the current origin, constructing a line segment /// Draw an angled line from the current origin, constructing a line segment
@ -828,7 +852,9 @@ pub async fn start_sketch_at(exec_state: &mut ExecState, args: Args) -> Result<K
let data: [f64; 2] = args.get_data()?; let data: [f64; 2] = args.get_data()?;
let sketch = inner_start_sketch_at(data, exec_state, args).await?; let sketch = inner_start_sketch_at(data, exec_state, args).await?;
Ok(KclValue::new_user_val(sketch.meta.clone(), sketch)) Ok(KclValue::Sketch {
value: Box::new(sketch),
})
} }
/// Start a new 2-dimensional sketch at a given point on the 'XY' plane. /// Start a new 2-dimensional sketch at a given point on the 'XY' plane.
@ -1135,7 +1161,9 @@ pub async fn start_profile_at(exec_state: &mut ExecState, args: Args) -> Result<
args.get_data_and_sketch_surface()?; args.get_data_and_sketch_surface()?;
let sketch = inner_start_profile_at(start, sketch_surface, tag, exec_state, args).await?; let sketch = inner_start_profile_at(start, sketch_surface, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(sketch.meta.clone(), sketch)) Ok(KclValue::Sketch {
value: Box::new(sketch),
})
} }
/// Start a new profile at a given point. /// Start a new profile at a given point.
@ -1262,7 +1290,7 @@ pub(crate) async fn inner_start_profile_at(
pub async fn profile_start_x(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn profile_start_x(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let sketch: Sketch = args.get_sketch()?; let sketch: Sketch = args.get_sketch()?;
let x = inner_profile_start_x(sketch)?; let x = inner_profile_start_x(sketch)?;
args.make_user_val_from_f64(x) Ok(args.make_user_val_from_f64(x))
} }
/// Extract the provided 2-dimensional sketch's profile's origin's 'x' /// Extract the provided 2-dimensional sketch's profile's origin's 'x'
@ -1286,7 +1314,7 @@ pub(crate) fn inner_profile_start_x(sketch: Sketch) -> Result<f64, KclError> {
pub async fn profile_start_y(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn profile_start_y(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let sketch: Sketch = args.get_sketch()?; let sketch: Sketch = args.get_sketch()?;
let x = inner_profile_start_y(sketch)?; let x = inner_profile_start_y(sketch)?;
args.make_user_val_from_f64(x) Ok(args.make_user_val_from_f64(x))
} }
/// Extract the provided 2-dimensional sketch's profile's origin's 'y' /// Extract the provided 2-dimensional sketch's profile's origin's 'y'
@ -1309,15 +1337,7 @@ pub(crate) fn inner_profile_start_y(sketch: Sketch) -> Result<f64, KclError> {
pub async fn profile_start(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn profile_start(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let sketch: Sketch = args.get_sketch()?; let sketch: Sketch = args.get_sketch()?;
let point = inner_profile_start(sketch)?; let point = inner_profile_start(sketch)?;
Ok(KclValue::UserVal(UserVal { Ok(KclValue::from_point2d(point, args.into()))
value: serde_json::to_value(point).map_err(|e| {
KclError::Type(KclErrorDetails {
message: format!("Failed to convert point to json: {}", e),
source_ranges: vec![args.source_range],
})
})?,
meta: Default::default(),
}))
} }
/// Extract the provided 2-dimensional sketch's profile's origin /// Extract the provided 2-dimensional sketch's profile's origin
@ -1345,7 +1365,9 @@ pub async fn close(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
let new_sketch = inner_close(sketch, tag, exec_state, args).await?; let new_sketch = inner_close(sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::Sketch {
value: Box::new(new_sketch),
})
} }
/// Construct a line segment from the current origin back to the profile's /// Construct a line segment from the current origin back to the profile's
@ -1452,7 +1474,9 @@ pub async fn arc(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
let (data, sketch, tag): (ArcData, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?; let (data, sketch, tag): (ArcData, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?;
let new_sketch = inner_arc(data, sketch, tag, exec_state, args).await?; let new_sketch = inner_arc(data, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::Sketch {
value: Box::new(new_sketch),
})
} }
/// Draw a curved line segment along an imaginary circle. /// Draw a curved line segment along an imaginary circle.
@ -1573,7 +1597,9 @@ pub async fn tangential_arc(exec_state: &mut ExecState, args: Args) -> Result<Kc
let (data, sketch, tag): (TangentialArcData, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?; let (data, sketch, tag): (TangentialArcData, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?;
let new_sketch = inner_tangential_arc(data, sketch, tag, exec_state, args).await?; let new_sketch = inner_tangential_arc(data, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::Sketch {
value: Box::new(new_sketch),
})
} }
/// Draw a curved line segment along part of an imaginary circle. /// Draw a curved line segment along part of an imaginary circle.
@ -1701,7 +1727,9 @@ pub async fn tangential_arc_to(exec_state: &mut ExecState, args: Args) -> Result
let (to, sketch, tag): ([f64; 2], Sketch, Option<TagNode>) = super::args::FromArgs::from_args(&args, 0)?; let (to, sketch, tag): ([f64; 2], Sketch, Option<TagNode>) = super::args::FromArgs::from_args(&args, 0)?;
let new_sketch = inner_tangential_arc_to(to, sketch, tag, exec_state, args).await?; let new_sketch = inner_tangential_arc_to(to, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::Sketch {
value: Box::new(new_sketch),
})
} }
/// Draw a tangential arc to point some distance away.. /// Draw a tangential arc to point some distance away..
@ -1709,7 +1737,9 @@ pub async fn tangential_arc_to_relative(exec_state: &mut ExecState, args: Args)
let (delta, sketch, tag): ([f64; 2], Sketch, Option<TagNode>) = super::args::FromArgs::from_args(&args, 0)?; let (delta, sketch, tag): ([f64; 2], Sketch, Option<TagNode>) = super::args::FromArgs::from_args(&args, 0)?;
let new_sketch = inner_tangential_arc_to_relative(delta, sketch, tag, exec_state, args).await?; let new_sketch = inner_tangential_arc_to_relative(delta, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::Sketch {
value: Box::new(new_sketch),
})
} }
/// Starting at the current sketch's origin, draw a curved line segment along /// Starting at the current sketch's origin, draw a curved line segment along
@ -1873,11 +1903,11 @@ async fn inner_tangential_arc_to_relative(
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct BezierData { pub struct BezierData {
/// The to point. /// The to point.
to: [f64; 2], pub to: [f64; 2],
/// The first control point. /// The first control point.
control1: [f64; 2], pub control1: [f64; 2],
/// The second control point. /// The second control point.
control2: [f64; 2], pub control2: [f64; 2],
} }
/// Draw a bezier curve. /// Draw a bezier curve.
@ -1885,7 +1915,9 @@ pub async fn bezier_curve(exec_state: &mut ExecState, args: Args) -> Result<KclV
let (data, sketch, tag): (BezierData, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?; let (data, sketch, tag): (BezierData, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?;
let new_sketch = inner_bezier_curve(data, sketch, tag, exec_state, args).await?; let new_sketch = inner_bezier_curve(data, sketch, tag, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::Sketch {
value: Box::new(new_sketch),
})
} }
/// Draw a smooth, continuous, curved line segment from the current origin to /// Draw a smooth, continuous, curved line segment from the current origin to
@ -1965,7 +1997,9 @@ pub async fn hole(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
let (hole_sketch, sketch): (SketchSet, Sketch) = args.get_sketches()?; let (hole_sketch, sketch): (SketchSet, Sketch) = args.get_sketches()?;
let new_sketch = inner_hole(hole_sketch, sketch, exec_state, args).await?; let new_sketch = inner_hole(hole_sketch, sketch, exec_state, args).await?;
Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch)) Ok(KclValue::Sketch {
value: Box::new(new_sketch),
})
} }
/// Use a 2-dimensional sketch to cut a hole in another 2-dimensional sketch. /// Use a 2-dimensional sketch to cut a hole in another 2-dimensional sketch.

View File

@ -14,7 +14,7 @@ use crate::{
pub async fn mm(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn mm(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let result = inner_mm(&args)?; let result = inner_mm(&args)?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Millimeters conversion factor for current projects units. /// Millimeters conversion factor for current projects units.
@ -55,7 +55,7 @@ fn inner_mm(args: &Args) -> Result<f64, KclError> {
pub async fn inch(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn inch(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let result = inner_inch(&args)?; let result = inner_inch(&args)?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Inches conversion factor for current projects units. /// Inches conversion factor for current projects units.
@ -96,7 +96,7 @@ fn inner_inch(args: &Args) -> Result<f64, KclError> {
pub async fn ft(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn ft(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let result = inner_ft(&args)?; let result = inner_ft(&args)?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Feet conversion factor for current projects units. /// Feet conversion factor for current projects units.
@ -138,7 +138,7 @@ fn inner_ft(args: &Args) -> Result<f64, KclError> {
pub async fn m(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn m(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let result = inner_m(&args)?; let result = inner_m(&args)?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Meters conversion factor for current projects units. /// Meters conversion factor for current projects units.
@ -180,7 +180,7 @@ fn inner_m(args: &Args) -> Result<f64, KclError> {
pub async fn cm(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn cm(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let result = inner_cm(&args)?; let result = inner_cm(&args)?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Centimeters conversion factor for current projects units. /// Centimeters conversion factor for current projects units.
@ -222,7 +222,7 @@ fn inner_cm(args: &Args) -> Result<f64, KclError> {
pub async fn yd(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn yd(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let result = inner_yd(&args)?; let result = inner_yd(&args)?;
args.make_user_val_from_f64(result) Ok(args.make_user_val_from_f64(result))
} }
/// Yards conversion factor for current projects units. /// Yards conversion factor for current projects units.

File diff suppressed because it is too large Load Diff

View File

@ -8,36 +8,67 @@ snapshot_kind: text
{ {
"bindings": { "bindings": {
"HALF_TURN": { "HALF_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 180.0,
"value": 180,
"__meta": [] "__meta": []
}, },
"QUARTER_TURN": { "QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 90.0,
"value": 90,
"__meta": [] "__meta": []
}, },
"THREE_QUARTER_TURN": { "THREE_QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 270.0,
"value": 270,
"__meta": [] "__meta": []
}, },
"ZERO": { "ZERO": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 0.0,
"value": 0,
"__meta": [] "__meta": []
}, },
"arr": { "arr": {
"type": "UserVal", "type": "Array",
"type": "UserVal",
"value": [ "value": [
1, {
2, "type": "Int",
3 "value": 1,
"__meta": [
{
"sourceRange": [
7,
8,
0
]
}
]
},
{
"type": "Int",
"value": 2,
"__meta": [
{
"sourceRange": [
10,
11,
0
]
}
]
},
{
"type": "Int",
"value": 3,
"__meta": [
{
"sourceRange": [
13,
14,
0
]
}
]
}
], ],
"__meta": [ "__meta": [
{ {
@ -50,13 +81,60 @@ snapshot_kind: text
] ]
}, },
"new_arr1": { "new_arr1": {
"type": "UserVal", "type": "Array",
"type": "UserVal",
"value": [ "value": [
1, {
2, "type": "Int",
3, "value": 1,
4 "__meta": [
{
"sourceRange": [
7,
8,
0
]
}
]
},
{
"type": "Int",
"value": 2,
"__meta": [
{
"sourceRange": [
10,
11,
0
]
}
]
},
{
"type": "Int",
"value": 3,
"__meta": [
{
"sourceRange": [
13,
14,
0
]
}
]
},
{
"type": "Int",
"value": 4,
"__meta": [
{
"sourceRange": [
37,
38,
0
]
}
]
}
], ],
"__meta": [ "__meta": [
{ {
@ -69,14 +147,73 @@ snapshot_kind: text
] ]
}, },
"new_arr2": { "new_arr2": {
"type": "UserVal", "type": "Array",
"type": "UserVal",
"value": [ "value": [
1, {
2, "type": "Int",
3, "value": 1,
4, "__meta": [
5 {
"sourceRange": [
7,
8,
0
]
}
]
},
{
"type": "Int",
"value": 2,
"__meta": [
{
"sourceRange": [
10,
11,
0
]
}
]
},
{
"type": "Int",
"value": 3,
"__meta": [
{
"sourceRange": [
13,
14,
0
]
}
]
},
{
"type": "Int",
"value": 4,
"__meta": [
{
"sourceRange": [
37,
38,
0
]
}
]
},
{
"type": "Int",
"value": 5,
"__meta": [
{
"sourceRange": [
66,
67,
0
]
}
]
}
], ],
"__meta": [ "__meta": [
{ {

View File

@ -8,32 +8,27 @@ snapshot_kind: text
{ {
"bindings": { "bindings": {
"HALF_TURN": { "HALF_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 180.0,
"value": 180,
"__meta": [] "__meta": []
}, },
"QUARTER_TURN": { "QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 90.0,
"value": 90,
"__meta": [] "__meta": []
}, },
"THREE_QUARTER_TURN": { "THREE_QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 270.0,
"value": 270,
"__meta": [] "__meta": []
}, },
"ZERO": { "ZERO": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 0.0,
"value": 0,
"__meta": [] "__meta": []
}, },
"five": { "five": {
"type": "UserVal", "type": "Int",
"type": "UserVal",
"value": 5, "value": 5,
"__meta": [ "__meta": [
{ {
@ -46,8 +41,7 @@ snapshot_kind: text
] ]
}, },
"four": { "four": {
"type": "UserVal", "type": "Int",
"type": "UserVal",
"value": 4, "value": 4,
"__meta": [ "__meta": [
{ {
@ -60,14 +54,73 @@ snapshot_kind: text
] ]
}, },
"r1": { "r1": {
"type": "UserVal", "type": "Array",
"type": "UserVal",
"value": [ "value": [
0, {
1, "type": "Int",
2, "value": 0,
3, "__meta": [
4 {
"sourceRange": [
5,
11,
0
]
}
]
},
{
"type": "Int",
"value": 1,
"__meta": [
{
"sourceRange": [
5,
11,
0
]
}
]
},
{
"type": "Int",
"value": 2,
"__meta": [
{
"sourceRange": [
5,
11,
0
]
}
]
},
{
"type": "Int",
"value": 3,
"__meta": [
{
"sourceRange": [
5,
11,
0
]
}
]
},
{
"type": "Int",
"value": 4,
"__meta": [
{
"sourceRange": [
5,
11,
0
]
}
]
}
], ],
"__meta": [ "__meta": [
{ {
@ -80,14 +133,73 @@ snapshot_kind: text
] ]
}, },
"r2": { "r2": {
"type": "UserVal", "type": "Array",
"type": "UserVal",
"value": [ "value": [
0, {
1, "type": "Int",
2, "value": 0,
3, "__meta": [
4 {
"sourceRange": [
95,
107,
0
]
}
]
},
{
"type": "Int",
"value": 1,
"__meta": [
{
"sourceRange": [
95,
107,
0
]
}
]
},
{
"type": "Int",
"value": 2,
"__meta": [
{
"sourceRange": [
95,
107,
0
]
}
]
},
{
"type": "Int",
"value": 3,
"__meta": [
{
"sourceRange": [
95,
107,
0
]
}
]
},
{
"type": "Int",
"value": 4,
"__meta": [
{
"sourceRange": [
95,
107,
0
]
}
]
}
], ],
"__meta": [ "__meta": [
{ {
@ -100,15 +212,86 @@ snapshot_kind: text
] ]
}, },
"r3": { "r3": {
"type": "UserVal", "type": "Array",
"type": "UserVal",
"value": [ "value": [
0, {
1, "type": "Int",
2, "value": 0,
3, "__meta": [
4, {
5 "sourceRange": [
194,
206,
0
]
}
]
},
{
"type": "Int",
"value": 1,
"__meta": [
{
"sourceRange": [
194,
206,
0
]
}
]
},
{
"type": "Int",
"value": 2,
"__meta": [
{
"sourceRange": [
194,
206,
0
]
}
]
},
{
"type": "Int",
"value": 3,
"__meta": [
{
"sourceRange": [
194,
206,
0
]
}
]
},
{
"type": "Int",
"value": 4,
"__meta": [
{
"sourceRange": [
194,
206,
0
]
}
]
},
{
"type": "Int",
"value": 5,
"__meta": [
{
"sourceRange": [
194,
206,
0
]
}
]
}
], ],
"__meta": [ "__meta": [
{ {
@ -121,13 +304,60 @@ snapshot_kind: text
] ]
}, },
"r4": { "r4": {
"type": "UserVal", "type": "Array",
"type": "UserVal",
"value": [ "value": [
1, {
2, "type": "Int",
3, "value": 1,
4 "__meta": [
{
"sourceRange": [
341,
373,
0
]
}
]
},
{
"type": "Int",
"value": 2,
"__meta": [
{
"sourceRange": [
341,
373,
0
]
}
]
},
{
"type": "Int",
"value": 3,
"__meta": [
{
"sourceRange": [
341,
373,
0
]
}
]
},
{
"type": "Int",
"value": 4,
"__meta": [
{
"sourceRange": [
341,
373,
0
]
}
]
}
], ],
"__meta": [ "__meta": [
{ {
@ -140,8 +370,7 @@ snapshot_kind: text
] ]
}, },
"zero": { "zero": {
"type": "UserVal", "type": "Int",
"type": "UserVal",
"value": 0, "value": 0,
"__meta": [ "__meta": [
{ {

View File

@ -8,44 +8,171 @@ snapshot_kind: text
{ {
"bindings": { "bindings": {
"HALF_TURN": { "HALF_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 180.0,
"value": 180,
"__meta": [] "__meta": []
}, },
"QUARTER_TURN": { "QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 90.0,
"value": 90,
"__meta": [] "__meta": []
}, },
"THREE_QUARTER_TURN": { "THREE_QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 270.0,
"value": 270,
"__meta": [] "__meta": []
}, },
"ZERO": { "ZERO": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 0.0,
"value": 0,
"__meta": [] "__meta": []
}, },
"xs": { "xs": {
"type": "UserVal", "type": "Array",
"type": "UserVal",
"value": [ "value": [
-5, {
-4, "type": "Int",
-3, "value": -5,
-2, "__meta": [
-1, {
0, "sourceRange": [
1, 5,
2, 19,
3, 0
4, ]
5 }
]
},
{
"type": "Int",
"value": -4,
"__meta": [
{
"sourceRange": [
5,
19,
0
]
}
]
},
{
"type": "Int",
"value": -3,
"__meta": [
{
"sourceRange": [
5,
19,
0
]
}
]
},
{
"type": "Int",
"value": -2,
"__meta": [
{
"sourceRange": [
5,
19,
0
]
}
]
},
{
"type": "Int",
"value": -1,
"__meta": [
{
"sourceRange": [
5,
19,
0
]
}
]
},
{
"type": "Int",
"value": 0,
"__meta": [
{
"sourceRange": [
5,
19,
0
]
}
]
},
{
"type": "Int",
"value": 1,
"__meta": [
{
"sourceRange": [
5,
19,
0
]
}
]
},
{
"type": "Int",
"value": 2,
"__meta": [
{
"sourceRange": [
5,
19,
0
]
}
]
},
{
"type": "Int",
"value": 3,
"__meta": [
{
"sourceRange": [
5,
19,
0
]
}
]
},
{
"type": "Int",
"value": 4,
"__meta": [
{
"sourceRange": [
5,
19,
0
]
}
]
},
{
"type": "Int",
"value": 5,
"__meta": [
{
"sourceRange": [
5,
19,
0
]
}
]
}
], ],
"__meta": [ "__meta": [
{ {

View File

@ -8,27 +8,23 @@ snapshot_kind: text
{ {
"bindings": { "bindings": {
"HALF_TURN": { "HALF_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 180.0,
"value": 180,
"__meta": [] "__meta": []
}, },
"QUARTER_TURN": { "QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 90.0,
"value": 90,
"__meta": [] "__meta": []
}, },
"THREE_QUARTER_TURN": { "THREE_QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 270.0,
"value": 270,
"__meta": [] "__meta": []
}, },
"ZERO": { "ZERO": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 0.0,
"value": 0,
"__meta": [] "__meta": []
} }
}, },

View File

@ -8,27 +8,23 @@ snapshot_kind: text
{ {
"bindings": { "bindings": {
"HALF_TURN": { "HALF_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 180.0,
"value": 180,
"__meta": [] "__meta": []
}, },
"QUARTER_TURN": { "QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 90.0,
"value": 90,
"__meta": [] "__meta": []
}, },
"THREE_QUARTER_TURN": { "THREE_QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 270.0,
"value": 270,
"__meta": [] "__meta": []
}, },
"ZERO": { "ZERO": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 0.0,
"value": 0,
"__meta": [] "__meta": []
}, },
"cube": { "cube": {
@ -717,27 +713,23 @@ snapshot_kind: text
{ {
"bindings": { "bindings": {
"HALF_TURN": { "HALF_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 180.0,
"value": 180,
"__meta": [] "__meta": []
}, },
"QUARTER_TURN": { "QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 90.0,
"value": 90,
"__meta": [] "__meta": []
}, },
"THREE_QUARTER_TURN": { "THREE_QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 270.0,
"value": 270,
"__meta": [] "__meta": []
}, },
"ZERO": { "ZERO": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 0.0,
"value": 0,
"__meta": [] "__meta": []
} }
}, },

View File

@ -8,27 +8,23 @@ snapshot_kind: text
{ {
"bindings": { "bindings": {
"HALF_TURN": { "HALF_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 180.0,
"value": 180,
"__meta": [] "__meta": []
}, },
"QUARTER_TURN": { "QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 90.0,
"value": 90,
"__meta": [] "__meta": []
}, },
"THREE_QUARTER_TURN": { "THREE_QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 270.0,
"value": 270,
"__meta": [] "__meta": []
}, },
"ZERO": { "ZERO": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 0.0,
"value": 0,
"__meta": [] "__meta": []
}, },
"increment": { "increment": {
@ -89,27 +85,23 @@ snapshot_kind: text
{ {
"bindings": { "bindings": {
"HALF_TURN": { "HALF_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 180.0,
"value": 180,
"__meta": [] "__meta": []
}, },
"QUARTER_TURN": { "QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 90.0,
"value": 90,
"__meta": [] "__meta": []
}, },
"THREE_QUARTER_TURN": { "THREE_QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 270.0,
"value": 270,
"__meta": [] "__meta": []
}, },
"ZERO": { "ZERO": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 0.0,
"value": 0,
"__meta": [] "__meta": []
} }
}, },
@ -130,12 +122,47 @@ snapshot_kind: text
] ]
}, },
"xs": { "xs": {
"type": "UserVal", "type": "Array",
"type": "UserVal",
"value": [ "value": [
0, {
1, "type": "Int",
2 "value": 0,
"__meta": [
{
"sourceRange": [
47,
53,
0
]
}
]
},
{
"type": "Int",
"value": 1,
"__meta": [
{
"sourceRange": [
47,
53,
0
]
}
]
},
{
"type": "Int",
"value": 2,
"__meta": [
{
"sourceRange": [
47,
53,
0
]
}
]
}
], ],
"__meta": [ "__meta": [
{ {
@ -148,12 +175,89 @@ snapshot_kind: text
] ]
}, },
"ys": { "ys": {
"type": "UserVal", "type": "Array",
"type": "UserVal",
"value": [ "value": [
2.0, {
3.0, "type": "Number",
4.0 "value": 2.0,
"__meta": [
{
"sourceRange": [
47,
53,
0
]
},
{
"sourceRange": [
37,
38,
0
]
},
{
"sourceRange": [
37,
38,
0
]
}
]
},
{
"type": "Number",
"value": 3.0,
"__meta": [
{
"sourceRange": [
47,
53,
0
]
},
{
"sourceRange": [
37,
38,
0
]
},
{
"sourceRange": [
37,
38,
0
]
}
]
},
{
"type": "Number",
"value": 4.0,
"__meta": [
{
"sourceRange": [
47,
53,
0
]
},
{
"sourceRange": [
37,
38,
0
]
},
{
"sourceRange": [
37,
38,
0
]
}
]
}
], ],
"__meta": [ "__meta": [
{ {

View File

@ -8,27 +8,23 @@ snapshot_kind: text
{ {
"bindings": { "bindings": {
"HALF_TURN": { "HALF_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 180.0,
"value": 180,
"__meta": [] "__meta": []
}, },
"QUARTER_TURN": { "QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 90.0,
"value": 90,
"__meta": [] "__meta": []
}, },
"THREE_QUARTER_TURN": { "THREE_QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 270.0,
"value": 270,
"__meta": [] "__meta": []
}, },
"ZERO": { "ZERO": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 0.0,
"value": 0,
"__meta": [] "__meta": []
}, },
"part001": { "part001": {

View File

@ -8,32 +8,27 @@ snapshot_kind: text
{ {
"bindings": { "bindings": {
"HALF_TURN": { "HALF_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 180.0,
"value": 180,
"__meta": [] "__meta": []
}, },
"QUARTER_TURN": { "QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 90.0,
"value": 90,
"__meta": [] "__meta": []
}, },
"THREE_QUARTER_TURN": { "THREE_QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 270.0,
"value": 270,
"__meta": [] "__meta": []
}, },
"ZERO": { "ZERO": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 0.0,
"value": 0,
"__meta": [] "__meta": []
}, },
"a": { "a": {
"type": "UserVal", "type": "Int",
"type": "UserVal",
"value": 3, "value": 3,
"__meta": [ "__meta": [
{ {
@ -46,8 +41,7 @@ snapshot_kind: text
] ]
}, },
"b": { "b": {
"type": "UserVal", "type": "Int",
"type": "UserVal",
"value": 4, "value": 4,
"__meta": [ "__meta": [
{ {
@ -60,8 +54,7 @@ snapshot_kind: text
] ]
}, },
"c": { "c": {
"type": "UserVal", "type": "Int",
"type": "UserVal",
"value": 5, "value": 5,
"__meta": [ "__meta": [
{ {

View File

@ -8,36 +8,67 @@ snapshot_kind: text
{ {
"bindings": { "bindings": {
"HALF_TURN": { "HALF_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 180.0,
"value": 180,
"__meta": [] "__meta": []
}, },
"QUARTER_TURN": { "QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 90.0,
"value": 90,
"__meta": [] "__meta": []
}, },
"THREE_QUARTER_TURN": { "THREE_QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 270.0,
"value": 270,
"__meta": [] "__meta": []
}, },
"ZERO": { "ZERO": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 0.0,
"value": 0,
"__meta": [] "__meta": []
}, },
"array": { "array": {
"type": "UserVal", "type": "Array",
"type": "UserVal",
"value": [ "value": [
90, {
91, "type": "Int",
92 "value": 90,
"__meta": [
{
"sourceRange": [
44,
46,
0
]
}
]
},
{
"type": "Int",
"value": 91,
"__meta": [
{
"sourceRange": [
48,
50,
0
]
}
]
},
{
"type": "Int",
"value": 92,
"__meta": [
{
"sourceRange": [
52,
54,
0
]
}
]
}
], ],
"__meta": [ "__meta": [
{ {
@ -50,8 +81,7 @@ snapshot_kind: text
] ]
}, },
"i": { "i": {
"type": "UserVal", "type": "Int",
"type": "UserVal",
"value": 1, "value": 1,
"__meta": [ "__meta": [
{ {
@ -64,28 +94,26 @@ snapshot_kind: text
] ]
}, },
"result0": { "result0": {
"type": "UserVal", "type": "Int",
"type": "UserVal",
"value": 91, "value": 91,
"__meta": [ "__meta": [
{ {
"sourceRange": [ "sourceRange": [
93, 48,
101, 50,
0 0
] ]
} }
] ]
}, },
"result1": { "result1": {
"type": "UserVal", "type": "Int",
"type": "UserVal",
"value": 91, "value": 91,
"__meta": [ "__meta": [
{ {
"sourceRange": [ "sourceRange": [
277, 48,
285, 50,
0 0
] ]
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 51 KiB

View File

@ -8,35 +8,54 @@ snapshot_kind: text
{ {
"bindings": { "bindings": {
"HALF_TURN": { "HALF_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 180.0,
"value": 180,
"__meta": [] "__meta": []
}, },
"QUARTER_TURN": { "QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 90.0,
"value": 90,
"__meta": [] "__meta": []
}, },
"THREE_QUARTER_TURN": { "THREE_QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 270.0,
"value": 270,
"__meta": [] "__meta": []
}, },
"ZERO": { "ZERO": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 0.0,
"value": 0,
"__meta": [] "__meta": []
}, },
"obj": { "obj": {
"type": "UserVal", "type": "Object",
"type": "UserVal",
"value": { "value": {
"bar": 0, "bar": {
"foo": 1 "type": "Int",
"value": 0,
"__meta": [
{
"sourceRange": [
71,
72,
0
]
}
]
},
"foo": {
"type": "Int",
"value": 1,
"__meta": [
{
"sourceRange": [
63,
64,
0
]
}
]
}
}, },
"__meta": [ "__meta": [
{ {
@ -49,12 +68,47 @@ snapshot_kind: text
] ]
}, },
"obj2": { "obj2": {
"type": "UserVal", "type": "Object",
"type": "UserVal",
"value": { "value": {
"inner": { "inner": {
"bar": 0, "type": "Object",
"foo": 1 "value": {
"bar": {
"type": "Int",
"value": 0,
"__meta": [
{
"sourceRange": [
71,
72,
0
]
}
]
},
"foo": {
"type": "Int",
"value": 1,
"__meta": [
{
"sourceRange": [
63,
64,
0
]
}
]
}
},
"__meta": [
{
"sourceRange": [
56,
74,
0
]
}
]
} }
}, },
"__meta": [ "__meta": [
@ -68,64 +122,59 @@ snapshot_kind: text
] ]
}, },
"one_a": { "one_a": {
"type": "UserVal", "type": "Int",
"type": "UserVal",
"value": 1, "value": 1,
"__meta": [ "__meta": [
{ {
"sourceRange": [ "sourceRange": [
122, 63,
132, 64,
0 0
] ]
} }
] ]
}, },
"one_b": { "one_b": {
"type": "UserVal", "type": "Int",
"type": "UserVal",
"value": 1, "value": 1,
"__meta": [ "__meta": [
{ {
"sourceRange": [ "sourceRange": [
356, 63,
362, 64,
0 0
] ]
} }
] ]
}, },
"one_c": { "one_c": {
"type": "UserVal", "type": "Int",
"type": "UserVal",
"value": 1, "value": 1,
"__meta": [ "__meta": [
{ {
"sourceRange": [ "sourceRange": [
553, 63,
570, 64,
0 0
] ]
} }
] ]
}, },
"one_d": { "one_d": {
"type": "UserVal", "type": "Int",
"type": "UserVal",
"value": 1, "value": 1,
"__meta": [ "__meta": [
{ {
"sourceRange": [ "sourceRange": [
757, 63,
770, 64,
0 0
] ]
} }
] ]
}, },
"p": { "p": {
"type": "UserVal", "type": "String",
"type": "UserVal",
"value": "foo", "value": "foo",
"__meta": [ "__meta": [
{ {

View File

@ -8,27 +8,23 @@ snapshot_kind: text
{ {
"bindings": { "bindings": {
"HALF_TURN": { "HALF_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 180.0,
"value": 180,
"__meta": [] "__meta": []
}, },
"QUARTER_TURN": { "QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 90.0,
"value": 90,
"__meta": [] "__meta": []
}, },
"THREE_QUARTER_TURN": { "THREE_QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 270.0,
"value": 270,
"__meta": [] "__meta": []
}, },
"ZERO": { "ZERO": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 0.0,
"value": 0,
"__meta": [] "__meta": []
}, },
"test": { "test": {
@ -295,27 +291,23 @@ snapshot_kind: text
{ {
"bindings": { "bindings": {
"HALF_TURN": { "HALF_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 180.0,
"value": 180,
"__meta": [] "__meta": []
}, },
"QUARTER_TURN": { "QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 90.0,
"value": 90,
"__meta": [] "__meta": []
}, },
"THREE_QUARTER_TURN": { "THREE_QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 270.0,
"value": 270,
"__meta": [] "__meta": []
}, },
"ZERO": { "ZERO": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 0.0,
"value": 0,
"__meta": [] "__meta": []
} }
}, },
@ -637,27 +629,23 @@ snapshot_kind: text
{ {
"bindings": { "bindings": {
"HALF_TURN": { "HALF_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 180.0,
"value": 180,
"__meta": [] "__meta": []
}, },
"QUARTER_TURN": { "QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 90.0,
"value": 90,
"__meta": [] "__meta": []
}, },
"THREE_QUARTER_TURN": { "THREE_QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 270.0,
"value": 270,
"__meta": [] "__meta": []
}, },
"ZERO": { "ZERO": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 0.0,
"value": 0,
"__meta": [] "__meta": []
}, },
"test": { "test": {
@ -924,27 +912,23 @@ snapshot_kind: text
{ {
"bindings": { "bindings": {
"HALF_TURN": { "HALF_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 180.0,
"value": 180,
"__meta": [] "__meta": []
}, },
"QUARTER_TURN": { "QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 90.0,
"value": 90,
"__meta": [] "__meta": []
}, },
"THREE_QUARTER_TURN": { "THREE_QUARTER_TURN": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 270.0,
"value": 270,
"__meta": [] "__meta": []
}, },
"ZERO": { "ZERO": {
"type": "UserVal", "type": "Number",
"type": "UserVal", "value": 0.0,
"value": 0,
"__meta": [] "__meta": []
} }
}, },
@ -982,45 +966,10 @@ snapshot_kind: text
] ]
}, },
"x": { "x": {
"type": "UserVal", "type": "Sketch",
"type": "UserVal",
"value": { "value": {
"__meta": [ "type": "Sketch",
{
"sourceRange": [
52,
77,
0
]
}
],
"id": "[uuid]", "id": "[uuid]",
"on": {
"__meta": [],
"id": "[uuid]",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"type": "plane",
"value": "XY",
"xAxis": {
"x": 1.0,
"y": 0.0,
"z": 0.0
},
"yAxis": {
"x": 0.0,
"y": 1.0,
"z": 0.0
},
"zAxis": {
"x": 0.0,
"y": 0.0,
"z": 1.0
}
},
"paths": [ "paths": [
{ {
"__geoMeta": { "__geoMeta": {
@ -1103,7 +1052,42 @@ snapshot_kind: text
"type": "ToPoint" "type": "ToPoint"
} }
], ],
"on": {
"type": "plane",
"id": "[uuid]",
"value": "XY",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"xAxis": {
"x": 1.0,
"y": 0.0,
"z": 0.0
},
"yAxis": {
"x": 0.0,
"y": 1.0,
"z": 0.0
},
"zAxis": {
"x": 0.0,
"y": 0.0,
"z": 1.0
},
"__meta": []
},
"start": { "start": {
"from": [
0.0,
0.0
],
"to": [
0.0,
0.0
],
"tag": null,
"__geoMeta": { "__geoMeta": {
"id": "[uuid]", "id": "[uuid]",
"sourceRange": [ "sourceRange": [
@ -1111,174 +1095,178 @@ snapshot_kind: text
77, 77,
0 0
] ]
}, }
"from": [
0.0,
0.0
],
"tag": null,
"to": [
0.0,
0.0
]
}, },
"type": "Sketch" "__meta": [
}, {
"__meta": [ "sourceRange": [
{ 52,
"sourceRange": [ 77,
52, 0
77, ]
0 }
] ]
} }
]
}, },
"x2": { "x2": {
"type": "UserVal", "type": "Object",
"type": "UserVal",
"value": { "value": {
"thing1": { "thing1": {
"thing2": { "type": "Object",
"__meta": [ "value": {
{ "thing2": {
"sourceRange": [ "type": "Sketch",
242, "value": {
267, "type": "Sketch",
0
]
}
],
"id": "[uuid]",
"on": {
"__meta": [],
"id": "[uuid]",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"type": "plane",
"value": "XY",
"xAxis": {
"x": 1.0,
"y": 0.0,
"z": 0.0
},
"yAxis": {
"x": 0.0,
"y": 1.0,
"z": 0.0
},
"zAxis": {
"x": 0.0,
"y": 0.0,
"z": 1.0
}
},
"paths": [
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
277,
292,
0
]
},
"from": [
0.0,
0.0
],
"tag": null,
"to": [
0.0,
1.0
],
"type": "ToPoint"
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
302,
317,
0
]
},
"from": [
0.0,
1.0
],
"tag": null,
"to": [
1.0,
1.0
],
"type": "ToPoint"
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
327,
343,
0
]
},
"from": [
1.0,
1.0
],
"tag": null,
"to": [
1.0,
0.0
],
"type": "ToPoint"
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
353,
361,
0
]
},
"from": [
1.0,
0.0
],
"tag": null,
"to": [
0.0,
0.0
],
"type": "ToPoint"
}
],
"start": {
"__geoMeta": {
"id": "[uuid]", "id": "[uuid]",
"sourceRange": [ "paths": [
242, {
267, "__geoMeta": {
0 "id": "[uuid]",
"sourceRange": [
277,
292,
0
]
},
"from": [
0.0,
0.0
],
"tag": null,
"to": [
0.0,
1.0
],
"type": "ToPoint"
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
302,
317,
0
]
},
"from": [
0.0,
1.0
],
"tag": null,
"to": [
1.0,
1.0
],
"type": "ToPoint"
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
327,
343,
0
]
},
"from": [
1.0,
1.0
],
"tag": null,
"to": [
1.0,
0.0
],
"type": "ToPoint"
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
353,
361,
0
]
},
"from": [
1.0,
0.0
],
"tag": null,
"to": [
0.0,
0.0
],
"type": "ToPoint"
}
],
"on": {
"type": "plane",
"id": "[uuid]",
"value": "XY",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"xAxis": {
"x": 1.0,
"y": 0.0,
"z": 0.0
},
"yAxis": {
"x": 0.0,
"y": 1.0,
"z": 0.0
},
"zAxis": {
"x": 0.0,
"y": 0.0,
"z": 1.0
},
"__meta": []
},
"start": {
"from": [
0.0,
0.0
],
"to": [
0.0,
0.0
],
"tag": null,
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
242,
267,
0
]
}
},
"__meta": [
{
"sourceRange": [
242,
267,
0
]
}
] ]
}, }
"from": [ }
0.0, },
0.0 "__meta": [
], {
"tag": null, "sourceRange": [
"to": [ 199,
0.0, 365,
0.0 0
] ]
}, }
"type": "Sketch" ]
}
} }
}, },
"__meta": [ "__meta": [

View File

@ -13,5 +13,5 @@ sq = square([0,0], 10)
cb = square([3,3], 4) cb = square([3,3], 4)
|> extrude(10, %) |> extrude(10, %)
pt1 = sq.paths[0] // pt1 = sq.paths[0]
pt2 = cb.value[0] // pt2 = cb.value[0]

View File

@ -1354,7 +1354,7 @@ secondSketch = startSketchOn(part001, '')
assert!(result.is_err()); assert!(result.is_err());
assert_eq!( assert_eq!(
result.err().unwrap().to_string(), result.err().unwrap().to_string(),
r#"semantic: KclErrorDetails { source_ranges: [SourceRange([260, 286, 0])], message: "Argument at index 1 was supposed to be type kcl_lib::std::sketch::FaceTag but found string (text)" }"# r#"semantic: KclErrorDetails { source_ranges: [SourceRange([260, 286, 0])], message: "Argument at index 1 was supposed to be type Option<kcl_lib::std::sketch::FaceTag> but found string (text)" }"#
); );
} }

View File

@ -73,7 +73,7 @@ gen_test_fail!(
); );
gen_test_fail!( gen_test_fail!(
invalid_index_negative, invalid_index_negative,
"semantic: '-1' is not a valid index, indices must be whole positive numbers" "semantic: '-1' is negative, so you can't index an array with it"
); );
gen_test_fail!( gen_test_fail!(
invalid_index_fractional, invalid_index_fractional,
@ -81,7 +81,7 @@ gen_test_fail!(
); );
gen_test_fail!( gen_test_fail!(
invalid_member_object, invalid_member_object,
"semantic: Only arrays and objects can be indexed, but you're trying to index a number" "semantic: Only arrays and objects can be indexed, but you're trying to index an integer"
); );
gen_test_fail!( gen_test_fail!(
invalid_member_object_prop, invalid_member_object_prop,
@ -107,7 +107,10 @@ gen_test_fail!(
// if_else_no_expr, // if_else_no_expr,
// "syntax: blocks inside an if/else expression must end in an expression" // "syntax: blocks inside an if/else expression must end in an expression"
// ); // );
gen_test_fail!(comparisons_multiple, "syntax: Invalid number: true"); gen_test_fail!(
comparisons_multiple,
"semantic: Expected a number, but found a boolean (true/false value)"
);
gen_test_fail!( gen_test_fail!(
import_cycle1, import_cycle1,
"import cycle: circular import of modules is not allowed: tests/executor/inputs/no_visuals/import_cycle2.kcl -> tests/executor/inputs/no_visuals/import_cycle3.kcl -> tests/executor/inputs/no_visuals/import_cycle1.kcl -> tests/executor/inputs/no_visuals/import_cycle2.kcl" "import cycle: circular import of modules is not allowed: tests/executor/inputs/no_visuals/import_cycle2.kcl -> tests/executor/inputs/no_visuals/import_cycle3.kcl -> tests/executor/inputs/no_visuals/import_cycle1.kcl -> tests/executor/inputs/no_visuals/import_cycle2.kcl"

View File

@ -4,7 +4,7 @@ use kcl_lib::{
modify::modify_ast_for_sketch, modify::modify_ast_for_sketch,
types::{ModuleId, Node, Program}, types::{ModuleId, Node, Program},
}, },
executor::{ExecutorContext, IdGenerator, KclValue, PlaneType, Sketch, SourceRange}, executor::{ExecutorContext, IdGenerator, KclValue, PlaneType, SourceRange},
}; };
use kittycad_modeling_cmds::{each_cmd as mcmd, length_unit::LengthUnit, shared::Point3d, ModelingCmd}; use kittycad_modeling_cmds::{each_cmd as mcmd, length_unit::LengthUnit, shared::Point3d, ModelingCmd};
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
@ -18,12 +18,9 @@ async fn setup(code: &str, name: &str) -> Result<(ExecutorContext, Node<Program>
// We need to get the sketch ID. // We need to get the sketch ID.
// Get the sketch ID from memory. // Get the sketch ID from memory.
let KclValue::UserVal(user_val) = exec_state.memory.get(name, SourceRange::default()).unwrap() else { let KclValue::Sketch { value: sketch } = exec_state.memory.get(name, SourceRange::default()).unwrap() else {
anyhow::bail!("part001 not found in memory: {:?}", exec_state.memory); anyhow::bail!("part001 not found in memory: {:?}", exec_state.memory);
}; };
let Some((sketch, _meta)) = user_val.get::<Sketch>() else {
anyhow::bail!("part001 was not a Sketch");
};
let sketch_id = sketch.id; let sketch_id = sketch.id;
let plane_id = uuid::Uuid::new_v4(); let plane_id = uuid::Uuid::new_v4();