Merge remote-tracking branch 'origin/main' into paultag/import
This commit is contained in:
@ -1,5 +1,7 @@
|
||||
//! Data on available annotations.
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use kittycad_modeling_cmds::coord::{System, KITTYCAD, OPENGL, VULKAN};
|
||||
|
||||
use crate::{
|
||||
@ -24,6 +26,33 @@ pub(super) const IMPORT_COORDS_VALUES: [(&str, &System); 3] =
|
||||
[("zoo", KITTYCAD), ("opengl", OPENGL), ("vulkan", VULKAN)];
|
||||
pub(super) const IMPORT_LENGTH_UNIT: &str = "lengthUnit";
|
||||
|
||||
pub(crate) const IMPL: &str = "impl";
|
||||
pub(crate) const IMPL_RUST: &str = "std_rust";
|
||||
pub(crate) const IMPL_KCL: &str = "kcl";
|
||||
pub(crate) const IMPL_PRIMITIVE: &str = "primitive";
|
||||
pub(super) const IMPL_VALUES: [&str; 3] = [IMPL_RUST, IMPL_KCL, IMPL_PRIMITIVE];
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug, Default)]
|
||||
pub enum Impl {
|
||||
#[default]
|
||||
Kcl,
|
||||
Rust,
|
||||
Primitive,
|
||||
}
|
||||
|
||||
impl FromStr for Impl {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
IMPL_RUST => Ok(Self::Rust),
|
||||
IMPL_KCL => Ok(Self::Kcl),
|
||||
IMPL_PRIMITIVE => Ok(Self::Primitive),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn settings_completion_text() -> String {
|
||||
format!("@{SETTINGS}({SETTINGS_UNIT_LENGTH} = mm, {SETTINGS_UNIT_ANGLE} = deg)")
|
||||
}
|
||||
@ -58,6 +87,32 @@ pub(super) fn expect_ident(expr: &Expr) -> Result<&str, KclError> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn get_impl(annotations: &[Node<Annotation>], source_range: SourceRange) -> Result<Option<Impl>, KclError> {
|
||||
for attr in annotations {
|
||||
if attr.name.is_some() || attr.properties.is_none() {
|
||||
continue;
|
||||
}
|
||||
for p in attr.properties.as_ref().unwrap() {
|
||||
if &*p.key.name == IMPL {
|
||||
if let Some(s) = p.value.ident_name() {
|
||||
return Impl::from_str(s).map(Some).map_err(|_| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"Invalid value for {} attribute, expected one of: {}",
|
||||
IMPL,
|
||||
IMPL_VALUES.join(", ")
|
||||
),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
impl UnitLen {
|
||||
pub(super) fn from_str(s: &str, source_range: SourceRange) -> Result<Self, KclError> {
|
||||
match s {
|
||||
|
@ -252,7 +252,7 @@ fn generate_changed_program(old_ast: Node<Program>, mut new_ast: Node<Program>,
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::execution::parse_execute;
|
||||
use crate::execution::{parse_execute, ExecTestResults};
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_get_changed_program_same_code() {
|
||||
@ -268,16 +268,16 @@ firstSketch = startSketchOn('XY')
|
||||
// Remove the end face for the extrusion.
|
||||
shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
|
||||
|
||||
let (program, _, ctx, _) = parse_execute(new).await.unwrap();
|
||||
let ExecTestResults { program, exec_ctxt, .. } = parse_execute(new).await.unwrap();
|
||||
|
||||
let result = get_changed_program(
|
||||
CacheInformation {
|
||||
ast: &program.ast,
|
||||
settings: &ctx.settings,
|
||||
settings: &exec_ctxt.settings,
|
||||
},
|
||||
CacheInformation {
|
||||
ast: &program.ast,
|
||||
settings: &ctx.settings,
|
||||
settings: &exec_ctxt.settings,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
@ -311,18 +311,18 @@ firstSketch = startSketchOn('XY')
|
||||
// Remove the end face for the extrusion.
|
||||
shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
|
||||
|
||||
let (program_old, _, ctx, _) = parse_execute(old).await.unwrap();
|
||||
let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old).await.unwrap();
|
||||
|
||||
let program_new = crate::Program::parse_no_errs(new).unwrap();
|
||||
|
||||
let result = get_changed_program(
|
||||
CacheInformation {
|
||||
ast: &program_old.ast,
|
||||
settings: &ctx.settings,
|
||||
ast: &program.ast,
|
||||
settings: &exec_ctxt.settings,
|
||||
},
|
||||
CacheInformation {
|
||||
ast: &program_new.ast,
|
||||
settings: &ctx.settings,
|
||||
settings: &exec_ctxt.settings,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
@ -356,18 +356,18 @@ firstSketch = startSketchOn('XY')
|
||||
// Remove the end face for the extrusion.
|
||||
shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
|
||||
|
||||
let (program, _, ctx, _) = parse_execute(old).await.unwrap();
|
||||
let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old).await.unwrap();
|
||||
|
||||
let program_new = crate::Program::parse_no_errs(new).unwrap();
|
||||
|
||||
let result = get_changed_program(
|
||||
CacheInformation {
|
||||
ast: &program.ast,
|
||||
settings: &ctx.settings,
|
||||
settings: &exec_ctxt.settings,
|
||||
},
|
||||
CacheInformation {
|
||||
ast: &program_new.ast,
|
||||
settings: &ctx.settings,
|
||||
settings: &exec_ctxt.settings,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
@ -405,18 +405,18 @@ firstSketch = startSketchOn('XY')
|
||||
// Remove the end face for the extrusion.
|
||||
shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
|
||||
|
||||
let (program, _, ctx, _) = parse_execute(old).await.unwrap();
|
||||
let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old).await.unwrap();
|
||||
|
||||
let program_new = crate::Program::parse_no_errs(new).unwrap();
|
||||
|
||||
let result = get_changed_program(
|
||||
CacheInformation {
|
||||
ast: &program.ast,
|
||||
settings: &ctx.settings,
|
||||
settings: &exec_ctxt.settings,
|
||||
},
|
||||
CacheInformation {
|
||||
ast: &program_new.ast,
|
||||
settings: &ctx.settings,
|
||||
settings: &exec_ctxt.settings,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
@ -439,10 +439,12 @@ firstSketch = startSketchOn('XY')
|
||||
// Remove the end face for the extrusion.
|
||||
shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
|
||||
|
||||
let (program, _, mut ctx, _) = parse_execute(new).await.unwrap();
|
||||
let ExecTestResults {
|
||||
program, mut exec_ctxt, ..
|
||||
} = parse_execute(new).await.unwrap();
|
||||
|
||||
// Change the settings to cm.
|
||||
ctx.settings.units = crate::UnitLength::Cm;
|
||||
exec_ctxt.settings.units = crate::UnitLength::Cm;
|
||||
|
||||
let result = get_changed_program(
|
||||
CacheInformation {
|
||||
@ -451,7 +453,7 @@ shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
|
||||
},
|
||||
CacheInformation {
|
||||
ast: &program.ast,
|
||||
settings: &ctx.settings,
|
||||
settings: &exec_ctxt.settings,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
@ -481,10 +483,12 @@ firstSketch = startSketchOn('XY')
|
||||
// Remove the end face for the extrusion.
|
||||
shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
|
||||
|
||||
let (program, _, mut ctx, _) = parse_execute(new).await.unwrap();
|
||||
let ExecTestResults {
|
||||
program, mut exec_ctxt, ..
|
||||
} = parse_execute(new).await.unwrap();
|
||||
|
||||
// Change the settings.
|
||||
ctx.settings.show_grid = !ctx.settings.show_grid;
|
||||
exec_ctxt.settings.show_grid = !exec_ctxt.settings.show_grid;
|
||||
|
||||
let result = get_changed_program(
|
||||
CacheInformation {
|
||||
@ -493,7 +497,7 @@ shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
|
||||
},
|
||||
CacheInformation {
|
||||
ast: &program.ast,
|
||||
settings: &ctx.settings,
|
||||
settings: &exec_ctxt.settings,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
@ -516,10 +520,12 @@ firstSketch = startSketchOn('XY')
|
||||
// Remove the end face for the extrusion.
|
||||
shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
|
||||
|
||||
let (program, _, mut ctx, _) = parse_execute(new).await.unwrap();
|
||||
let ExecTestResults {
|
||||
program, mut exec_ctxt, ..
|
||||
} = parse_execute(new).await.unwrap();
|
||||
|
||||
// Change the settings.
|
||||
ctx.settings.highlight_edges = !ctx.settings.highlight_edges;
|
||||
exec_ctxt.settings.highlight_edges = !exec_ctxt.settings.highlight_edges;
|
||||
|
||||
let result = get_changed_program(
|
||||
CacheInformation {
|
||||
@ -528,7 +534,7 @@ shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
|
||||
},
|
||||
CacheInformation {
|
||||
ast: &program.ast,
|
||||
settings: &ctx.settings,
|
||||
settings: &exec_ctxt.settings,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
@ -536,8 +542,8 @@ shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
|
||||
assert_eq!(result, CacheResult::NoAction(true));
|
||||
|
||||
// Change the settings back.
|
||||
let old_settings = ctx.settings.clone();
|
||||
ctx.settings.highlight_edges = !ctx.settings.highlight_edges;
|
||||
let old_settings = exec_ctxt.settings.clone();
|
||||
exec_ctxt.settings.highlight_edges = !exec_ctxt.settings.highlight_edges;
|
||||
|
||||
let result = get_changed_program(
|
||||
CacheInformation {
|
||||
@ -546,7 +552,7 @@ shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
|
||||
},
|
||||
CacheInformation {
|
||||
ast: &program.ast,
|
||||
settings: &ctx.settings,
|
||||
settings: &exec_ctxt.settings,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
@ -554,8 +560,8 @@ shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
|
||||
assert_eq!(result, CacheResult::NoAction(true));
|
||||
|
||||
// Change the settings back.
|
||||
let old_settings = ctx.settings.clone();
|
||||
ctx.settings.highlight_edges = !ctx.settings.highlight_edges;
|
||||
let old_settings = exec_ctxt.settings.clone();
|
||||
exec_ctxt.settings.highlight_edges = !exec_ctxt.settings.highlight_edges;
|
||||
|
||||
let result = get_changed_program(
|
||||
CacheInformation {
|
||||
@ -564,7 +570,7 @@ shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
|
||||
},
|
||||
CacheInformation {
|
||||
ast: &program.ast,
|
||||
settings: &ctx.settings,
|
||||
settings: &exec_ctxt.settings,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
@ -583,7 +589,7 @@ startSketchOn('XY')
|
||||
startSketchOn('XY')
|
||||
"#;
|
||||
|
||||
let (program, _, ctx, _) = parse_execute(old_code).await.unwrap();
|
||||
let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old_code).await.unwrap();
|
||||
|
||||
let mut new_program = crate::Program::parse_no_errs(new_code).unwrap();
|
||||
new_program.compute_digest();
|
||||
@ -591,11 +597,11 @@ startSketchOn('XY')
|
||||
let result = get_changed_program(
|
||||
CacheInformation {
|
||||
ast: &program.ast,
|
||||
settings: &ctx.settings,
|
||||
settings: &exec_ctxt.settings,
|
||||
},
|
||||
CacheInformation {
|
||||
ast: &new_program.ast,
|
||||
settings: &ctx.settings,
|
||||
settings: &exec_ctxt.settings,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
@ -197,6 +197,7 @@ pub enum OpKclValue {
|
||||
},
|
||||
Function {},
|
||||
Module {},
|
||||
Type {},
|
||||
KclNone {},
|
||||
}
|
||||
|
||||
@ -233,7 +234,7 @@ impl From<&KclValue> for OpKclValue {
|
||||
ty: ty.clone(),
|
||||
},
|
||||
KclValue::String { value, .. } => Self::String { value: value.clone() },
|
||||
KclValue::Array { value, .. } => {
|
||||
KclValue::MixedArray { value, .. } => {
|
||||
let value = value.iter().map(Self::from).collect();
|
||||
Self::Array { value }
|
||||
}
|
||||
@ -293,6 +294,7 @@ impl From<&KclValue> for OpKclValue {
|
||||
KclValue::Function { .. } => Self::Function {},
|
||||
KclValue::Module { .. } => Self::Module {},
|
||||
KclValue::KclNone { .. } => Self::KclNone {},
|
||||
KclValue::Type { .. } => Self::Type {},
|
||||
KclValue::Tombstone { .. } => unreachable!("Tombstone OpKclValue"),
|
||||
}
|
||||
}
|
||||
|
@ -327,6 +327,50 @@ impl ExecutorContext {
|
||||
}
|
||||
last_expr = None;
|
||||
}
|
||||
BodyItem::TypeDeclaration(ty) => {
|
||||
let metadata = Metadata::from(&**ty);
|
||||
let impl_kind = annotations::get_impl(&ty.outer_attrs, metadata.source_range)?.unwrap_or_default();
|
||||
match impl_kind {
|
||||
annotations::Impl::Rust => {
|
||||
let std_path = match &exec_state.mod_local.settings.std_path {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: "User-defined types are not yet supported.".to_owned(),
|
||||
source_ranges: vec![metadata.source_range],
|
||||
}));
|
||||
}
|
||||
};
|
||||
let value = KclValue::Type {
|
||||
value: Some(crate::std::std_ty(std_path, &ty.name.name)),
|
||||
meta: vec![metadata],
|
||||
};
|
||||
exec_state
|
||||
.mut_stack()
|
||||
.add(
|
||||
format!("{}{}", memory::TYPE_PREFIX, ty.name.name),
|
||||
value,
|
||||
metadata.source_range,
|
||||
)
|
||||
.map_err(|_| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: format!("Redefinition of type {}.", ty.name.name),
|
||||
source_ranges: vec![metadata.source_range],
|
||||
})
|
||||
})?;
|
||||
}
|
||||
// Do nothing for primitive types, they get special treatment and their declarations are just for documentation.
|
||||
annotations::Impl::Primitive => {}
|
||||
annotations::Impl::Kcl => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: "User-defined types are not yet supported.".to_owned(),
|
||||
source_ranges: vec![metadata.source_range],
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
last_expr = None;
|
||||
}
|
||||
BodyItem::ReturnStatement(return_statement) => {
|
||||
let metadata = Metadata::from(return_statement);
|
||||
|
||||
@ -561,21 +605,9 @@ impl ExecutorContext {
|
||||
}
|
||||
Expr::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, self).await?,
|
||||
Expr::FunctionExpression(function_expression) => {
|
||||
let mut rust_impl = false;
|
||||
for attr in annotations {
|
||||
if attr.name.is_some() || attr.properties.is_none() {
|
||||
continue;
|
||||
}
|
||||
for p in attr.properties.as_ref().unwrap() {
|
||||
if &*p.key.name == "impl" {
|
||||
if let Some(s) = p.value.ident_name() {
|
||||
if s == "std_rust" {
|
||||
rust_impl = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let rust_impl = annotations::get_impl(annotations, metadata.source_range)?
|
||||
.map(|s| s == annotations::Impl::Rust)
|
||||
.unwrap_or(false);
|
||||
|
||||
if rust_impl {
|
||||
if let Some(std_path) = &exec_state.mod_local.settings.std_path {
|
||||
@ -598,6 +630,7 @@ impl ExecutorContext {
|
||||
KclValue::Function {
|
||||
value: FunctionSource::User {
|
||||
ast: function_expression.clone(),
|
||||
settings: exec_state.mod_local.settings.clone(),
|
||||
memory: exec_state.mut_stack().snapshot(),
|
||||
},
|
||||
meta: vec![metadata.to_owned()],
|
||||
@ -665,7 +698,12 @@ impl ExecutorContext {
|
||||
}
|
||||
|
||||
fn coerce(value: KclValue, ty: &Node<Type>, exec_state: &mut ExecState) -> Result<KclValue, KclValue> {
|
||||
let ty = RuntimeType::from_parsed(ty.inner.clone(), &exec_state.mod_local.settings).ok_or_else(|| value.clone())?;
|
||||
let ty = RuntimeType::from_parsed(ty.inner.clone(), exec_state, (&value).into())
|
||||
.map_err(|e| {
|
||||
exec_state.err(e);
|
||||
value.clone()
|
||||
})?
|
||||
.ok_or_else(|| value.clone())?;
|
||||
if value.has_type(&ty) {
|
||||
return Ok(value);
|
||||
}
|
||||
@ -759,7 +797,7 @@ impl Node<MemberExpression> {
|
||||
}
|
||||
};
|
||||
|
||||
let KclValue::Array { value: array, meta: _ } = array else {
|
||||
let KclValue::MixedArray { 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()],
|
||||
@ -809,7 +847,7 @@ impl Node<MemberExpression> {
|
||||
source_ranges: vec![self.clone().into()],
|
||||
}))
|
||||
}
|
||||
(KclValue::Array { value: arr, meta: _ }, Property::UInt(index)) => {
|
||||
(KclValue::MixedArray { value: arr, meta: _ }, Property::UInt(index)) => {
|
||||
let value_of_arr = arr.get(index);
|
||||
if let Some(value) = value_of_arr {
|
||||
Ok(value.to_owned())
|
||||
@ -820,7 +858,7 @@ impl Node<MemberExpression> {
|
||||
}))
|
||||
}
|
||||
}
|
||||
(KclValue::Array { .. }, p) => {
|
||||
(KclValue::MixedArray { .. }, p) => {
|
||||
let t = p.type_name();
|
||||
let article = article_for(t);
|
||||
Err(KclError::Semantic(KclErrorDetails {
|
||||
@ -1494,7 +1532,7 @@ impl Node<ArrayExpression> {
|
||||
results.push(value);
|
||||
}
|
||||
|
||||
Ok(KclValue::Array {
|
||||
Ok(KclValue::MixedArray {
|
||||
value: results,
|
||||
meta: vec![self.into()],
|
||||
})
|
||||
@ -1543,7 +1581,7 @@ impl Node<ArrayRangeExpression> {
|
||||
let meta = vec![Metadata {
|
||||
source_range: self.into(),
|
||||
}];
|
||||
Ok(KclValue::Array {
|
||||
Ok(KclValue::MixedArray {
|
||||
value: range
|
||||
.into_iter()
|
||||
.map(|num| KclValue::Number {
|
||||
@ -1957,7 +1995,7 @@ impl FunctionSource {
|
||||
|
||||
func(exec_state, args).await.map(Some)
|
||||
}
|
||||
FunctionSource::User { ast, memory } => {
|
||||
FunctionSource::User { ast, memory, .. } => {
|
||||
call_user_defined_function(args, *memory, ast, exec_state, ctx).await
|
||||
}
|
||||
FunctionSource::None => unreachable!(),
|
||||
@ -2109,9 +2147,11 @@ p = {
|
||||
"#;
|
||||
|
||||
let result = parse_execute(program).await.unwrap();
|
||||
let mem = result.3.stack();
|
||||
let mem = result.exec_state.stack();
|
||||
assert!(matches!(
|
||||
mem.memory.get_from("p", result.1, SourceRange::default(), 0).unwrap(),
|
||||
mem.memory
|
||||
.get_from("p", result.mem_env, SourceRange::default(), 0)
|
||||
.unwrap(),
|
||||
KclValue::Plane { .. }
|
||||
));
|
||||
|
||||
@ -2147,8 +2187,12 @@ p2 = -p
|
||||
"#;
|
||||
|
||||
let result = parse_execute(program).await.unwrap();
|
||||
let mem = result.3.stack();
|
||||
match mem.memory.get_from("p2", result.1, SourceRange::default(), 0).unwrap() {
|
||||
let mem = result.exec_state.stack();
|
||||
match mem
|
||||
.memory
|
||||
.get_from("p2", result.mem_env, SourceRange::default(), 0)
|
||||
.unwrap()
|
||||
{
|
||||
KclValue::Plane { value } => assert_eq!(value.z_axis.z, -1.0),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
@ -243,7 +243,6 @@ pub struct Helix {
|
||||
pub meta: Vec<Metadata>,
|
||||
}
|
||||
|
||||
/// A plane.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@ -480,47 +479,6 @@ pub enum PlaneType {
|
||||
Uninit,
|
||||
}
|
||||
|
||||
/// A sketch is a collection of paths.
|
||||
///
|
||||
/// When you define a sketch to a variable like:
|
||||
///
|
||||
/// ```kcl
|
||||
/// mySketch = startSketchOn('XY')
|
||||
/// |> startProfileAt([-12, 12], %)
|
||||
/// |> line(end = [24, 0])
|
||||
/// |> line(end = [0, -24])
|
||||
/// |> line(end = [-24, 0])
|
||||
/// |> close()
|
||||
/// ```
|
||||
///
|
||||
/// The `mySketch` variable will be an executed `Sketch` object. Executed being past
|
||||
/// tense, because the engine has already executed the commands to create the sketch.
|
||||
///
|
||||
/// The previous sketch commands will never be executed again, in this case.
|
||||
///
|
||||
/// If you would like to encapsulate the commands to create the sketch any time you call it,
|
||||
/// you can use a function.
|
||||
///
|
||||
/// ```kcl
|
||||
/// fn createSketch() {
|
||||
/// return startSketchOn('XY')
|
||||
/// |> startProfileAt([-12, 12], %)
|
||||
/// |> line(end = [24, 0])
|
||||
/// |> line(end = [0, -24])
|
||||
/// |> line(end = [-24, 0])
|
||||
/// |> close()
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Now, every time you call `createSketch()`, the commands will be
|
||||
/// executed and a new sketch will be created.
|
||||
///
|
||||
/// When you assign the result of `createSketch()` to a variable (`mySketch = createSketch()`), you are assigning
|
||||
/// the executed sketch to that variable. Meaning that the sketch `mySketch` will not be executed
|
||||
/// again.
|
||||
///
|
||||
/// You can still execute _new_ commands on the sketch like `extrude`, `revolve`, `loft`, etc. and
|
||||
/// the sketch will be updated.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
@ -651,49 +609,6 @@ impl Sketch {
|
||||
}
|
||||
}
|
||||
|
||||
/// A solid is a collection of extrude surfaces.
|
||||
///
|
||||
/// When you define a solid to a variable like:
|
||||
///
|
||||
/// ```kcl
|
||||
/// myPart = startSketchOn('XY')
|
||||
/// |> startProfileAt([-12, 12], %)
|
||||
/// |> line(end = [24, 0])
|
||||
/// |> line(end = [0, -24])
|
||||
/// |> line(end = [-24, 0])
|
||||
/// |> close()
|
||||
/// |> extrude(length = 6)
|
||||
/// ```
|
||||
///
|
||||
/// The `myPart` variable will be an executed `Solid` object. Executed being past
|
||||
/// tense, because the engine has already executed the commands to create the solid.
|
||||
///
|
||||
/// The previous solid commands will never be executed again, in this case.
|
||||
///
|
||||
/// If you would like to encapsulate the commands to create the solid any time you call it,
|
||||
/// you can use a function.
|
||||
///
|
||||
/// ```kcl
|
||||
/// fn createPart() {
|
||||
/// return startSketchOn('XY')
|
||||
/// |> startProfileAt([-12, 12], %)
|
||||
/// |> line(end = [24, 0])
|
||||
/// |> line(end = [0, -24])
|
||||
/// |> line(end = [-24, 0])
|
||||
/// |> close()
|
||||
/// |> extrude(length = 6)
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Now, every time you call `createPart()`, the commands will be
|
||||
/// executed and a new solid will be created.
|
||||
///
|
||||
/// When you assign the result of `createPart()` to a variable (`myPart = createPart()`), you are assigning
|
||||
/// the executed solid to that variable. Meaning that the solid `myPart` will not be executed
|
||||
/// again.
|
||||
///
|
||||
/// You can still execute _new_ commands on the solid like `shell`, `fillet`, `chamfer`, etc.
|
||||
/// and the solid will be updated.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
|
@ -4,7 +4,10 @@ use anyhow::Result;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{memory::EnvironmentRef, MetaSettings};
|
||||
use super::{
|
||||
memory::{self, EnvironmentRef},
|
||||
MetaSettings,
|
||||
};
|
||||
use crate::{
|
||||
errors::KclErrorDetails,
|
||||
execution::{
|
||||
@ -25,7 +28,7 @@ use crate::{
|
||||
pub type KclObjectFields = HashMap<String, KclValue>;
|
||||
|
||||
/// Any KCL value.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum KclValue {
|
||||
@ -50,7 +53,7 @@ pub enum KclValue {
|
||||
#[serde(rename = "__meta")]
|
||||
meta: Vec<Metadata>,
|
||||
},
|
||||
Array {
|
||||
MixedArray {
|
||||
value: Vec<KclValue>,
|
||||
#[serde(rename = "__meta")]
|
||||
meta: Vec<Metadata>,
|
||||
@ -96,6 +99,13 @@ pub enum KclValue {
|
||||
#[serde(rename = "__meta")]
|
||||
meta: Vec<Metadata>,
|
||||
},
|
||||
#[ts(skip)]
|
||||
Type {
|
||||
#[serde(skip)]
|
||||
value: Option<(PrimitiveType, StdFnProps)>,
|
||||
#[serde(rename = "__meta")]
|
||||
meta: Vec<Metadata>,
|
||||
},
|
||||
KclNone {
|
||||
value: KclNone,
|
||||
#[serde(rename = "__meta")]
|
||||
@ -119,6 +129,7 @@ pub enum FunctionSource {
|
||||
},
|
||||
User {
|
||||
ast: crate::parsing::ast::types::BoxNode<FunctionExpression>,
|
||||
settings: MetaSettings,
|
||||
memory: EnvironmentRef,
|
||||
},
|
||||
}
|
||||
@ -184,10 +195,11 @@ impl From<KclValue> for Vec<SourceRange> {
|
||||
KclValue::Bool { meta, .. } => to_vec_sr(&meta),
|
||||
KclValue::Number { meta, .. } => to_vec_sr(&meta),
|
||||
KclValue::String { meta, .. } => to_vec_sr(&meta),
|
||||
KclValue::Array { meta, .. } => to_vec_sr(&meta),
|
||||
KclValue::MixedArray { meta, .. } => to_vec_sr(&meta),
|
||||
KclValue::Object { meta, .. } => to_vec_sr(&meta),
|
||||
KclValue::Module { meta, .. } => to_vec_sr(&meta),
|
||||
KclValue::Uuid { meta, .. } => to_vec_sr(&meta),
|
||||
KclValue::Type { meta, .. } => to_vec_sr(&meta),
|
||||
KclValue::KclNone { meta, .. } => to_vec_sr(&meta),
|
||||
KclValue::Tombstone { .. } => unreachable!("Tombstone SourceRange"),
|
||||
}
|
||||
@ -216,15 +228,23 @@ impl From<&KclValue> for Vec<SourceRange> {
|
||||
KclValue::Number { 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::MixedArray { meta, .. } => to_vec_sr(meta),
|
||||
KclValue::Object { meta, .. } => to_vec_sr(meta),
|
||||
KclValue::Module { meta, .. } => to_vec_sr(meta),
|
||||
KclValue::KclNone { meta, .. } => to_vec_sr(meta),
|
||||
KclValue::Type { meta, .. } => to_vec_sr(meta),
|
||||
KclValue::Tombstone { .. } => unreachable!("Tombstone &SourceRange"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&KclValue> for SourceRange {
|
||||
fn from(item: &KclValue) -> Self {
|
||||
let v: Vec<_> = item.into();
|
||||
v.into_iter().next().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
impl KclValue {
|
||||
pub(crate) fn metadata(&self) -> Vec<Metadata> {
|
||||
match self {
|
||||
@ -232,7 +252,7 @@ impl KclValue {
|
||||
KclValue::Bool { value: _, meta } => meta.clone(),
|
||||
KclValue::Number { meta, .. } => meta.clone(),
|
||||
KclValue::String { value: _, meta } => meta.clone(),
|
||||
KclValue::Array { value: _, meta } => meta.clone(),
|
||||
KclValue::MixedArray { value: _, meta } => meta.clone(),
|
||||
KclValue::Object { value: _, meta } => meta.clone(),
|
||||
KclValue::TagIdentifier(x) => x.meta.clone(),
|
||||
KclValue::TagDeclarator(x) => vec![x.metadata()],
|
||||
@ -247,6 +267,7 @@ impl KclValue {
|
||||
KclValue::Function { meta, .. } => meta.clone(),
|
||||
KclValue::Module { meta, .. } => meta.clone(),
|
||||
KclValue::KclNone { meta, .. } => meta.clone(),
|
||||
KclValue::Type { meta, .. } => meta.clone(),
|
||||
KclValue::Tombstone { .. } => unreachable!("Tombstone Metadata"),
|
||||
}
|
||||
}
|
||||
@ -268,7 +289,7 @@ impl KclValue {
|
||||
match self {
|
||||
KclValue::Solid { value } => Ok(SolidSet::Solid(value.clone())),
|
||||
KclValue::Solids { value } => Ok(SolidSet::Solids(value.clone())),
|
||||
KclValue::Array { value, .. } => {
|
||||
KclValue::MixedArray { value, .. } => {
|
||||
let solids: Vec<_> = value
|
||||
.iter()
|
||||
.enumerate()
|
||||
@ -314,9 +335,10 @@ impl KclValue {
|
||||
KclValue::Bool { .. } => "boolean (true/false value)",
|
||||
KclValue::Number { .. } => "number",
|
||||
KclValue::String { .. } => "string (text)",
|
||||
KclValue::Array { .. } => "array (list)",
|
||||
KclValue::MixedArray { .. } => "array (list)",
|
||||
KclValue::Object { .. } => "object",
|
||||
KclValue::Module { .. } => "module",
|
||||
KclValue::Type { .. } => "type",
|
||||
KclValue::KclNone { .. } => "None",
|
||||
KclValue::Tombstone { .. } => "TOMBSTONE",
|
||||
}
|
||||
@ -374,7 +396,7 @@ impl KclValue {
|
||||
|
||||
/// Put the point into a KCL value.
|
||||
pub fn from_point2d(p: [f64; 2], ty: NumericType, meta: Vec<Metadata>) -> Self {
|
||||
Self::Array {
|
||||
Self::MixedArray {
|
||||
value: vec![
|
||||
Self::Number {
|
||||
value: p[0],
|
||||
@ -430,7 +452,7 @@ impl KclValue {
|
||||
}
|
||||
|
||||
pub fn as_array(&self) -> Option<&[KclValue]> {
|
||||
if let KclValue::Array { value, meta: _ } = &self {
|
||||
if let KclValue::MixedArray { value, meta: _ } = &self {
|
||||
Some(value)
|
||||
} else {
|
||||
None
|
||||
@ -589,22 +611,23 @@ impl KclValue {
|
||||
KclValue::Sketches { .. } => Some(RuntimeType::Array(PrimitiveType::Sketch)),
|
||||
KclValue::Solid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Solid)),
|
||||
KclValue::Solids { .. } => Some(RuntimeType::Array(PrimitiveType::Solid)),
|
||||
KclValue::Array { value, .. } => Some(RuntimeType::Tuple(
|
||||
KclValue::MixedArray { value, .. } => Some(RuntimeType::Tuple(
|
||||
value
|
||||
.iter()
|
||||
.map(|v| v.principal_type().and_then(RuntimeType::primitive))
|
||||
.collect::<Option<Vec<_>>>()?,
|
||||
)),
|
||||
KclValue::Face { .. } => None,
|
||||
KclValue::Helix { .. } => None,
|
||||
KclValue::ImportedGeometry(..) => None,
|
||||
KclValue::Function { .. } => None,
|
||||
KclValue::Module { .. } => None,
|
||||
KclValue::TagIdentifier(_) => None,
|
||||
KclValue::TagDeclarator(_) => None,
|
||||
KclValue::KclNone { .. } => None,
|
||||
KclValue::Uuid { .. } => None,
|
||||
KclValue::Tombstone { .. } => None,
|
||||
KclValue::Helix { .. }
|
||||
| KclValue::ImportedGeometry(..)
|
||||
| KclValue::Function { .. }
|
||||
| KclValue::Module { .. }
|
||||
| KclValue::TagIdentifier(_)
|
||||
| KclValue::TagDeclarator(_)
|
||||
| KclValue::KclNone { .. }
|
||||
| KclValue::Type { .. }
|
||||
| KclValue::Uuid { .. }
|
||||
| KclValue::Tombstone { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -643,7 +666,7 @@ impl KclValue {
|
||||
result
|
||||
}
|
||||
KclValue::Function {
|
||||
value: FunctionSource::User { ast, memory },
|
||||
value: FunctionSource::User { ast, memory, .. },
|
||||
..
|
||||
} => crate::execution::exec_ast::call_user_defined_function(args, *memory, ast, exec_state, &ctx).await,
|
||||
_ => Err(KclError::Semantic(KclErrorDetails {
|
||||
@ -679,7 +702,7 @@ impl KclValue {
|
||||
todo!("Implement KCL stdlib fns with keyword args");
|
||||
}
|
||||
KclValue::Function {
|
||||
value: FunctionSource::User { ast, memory },
|
||||
value: FunctionSource::User { ast, memory, .. },
|
||||
..
|
||||
} => {
|
||||
crate::execution::exec_ast::call_user_defined_function_kw(args.kw_args, *memory, ast, exec_state, &ctx)
|
||||
@ -701,7 +724,7 @@ impl KclValue {
|
||||
KclValue::TagDeclarator(tag) => Some(format!("${}", tag.name)),
|
||||
KclValue::TagIdentifier(tag) => Some(format!("${}", tag.value)),
|
||||
// TODO better Array and Object stringification
|
||||
KclValue::Array { .. } => Some("[...]".to_owned()),
|
||||
KclValue::MixedArray { .. } => Some("[...]".to_owned()),
|
||||
KclValue::Object { .. } => Some("{ ... }".to_owned()),
|
||||
KclValue::Module { .. }
|
||||
| KclValue::Solid { .. }
|
||||
@ -714,6 +737,7 @@ impl KclValue {
|
||||
| KclValue::Plane { .. }
|
||||
| KclValue::Face { .. }
|
||||
| KclValue::KclNone { .. }
|
||||
| KclValue::Type { .. }
|
||||
| KclValue::Tombstone { .. } => None,
|
||||
}
|
||||
}
|
||||
@ -728,21 +752,29 @@ pub enum RuntimeType {
|
||||
}
|
||||
|
||||
impl RuntimeType {
|
||||
pub fn from_parsed(value: Type, settings: &super::MetaSettings) -> Option<Self> {
|
||||
match value {
|
||||
Type::Primitive(pt) => Some(RuntimeType::Primitive(PrimitiveType::from_parsed(pt, settings)?)),
|
||||
Type::Array(pt) => Some(RuntimeType::Array(PrimitiveType::from_parsed(pt, settings)?)),
|
||||
Type::Object { properties } => Some(RuntimeType::Object(
|
||||
properties
|
||||
.into_iter()
|
||||
.map(|p| {
|
||||
p.type_.and_then(|t| {
|
||||
RuntimeType::from_parsed(t.inner, settings).map(|ty| (p.identifier.inner.name, ty))
|
||||
})
|
||||
})
|
||||
.collect::<Option<Vec<_>>>()?,
|
||||
)),
|
||||
}
|
||||
pub fn from_parsed(
|
||||
value: Type,
|
||||
exec_state: &mut ExecState,
|
||||
source_range: SourceRange,
|
||||
) -> Result<Option<Self>, CompilationError> {
|
||||
Ok(match value {
|
||||
Type::Primitive(pt) => {
|
||||
PrimitiveType::from_parsed(pt, exec_state, source_range)?.map(RuntimeType::Primitive)
|
||||
}
|
||||
Type::Array(pt) => PrimitiveType::from_parsed(pt, exec_state, source_range)?.map(RuntimeType::Array),
|
||||
Type::Object { properties } => properties
|
||||
.into_iter()
|
||||
.map(|p| {
|
||||
let pt = match p.type_ {
|
||||
Some(t) => t,
|
||||
None => return Ok(None),
|
||||
};
|
||||
Ok(RuntimeType::from_parsed(pt.inner, exec_state, source_range)?
|
||||
.map(|ty| (p.identifier.inner.name, ty)))
|
||||
})
|
||||
.collect::<Result<Option<Vec<_>>, CompilationError>>()?
|
||||
.map(RuntimeType::Object),
|
||||
})
|
||||
}
|
||||
|
||||
// Subtype with no coercion, including refining numeric types.
|
||||
@ -802,16 +834,33 @@ pub enum PrimitiveType {
|
||||
}
|
||||
|
||||
impl PrimitiveType {
|
||||
fn from_parsed(value: AstPrimitiveType, settings: &super::MetaSettings) -> Option<Self> {
|
||||
match value {
|
||||
fn from_parsed(
|
||||
value: AstPrimitiveType,
|
||||
exec_state: &mut ExecState,
|
||||
source_range: SourceRange,
|
||||
) -> Result<Option<Self>, CompilationError> {
|
||||
Ok(match value {
|
||||
AstPrimitiveType::String => Some(PrimitiveType::String),
|
||||
AstPrimitiveType::Boolean => Some(PrimitiveType::Boolean),
|
||||
AstPrimitiveType::Number(suffix) => Some(PrimitiveType::Number(NumericType::from_parsed(suffix, settings))),
|
||||
AstPrimitiveType::Sketch => Some(PrimitiveType::Sketch),
|
||||
AstPrimitiveType::Solid => Some(PrimitiveType::Solid),
|
||||
AstPrimitiveType::Plane => Some(PrimitiveType::Plane),
|
||||
AstPrimitiveType::Number(suffix) => Some(PrimitiveType::Number(NumericType::from_parsed(
|
||||
suffix,
|
||||
&exec_state.mod_local.settings,
|
||||
))),
|
||||
AstPrimitiveType::Named(name) => {
|
||||
let ty_val = exec_state
|
||||
.stack()
|
||||
.get(&format!("{}{}", memory::TYPE_PREFIX, name.name), source_range)
|
||||
.map_err(|_| CompilationError::err(source_range, format!("Unknown type: {}", name.name)))?;
|
||||
|
||||
let (ty, _) = match ty_val {
|
||||
KclValue::Type { value: Some(ty), .. } => ty,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
Some(ty.clone())
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -250,6 +250,8 @@ use crate::{
|
||||
|
||||
/// The distinguished name of the return value of a function.
|
||||
pub(crate) const RETURN_NAME: &str = "__return";
|
||||
/// Low-budget namespacing for types.
|
||||
pub(crate) const TYPE_PREFIX: &str = "__ty_";
|
||||
|
||||
/// KCL memory. There should be only one ProgramMemory for the interpretation of a program (
|
||||
/// including other modules). Multiple interpretation runs should have fresh instances.
|
||||
@ -685,14 +687,6 @@ impl Stack {
|
||||
.insert_or_update(key, value, self.id);
|
||||
}
|
||||
|
||||
/// Delete an item from memory.
|
||||
///
|
||||
/// Item will be preserved in any snapshots.
|
||||
pub fn clear(&mut self, key: String) {
|
||||
self.memory.stats.mutation_count.fetch_add(1, Ordering::Relaxed);
|
||||
self.memory.get_env(self.current_env.index()).clear(key, self.id);
|
||||
}
|
||||
|
||||
/// Get a value from the program memory.
|
||||
/// Return Err if not found.
|
||||
pub fn get(&self, var: &str, source_range: SourceRange) -> Result<&KclValue, KclError> {
|
||||
@ -1165,19 +1159,6 @@ mod env {
|
||||
self.get_mut_bindings(owner).insert(key, value);
|
||||
}
|
||||
|
||||
/// Delete a key/value.
|
||||
///
|
||||
/// We want to preserve the snapshot, so we can't just remove the element. We copy the deleted
|
||||
/// value to the snapshot and replace the current value with a tombstone.
|
||||
pub(super) fn clear(&self, key: String, owner: usize) {
|
||||
if self.get_bindings().contains_key(&key) {
|
||||
let old = self.get_mut_bindings(owner).insert(key.clone(), tombstone()).unwrap();
|
||||
if let Some(s) = self.cur_snapshot(owner) {
|
||||
s.data.insert(key, old);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Was the key contained in this environment at the specified point in time.
|
||||
fn snapshot_contains_key(&self, key: &str, snapshot: SnapshotRef) -> bool {
|
||||
for i in snapshot.index()..self.snapshots_len() {
|
||||
@ -1571,61 +1552,6 @@ mod test {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snap_env_clear() {
|
||||
let mem = &mut Stack::new_for_tests();
|
||||
mem.add("a".to_owned(), val(1), sr()).unwrap();
|
||||
|
||||
mem.add("b".to_owned(), val(3), sr()).unwrap();
|
||||
let sn = mem.snapshot();
|
||||
|
||||
mem.push_new_env_for_call(sn);
|
||||
mem.snapshot();
|
||||
mem.add("b".to_owned(), val(4), sr()).unwrap();
|
||||
mem.snapshot();
|
||||
mem.clear("b".to_owned());
|
||||
mem.clear("a".to_owned());
|
||||
|
||||
assert_get(mem, "b", 3);
|
||||
assert_get(mem, "a", 1);
|
||||
|
||||
mem.pop_env();
|
||||
assert_get(mem, "b", 3);
|
||||
assert_get(mem, "a", 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snap_env_clear2() {
|
||||
let mem = &mut Stack::new_for_tests();
|
||||
mem.add("a".to_owned(), val(1), sr()).unwrap();
|
||||
mem.add("b".to_owned(), val(3), sr()).unwrap();
|
||||
let sn1 = mem.snapshot();
|
||||
mem.clear("b".to_owned());
|
||||
mem.clear("a".to_owned());
|
||||
mem.get("b", SourceRange::default()).unwrap_err();
|
||||
mem.get("a", SourceRange::default()).unwrap_err();
|
||||
|
||||
let sn = mem.snapshot();
|
||||
mem.push_new_env_for_call(sn);
|
||||
mem.add("b".to_owned(), val(4), sr()).unwrap();
|
||||
let sn2 = mem.snapshot();
|
||||
mem.clear("b".to_owned());
|
||||
mem.clear("a".to_owned());
|
||||
mem.get("b", SourceRange::default()).unwrap_err();
|
||||
mem.get("a", SourceRange::default()).unwrap_err();
|
||||
|
||||
mem.pop_env();
|
||||
mem.get("b", SourceRange::default()).unwrap_err();
|
||||
mem.get("a", SourceRange::default()).unwrap_err();
|
||||
|
||||
assert_get_from(mem, "a", 1, sn1);
|
||||
assert_get_from(mem, "b", 3, sn1);
|
||||
mem.memory
|
||||
.get_from_unchecked("a", sn2, SourceRange::default())
|
||||
.unwrap_err();
|
||||
assert_get_from(mem, "b", 4, sn2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn squash_env() {
|
||||
let mem = &mut Stack::new_for_tests();
|
||||
@ -1639,6 +1565,7 @@ mod test {
|
||||
KclValue::Function {
|
||||
value: FunctionSource::User {
|
||||
ast: crate::parsing::ast::types::FunctionExpression::dummy(),
|
||||
settings: crate::MetaSettings::default(),
|
||||
memory: sn2,
|
||||
},
|
||||
meta: Vec::new(),
|
||||
|
@ -14,7 +14,7 @@ pub(crate) use import::{
|
||||
import_foreign, send_to_engine as send_import_to_engine, PreImportedGeometry, ZOO_COORD_SYSTEM,
|
||||
};
|
||||
use indexmap::IndexMap;
|
||||
pub use kcl_value::{KclObjectFields, KclValue, UnitAngle, UnitLen};
|
||||
pub use kcl_value::{KclObjectFields, KclValue, PrimitiveType, UnitAngle, UnitLen};
|
||||
use kcmc::{
|
||||
each_cmd as mcmd,
|
||||
ok_response::{output::TakeSnapshot, OkModelingCmdResponse},
|
||||
@ -55,7 +55,7 @@ mod memory;
|
||||
mod state;
|
||||
|
||||
/// Outcome of executing a program. This is used in TS.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ts_rs::TS)]
|
||||
#[derive(Debug, Clone, Serialize, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ExecOutcome {
|
||||
@ -517,7 +517,6 @@ impl ExecutorContext {
|
||||
&self,
|
||||
program: crate::Program,
|
||||
use_prev_memory: bool,
|
||||
variables: IndexMap<String, KclValue>,
|
||||
) -> Result<ExecOutcome, KclErrorWithOutputs> {
|
||||
assert!(self.is_mock());
|
||||
|
||||
@ -531,22 +530,9 @@ impl ExecutorContext {
|
||||
self.prepare_mem(&mut exec_state).await?
|
||||
};
|
||||
|
||||
let mut to_restore = Vec::new();
|
||||
{
|
||||
let mem = exec_state.mut_stack();
|
||||
|
||||
// Push a scope so that old variables can be overwritten (since we might be re-executing some
|
||||
// part of the scene).
|
||||
mem.push_new_env_for_scope();
|
||||
|
||||
// Add any extra variables to memory (we want to remove these variables after execution, but
|
||||
// can't do this using scopes because we want to keep the results of computation in other cases).
|
||||
for (k, v) in variables {
|
||||
to_restore.push((k.clone(), mem.get(&k, SourceRange::default()).ok().cloned()));
|
||||
mem.add(k, v, SourceRange::synthetic())
|
||||
.map_err(KclErrorWithOutputs::no_outputs)?;
|
||||
}
|
||||
}
|
||||
// Push a scope so that old variables can be overwritten (since we might be re-executing some
|
||||
// part of the scene).
|
||||
exec_state.mut_stack().push_new_env_for_scope();
|
||||
|
||||
let result = self.inner_run(&program, &mut exec_state, true).await?;
|
||||
|
||||
@ -558,12 +544,6 @@ impl ExecutorContext {
|
||||
let outcome = exec_state.to_mock_wasm_outcome(result.0);
|
||||
|
||||
mem.squash_env(result.0);
|
||||
for (k, v) in to_restore {
|
||||
match v {
|
||||
Some(v) => mem.insert_or_update(k, v),
|
||||
None => mem.clear(k),
|
||||
}
|
||||
}
|
||||
cache::write_old_memory(mem).await;
|
||||
|
||||
Ok(outcome)
|
||||
@ -645,8 +625,6 @@ impl ExecutorContext {
|
||||
let mut exec_state = old_state;
|
||||
exec_state.reset(&self.settings);
|
||||
|
||||
exec_state.add_root_module_contents(&program);
|
||||
|
||||
// We don't do this in mock mode since there is no engine connection
|
||||
// anyways and from the TS side we override memory and don't want to clear it.
|
||||
self.send_clear_scene(&mut exec_state, Default::default())
|
||||
@ -656,7 +634,6 @@ impl ExecutorContext {
|
||||
(exec_state, false)
|
||||
} else {
|
||||
old_state.mut_stack().restore_env(result_env);
|
||||
old_state.add_root_module_contents(&program);
|
||||
|
||||
(old_state, true)
|
||||
};
|
||||
@ -664,7 +641,6 @@ impl ExecutorContext {
|
||||
(program, exec_state, preserve_mem)
|
||||
} else {
|
||||
let mut exec_state = ExecState::new(&self.settings);
|
||||
exec_state.add_root_module_contents(&program);
|
||||
self.send_clear_scene(&mut exec_state, Default::default())
|
||||
.await
|
||||
.map_err(KclErrorWithOutputs::no_outputs)?;
|
||||
@ -703,28 +679,7 @@ impl ExecutorContext {
|
||||
&self,
|
||||
program: &crate::Program,
|
||||
exec_state: &mut ExecState,
|
||||
) -> Result<(EnvironmentRef, Option<ModelingSessionData>), KclError> {
|
||||
self.run_with_ui_outputs(program, exec_state)
|
||||
.await
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
/// Perform the execution of a program.
|
||||
///
|
||||
/// You can optionally pass in some initialization memory for partial
|
||||
/// execution.
|
||||
///
|
||||
/// The error includes additional outputs used for the feature tree and
|
||||
/// artifact graph.
|
||||
pub async fn run_with_ui_outputs(
|
||||
&self,
|
||||
program: &crate::Program,
|
||||
exec_state: &mut ExecState,
|
||||
) -> Result<(EnvironmentRef, Option<ModelingSessionData>), KclErrorWithOutputs> {
|
||||
exec_state.add_root_module_contents(program);
|
||||
self.send_clear_scene(exec_state, Default::default())
|
||||
.await
|
||||
.map_err(KclErrorWithOutputs::no_outputs)?;
|
||||
self.inner_run(program, exec_state, false).await
|
||||
}
|
||||
|
||||
@ -736,6 +691,8 @@ impl ExecutorContext {
|
||||
exec_state: &mut ExecState,
|
||||
preserve_mem: bool,
|
||||
) -> Result<(EnvironmentRef, Option<ModelingSessionData>), KclErrorWithOutputs> {
|
||||
exec_state.add_root_module_contents(program);
|
||||
|
||||
let _stats = crate::log::LogPerfStats::new("Interpretation");
|
||||
|
||||
// Re-apply the settings, in case the cache was busted.
|
||||
@ -902,18 +859,83 @@ impl ExecutorContext {
|
||||
Ok(contents)
|
||||
}
|
||||
|
||||
/// Export the current scene as a CAD file.
|
||||
pub async fn export(
|
||||
&self,
|
||||
format: kittycad_modeling_cmds::format::OutputFormat3d,
|
||||
) -> Result<Vec<kittycad_modeling_cmds::websocket::RawFile>, KclError> {
|
||||
let resp = self
|
||||
.engine
|
||||
.send_modeling_cmd(
|
||||
uuid::Uuid::new_v4(),
|
||||
crate::SourceRange::default(),
|
||||
&kittycad_modeling_cmds::ModelingCmd::Export(kittycad_modeling_cmds::Export {
|
||||
entity_ids: vec![],
|
||||
format,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let kittycad_modeling_cmds::websocket::OkWebSocketResponseData::Export { files } = resp else {
|
||||
return Err(KclError::Internal(crate::errors::KclErrorDetails {
|
||||
message: format!("Expected Export response, got {resp:?}",),
|
||||
source_ranges: vec![SourceRange::default()],
|
||||
}));
|
||||
};
|
||||
|
||||
Ok(files)
|
||||
}
|
||||
|
||||
/// Export the current scene as a STEP file.
|
||||
pub async fn export_step(
|
||||
&self,
|
||||
deterministic_time: bool,
|
||||
) -> Result<Vec<kittycad_modeling_cmds::websocket::RawFile>, KclError> {
|
||||
let mut files = self
|
||||
.export(kittycad_modeling_cmds::format::OutputFormat3d::Step(
|
||||
kittycad_modeling_cmds::format::step::export::Options {
|
||||
coords: *kittycad_modeling_cmds::coord::KITTYCAD,
|
||||
created: None,
|
||||
},
|
||||
))
|
||||
.await?;
|
||||
|
||||
if deterministic_time {
|
||||
for kittycad_modeling_cmds::websocket::RawFile { contents, .. } in &mut files {
|
||||
use std::fmt::Write;
|
||||
let utf8 = std::str::from_utf8(contents).unwrap();
|
||||
let mut postprocessed = String::new();
|
||||
for line in utf8.lines() {
|
||||
if line.starts_with("FILE_NAME") {
|
||||
let name = "test.step";
|
||||
let time = "2021-01-01T00:00:00Z";
|
||||
let author = "Test";
|
||||
let org = "Zoo";
|
||||
let version = "zoo.dev beta";
|
||||
let system = "zoo.dev";
|
||||
let authorization = "Test";
|
||||
writeln!(&mut postprocessed, "FILE_NAME('{name}', '{time}', ('{author}'), ('{org}'), '{version}', '{system}', '{authorization}');").unwrap();
|
||||
} else {
|
||||
writeln!(&mut postprocessed, "{line}").unwrap();
|
||||
}
|
||||
}
|
||||
*contents = postprocessed.into_bytes();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(files)
|
||||
}
|
||||
|
||||
pub async fn close(&self) {
|
||||
self.engine.close().await;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) async fn parse_execute(
|
||||
code: &str,
|
||||
) -> Result<(crate::Program, EnvironmentRef, ExecutorContext, ExecState), KclError> {
|
||||
pub(crate) async fn parse_execute(code: &str) -> Result<ExecTestResults, KclError> {
|
||||
let program = crate::Program::parse_no_errs(code)?;
|
||||
|
||||
let ctx = ExecutorContext {
|
||||
let exec_ctxt = ExecutorContext {
|
||||
engine: Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new().await.map_err(|err| {
|
||||
KclError::Internal(crate::errors::KclErrorDetails {
|
||||
@ -927,10 +949,24 @@ pub(crate) async fn parse_execute(
|
||||
settings: Default::default(),
|
||||
context_type: ContextType::Mock,
|
||||
};
|
||||
let mut exec_state = ExecState::new(&ctx.settings);
|
||||
let result = ctx.run(&program, &mut exec_state).await?;
|
||||
let mut exec_state = ExecState::new(&exec_ctxt.settings);
|
||||
let result = exec_ctxt.run(&program, &mut exec_state).await?;
|
||||
|
||||
Ok((program, result.0, ctx, exec_state))
|
||||
Ok(ExecTestResults {
|
||||
program,
|
||||
mem_env: result.0,
|
||||
exec_ctxt,
|
||||
exec_state,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ExecTestResults {
|
||||
program: crate::Program,
|
||||
mem_env: EnvironmentRef,
|
||||
exec_ctxt: ExecutorContext,
|
||||
exec_state: ExecState,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -953,8 +989,8 @@ mod tests {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_execute_warn() {
|
||||
let text = "@blah";
|
||||
let (_, _, _, exec_state) = parse_execute(text).await.unwrap();
|
||||
let errs = exec_state.errors();
|
||||
let result = parse_execute(text).await.unwrap();
|
||||
let errs = result.exec_state.errors();
|
||||
assert_eq!(errs.len(), 1);
|
||||
assert_eq!(errs[0].severity, crate::errors::Severity::Warning);
|
||||
assert!(
|
||||
@ -967,8 +1003,8 @@ mod tests {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_warn_on_deprecated() {
|
||||
let text = "p = pi()";
|
||||
let (_, _, _, exec_state) = parse_execute(text).await.unwrap();
|
||||
let errs = exec_state.errors();
|
||||
let result = parse_execute(text).await.unwrap();
|
||||
let errs = result.exec_state.errors();
|
||||
assert_eq!(errs.len(), 1);
|
||||
assert_eq!(errs[0].severity, crate::errors::Severity::Warning);
|
||||
assert!(
|
||||
@ -1054,8 +1090,8 @@ const objExpShouldNotBeIncluded = { a: 1, b: 2, c: 3 }
|
||||
|
||||
const part001 = startSketchOn(XY)
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> yLineTo(1, %)
|
||||
|> xLine(3.84, %) // selection-range-7ish-before-this
|
||||
|> yLine(endAbsolute = 1)
|
||||
|> xLine(length = 3.84) // selection-range-7ish-before-this
|
||||
|
||||
const variableBelowShouldNotBeIncluded = 3
|
||||
"#;
|
||||
@ -1344,8 +1380,8 @@ const answer = returnX()"#;
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_override_prelude() {
|
||||
let text = "PI = 3.0";
|
||||
let (_, _, _, exec_state) = parse_execute(text).await.unwrap();
|
||||
let errs = exec_state.errors();
|
||||
let result = parse_execute(text).await.unwrap();
|
||||
let errs = result.exec_state.errors();
|
||||
assert!(errs.is_empty());
|
||||
}
|
||||
|
||||
@ -1411,50 +1447,79 @@ let shape = layer() |> patternTransform(instances = 10, transform = transform)
|
||||
#[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 (_, env, _, exec_state) = parse_execute(ast).await.unwrap();
|
||||
assert_eq!(5.0, mem_get_json(exec_state.stack(), env, "myVar").as_f64().unwrap());
|
||||
let result = parse_execute(ast).await.unwrap();
|
||||
assert_eq!(
|
||||
5.0,
|
||||
mem_get_json(result.exec_state.stack(), result.mem_env, "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 (_, env, _, exec_state) = parse_execute(ast).await.unwrap();
|
||||
assert_eq!(7.4, mem_get_json(exec_state.stack(), env, "myVar").as_f64().unwrap());
|
||||
let result = parse_execute(ast).await.unwrap();
|
||||
assert_eq!(
|
||||
7.4,
|
||||
mem_get_json(result.exec_state.stack(), result.mem_env, "myVar")
|
||||
.as_f64()
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_math_execute_start_negative() {
|
||||
let ast = r#"const myVar = -5 + 6"#;
|
||||
let (_, env, _, exec_state) = parse_execute(ast).await.unwrap();
|
||||
assert_eq!(1.0, mem_get_json(exec_state.stack(), env, "myVar").as_f64().unwrap());
|
||||
let result = parse_execute(ast).await.unwrap();
|
||||
assert_eq!(
|
||||
1.0,
|
||||
mem_get_json(result.exec_state.stack(), result.mem_env, "myVar")
|
||||
.as_f64()
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_math_execute_with_pi() {
|
||||
let ast = r#"const myVar = PI * 2"#;
|
||||
let (_, env, _, exec_state) = parse_execute(ast).await.unwrap();
|
||||
let result = parse_execute(ast).await.unwrap();
|
||||
assert_eq!(
|
||||
std::f64::consts::TAU,
|
||||
mem_get_json(exec_state.stack(), env, "myVar").as_f64().unwrap()
|
||||
mem_get_json(result.exec_state.stack(), result.mem_env, "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 (_, env, _, exec_state) = parse_execute(ast).await.unwrap();
|
||||
assert_eq!(7.4, mem_get_json(exec_state.stack(), env, "thing").as_f64().unwrap());
|
||||
let result = parse_execute(ast).await.unwrap();
|
||||
assert_eq!(
|
||||
7.4,
|
||||
mem_get_json(result.exec_state.stack(), result.mem_env, "thing")
|
||||
.as_f64()
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_unit_default() {
|
||||
let ast = r#"const inMm = 25.4 * mm()
|
||||
const inInches = 1.0 * inch()"#;
|
||||
let (_, env, _, exec_state) = parse_execute(ast).await.unwrap();
|
||||
assert_eq!(25.4, mem_get_json(exec_state.stack(), env, "inMm").as_f64().unwrap());
|
||||
let result = parse_execute(ast).await.unwrap();
|
||||
assert_eq!(
|
||||
25.4,
|
||||
mem_get_json(exec_state.stack(), env, "inInches").as_f64().unwrap()
|
||||
mem_get_json(result.exec_state.stack(), result.mem_env, "inMm")
|
||||
.as_f64()
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
25.4,
|
||||
mem_get_json(result.exec_state.stack(), result.mem_env, "inInches")
|
||||
.as_f64()
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
@ -1463,12 +1528,20 @@ const inInches = 1.0 * inch()"#;
|
||||
let ast = r#"@settings(defaultLengthUnit = inch)
|
||||
const inMm = 25.4 * mm()
|
||||
const inInches = 1.0 * inch()"#;
|
||||
let (_, env, _, exec_state) = parse_execute(ast).await.unwrap();
|
||||
let result = parse_execute(ast).await.unwrap();
|
||||
assert_eq!(
|
||||
1.0,
|
||||
mem_get_json(exec_state.stack(), env, "inMm").as_f64().unwrap().round()
|
||||
mem_get_json(result.exec_state.stack(), result.mem_env, "inMm")
|
||||
.as_f64()
|
||||
.unwrap()
|
||||
.round()
|
||||
);
|
||||
assert_eq!(
|
||||
1.0,
|
||||
mem_get_json(result.exec_state.stack(), result.mem_env, "inInches")
|
||||
.as_f64()
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(1.0, mem_get_json(exec_state.stack(), env, "inInches").as_f64().unwrap());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1476,12 +1549,20 @@ const inInches = 1.0 * inch()"#;
|
||||
let ast = r#"@settings(defaultLengthUnit = in)
|
||||
const inMm = 25.4 * mm()
|
||||
const inInches = 2.0 * inch()"#;
|
||||
let (_, env, _, exec_state) = parse_execute(ast).await.unwrap();
|
||||
let result = parse_execute(ast).await.unwrap();
|
||||
assert_eq!(
|
||||
1.0,
|
||||
mem_get_json(exec_state.stack(), env, "inMm").as_f64().unwrap().round()
|
||||
mem_get_json(result.exec_state.stack(), result.mem_env, "inMm")
|
||||
.as_f64()
|
||||
.unwrap()
|
||||
.round()
|
||||
);
|
||||
assert_eq!(
|
||||
2.0,
|
||||
mem_get_json(result.exec_state.stack(), result.mem_env, "inInches")
|
||||
.as_f64()
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(2.0, mem_get_json(exec_state.stack(), env, "inInches").as_f64().unwrap());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1520,17 +1601,31 @@ fn check = (x) => {
|
||||
}
|
||||
check(false)
|
||||
"#;
|
||||
let (_, env, _, exec_state) = parse_execute(ast).await.unwrap();
|
||||
let result = parse_execute(ast).await.unwrap();
|
||||
assert_eq!(
|
||||
false,
|
||||
mem_get_json(exec_state.stack(), env, "notTrue").as_bool().unwrap()
|
||||
mem_get_json(result.exec_state.stack(), result.mem_env, "notTrue")
|
||||
.as_bool()
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
true,
|
||||
mem_get_json(exec_state.stack(), env, "notFalse").as_bool().unwrap()
|
||||
mem_get_json(result.exec_state.stack(), result.mem_env, "notFalse")
|
||||
.as_bool()
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
true,
|
||||
mem_get_json(result.exec_state.stack(), result.mem_env, "c")
|
||||
.as_bool()
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
false,
|
||||
mem_get_json(result.exec_state.stack(), result.mem_env, "d")
|
||||
.as_bool()
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(true, mem_get_json(exec_state.stack(), env, "c").as_bool().unwrap());
|
||||
assert_eq!(false, mem_get_json(exec_state.stack(), env, "d").as_bool().unwrap());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1783,9 +1878,9 @@ let w = f() + f()
|
||||
async fn kcl_test_ids_stable_between_executions() {
|
||||
let code = r#"sketch001 = startSketchOn(XZ)
|
||||
|> startProfileAt([61.74, 206.13], %)
|
||||
|> xLine(305.11, %, $seg01)
|
||||
|> yLine(-291.85, %)
|
||||
|> xLine(-segLen(seg01), %)
|
||||
|> xLine(length = 305.11, tag = $seg01)
|
||||
|> yLine(length = -291.85)
|
||||
|> xLine(length = -segLen(seg01))
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> extrude(length = 40.14)
|
||||
@ -1808,9 +1903,9 @@ let w = f() + f()
|
||||
|
||||
let code = r#"sketch001 = startSketchOn(XZ)
|
||||
|> startProfileAt([62.74, 206.13], %)
|
||||
|> xLine(305.11, %, $seg01)
|
||||
|> yLine(-291.85, %)
|
||||
|> xLine(-segLen(seg01), %)
|
||||
|> xLine(length = 305.11, tag = $seg01)
|
||||
|> yLine(length = -291.85)
|
||||
|> xLine(length = -segLen(seg01))
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> extrude(length = 40.14)
|
||||
@ -1834,9 +1929,9 @@ let w = f() + f()
|
||||
async fn kcl_test_changing_a_setting_updates_the_cached_state() {
|
||||
let code = r#"sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([61.74, 206.13], %)
|
||||
|> xLine(305.11, %, $seg01)
|
||||
|> yLine(-291.85, %)
|
||||
|> xLine(-segLen(seg01), %)
|
||||
|> xLine(length = 305.11, tag = $seg01)
|
||||
|> yLine(length = -291.85)
|
||||
|> xLine(length = -segLen(seg01))
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> extrude(length = 40.14)
|
||||
@ -1885,33 +1980,6 @@ let w = f() + f()
|
||||
assert_eq!(settings_state, ctx.settings);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn mock_variables() {
|
||||
let ctx = ExecutorContext::new_mock().await;
|
||||
|
||||
let program = crate::Program::parse_no_errs("x = y").unwrap();
|
||||
let mut vars = IndexMap::new();
|
||||
vars.insert(
|
||||
"y".to_owned(),
|
||||
KclValue::Number {
|
||||
value: 2.0,
|
||||
ty: kcl_value::NumericType::Unknown,
|
||||
meta: Vec::new(),
|
||||
},
|
||||
);
|
||||
let result = ctx.run_mock(program, true, vars).await.unwrap();
|
||||
assert_eq!(result.variables.get("x").unwrap().as_f64().unwrap(), 2.0);
|
||||
cache::read_old_memory()
|
||||
.await
|
||||
.unwrap()
|
||||
.get("y", SourceRange::default())
|
||||
.unwrap_err();
|
||||
|
||||
let program2 = crate::Program::parse_no_errs("z = x + 1").unwrap();
|
||||
let result = ctx.run_mock(program2, true, IndexMap::new()).await.unwrap();
|
||||
assert_eq!(result.variables.get("z").unwrap().as_f64().unwrap(), 3.0);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn mock_after_not_mock() {
|
||||
let ctx = ExecutorContext::new_with_default_client(UnitLength::Mm).await.unwrap();
|
||||
@ -1921,7 +1989,7 @@ let w = f() + f()
|
||||
|
||||
let ctx2 = ExecutorContext::new_mock().await;
|
||||
let program2 = crate::Program::parse_no_errs("z = x + 1").unwrap();
|
||||
let result = ctx2.run_mock(program2, true, IndexMap::new()).await.unwrap();
|
||||
let result = ctx2.run_mock(program2, true).await.unwrap();
|
||||
assert_eq!(result.variables.get("z").unwrap().as_f64().unwrap(), 3.0);
|
||||
}
|
||||
}
|
||||
|
@ -10,8 +10,10 @@ use uuid::Uuid;
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails, Severity},
|
||||
execution::{
|
||||
annotations, kcl_value, memory::ProgramMemory, memory::Stack, Artifact, ArtifactCommand, ArtifactGraph,
|
||||
ArtifactId, EnvironmentRef, ExecOutcome, ExecutorSettings, KclValue, Operation, UnitAngle, UnitLen,
|
||||
annotations, kcl_value,
|
||||
memory::{ProgramMemory, Stack},
|
||||
Artifact, ArtifactCommand, ArtifactGraph, ArtifactId, EnvironmentRef, ExecOutcome, ExecutorSettings, KclValue,
|
||||
Operation, UnitAngle, UnitLen,
|
||||
},
|
||||
modules::{ModuleId, ModuleInfo, ModuleLoader, ModulePath, ModuleRepr, ModuleSource},
|
||||
parsing::ast::types::Annotation,
|
||||
|
Reference in New Issue
Block a user