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 |
|----------|------|-------------|----------|
| `type` |enum: `UserVal`| | No |
| `value` |``| | No |
| `type` |enum: `Uuid`| | 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 |
@ -111,6 +213,38 @@ A face.
| `__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.
@ -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()
for (const { key, value } of availableVarInfo.variables) {
const error = _programMem.set(key, {
type: 'UserVal',
type: 'String',
value,
__meta: [],
})

View File

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

View File

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

View File

@ -58,7 +58,13 @@ const newVar = myVar + 1`
`
const mem = await exe(code)
// 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([
{
type: 'ToPoint',
@ -150,7 +156,7 @@ const newVar = myVar + 1`
].join('\n')
const mem = await exe(code)
expect(mem.get('mySk1')).toEqual({
type: 'UserVal',
type: 'Sketch',
value: {
type: 'Sketch',
on: expect.any(Object),
@ -215,7 +221,6 @@ const newVar = myVar + 1`
id: expect.any(String),
__meta: [{ sourceRange: [39, 63, 0] }],
},
__meta: [{ sourceRange: [39, 63, 0] }],
})
})
it('execute array expression', async () => {
@ -225,7 +230,7 @@ const newVar = myVar + 1`
const mem = await exe(code)
// TODO path to node is probably wrong here, zero indexes are not correct
expect(mem.get('three')).toEqual({
type: 'UserVal',
type: 'Int',
value: 3,
__meta: [
{
@ -234,8 +239,17 @@ const newVar = myVar + 1`
],
})
expect(mem.get('yo')).toEqual({
type: 'UserVal',
value: [1, '2', 3, 9],
type: 'Array',
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: [
{
sourceRange: [27, 49, 0],
@ -253,8 +267,25 @@ const newVar = myVar + 1`
].join('\n')
const mem = await exe(code)
expect(mem.get('yo')).toEqual({
type: 'UserVal',
value: { aStr: 'str', anum: 2, identifier: 3, binExp: 9 },
type: 'Object',
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: [
{
sourceRange: [27, 83, 0],
@ -268,11 +299,11 @@ const newVar = myVar + 1`
)
const mem = await exe(code)
expect(mem.get('myVar')).toEqual({
type: 'UserVal',
type: 'String',
value: '123',
__meta: [
{
sourceRange: [41, 50, 0],
sourceRange: [19, 24, 0],
},
],
})
@ -356,7 +387,26 @@ describe('testing math operators', () => {
it('with unaryExpression in ArrayExpression', async () => {
const code = 'const myVar = [1,-legLen(5, 4)]'
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 () => {
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
// with the latest changes in github. We won't be hard coding the filenames
it(
'should run through all the files',
async () => {
for (let i = 0; i < files.length; i++) {
const file: KclSampleFile = files[i]
files.forEach((file: KclSampleFile) => {
it(`should parse ${file.filename} without errors`, async () => {
const code = await getKclSampleCodeFromGithub(file.filename)
const parsed = parse(code)
assert(!(parsed instanceof Error))
}
},
files.length * 1000
)
}, 1000)
})
})
describe('when performing enginelessExecutor', () => {

View File

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

View File

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

View File

@ -27,7 +27,7 @@ pub use crate::ast::types::{
use crate::{
docs::StdLibFn,
errors::KclError,
executor::{ExecState, ExecutorContext, KclValue, Metadata, SourceRange, TagIdentifier, UserVal},
executor::{ExecState, ExecutorContext, KclValue, Metadata, SourceRange, TagIdentifier},
parser::PIPE_OPERATOR,
std::kcl_stdlib::KclStdLibFn,
};
@ -59,6 +59,14 @@ pub struct Node<T> {
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> {
fn schema_name() -> String {
T::schema_name()
@ -1708,34 +1716,26 @@ impl Literal {
impl From<Node<Literal>> for KclValue {
fn from(literal: Node<Literal>) -> Self {
KclValue::UserVal(UserVal {
value: JValue::from(literal.value.clone()),
meta: vec![Metadata {
source_range: literal.into(),
}],
})
let meta = vec![literal.metadata()];
match literal.inner.value {
LiteralValue::IInteger(value) => KclValue::Int { value, meta },
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 {
fn from(literal: &Node<Literal>) -> Self {
KclValue::UserVal(UserVal {
value: JValue::from(literal.value.clone()),
meta: vec![Metadata {
source_range: literal.into(),
}],
})
Self::from(literal.to_owned())
}
}
impl From<&BoxNode<Literal>> for KclValue {
fn from(literal: &BoxNode<Literal>) -> Self {
KclValue::UserVal(UserVal {
value: JValue::from(literal.value.clone()),
meta: vec![Metadata {
source_range: literal.into(),
}],
})
let b: &Node<Literal> = literal;
Self::from(b)
}
}
@ -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)]
mod tests {
use pretty_assertions::assert_eq;

View File

@ -1,18 +1,21 @@
use std::collections::HashMap;
use super::{
human_friendly_type, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart,
CallExpression, Expr, IfExpression, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node,
ObjectExpression, TagDeclarator, UnaryExpression, UnaryOperator,
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, CallExpression, Expr,
IfExpression, KclNone, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node, ObjectExpression,
TagDeclarator, UnaryExpression, UnaryOperator,
};
use crate::{
errors::{KclError, KclErrorDetails},
executor::{
BodyType, ExecState, ExecutorContext, KclValue, Metadata, Sketch, SourceRange, StatementKind, TagEngineInfo,
TagIdentifier, UserVal,
BodyType, ExecState, ExecutorContext, KclValue, Metadata, SourceRange, StatementKind, TagEngineInfo,
TagIdentifier,
},
std::FunctionKind,
};
use async_recursion::async_recursion;
use serde_json::Value as JValue;
const FLOAT_TO_INT_MAX_DELTA: f64 = 0.01;
impl BinaryPart {
#[async_recursion]
@ -42,28 +45,21 @@ impl Node<MemberExpression> {
}
};
let array_json = array.get_json_value()?;
let KclValue::Array { value: array, meta: _ } = array else {
return Err(KclError::Semantic(KclErrorDetails {
message: format!("MemberExpression array is not an array: {:?}", array),
source_ranges: vec![self.clone().into()],
}));
};
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(),
}],
}))
Ok(value.to_owned())
} else {
Err(KclError::UndefinedValue(KclErrorDetails {
message: format!("index {} not found in array", index),
source_ranges: vec![self.clone().into()],
}))
}
} else {
Err(KclError::Semantic(KclErrorDetails {
message: format!("MemberExpression array is not an array: {:?}", array),
source_ranges: vec![self.clone().into()],
}))
}
}
pub fn get_result(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
@ -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.
match (object_json, property) {
(JValue::Object(map), Property::String(property)) => {
match (object, property) {
(KclValue::Object { value: map, meta: _ }, Property::String(property)) => {
if let Some(value) = map.get(&property) {
Ok(KclValue::UserVal(UserVal {
value: value.clone(),
meta: vec![Metadata {
source_range: self.into(),
}],
}))
Ok(value.to_owned())
} else {
Err(KclError::UndefinedValue(KclErrorDetails {
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) => {
let t = p.type_name();
let article = article_for(t);
Err(KclError::Semantic(KclErrorDetails {
message: format!(
"Only strings can be used as the property of an object, but you're using a {}",
p.type_name()
"Only strings can be used as the property of an object, but you're using {article} {t}",
),
source_ranges: vec![self.clone().into()],
})),
(JValue::Array(arr), Property::Number(index)) => {
let value_of_arr: Option<&JValue> = arr.get(index);
if let Some(value) = value_of_arr {
Ok(KclValue::UserVal(UserVal {
value: value.clone(),
meta: vec![Metadata {
source_range: self.into(),
}],
}))
}
(KclValue::Array { value: arr, meta: _ }, Property::Number(index)) => {
let value_of_arr = arr.get(index);
if let Some(value) = value_of_arr {
Ok(value.to_owned())
} else {
Err(KclError::UndefinedValue(KclErrorDetails {
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) => {
let t = p.type_name();
let article = article_for(t);
Err(KclError::Semantic(KclErrorDetails {
message: format!(
"Only integers >= 0 can be used as the index of an array, but you're using a {}",
p.type_name()
"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 = human_friendly_type(&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 a {t}"),
message: format!(
"Only arrays and objects can be indexed, but you're trying to index {article} {t}"
),
source_ranges: vec![self.clone().into()],
}))
}
@ -140,81 +146,134 @@ impl Node<MemberExpression> {
impl Node<BinaryExpression> {
#[async_recursion]
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
let left_json_value = self.left.get_result(exec_state, ctx).await?.get_json_value()?;
let right_json_value = self.right.get_result(exec_state, ctx).await?.get_json_value()?;
let left_value = self.left.get_result(exec_state, ctx).await?;
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.
if self.operator == BinaryOperator::Add {
if let (Some(left), Some(right)) = (
parse_json_value_as_string(&left_json_value),
parse_json_value_as_string(&right_json_value),
) {
let value = serde_json::Value::String(format!("{}{}", left, right));
return Ok(KclValue::UserVal(UserVal {
value,
meta: vec![Metadata {
source_range: self.into(),
}],
}));
if let (KclValue::String { value: left, meta: _ }, KclValue::String { value: right, meta: _ }) =
(&left_value, &right_value)
{
return Ok(KclValue::String {
value: format!("{}{}", left, right),
meta,
});
}
}
let left = parse_json_number_as_f64(&left_json_value, self.left.clone().into())?;
let right = parse_json_number_as_f64(&right_json_value, self.right.clone().into())?;
let left = parse_number_as_f64(&left_value, self.left.clone().into())?;
let right = parse_number_as_f64(&right_value, self.right.clone().into())?;
let value: serde_json::Value = match self.operator {
BinaryOperator::Add => (left + right).into(),
BinaryOperator::Sub => (left - right).into(),
BinaryOperator::Mul => (left * right).into(),
BinaryOperator::Div => (left / right).into(),
BinaryOperator::Mod => (left % right).into(),
BinaryOperator::Pow => (left.powf(right)).into(),
BinaryOperator::Eq => (left == right).into(),
BinaryOperator::Neq => (left != right).into(),
BinaryOperator::Gt => (left > right).into(),
BinaryOperator::Gte => (left >= right).into(),
BinaryOperator::Lt => (left < right).into(),
BinaryOperator::Lte => (left <= right).into(),
let value = match self.operator {
BinaryOperator::Add => KclValue::Number {
value: left + right,
meta,
},
BinaryOperator::Sub => KclValue::Number {
value: left - right,
meta,
},
BinaryOperator::Mul => KclValue::Number {
value: left * right,
meta,
},
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 {
value,
meta: vec![Metadata {
source_range: self.into(),
}],
}))
Ok(value)
}
}
impl Node<UnaryExpression> {
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
if self.operator == UnaryOperator::Not {
let value = self.argument.get_result(exec_state, ctx).await?.get_json_value()?;
let Some(bool_value) = json_as_bool(&value) else {
let value = self.argument.get_result(exec_state, ctx).await?;
let KclValue::Bool {
value: bool_value,
meta: _,
} = value
else {
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()],
}));
};
let negated = !bool_value;
return Ok(KclValue::UserVal(UserVal {
value: serde_json::Value::Bool(negated),
meta: vec![Metadata {
let meta = vec![Metadata {
source_range: self.into(),
}],
}));
}];
let negated = KclValue::Bool {
value: !bool_value,
meta,
};
return Ok(negated);
}
let num = parse_json_number_as_f64(
&self.argument.get_result(exec_state, ctx).await?.get_json_value()?,
self.into(),
)?;
Ok(KclValue::UserVal(UserVal {
value: (-(num)).into(),
meta: vec![Metadata {
let value = &self.argument.get_result(exec_state, ctx).await?;
match value {
KclValue::Number { value, meta: _ } => {
let meta = vec![Metadata {
source_range: self.into(),
}],
}))
}];
Ok(KclValue::Number { value: -value, meta })
}
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
// and it works.
match result {
KclValue::UserVal(ref mut uval) => {
uval.mutate(|sketch: &mut Sketch| {
KclValue::Sketch { value: ref mut sketch } => {
for (_, tag) in sketch.tags.iter() {
exec_state.memory.update_tag(&tag.value, tag.clone())?;
}
Ok::<_, KclError>(())
})?;
}
KclValue::Solid(ref mut solid) => {
for value in &solid.value {
@ -425,10 +481,10 @@ impl Node<CallExpression> {
} else {
fn_memory.add(
&param.identifier.name,
KclValue::UserVal(UserVal {
value: serde_json::value::Value::Null,
meta: Default::default(),
}),
KclValue::KclNone {
value: KclNone::new(),
meta: vec![self.into()],
},
param.identifier.clone().into(),
)?;
}
@ -531,15 +587,13 @@ impl Node<ArrayExpression> {
.execute_expr(element, exec_state, &metadata, StatementKind::Expression)
.await?;
results.push(value.get_json_value()?);
results.push(value);
}
Ok(KclValue::UserVal(UserVal {
value: results.into(),
meta: vec![Metadata {
source_range: self.into(),
}],
}))
Ok(KclValue::Array {
value: results,
meta: vec![self.into()],
})
}
}
@ -549,15 +603,19 @@ impl Node<ArrayRangeExpression> {
let metadata = Metadata::from(&self.start_element);
let start = ctx
.execute_expr(&self.start_element, exec_state, &metadata, StatementKind::Expression)
.await?
.get_json_value()?;
let start = parse_json_number_as_i64(&start, (&self.start_element).into())?;
.await?;
let start = start.as_int().ok_or(KclError::Semantic(KclErrorDetails {
source_ranges: vec![self.into()],
message: format!("Expected int but found {}", start.human_friendly_type()),
}))?;
let metadata = Metadata::from(&self.end_element);
let end = ctx
.execute_expr(&self.end_element, exec_state, &metadata, StatementKind::Expression)
.await?
.get_json_value()?;
let end = parse_json_number_as_i64(&end, (&self.end_element).into())?;
.await?;
let end = end.as_int().ok_or(KclError::Semantic(KclErrorDetails {
source_ranges: vec![self.into()],
message: format!("Expected int but found {}", end.human_friendly_type()),
}))?;
if end < start {
return Err(KclError::Semantic(KclErrorDetails {
@ -567,91 +625,73 @@ impl Node<ArrayRangeExpression> {
}
let range: Vec<_> = if self.end_inclusive {
(start..=end).map(JValue::from).collect()
(start..=end).collect()
} else {
(start..end).map(JValue::from).collect()
(start..end).collect()
};
Ok(KclValue::UserVal(UserVal {
value: range.into(),
meta: vec![Metadata {
let 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> {
#[async_recursion]
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 {
let metadata = Metadata::from(&property.value);
let result = ctx
.execute_expr(&property.value, exec_state, &metadata, StatementKind::Expression)
.await?;
object.insert(property.key.name.clone(), result.get_json_value()?);
object.insert(property.key.name.clone(), result);
}
Ok(KclValue::UserVal(UserVal {
value: object.into(),
Ok(KclValue::Object {
value: object,
meta: vec![Metadata {
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 {
Err(KclError::Syntax(KclErrorDetails {
source_ranges: vec![source_range],
message: format!("Invalid integer: {}", j),
}))
"a"
}
}
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),
})
})
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 {
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())
let actual_type = v.human_friendly_type();
let article = if actual_type.starts_with(['a', 'e', 'i', 'o', 'u']) {
"an"
} 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,
"a"
};
Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![source_range],
message: format!("Expected a number, but found {article} {actual_type}",),
}))
}
}
@ -724,15 +764,7 @@ impl Property {
} else {
// Actually evaluate memory to compute the property.
let prop = exec_state.memory.get(name, property_src)?;
let KclValue::UserVal(prop) = prop else {
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: property_sr,
message: format!(
"{name} is not a valid property/index, you can only use a string or int (>= 0) here",
),
}));
};
jvalue_to_prop(&prop.value, property_sr, name)
jvalue_to_prop(prop, property_sr, name)
}
}
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| {
Err::<Property, _>(KclError::Semantic(KclErrorDetails {
source_ranges: property_sr,
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 {
JValue::Number(ref num) => {
let maybe_uint = num.as_u64().and_then(|x| usize::try_from(x).ok());
if let Some(uint) = maybe_uint {
KclValue::Int { value:num, meta: _ } => {
let maybe_int: Result<usize, _> = (*num).try_into();
if let Ok(uint) = maybe_int {
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 if let Some(fnum) = num.as_f64() {
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!("'{num}' is negative, so you can't index an array with it"))
}
}
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!("'{fnum}' is not a valid index, {MUST_BE_POSINT}, {TRY_INT}"))
}
} else {
make_err(format!("'{num}' is not a valid index, {MUST_BE_POSINT}"))
make_err(format!("'{num}' is not an integer, so you can't index an array with it"))
}
}
JValue::String(ref x) => Ok(Property::String(x.to_owned())),
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"))
}

View File

@ -4,10 +4,7 @@ use databake::*;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::{
ast::types::ConstraintLevel,
executor::{KclValue, UserVal},
};
use crate::{ast::types::ConstraintLevel, executor::KclValue};
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.
/// (remember, parameters are in the function declaration,
/// 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)]
#[ts(export)]
#[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 {
fn from(none: &KclNone) -> Self {
let val = UserVal::from(none);
KclValue::UserVal(val)
KclValue::KclNone {
value: *none,
meta: Default::default(),
}
}
}

View File

@ -19,7 +19,6 @@ use kittycad_modeling_cmds::length_unit::LengthUnit;
use parse_display::{Display, FromStr};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value as JValue;
use tower_lsp::lsp_types::{Position as LspPosition, Range as LspRange};
type Point2D = kcmc::shared::Point2d<f64>;
@ -27,8 +26,8 @@ type Point3D = kcmc::shared::Point3d<f64>;
use crate::{
ast::types::{
human_friendly_type, BodyItem, Expr, FunctionExpression, ItemVisibility, KclNone, ModuleId, Node, NodeRef,
Program, TagDeclarator, TagNode,
BodyItem, Expr, FunctionExpression, ItemVisibility, KclNone, ModuleId, Node, NodeRef, Program, TagDeclarator,
TagNode,
},
engine::{EngineManager, ExecutionKind},
errors::{KclError, KclErrorDetails},
@ -201,33 +200,18 @@ impl Environment {
Self {
// Prelude
bindings: HashMap::from([
(
"ZERO".to_string(),
KclValue::UserVal(UserVal {
value: serde_json::Value::Number(serde_json::value::Number::from(0)),
meta: Default::default(),
}),
),
("ZERO".to_string(), KclValue::from_number(0.0, Default::default())),
(
"QUARTER_TURN".to_string(),
KclValue::UserVal(UserVal {
value: serde_json::Value::Number(serde_json::value::Number::from(90)),
meta: Default::default(),
}),
KclValue::from_number(90.0, Default::default()),
),
(
"HALF_TURN".to_string(),
KclValue::UserVal(UserVal {
value: serde_json::Value::Number(serde_json::value::Number::from(180)),
meta: Default::default(),
}),
KclValue::from_number(180.0, Default::default()),
),
(
"THREE_QUARTER_TURN".to_string(),
KclValue::UserVal(UserVal {
value: serde_json::Value::Number(serde_json::value::Number::from(270)),
meta: Default::default(),
}),
KclValue::from_number(270.0, Default::default()),
),
]),
parent: None,
@ -264,22 +248,15 @@ impl Environment {
}
for (_, val) in self.bindings.iter_mut() {
let KclValue::UserVal(v) = val else { continue };
let meta = v.meta.clone();
let maybe_sg: Result<Sketch, _> = serde_json::from_value(v.value.clone());
let Ok(mut sketch) = maybe_sg else {
continue;
};
let KclValue::Sketch { value } = val else { continue };
let mut sketch = value.to_owned();
if sketch.original_id == sg.original_id {
for tag in sg.tags.iter() {
sketch.tags.insert(tag.0.clone(), tag.1.clone());
}
}
*val = KclValue::UserVal(UserVal {
meta,
value: serde_json::to_value(sketch).expect("can always turn Sketch into JSON"),
});
*val = KclValue::Sketch { value: sketch };
}
}
}
@ -360,12 +337,52 @@ impl IdGenerator {
#[ts(export)]
#[serde(tag = "type")]
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>),
TagDeclarator(crate::ast::types::BoxNode<TagDeclarator>),
Plane(Box<Plane>),
Face(Box<Face>),
Sketch {
value: Box<Sketch>,
},
Sketches {
value: Vec<Box<Sketch>>,
},
Solid(Box<Solid>),
Solids {
value: Vec<Box<Solid>>,
@ -380,31 +397,55 @@ pub enum KclValue {
#[serde(rename = "__meta")]
meta: Vec<Metadata>,
},
KclNone {
value: KclNone,
#[serde(rename = "__meta")]
meta: Vec<Metadata>,
},
}
impl KclValue {
pub(crate) fn new_user_val<T: Serialize>(meta: Vec<Metadata>, val: T) -> Self {
Self::UserVal(UserVal::new(meta, val))
pub(crate) fn metadata(&self) -> Vec<Metadata> {
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> {
match self {
KclValue::Solid(e) => Ok(SolidSet::Solid(e.clone())),
KclValue::Solids { value } => Ok(SolidSet::Solids(value.clone())),
KclValue::UserVal(value) => {
let value = value.value.clone();
match value {
JValue::Null | JValue::Bool(_) | JValue::Number(_) | JValue::String(_) => Err(anyhow::anyhow!(
"Failed to deserialize solid set from JSON {}",
human_friendly_type(&value)
)),
JValue::Array(_) => serde_json::from_value::<Vec<Box<Solid>>>(value)
.map(SolidSet::from)
.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)),
}
KclValue::Array { value, .. } => {
let solids: Vec<_> = value
.iter()
.enumerate()
.map(|(i, v)| {
v.as_solid().map(|v| v.to_owned()).map(Box::new).ok_or_else(|| {
anyhow::anyhow!(
"expected this array to only contain solids, but element {i} was actually {}",
v.human_friendly_type()
)
})
})
.collect::<Result<_, _>>()?;
Ok(SolidSet::Solids(solids))
}
_ => anyhow::bail!("Not a solid or solids: {:?}", self),
}
@ -414,43 +455,44 @@ impl KclValue {
/// on for program logic.
pub(crate) fn human_friendly_type(&self) -> &'static str {
match self {
KclValue::UserVal(u) => human_friendly_type(&u.value),
KclValue::Uuid { .. } => "Unique ID (uuid)",
KclValue::TagDeclarator(_) => "TagDeclarator",
KclValue::TagIdentifier(_) => "TagIdentifier",
KclValue::Solid(_) => "Solid",
KclValue::Solids { .. } => "Solids",
KclValue::Sketch { .. } => "Sketch",
KclValue::Sketches { .. } => "Sketches",
KclValue::ImportedGeometry(_) => "ImportedGeometry",
KclValue::Function { .. } => "Function",
KclValue::Plane(_) => "Plane",
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 {
match self {
KclValue::UserVal(..)
| KclValue::TagIdentifier(..)
| KclValue::TagDeclarator(..)
| KclValue::Plane(..)
| KclValue::Face(..)
| KclValue::Solid(..)
| KclValue::Solids { .. }
| KclValue::ImportedGeometry(..) => false,
KclValue::Function { .. } => true,
}
matches!(self, KclValue::Function { .. })
}
}
impl From<SketchSet> for KclValue {
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 {
fn from(sg: Vec<Box<Sketch>>) -> Self {
let meta = sg.iter().flat_map(|sg| sg.meta.clone()).collect();
KclValue::UserVal(UserVal::new(meta, sg))
KclValue::Sketches { value: sg }
}
}
@ -815,52 +857,6 @@ pub enum PlaneType {
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)]
#[ts(export)]
#[serde(tag = "type", rename_all = "camelCase")]
@ -922,108 +918,177 @@ pub type MemoryFunction =
impl From<KclValue> for Vec<SourceRange> {
fn from(item: KclValue) -> Self {
match item {
KclValue::UserVal(u) => u.meta.iter().map(|m| m.source_range).collect(),
KclValue::TagDeclarator(t) => vec![(&t).into()],
KclValue::TagIdentifier(t) => t.meta.iter().map(|m| m.source_range).collect(),
KclValue::Solid(e) => e.meta.iter().map(|m| m.source_range).collect(),
KclValue::Solids { value } => value
.iter()
.flat_map(|eg| eg.meta.iter().map(|m| m.source_range))
.collect(),
KclValue::ImportedGeometry(i) => i.meta.iter().map(|m| m.source_range).collect(),
KclValue::Function { meta, .. } => meta.iter().map(|m| m.source_range).collect(),
KclValue::Plane(p) => p.meta.iter().map(|m| m.source_range).collect(),
KclValue::Face(f) => f.meta.iter().map(|m| m.source_range).collect(),
KclValue::TagDeclarator(t) => vec![SourceRange([t.start, t.end, t.module_id.0 as usize])],
KclValue::TagIdentifier(t) => to_vec_sr(&t.meta),
KclValue::Solid(e) => to_vec_sr(&e.meta),
KclValue::Solids { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(),
KclValue::Sketch { value } => to_vec_sr(&value.meta),
KclValue::Sketches { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(),
KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta),
KclValue::Function { meta, .. } => to_vec_sr(&meta),
KclValue::Plane(p) => to_vec_sr(&p.meta),
KclValue::Face(f) => to_vec_sr(&f.meta),
KclValue::Bool { meta, .. } => to_vec_sr(&meta),
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> {
fn from(item: &KclValue) -> Self {
match item {
KclValue::UserVal(u) => u.meta.iter().map(|m| m.source_range).collect(),
KclValue::TagDeclarator(ref t) => vec![t.into()],
KclValue::TagIdentifier(t) => t.meta.iter().map(|m| m.source_range).collect(),
KclValue::Solid(e) => e.meta.iter().map(|m| m.source_range).collect(),
KclValue::Solids { value } => value
.iter()
.flat_map(|eg| eg.meta.iter().map(|m| m.source_range))
.collect(),
KclValue::ImportedGeometry(i) => i.meta.iter().map(|m| m.source_range).collect(),
KclValue::Function { meta, .. } => meta.iter().map(|m| m.source_range).collect(),
KclValue::Plane(p) => p.meta.iter().map(|m| m.source_range).collect(),
KclValue::Face(f) => f.meta.iter().map(|m| m.source_range).collect(),
KclValue::TagDeclarator(t) => vec![SourceRange([t.start, t.end, t.module_id.0 as usize])],
KclValue::TagIdentifier(t) => to_vec_sr(&t.meta),
KclValue::Solid(e) => to_vec_sr(&e.meta),
KclValue::Solids { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(),
KclValue::Sketch { value } => to_vec_sr(&value.meta),
KclValue::Sketches { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(),
KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta),
KclValue::Function { meta, .. } => to_vec_sr(meta),
KclValue::Plane(p) => to_vec_sr(&p.meta),
KclValue::Face(f) => to_vec_sr(&f.meta),
KclValue::Bool { meta, .. } => to_vec_sr(meta),
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 {
pub fn get_json_value(&self) -> Result<serde_json::Value, KclError> {
if let KclValue::UserVal(user_val) = self {
Ok(user_val.value.clone())
} else {
serde_json::to_value(self).map_err(|err| {
KclError::Semantic(KclErrorDetails {
message: format!("Cannot convert memory item to json value: {:?}", err),
source_ranges: self.clone().into(),
})
})
/// Put the number into a KCL value.
pub fn from_number(f: f64, meta: Vec<Metadata>) -> Self {
Self::Number { value: f, meta }
}
/// Put the point into a KCL value.
pub fn from_point2d(p: [f64; 2], meta: Vec<Metadata>) -> Self {
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 fn get_json<T: serde::de::DeserializeOwned>(&self) -> Result<T, KclError> {
let json = self.get_json_value()?;
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);
}
pub(crate) fn as_usize(&self) -> Option<usize> {
match self {
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(),
})
})
.map(Some)
}
pub fn as_user_val(&self) -> Option<&UserVal> {
if let KclValue::UserVal(x) = self {
Some(x)
pub fn as_int(&self) -> Option<i64> {
if let KclValue::Int { value, meta: _ } = &self {
Some(*value)
} else {
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> {
let err = KclError::Semantic(KclErrorDetails {
let u = self.as_int().and_then(|n| u64::try_from(n).ok()).ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
message: "Expected an integer >= 0".to_owned(),
source_ranges: source_ranges.clone(),
})
})?;
u32::try_from(u).map_err(|_| {
KclError::Semantic(KclErrorDetails {
message: "Number was too big".to_owned(),
source_ranges,
});
self.as_user_val()
.and_then(|uv| uv.value.as_number())
.and_then(|n| n.as_u64())
.and_then(|n| u32::try_from(n).ok())
.ok_or(err)
})
})
}
/// If this value is of type function, return it.
@ -1048,16 +1113,6 @@ impl KclValue {
pub fn get_tag_identifier(&self) -> Result<TagIdentifier, KclError> {
match self {
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 {
message: format!("Not a tag identifier: {:?}", self),
source_ranges: self.clone().into(),
@ -1089,19 +1144,13 @@ impl KclValue {
/// If this KCL value is a bool, retrieve it.
pub fn get_bool(&self) -> Result<bool, KclError> {
let Self::UserVal(uv) = self else {
let Self::Bool { value: b, .. } = self else {
return Err(KclError::Type(KclErrorDetails {
source_ranges: self.into(),
message: format!("Expected bool, found {}", self.human_friendly_type()),
}));
};
let JValue::Bool(b) = uv.value else {
return Err(KclError::Type(KclErrorDetails {
source_ranges: self.into(),
message: format!("Expected bool, found {}", human_friendly_type(&uv.value)),
}));
};
Ok(b)
Ok(*b)
}
/// 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.
#[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)]
#[serde(rename_all = "camelCase")]
pub struct Metadata {
@ -1563,6 +1612,12 @@ pub struct Metadata {
pub source_range: SourceRange,
}
impl From<Metadata> for Vec<SourceRange> {
fn from(meta: Metadata) -> Self {
vec![meta.source_range]
}
}
impl From<SourceRange> for Metadata {
fn from(source_range: SourceRange) -> Self {
Self { source_range }
@ -2655,74 +2710,8 @@ mod tests {
}
/// Convenience function to get a JSON value from memory and unwrap.
fn mem_get_json(memory: &ProgramMemory, name: &str) -> serde_json::Value {
memory
.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()
);
fn mem_get_json(memory: &ProgramMemory, name: &str) -> KclValue {
memory.get(name, SourceRange::default()).unwrap().to_owned()
}
#[tokio::test(flavor = "multi_thread")]
@ -3120,200 +3109,41 @@ let shape = layer() |> patternTransform(10, transform, %)
);
}
#[tokio::test(flavor = "multi_thread")]
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()
);
}
// ADAM: Move some of these into simulation tests.
#[tokio::test(flavor = "multi_thread")]
async fn test_math_execute_with_functions() {
let ast = r#"const myVar = 2 + min(100, -1 + legLen(5, 3))"#;
let memory = parse_execute(ast).await.unwrap();
assert_eq!(
serde_json::json!(5.0),
memory
.get("myVar", SourceRange::default())
.unwrap()
.get_json_value()
.unwrap()
);
assert_eq!(5.0, mem_get_json(&memory, "myVar").as_f64().unwrap());
}
#[tokio::test(flavor = "multi_thread")]
async fn test_math_execute() {
let ast = r#"const myVar = 1 + 2 * (3 - 4) / -5 + 6"#;
let memory = parse_execute(ast).await.unwrap();
assert_eq!(
serde_json::json!(7.4),
memory
.get("myVar", SourceRange::default())
.unwrap()
.get_json_value()
.unwrap()
);
assert_eq!(7.4, mem_get_json(&memory, "myVar").as_f64().unwrap());
}
#[tokio::test(flavor = "multi_thread")]
async fn test_math_execute_start_negative() {
let ast = r#"const myVar = -5 + 6"#;
let memory = parse_execute(ast).await.unwrap();
assert_eq!(
serde_json::json!(1.0),
memory
.get("myVar", SourceRange::default())
.unwrap()
.get_json_value()
.unwrap()
);
assert_eq!(1.0, mem_get_json(&memory, "myVar").as_f64().unwrap());
}
#[tokio::test(flavor = "multi_thread")]
async fn test_math_execute_with_pi() {
let ast = r#"const myVar = pi() * 2"#;
let memory = parse_execute(ast).await.unwrap();
assert_eq!(
serde_json::json!(std::f64::consts::TAU),
memory
.get("myVar", SourceRange::default())
.unwrap()
.get_json_value()
.unwrap()
);
assert_eq!(std::f64::consts::TAU, mem_get_json(&memory, "myVar").as_f64().unwrap());
}
#[tokio::test(flavor = "multi_thread")]
async fn test_math_define_decimal_without_leading_zero() {
let ast = r#"let thing = .4 + 7"#;
let memory = parse_execute(ast).await.unwrap();
assert_eq!(
serde_json::json!(7.4),
memory
.get("thing", SourceRange::default())
.unwrap()
.get_json_value()
.unwrap()
);
assert_eq!(7.4, mem_get_json(&memory, "thing").as_f64().unwrap());
}
#[tokio::test(flavor = "multi_thread")]
@ -3353,10 +3183,10 @@ fn check = (x) => {
check(false)
"#;
let mem = parse_execute(ast).await.unwrap();
assert_eq!(serde_json::json!(false), mem_get_json(&mem, "notTrue"));
assert_eq!(serde_json::json!(true), mem_get_json(&mem, "notFalse"));
assert_eq!(serde_json::json!(true), mem_get_json(&mem, "c"));
assert_eq!(serde_json::json!(false), mem_get_json(&mem, "d"));
assert_eq!(false, mem_get_json(&mem, "notTrue").as_bool().unwrap());
assert_eq!(true, mem_get_json(&mem, "notFalse").as_bool().unwrap());
assert_eq!(true, mem_get_json(&mem, "c").as_bool().unwrap());
assert_eq!(false, mem_get_json(&mem, "d").as_bool().unwrap());
}
#[tokio::test(flavor = "multi_thread")]
@ -3369,7 +3199,7 @@ let notNull = !myNull
assert_eq!(
parse_execute(code1).await.unwrap_err().downcast::<KclError>().unwrap(),
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])],
})
);
@ -3378,7 +3208,7 @@ let notNull = !myNull
assert_eq!(
parse_execute(code2).await.unwrap_err().downcast::<KclError>().unwrap(),
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])],
})
);
@ -3389,7 +3219,7 @@ let notEmptyString = !""
assert_eq!(
parse_execute(code3).await.unwrap_err().downcast::<KclError>().unwrap(),
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])],
})
);
@ -3401,7 +3231,7 @@ let notMember = !obj.a
assert_eq!(
parse_execute(code4).await.unwrap_err().downcast::<KclError>().unwrap(),
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])],
})
);
@ -3412,7 +3242,7 @@ let notArray = !a";
assert_eq!(
parse_execute(code5).await.unwrap_err().downcast::<KclError>().unwrap(),
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])],
})
);
@ -3423,7 +3253,7 @@ let notObject = !x";
assert_eq!(
parse_execute(code6).await.unwrap_err().downcast::<KclError>().unwrap(),
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])],
})
);
@ -3451,7 +3281,7 @@ let notTagDeclarator = !myTagDeclarator";
assert!(
tag_declarator_err
.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: {:?}",
tag_declarator_err
);
@ -3465,7 +3295,7 @@ let notTagIdentifier = !myTag";
assert!(
tag_identifier_err
.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: {:?}",
tag_identifier_err
);
@ -3603,10 +3433,10 @@ let w = f() + f()
fn test_assign_args_to_params() {
// Set up a little framework for this test.
fn mem(number: usize) -> KclValue {
KclValue::UserVal(UserVal {
value: number.into(),
KclValue::Int {
value: number as i64,
meta: Default::default(),
})
}
}
fn ident(s: &'static str) -> Node<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 serde_json::Value as JValue;
use super::{args::FromArgs, Args, FnAsArg};
use crate::{
errors::{KclError, KclErrorDetails},
executor::{ExecState, KclValue, SourceRange, UserVal},
executor::{ExecState, KclValue, SourceRange},
function_param::FunctionParam,
};
/// Apply a function to each element of an array.
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: Vec<KclValue> = array
.into_iter()
.map(|jval| {
KclValue::UserVal(UserVal {
value: jval,
meta: vec![args.source_range.into()],
})
})
.collect();
let (array, f): (Vec<KclValue>, FnAsArg<'_>) = FromArgs::from_args(&args, 0)?;
let meta = vec![args.source_range.into()];
let map_fn = FunctionParam {
inner: f.func,
fn_expr: f.expr,
meta: vec![args.source_range.into()],
meta: meta.clone(),
ctx: args.ctx.clone(),
memory: *f.memory,
};
let new_array = inner_map(array, map_fn, exec_state, &args).await?;
let unwrapped = new_array
.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))
Ok(KclValue::Array { value: new_array, meta })
}
/// 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.
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: Vec<KclValue> = array
.into_iter()
.map(|jval| {
KclValue::UserVal(UserVal {
value: jval,
meta: vec![args.source_range.into()],
})
})
.collect();
let (array, start, f): (Vec<KclValue>, KclValue, FnAsArg<'_>) = FromArgs::from_args(&args, 0)?;
let reduce_fn = FunctionParam {
inner: f.func,
fn_expr: f.expr,
@ -206,50 +175,26 @@ async fn call_reduce_closure<'a>(
#[stdlib {
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
let mut unwrapped_array = array
.into_iter()
.map(|k| match k {
KclValue::UserVal(user_val) => Ok(user_val.value),
_ => Err(KclError::Semantic(KclErrorDetails {
message: "Expected a UserVal in array".to_string(),
source_ranges: vec![args.source_range],
})),
array.push(elem);
Ok(KclValue::Array {
value: array,
meta: vec![args.source_range.into()],
})
.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> {
// 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 array: Vec<KclValue> = array_jvalues
.into_iter()
.map(|jval| KclValue::UserVal(UserVal::new(vec![args.source_range.into()], jval)))
.collect();
// Call the inner_push function
let meta = vec![args.source_range];
let KclValue::Array { value: array, meta: _ } = val else {
let actual_type = val.human_friendly_type();
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: meta,
message: format!("You can't push to a value of type {actual_type}, only an array"),
}));
};
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> {
let (data, description): (bool, String) = args.get_data()?;
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
@ -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> {
let (left, right, description): (f64, f64, String) = args.get_data()?;
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,
@ -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> {
let (left, right, description): (f64, f64, String) = args.get_data()?;
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,
@ -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> {
let (left, right, epsilon, description): (f64, f64, f64, String) = args.get_data()?;
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,
@ -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> {
let (left, right, description): (f64, f64, String) = args.get_data()?;
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,
@ -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> {
let (left, right, description): (f64, f64, String) = args.get_data()?;
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,

View File

@ -233,7 +233,7 @@ pub(crate) async fn do_post_extrude(
tag: path.get_base().tag.clone(),
geo_meta: GeoMeta {
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)
@ -244,7 +244,7 @@ pub(crate) async fn do_post_extrude(
tag: path.get_base().tag.clone(),
geo_meta: GeoMeta {
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)
@ -259,7 +259,7 @@ pub(crate) async fn do_post_extrude(
tag: path.get_base().tag.clone(),
geo_meta: GeoMeta {
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)

View File

@ -14,7 +14,7 @@ use uuid::Uuid;
use crate::{
ast::types::TagNode,
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,
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 edge = inner_get_opposite_edge(tag, exec_state, args.clone()).await?;
Ok(KclValue::UserVal(UserVal {
value: serde_json::to_value(edge).map_err(|e| {
KclError::Type(KclErrorDetails {
message: format!("Failed to convert Uuid to json: {}", e),
source_ranges: vec![args.source_range],
})
})?,
Ok(KclValue::Uuid {
value: edge,
meta: vec![args.source_range.into()],
}))
})
}
/// 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 edge = inner_get_next_adjacent_edge(tag, exec_state, args.clone()).await?;
Ok(KclValue::UserVal(UserVal {
value: serde_json::to_value(edge).map_err(|e| {
KclError::Type(KclErrorDetails {
message: format!("Failed to convert Uuid to json: {}", e),
source_ranges: vec![args.source_range],
})
})?,
Ok(KclValue::Uuid {
value: edge,
meta: vec![args.source_range.into()],
}))
})
}
/// 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 edge = inner_get_previous_adjacent_edge(tag, exec_state, args.clone()).await?;
Ok(KclValue::UserVal(UserVal {
value: serde_json::to_value(edge).map_err(|e| {
KclError::Type(KclErrorDetails {
message: format!("Failed to convert Uuid to json: {}", e),
source_ranges: vec![args.source_range],
})
})?,
Ok(KclValue::Uuid {
value: edge,
meta: vec![args.source_range.into()],
}))
})
}
/// 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 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).
@ -70,7 +70,7 @@ pub async fn sin(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
let num = args.get_number()?;
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).
@ -100,7 +100,7 @@ pub async fn tan(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
let num = args.get_number()?;
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).
@ -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> {
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 (π).
@ -155,7 +155,7 @@ pub async fn sqrt(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
let num = args.get_number()?;
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.
@ -185,7 +185,7 @@ pub async fn abs(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
let num = args.get_number()?;
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.
@ -222,7 +222,7 @@ pub async fn floor(_exec_state: &mut ExecState, args: Args) -> Result<KclValue,
let num = args.get_number()?;
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.
@ -250,7 +250,7 @@ pub async fn ceil(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
let num = args.get_number()?;
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.
@ -278,7 +278,7 @@ pub async fn min(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
let nums = args.get_number_array()?;
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.
@ -315,7 +315,7 @@ pub async fn max(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
let nums = args.get_number_array()?;
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.
@ -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])?;
args.make_user_val_from_f64(result)
Ok(args.make_user_val_from_f64(result))
}
/// 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 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).
@ -427,7 +427,7 @@ pub async fn asin(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
let num = args.get_number()?;
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).
@ -457,7 +457,7 @@ pub async fn atan(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
let num = args.get_number()?;
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).
@ -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])?;
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.
@ -536,7 +536,7 @@ pub async fn log2(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
let num = args.get_number()?;
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.
@ -564,7 +564,7 @@ pub async fn log10(_exec_state: &mut ExecState, args: Args) -> Result<KclValue,
let num = args.get_number()?;
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.
@ -592,7 +592,7 @@ pub async fn ln(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
let num = args.get_number()?;
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.
@ -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> {
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`.
@ -648,7 +648,7 @@ fn inner_e() -> Result<f64, KclError> {
pub async fn tau(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
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π.
@ -678,7 +678,7 @@ pub async fn to_radians(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
let num = args.get_number()?;
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.
@ -708,7 +708,7 @@ pub async fn to_degrees(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
let num = args.get_number()?;
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.

View File

@ -244,7 +244,7 @@ pub enum FunctionKind {
pub async fn leg_length(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (hypotenuse, leg) = args.get_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.
@ -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> {
let (hypotenuse, leg) = args.get_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.
@ -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> {
let (hypotenuse, leg) = args.get_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.

View File

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

View File

@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
use crate::{
errors::KclError,
executor::{ExecState, KclValue, Metadata, Plane, UserVal},
executor::{ExecState, KclValue, Plane},
std::{sketch::PlaneData, Args},
};
@ -50,15 +50,9 @@ impl From<StandardPlane> for PlaneData {
/// Offset a plane by a distance along its normal.
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 plane = inner_offset_plane(std_plane, offset, exec_state).await?;
Ok(KclValue::UserVal(UserVal::new(
vec![Metadata {
source_range: args.source_range,
}],
plane,
)))
let plane_data = 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)))
}
/// 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])
/// ```
/// ```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 {
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 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.
@ -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 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.
@ -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 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.
@ -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 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.
@ -251,7 +251,7 @@ pub async fn last_segment_x(_exec_state: &mut ExecState, args: Args) -> Result<K
let sketch = args.get_sketch()?;
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
@ -291,7 +291,7 @@ pub async fn last_segment_y(_exec_state: &mut ExecState, args: Args) -> Result<K
let sketch = args.get_sketch()?;
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
@ -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> {
let tag: TagIdentifier = args.get_data()?;
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.
@ -376,7 +376,7 @@ pub async fn segment_angle(exec_state: &mut ExecState, args: Args) -> Result<Kcl
let tag: TagIdentifier = args.get_data()?;
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.
@ -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> {
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())?;
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.
@ -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> {
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())?;
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.

View File

@ -48,7 +48,9 @@ pub async fn circle(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
args.get_circle_args()?;
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
@ -166,7 +168,7 @@ pub struct PolygonData {
pub center: [f64; 2],
/// The type of the polygon (inscribed or circumscribed)
#[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
#[serde(default = "default_inscribed")]
pub inscribed: bool,
@ -182,7 +184,9 @@ pub async fn polygon(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
args.get_polygon_args()?;
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.

View File

@ -17,7 +17,7 @@ use crate::{
errors::{KclError, KclErrorDetails},
executor::{
BasePath, ExecState, Face, GeoMeta, KclValue, Path, Plane, Point2d, Point3d, Sketch, SketchSet, SketchSurface,
Solid, TagEngineInfo, TagIdentifier, UserVal,
Solid, TagEngineInfo, TagIdentifier,
},
std::{
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 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.
@ -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 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.
@ -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 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.
@ -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 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
@ -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 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
@ -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 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
@ -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 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
@ -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 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
@ -573,9 +589,9 @@ async fn inner_angled_line_of_x_length(
#[serde(rename_all = "camelCase")]
pub struct AngledLineToData {
/// The angle of the line.
angle: f64,
pub angle: f64,
/// The point to draw to.
to: f64,
pub to: f64,
}
/// 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 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
@ -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?;
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
@ -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 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
@ -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>) =
args.get_data_and_sketch_and_tag()?;
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
@ -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 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.
@ -1135,7 +1161,9 @@ pub async fn start_profile_at(exec_state: &mut ExecState, args: Args) -> Result<
args.get_data_and_sketch_surface()?;
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.
@ -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> {
let sketch: Sketch = args.get_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'
@ -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> {
let sketch: Sketch = args.get_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'
@ -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> {
let sketch: Sketch = args.get_sketch()?;
let point = inner_profile_start(sketch)?;
Ok(KclValue::UserVal(UserVal {
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(),
}))
Ok(KclValue::from_point2d(point, args.into()))
}
/// 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?;
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
@ -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 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.
@ -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 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.
@ -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 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..
@ -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 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
@ -1873,11 +1903,11 @@ async fn inner_tangential_arc_to_relative(
#[serde(rename_all = "camelCase")]
pub struct BezierData {
/// The to point.
to: [f64; 2],
pub to: [f64; 2],
/// The first control point.
control1: [f64; 2],
pub control1: [f64; 2],
/// The second control point.
control2: [f64; 2],
pub control2: [f64; 2],
}
/// 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 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
@ -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 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.

View File

@ -14,7 +14,7 @@ use crate::{
pub async fn mm(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
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.
@ -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> {
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.
@ -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> {
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.
@ -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> {
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.
@ -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> {
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.
@ -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> {
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.

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,36 +8,67 @@ snapshot_kind: text
{
"bindings": {
"HALF_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 180,
"type": "Number",
"value": 180.0,
"__meta": []
},
"QUARTER_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 90,
"type": "Number",
"value": 90.0,
"__meta": []
},
"THREE_QUARTER_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 270,
"type": "Number",
"value": 270.0,
"__meta": []
},
"ZERO": {
"type": "UserVal",
"type": "UserVal",
"value": 0,
"type": "Number",
"value": 0.0,
"__meta": []
},
"array": {
"type": "UserVal",
"type": "UserVal",
"type": "Array",
"value": [
90,
91,
92
{
"type": "Int",
"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": [
{
@ -50,8 +81,7 @@ snapshot_kind: text
]
},
"i": {
"type": "UserVal",
"type": "UserVal",
"type": "Int",
"value": 1,
"__meta": [
{
@ -64,28 +94,26 @@ snapshot_kind: text
]
},
"result0": {
"type": "UserVal",
"type": "UserVal",
"type": "Int",
"value": 91,
"__meta": [
{
"sourceRange": [
93,
101,
48,
50,
0
]
}
]
},
"result1": {
"type": "UserVal",
"type": "UserVal",
"type": "Int",
"value": 91,
"__meta": [
{
"sourceRange": [
277,
285,
48,
50,
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": {
"HALF_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 180,
"type": "Number",
"value": 180.0,
"__meta": []
},
"QUARTER_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 90,
"type": "Number",
"value": 90.0,
"__meta": []
},
"THREE_QUARTER_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 270,
"type": "Number",
"value": 270.0,
"__meta": []
},
"ZERO": {
"type": "UserVal",
"type": "UserVal",
"value": 0,
"type": "Number",
"value": 0.0,
"__meta": []
},
"obj": {
"type": "UserVal",
"type": "UserVal",
"type": "Object",
"value": {
"bar": 0,
"foo": 1
"bar": {
"type": "Int",
"value": 0,
"__meta": [
{
"sourceRange": [
71,
72,
0
]
}
]
},
"foo": {
"type": "Int",
"value": 1,
"__meta": [
{
"sourceRange": [
63,
64,
0
]
}
]
}
},
"__meta": [
{
@ -49,12 +68,47 @@ snapshot_kind: text
]
},
"obj2": {
"type": "UserVal",
"type": "UserVal",
"type": "Object",
"value": {
"inner": {
"bar": 0,
"foo": 1
"type": "Object",
"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": [
@ -68,64 +122,59 @@ snapshot_kind: text
]
},
"one_a": {
"type": "UserVal",
"type": "UserVal",
"type": "Int",
"value": 1,
"__meta": [
{
"sourceRange": [
122,
132,
63,
64,
0
]
}
]
},
"one_b": {
"type": "UserVal",
"type": "UserVal",
"type": "Int",
"value": 1,
"__meta": [
{
"sourceRange": [
356,
362,
63,
64,
0
]
}
]
},
"one_c": {
"type": "UserVal",
"type": "UserVal",
"type": "Int",
"value": 1,
"__meta": [
{
"sourceRange": [
553,
570,
63,
64,
0
]
}
]
},
"one_d": {
"type": "UserVal",
"type": "UserVal",
"type": "Int",
"value": 1,
"__meta": [
{
"sourceRange": [
757,
770,
63,
64,
0
]
}
]
},
"p": {
"type": "UserVal",
"type": "UserVal",
"type": "String",
"value": "foo",
"__meta": [
{

View File

@ -8,27 +8,23 @@ snapshot_kind: text
{
"bindings": {
"HALF_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 180,
"type": "Number",
"value": 180.0,
"__meta": []
},
"QUARTER_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 90,
"type": "Number",
"value": 90.0,
"__meta": []
},
"THREE_QUARTER_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 270,
"type": "Number",
"value": 270.0,
"__meta": []
},
"ZERO": {
"type": "UserVal",
"type": "UserVal",
"value": 0,
"type": "Number",
"value": 0.0,
"__meta": []
},
"test": {
@ -295,27 +291,23 @@ snapshot_kind: text
{
"bindings": {
"HALF_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 180,
"type": "Number",
"value": 180.0,
"__meta": []
},
"QUARTER_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 90,
"type": "Number",
"value": 90.0,
"__meta": []
},
"THREE_QUARTER_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 270,
"type": "Number",
"value": 270.0,
"__meta": []
},
"ZERO": {
"type": "UserVal",
"type": "UserVal",
"value": 0,
"type": "Number",
"value": 0.0,
"__meta": []
}
},
@ -637,27 +629,23 @@ snapshot_kind: text
{
"bindings": {
"HALF_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 180,
"type": "Number",
"value": 180.0,
"__meta": []
},
"QUARTER_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 90,
"type": "Number",
"value": 90.0,
"__meta": []
},
"THREE_QUARTER_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 270,
"type": "Number",
"value": 270.0,
"__meta": []
},
"ZERO": {
"type": "UserVal",
"type": "UserVal",
"value": 0,
"type": "Number",
"value": 0.0,
"__meta": []
},
"test": {
@ -924,27 +912,23 @@ snapshot_kind: text
{
"bindings": {
"HALF_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 180,
"type": "Number",
"value": 180.0,
"__meta": []
},
"QUARTER_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 90,
"type": "Number",
"value": 90.0,
"__meta": []
},
"THREE_QUARTER_TURN": {
"type": "UserVal",
"type": "UserVal",
"value": 270,
"type": "Number",
"value": 270.0,
"__meta": []
},
"ZERO": {
"type": "UserVal",
"type": "UserVal",
"value": 0,
"type": "Number",
"value": 0.0,
"__meta": []
}
},
@ -982,45 +966,10 @@ snapshot_kind: text
]
},
"x": {
"type": "UserVal",
"type": "UserVal",
"type": "Sketch",
"value": {
"__meta": [
{
"sourceRange": [
52,
77,
0
]
}
],
"type": "Sketch",
"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": {
@ -1103,63 +1052,15 @@ snapshot_kind: text
"type": "ToPoint"
}
],
"start": {
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
52,
77,
0
]
},
"from": [
0.0,
0.0
],
"tag": null,
"to": [
0.0,
0.0
]
},
"type": "Sketch"
},
"__meta": [
{
"sourceRange": [
52,
77,
0
]
}
]
},
"x2": {
"type": "UserVal",
"type": "UserVal",
"value": {
"thing1": {
"thing2": {
"__meta": [
{
"sourceRange": [
242,
267,
0
]
}
],
"id": "[uuid]",
"on": {
"__meta": [],
"type": "plane",
"id": "[uuid]",
"value": "XY",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"type": "plane",
"value": "XY",
"xAxis": {
"x": 1.0,
"y": 0.0,
@ -1174,8 +1075,50 @@ snapshot_kind: text
"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": [
52,
77,
0
]
}
},
"__meta": [
{
"sourceRange": [
52,
77,
0
]
}
]
}
},
"x2": {
"type": "Object",
"value": {
"thing1": {
"type": "Object",
"value": {
"thing2": {
"type": "Sketch",
"value": {
"type": "Sketch",
"id": "[uuid]",
"paths": [
{
"__geoMeta": {
@ -1258,7 +1201,42 @@ snapshot_kind: text
"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": [
@ -1266,19 +1244,29 @@ snapshot_kind: text
267,
0
]
},
"from": [
0.0,
0.0
],
"tag": null,
"to": [
0.0,
0.0
]
},
"type": "Sketch"
}
},
"__meta": [
{
"sourceRange": [
242,
267,
0
]
}
]
}
}
},
"__meta": [
{
"sourceRange": [
199,
365,
0
]
}
]
}
},
"__meta": [

View File

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

View File

@ -1354,7 +1354,7 @@ secondSketch = startSketchOn(part001, '')
assert!(result.is_err());
assert_eq!(
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!(
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!(
invalid_index_fractional,
@ -81,7 +81,7 @@ gen_test_fail!(
);
gen_test_fail!(
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!(
invalid_member_object_prop,
@ -107,7 +107,10 @@ gen_test_fail!(
// if_else_no_expr,
// "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!(
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"

View File

@ -4,7 +4,7 @@ use kcl_lib::{
modify::modify_ast_for_sketch,
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 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.
// 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);
};
let Some((sketch, _meta)) = user_val.get::<Sketch>() else {
anyhow::bail!("part001 was not a Sketch");
};
let sketch_id = sketch.id;
let plane_id = uuid::Uuid::new_v4();