Merge remote-tracking branch 'origin' into paultag/import
This commit is contained in:
@ -6,7 +6,7 @@ use kittycad_modeling_cmds::{
|
||||
ok_response::OkModelingCmdResponse,
|
||||
shared::ExtrusionFaceCapType,
|
||||
websocket::{BatchResponse, OkWebSocketResponseData, WebSocketResponse},
|
||||
EnableSketchMode, ModelingCmd, SketchModeDisable,
|
||||
EnableSketchMode, ModelingCmd,
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{ser::SerializeSeq, Deserialize, Serialize};
|
||||
@ -498,13 +498,23 @@ pub(super) fn build_artifact_graph(
|
||||
) -> Result<ArtifactGraph, KclError> {
|
||||
let mut map = IndexMap::new();
|
||||
|
||||
let mut path_to_plane_id_map = FnvHashMap::default();
|
||||
let mut current_plane_id = None;
|
||||
|
||||
for artifact_command in artifact_commands {
|
||||
if let ModelingCmd::EnableSketchMode(EnableSketchMode { entity_id, .. }) = artifact_command.command {
|
||||
current_plane_id = Some(entity_id);
|
||||
}
|
||||
if let ModelingCmd::SketchModeDisable(SketchModeDisable { .. }) = artifact_command.command {
|
||||
// If we get a start path command, we need to set the plane ID to the
|
||||
// current plane ID.
|
||||
// THIS IS THE ONLY THING WE CAN ASSUME IS ALWAYS SEQUENTIAL SINCE ITS PART OF THE
|
||||
// SAME ATOMIC COMMANDS BATCHING.
|
||||
if let ModelingCmd::StartPath(_) = artifact_command.command {
|
||||
if let Some(plane_id) = current_plane_id {
|
||||
path_to_plane_id_map.insert(artifact_command.cmd_id, plane_id);
|
||||
}
|
||||
}
|
||||
if let ModelingCmd::SketchModeDisable(_) = artifact_command.command {
|
||||
current_plane_id = None;
|
||||
}
|
||||
|
||||
@ -513,7 +523,7 @@ pub(super) fn build_artifact_graph(
|
||||
&map,
|
||||
artifact_command,
|
||||
&flattened_responses,
|
||||
current_plane_id,
|
||||
&path_to_plane_id_map,
|
||||
ast,
|
||||
exec_artifacts,
|
||||
)?;
|
||||
@ -609,7 +619,7 @@ fn artifacts_to_update(
|
||||
artifacts: &IndexMap<ArtifactId, Artifact>,
|
||||
artifact_command: &ArtifactCommand,
|
||||
responses: &FnvHashMap<Uuid, OkModelingCmdResponse>,
|
||||
current_plane_id: Option<Uuid>,
|
||||
path_to_plane_id_map: &FnvHashMap<Uuid, Uuid>,
|
||||
_ast: &Node<Program>,
|
||||
exec_artifacts: &IndexMap<ArtifactId, Artifact>,
|
||||
) -> Result<Vec<Artifact>, KclError> {
|
||||
@ -643,20 +653,12 @@ fn artifacts_to_update(
|
||||
code_ref: CodeRef { range, path_to_node },
|
||||
})]);
|
||||
}
|
||||
ModelingCmd::EnableSketchMode(_) => {
|
||||
let current_plane_id = current_plane_id.ok_or_else(|| {
|
||||
KclError::Internal(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a current plane ID when processing EnableSketchMode command, but we have none: {id:?}"
|
||||
),
|
||||
source_ranges: vec![range],
|
||||
})
|
||||
})?;
|
||||
let existing_plane = artifacts.get(&ArtifactId::new(current_plane_id));
|
||||
ModelingCmd::EnableSketchMode(EnableSketchMode { entity_id, .. }) => {
|
||||
let existing_plane = artifacts.get(&ArtifactId::new(*entity_id));
|
||||
match existing_plane {
|
||||
Some(Artifact::Wall(wall)) => {
|
||||
return Ok(vec![Artifact::Wall(Wall {
|
||||
id: current_plane_id.into(),
|
||||
id: entity_id.into(),
|
||||
seg_id: wall.seg_id,
|
||||
edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
|
||||
sweep_id: wall.sweep_id,
|
||||
@ -666,7 +668,7 @@ fn artifacts_to_update(
|
||||
}
|
||||
Some(Artifact::Cap(cap)) => {
|
||||
return Ok(vec![Artifact::Cap(Cap {
|
||||
id: current_plane_id.into(),
|
||||
id: entity_id.into(),
|
||||
sub_type: cap.sub_type,
|
||||
edge_cut_edge_ids: cap.edge_cut_edge_ids.clone(),
|
||||
sweep_id: cap.sweep_id,
|
||||
@ -680,7 +682,7 @@ fn artifacts_to_update(
|
||||
_ => Vec::new(),
|
||||
};
|
||||
return Ok(vec![Artifact::Plane(Plane {
|
||||
id: current_plane_id.into(),
|
||||
id: entity_id.into(),
|
||||
path_ids,
|
||||
code_ref: CodeRef { range, path_to_node },
|
||||
})]);
|
||||
@ -689,7 +691,7 @@ fn artifacts_to_update(
|
||||
}
|
||||
ModelingCmd::StartPath(_) => {
|
||||
let mut return_arr = Vec::new();
|
||||
let current_plane_id = current_plane_id.ok_or_else(|| {
|
||||
let current_plane_id = path_to_plane_id_map.get(&artifact_command.cmd_id).ok_or_else(|| {
|
||||
KclError::Internal(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected a current plane ID when processing StartPath command, but we have none: {id:?}"
|
||||
@ -699,24 +701,24 @@ fn artifacts_to_update(
|
||||
})?;
|
||||
return_arr.push(Artifact::Path(Path {
|
||||
id,
|
||||
plane_id: current_plane_id.into(),
|
||||
plane_id: (*current_plane_id).into(),
|
||||
seg_ids: Vec::new(),
|
||||
sweep_id: None,
|
||||
solid2d_id: None,
|
||||
code_ref: CodeRef { range, path_to_node },
|
||||
}));
|
||||
let plane = artifacts.get(&ArtifactId::new(current_plane_id));
|
||||
let plane = artifacts.get(&ArtifactId::new(*current_plane_id));
|
||||
if let Some(Artifact::Plane(plane)) = plane {
|
||||
let code_ref = plane.code_ref.clone();
|
||||
return_arr.push(Artifact::Plane(Plane {
|
||||
id: current_plane_id.into(),
|
||||
id: (*current_plane_id).into(),
|
||||
path_ids: vec![id],
|
||||
code_ref,
|
||||
}));
|
||||
}
|
||||
if let Some(Artifact::Wall(wall)) = plane {
|
||||
return_arr.push(Artifact::Wall(Wall {
|
||||
id: current_plane_id.into(),
|
||||
id: (*current_plane_id).into(),
|
||||
seg_id: wall.seg_id,
|
||||
edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
|
||||
sweep_id: wall.sweep_id,
|
||||
@ -726,7 +728,7 @@ fn artifacts_to_update(
|
||||
}
|
||||
if let Some(Artifact::Cap(cap)) = plane {
|
||||
return_arr.push(Artifact::Cap(Cap {
|
||||
id: current_plane_id.into(),
|
||||
id: (*current_plane_id).into(),
|
||||
sub_type: cap.sub_type,
|
||||
edge_cut_edge_ids: cap.edge_cut_edge_ids.clone(),
|
||||
sweep_id: cap.sweep_id,
|
||||
|
@ -180,15 +180,9 @@ pub enum OpKclValue {
|
||||
Sketch {
|
||||
value: Box<OpSketch>,
|
||||
},
|
||||
Sketches {
|
||||
value: Vec<OpSketch>,
|
||||
},
|
||||
Solid {
|
||||
value: Box<OpSolid>,
|
||||
},
|
||||
Solids {
|
||||
value: Vec<OpSolid>,
|
||||
},
|
||||
Helix {
|
||||
value: Box<OpHelix>,
|
||||
},
|
||||
@ -234,7 +228,7 @@ impl From<&KclValue> for OpKclValue {
|
||||
ty: ty.clone(),
|
||||
},
|
||||
KclValue::String { value, .. } => Self::String { value: value.clone() },
|
||||
KclValue::MixedArray { value, .. } => {
|
||||
KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } => {
|
||||
let value = value.iter().map(Self::from).collect();
|
||||
Self::Array { value }
|
||||
}
|
||||
@ -244,7 +238,7 @@ impl From<&KclValue> for OpKclValue {
|
||||
}
|
||||
KclValue::TagIdentifier(tag_identifier) => Self::TagIdentifier {
|
||||
value: tag_identifier.value.clone(),
|
||||
artifact_id: tag_identifier.info.as_ref().map(|info| ArtifactId::new(info.id)),
|
||||
artifact_id: tag_identifier.get_cur_info().map(|info| ArtifactId::new(info.id)),
|
||||
},
|
||||
KclValue::TagDeclarator(node) => Self::TagDeclarator {
|
||||
name: node.name.clone(),
|
||||
@ -260,29 +254,11 @@ impl From<&KclValue> for OpKclValue {
|
||||
artifact_id: value.artifact_id,
|
||||
}),
|
||||
},
|
||||
KclValue::Sketches { value } => {
|
||||
let value = value
|
||||
.iter()
|
||||
.map(|sketch| OpSketch {
|
||||
artifact_id: sketch.artifact_id,
|
||||
})
|
||||
.collect();
|
||||
Self::Sketches { value }
|
||||
}
|
||||
KclValue::Solid { value } => Self::Solid {
|
||||
value: Box::new(OpSolid {
|
||||
artifact_id: value.artifact_id,
|
||||
}),
|
||||
},
|
||||
KclValue::Solids { value } => {
|
||||
let value = value
|
||||
.iter()
|
||||
.map(|solid| OpSolid {
|
||||
artifact_id: solid.artifact_id,
|
||||
})
|
||||
.collect();
|
||||
Self::Solids { value }
|
||||
}
|
||||
KclValue::Helix { value } => Self::Helix {
|
||||
value: Box::new(OpHelix {
|
||||
artifact_id: value.artifact_id,
|
||||
@ -295,7 +271,6 @@ impl From<&KclValue> for OpKclValue {
|
||||
KclValue::Module { .. } => Self::Module {},
|
||||
KclValue::KclNone { .. } => Self::KclNone {},
|
||||
KclValue::Type { .. } => Self::Type {},
|
||||
KclValue::Tombstone { .. } => unreachable!("Tombstone OpKclValue"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,11 +8,11 @@ use crate::{
|
||||
execution::{
|
||||
annotations,
|
||||
cad_op::{OpArg, OpKclValue, Operation},
|
||||
kcl_value::{FunctionSource, NumericType, PrimitiveType, RuntimeType},
|
||||
kcl_value::{FunctionSource, NumericType, RuntimeType},
|
||||
memory,
|
||||
state::ModuleState,
|
||||
BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, Metadata, Plane, PlaneType, Point3d,
|
||||
TagEngineInfo, TagIdentifier,
|
||||
BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, Metadata, PlaneType, TagEngineInfo,
|
||||
TagIdentifier,
|
||||
},
|
||||
modules::{ModuleId, ModulePath, ModuleRepr},
|
||||
parsing::ast::types::{
|
||||
@ -23,7 +23,7 @@ use crate::{
|
||||
},
|
||||
source_range::SourceRange,
|
||||
std::{
|
||||
args::{Arg, FromKclValue, KwArgs},
|
||||
args::{Arg, KwArgs},
|
||||
FunctionKind,
|
||||
},
|
||||
CompilationError,
|
||||
@ -55,10 +55,9 @@ impl ExecutorContext {
|
||||
for annotation in annotations {
|
||||
if annotation.name() == Some(annotations::SETTINGS) {
|
||||
if matches!(body_type, BodyType::Root) {
|
||||
let old_units = exec_state.length_unit();
|
||||
exec_state.mod_local.settings.update_from_annotation(annotation)?;
|
||||
let new_units = exec_state.length_unit();
|
||||
if !self.engine.execution_kind().await.is_isolated() && old_units != new_units {
|
||||
if !self.engine.execution_kind().await.is_isolated() {
|
||||
self.engine
|
||||
.set_units(new_units.into(), annotation.as_source_range())
|
||||
.await?;
|
||||
@ -94,6 +93,7 @@ impl ExecutorContext {
|
||||
exec_state: &mut ExecState,
|
||||
exec_kind: ExecutionKind,
|
||||
preserve_mem: bool,
|
||||
module_id: ModuleId,
|
||||
path: &ModulePath,
|
||||
) -> Result<(Option<KclValue>, EnvironmentRef, Vec<String>), KclError> {
|
||||
crate::log::log(format!("enter module {path} {}", exec_state.stack()));
|
||||
@ -101,7 +101,12 @@ impl ExecutorContext {
|
||||
let old_units = exec_state.length_unit();
|
||||
let original_execution = self.engine.replace_execution_kind(exec_kind).await;
|
||||
|
||||
let mut local_state = ModuleState::new(&self.settings, path.std_path(), exec_state.stack().memory.clone());
|
||||
let mut local_state = ModuleState::new(
|
||||
&self.settings,
|
||||
path.std_path(),
|
||||
exec_state.stack().memory.clone(),
|
||||
Some(module_id),
|
||||
);
|
||||
if !preserve_mem {
|
||||
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
|
||||
}
|
||||
@ -504,7 +509,7 @@ impl ExecutorContext {
|
||||
ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
|
||||
ModuleRepr::Kcl(_, Some((env_ref, items))) => Ok((*env_ref, items.clone())),
|
||||
ModuleRepr::Kcl(program, cache) => self
|
||||
.exec_module_from_ast(program, &path, exec_state, exec_kind, source_range)
|
||||
.exec_module_from_ast(program, module_id, &path, exec_state, exec_kind, source_range)
|
||||
.await
|
||||
.map(|(_, er, items)| {
|
||||
*cache = Some((er, items.clone()));
|
||||
@ -535,7 +540,7 @@ impl ExecutorContext {
|
||||
let result = match &repr {
|
||||
ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
|
||||
ModuleRepr::Kcl(program, _) => self
|
||||
.exec_module_from_ast(program, &path, exec_state, exec_kind, source_range)
|
||||
.exec_module_from_ast(program, module_id, &path, exec_state, exec_kind, source_range)
|
||||
.await
|
||||
.map(|(val, _, _)| val),
|
||||
ModuleRepr::Foreign(geom) => super::import::send_to_engine(geom.clone(), self)
|
||||
@ -551,13 +556,16 @@ impl ExecutorContext {
|
||||
async fn exec_module_from_ast(
|
||||
&self,
|
||||
program: &Node<Program>,
|
||||
module_id: ModuleId,
|
||||
path: &ModulePath,
|
||||
exec_state: &mut ExecState,
|
||||
exec_kind: ExecutionKind,
|
||||
source_range: SourceRange,
|
||||
) -> Result<(Option<KclValue>, EnvironmentRef, Vec<String>), KclError> {
|
||||
exec_state.global.mod_loader.enter_module(path);
|
||||
let result = self.exec_module_body(program, exec_state, exec_kind, false, path).await;
|
||||
let result = self
|
||||
.exec_module_body(program, exec_state, exec_kind, false, module_id, path)
|
||||
.await;
|
||||
exec_state.global.mod_loader.leave_module(path);
|
||||
|
||||
result.map_err(|err| {
|
||||
@ -690,11 +698,11 @@ impl ExecutorContext {
|
||||
let result = self
|
||||
.execute_expr(&expr.expr, exec_state, metadata, &[], statement_kind)
|
||||
.await?;
|
||||
coerce(result, &expr.ty, exec_state).map_err(|value| {
|
||||
coerce(&result, &expr.ty, exec_state).ok_or_else(|| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"could not coerce {} value to type {}",
|
||||
value.human_friendly_type(),
|
||||
result.human_friendly_type(),
|
||||
expr.ty
|
||||
),
|
||||
source_ranges: vec![expr.into()],
|
||||
@ -706,72 +714,14 @@ 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, (&value).into())
|
||||
fn coerce(value: &KclValue, ty: &Node<Type>, exec_state: &mut ExecState) -> Option<KclValue> {
|
||||
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);
|
||||
}
|
||||
})
|
||||
.ok()??;
|
||||
|
||||
// TODO coerce numeric types
|
||||
|
||||
if let KclValue::Object { value, meta } = value {
|
||||
return match ty {
|
||||
RuntimeType::Primitive(PrimitiveType::Plane) => {
|
||||
let origin = value
|
||||
.get("origin")
|
||||
.and_then(Point3d::from_kcl_val)
|
||||
.ok_or_else(|| KclValue::Object {
|
||||
value: value.clone(),
|
||||
meta: meta.clone(),
|
||||
})?;
|
||||
let x_axis = value
|
||||
.get("xAxis")
|
||||
.and_then(Point3d::from_kcl_val)
|
||||
.ok_or_else(|| KclValue::Object {
|
||||
value: value.clone(),
|
||||
meta: meta.clone(),
|
||||
})?;
|
||||
let y_axis = value
|
||||
.get("yAxis")
|
||||
.and_then(Point3d::from_kcl_val)
|
||||
.ok_or_else(|| KclValue::Object {
|
||||
value: value.clone(),
|
||||
meta: meta.clone(),
|
||||
})?;
|
||||
let z_axis = value
|
||||
.get("zAxis")
|
||||
.and_then(Point3d::from_kcl_val)
|
||||
.ok_or_else(|| KclValue::Object {
|
||||
value: value.clone(),
|
||||
meta: meta.clone(),
|
||||
})?;
|
||||
|
||||
let id = exec_state.global.id_generator.next_uuid();
|
||||
let plane = Plane {
|
||||
id,
|
||||
artifact_id: id.into(),
|
||||
origin,
|
||||
x_axis,
|
||||
y_axis,
|
||||
z_axis,
|
||||
value: PlaneType::Uninit,
|
||||
// TODO use length unit from origin
|
||||
units: exec_state.length_unit(),
|
||||
meta,
|
||||
};
|
||||
|
||||
Ok(KclValue::Plane { value: Box::new(plane) })
|
||||
}
|
||||
_ => Err(KclValue::Object { value, meta }),
|
||||
};
|
||||
}
|
||||
|
||||
Err(value)
|
||||
value.coerce(&ty, exec_state)
|
||||
}
|
||||
|
||||
impl BinaryPart {
|
||||
@ -797,33 +747,7 @@ impl BinaryPart {
|
||||
}
|
||||
|
||||
impl Node<MemberExpression> {
|
||||
pub fn get_result_array(&self, exec_state: &mut ExecState, index: usize) -> Result<KclValue, KclError> {
|
||||
let array = match &self.object {
|
||||
MemberObject::MemberExpression(member_expr) => member_expr.get_result(exec_state)?,
|
||||
MemberObject::Identifier(identifier) => {
|
||||
let value = exec_state.stack().get(&identifier.name, identifier.into())?;
|
||||
value.clone()
|
||||
}
|
||||
};
|
||||
|
||||
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()],
|
||||
}));
|
||||
};
|
||||
|
||||
if let Some(value) = array.get(index) {
|
||||
Ok(value.to_owned())
|
||||
} else {
|
||||
Err(KclError::UndefinedValue(KclErrorDetails {
|
||||
message: format!("index {} not found in array", index),
|
||||
source_ranges: vec![self.clone().into()],
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_result(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
|
||||
fn get_result(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
|
||||
let property = Property::try_from(self.computed, self.property.clone(), exec_state, self.into())?;
|
||||
let object = match &self.object {
|
||||
// TODO: Don't use recursion here, use a loop.
|
||||
@ -1424,11 +1348,22 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
|
||||
// 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::Sketch { value: ref mut sketch } => {
|
||||
for (_, tag) in sketch.tags.iter() {
|
||||
exec_state
|
||||
.mut_stack()
|
||||
.insert_or_update(tag.value.clone(), KclValue::TagIdentifier(Box::new(tag.clone())));
|
||||
KclValue::Sketch { value } => {
|
||||
for (name, tag) in value.tags.iter() {
|
||||
if exec_state.stack().cur_frame_contains(name) {
|
||||
exec_state.mut_stack().update(name, |v, _| {
|
||||
v.as_mut_tag().unwrap().merge_info(tag);
|
||||
});
|
||||
} else {
|
||||
exec_state
|
||||
.mut_stack()
|
||||
.add(
|
||||
name.to_owned(),
|
||||
KclValue::TagIdentifier(Box::new(tag.clone())),
|
||||
SourceRange::default(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
KclValue::Solid { ref mut value } => {
|
||||
@ -1437,7 +1372,7 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
|
||||
// Get the past tag and update it.
|
||||
let tag_id = if let Some(t) = value.sketch.tags.get(&tag.name) {
|
||||
let mut t = t.clone();
|
||||
let Some(ref info) = t.info else {
|
||||
let Some(info) = t.get_cur_info() else {
|
||||
return Err(KclError::Internal(KclErrorDetails {
|
||||
message: format!("Tag {} does not have path info", tag.name),
|
||||
source_ranges: vec![tag.into()],
|
||||
@ -1447,59 +1382,70 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
|
||||
let mut info = info.clone();
|
||||
info.surface = Some(v.clone());
|
||||
info.sketch = value.id;
|
||||
t.info = Some(info);
|
||||
t.info.push((exec_state.stack().current_epoch(), info));
|
||||
t
|
||||
} else {
|
||||
// It's probably a fillet or a chamfer.
|
||||
// Initialize it.
|
||||
TagIdentifier {
|
||||
value: tag.name.clone(),
|
||||
info: Some(TagEngineInfo {
|
||||
id: v.get_id(),
|
||||
surface: Some(v.clone()),
|
||||
path: None,
|
||||
sketch: value.id,
|
||||
}),
|
||||
info: vec![(
|
||||
exec_state.stack().current_epoch(),
|
||||
TagEngineInfo {
|
||||
id: v.get_id(),
|
||||
surface: Some(v.clone()),
|
||||
path: None,
|
||||
sketch: value.id,
|
||||
},
|
||||
)],
|
||||
meta: vec![Metadata {
|
||||
source_range: tag.clone().into(),
|
||||
}],
|
||||
}
|
||||
};
|
||||
|
||||
exec_state
|
||||
.mut_stack()
|
||||
.insert_or_update(tag.name.clone(), KclValue::TagIdentifier(Box::new(tag_id.clone())));
|
||||
|
||||
// update the sketch tags.
|
||||
value.sketch.tags.insert(tag.name.clone(), tag_id);
|
||||
value.sketch.merge_tags(Some(&tag_id).into_iter());
|
||||
|
||||
if exec_state.stack().cur_frame_contains(&tag.name) {
|
||||
exec_state.mut_stack().update(&tag.name, |v, _| {
|
||||
v.as_mut_tag().unwrap().merge_info(&tag_id);
|
||||
});
|
||||
} else {
|
||||
exec_state
|
||||
.mut_stack()
|
||||
.add(
|
||||
tag.name.clone(),
|
||||
KclValue::TagIdentifier(Box::new(tag_id)),
|
||||
SourceRange::default(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the stale sketch in memory and update it.
|
||||
if !value.sketch.tags.is_empty() {
|
||||
let updates: Vec<_> = exec_state
|
||||
let sketches_to_update: Vec<_> = exec_state
|
||||
.stack()
|
||||
.find_all_in_current_env(|v| match v {
|
||||
.find_keys_in_current_env(|v| match v {
|
||||
KclValue::Sketch { value: sk } => sk.artifact_id == value.sketch.artifact_id,
|
||||
_ => false,
|
||||
})
|
||||
.map(|(k, v)| {
|
||||
let mut sketch = v.as_sketch().unwrap().clone();
|
||||
for (tag_name, tag_id) in value.sketch.tags.iter() {
|
||||
sketch.tags.insert(tag_name.clone(), tag_id.clone());
|
||||
}
|
||||
(
|
||||
k.clone(),
|
||||
KclValue::Sketch {
|
||||
value: Box::new(sketch),
|
||||
},
|
||||
)
|
||||
})
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
updates
|
||||
.into_iter()
|
||||
.for_each(|(k, v)| exec_state.mut_stack().insert_or_update(k, v))
|
||||
for k in sketches_to_update {
|
||||
exec_state.mut_stack().update(&k, |v, _| {
|
||||
let sketch = v.as_mut_sketch().unwrap();
|
||||
sketch.merge_tags(value.sketch.tags.values());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } => {
|
||||
for v in value {
|
||||
update_memory_for_tags_of_geometry(v, exec_state)?;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@ -1511,7 +1457,7 @@ impl Node<TagDeclarator> {
|
||||
pub async fn execute(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
|
||||
let memory_item = KclValue::TagIdentifier(Box::new(TagIdentifier {
|
||||
value: self.name.clone(),
|
||||
info: None,
|
||||
info: Vec::new(),
|
||||
meta: vec![Metadata {
|
||||
source_range: self.into(),
|
||||
}],
|
||||
@ -2026,8 +1972,8 @@ mod test {
|
||||
use std::sync::Arc;
|
||||
use tokio::{sync::RwLock, task::JoinSet};
|
||||
|
||||
#[test]
|
||||
fn test_assign_args_to_params() {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_assign_args_to_params() {
|
||||
// Set up a little framework for this test.
|
||||
fn mem(number: usize) -> KclValue {
|
||||
KclValue::Number {
|
||||
@ -2138,7 +2084,16 @@ mod test {
|
||||
digest: None,
|
||||
});
|
||||
let args = args.into_iter().map(Arg::synthetic).collect();
|
||||
let mut exec_state = ExecState::new(&Default::default());
|
||||
let exec_ctxt = ExecutorContext {
|
||||
engine: Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new().await.unwrap(),
|
||||
)),
|
||||
fs: Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: Arc::new(RwLock::new(crate::std::StdLib::new())),
|
||||
settings: Default::default(),
|
||||
context_type: ContextType::Mock,
|
||||
};
|
||||
let mut exec_state = ExecState::new(&exec_ctxt);
|
||||
exec_state.mod_local.stack = Stack::new_for_tests();
|
||||
let actual = assign_args_to_params(func_expr, args, &mut exec_state).map(|_| exec_state.mod_local.stack);
|
||||
assert_eq!(
|
||||
@ -2278,7 +2233,7 @@ import 'a.kcl'
|
||||
settings: Default::default(),
|
||||
context_type: ContextType::Mock,
|
||||
};
|
||||
let mut exec_state = ExecState::new(&exec_ctxt.settings);
|
||||
let mut exec_state = ExecState::new(&exec_ctxt);
|
||||
|
||||
eprintln!("{:?}", exec_ctxt);
|
||||
|
||||
|
@ -3,15 +3,14 @@ use std::ops::{Add, AddAssign, Mul};
|
||||
use anyhow::Result;
|
||||
use indexmap::IndexMap;
|
||||
use kittycad_modeling_cmds as kcmc;
|
||||
use kittycad_modeling_cmds::length_unit::LengthUnit;
|
||||
use kittycad_modeling_cmds::{each_cmd as mcmd, length_unit::LengthUnit, websocket::ModelingCmdReq, ModelingCmd};
|
||||
use parse_display::{Display, FromStr};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::ArtifactId;
|
||||
use crate::{
|
||||
errors::KclError,
|
||||
execution::{ExecState, Metadata, TagEngineInfo, TagIdentifier, UnitLen},
|
||||
execution::{ArtifactId, ExecState, Metadata, TagEngineInfo, TagIdentifier, UnitLen},
|
||||
parsing::ast::types::{Node, NodeRef, TagDeclarator, TagNode},
|
||||
std::sketch::PlaneData,
|
||||
};
|
||||
@ -24,8 +23,8 @@ type Point3D = kcmc::shared::Point3d<f64>;
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum Geometry {
|
||||
Sketch(Box<Sketch>),
|
||||
Solid(Box<Solid>),
|
||||
Sketch(Sketch),
|
||||
Solid(Solid),
|
||||
}
|
||||
|
||||
impl Geometry {
|
||||
@ -53,8 +52,8 @@ impl Geometry {
|
||||
#[serde(tag = "type")]
|
||||
#[allow(clippy::vec_box)]
|
||||
pub enum Geometries {
|
||||
Sketches(Vec<Box<Sketch>>),
|
||||
Solids(Vec<Box<Solid>>),
|
||||
Sketches(Vec<Sketch>),
|
||||
Solids(Vec<Solid>),
|
||||
}
|
||||
|
||||
impl From<Geometry> for Geometries {
|
||||
@ -66,150 +65,6 @@ impl From<Geometry> for Geometries {
|
||||
}
|
||||
}
|
||||
|
||||
/// A sketch or a group of sketches.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[allow(clippy::vec_box)]
|
||||
pub enum SketchSet {
|
||||
Sketch(Box<Sketch>),
|
||||
Sketches(Vec<Box<Sketch>>),
|
||||
}
|
||||
|
||||
impl SketchSet {
|
||||
pub fn meta(&self) -> Vec<Metadata> {
|
||||
match self {
|
||||
SketchSet::Sketch(sg) => sg.meta.clone(),
|
||||
SketchSet::Sketches(sg) => sg.iter().flat_map(|sg| sg.meta.clone()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SketchSet> for Vec<Sketch> {
|
||||
fn from(value: SketchSet) -> Self {
|
||||
match value {
|
||||
SketchSet::Sketch(sg) => vec![*sg],
|
||||
SketchSet::Sketches(sgs) => sgs.into_iter().map(|sg| *sg).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Sketch> for SketchSet {
|
||||
fn from(sg: Sketch) -> Self {
|
||||
SketchSet::Sketch(Box::new(sg))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<Sketch>> for SketchSet {
|
||||
fn from(sg: Box<Sketch>) -> Self {
|
||||
SketchSet::Sketch(sg)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Sketch>> for SketchSet {
|
||||
fn from(sg: Vec<Sketch>) -> Self {
|
||||
if sg.len() == 1 {
|
||||
SketchSet::Sketch(Box::new(sg[0].clone()))
|
||||
} else {
|
||||
SketchSet::Sketches(sg.into_iter().map(Box::new).collect())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Box<Sketch>>> for SketchSet {
|
||||
fn from(sg: Vec<Box<Sketch>>) -> Self {
|
||||
if sg.len() == 1 {
|
||||
SketchSet::Sketch(sg[0].clone())
|
||||
} else {
|
||||
SketchSet::Sketches(sg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SketchSet> for Vec<Box<Sketch>> {
|
||||
fn from(sg: SketchSet) -> Self {
|
||||
match sg {
|
||||
SketchSet::Sketch(sg) => vec![sg],
|
||||
SketchSet::Sketches(sgs) => sgs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Sketch> for Vec<Box<Sketch>> {
|
||||
fn from(sg: &Sketch) -> Self {
|
||||
vec![Box::new(sg.clone())]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<Sketch>> for Vec<Box<Sketch>> {
|
||||
fn from(sg: Box<Sketch>) -> Self {
|
||||
vec![sg]
|
||||
}
|
||||
}
|
||||
|
||||
/// A solid or a group of solids.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[allow(clippy::vec_box)]
|
||||
pub enum SolidSet {
|
||||
Solid(Box<Solid>),
|
||||
Solids(Vec<Box<Solid>>),
|
||||
}
|
||||
|
||||
impl From<Solid> for SolidSet {
|
||||
fn from(eg: Solid) -> Self {
|
||||
SolidSet::Solid(Box::new(eg))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<Solid>> for SolidSet {
|
||||
fn from(eg: Box<Solid>) -> Self {
|
||||
SolidSet::Solid(eg)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Solid>> for SolidSet {
|
||||
fn from(eg: Vec<Solid>) -> Self {
|
||||
if eg.len() == 1 {
|
||||
SolidSet::Solid(Box::new(eg[0].clone()))
|
||||
} else {
|
||||
SolidSet::Solids(eg.into_iter().map(Box::new).collect())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Box<Solid>>> for SolidSet {
|
||||
fn from(eg: Vec<Box<Solid>>) -> Self {
|
||||
if eg.len() == 1 {
|
||||
SolidSet::Solid(eg[0].clone())
|
||||
} else {
|
||||
SolidSet::Solids(eg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SolidSet> for Vec<Box<Solid>> {
|
||||
fn from(eg: SolidSet) -> Self {
|
||||
match eg {
|
||||
SolidSet::Solid(eg) => vec![eg],
|
||||
SolidSet::Solids(egs) => egs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Solid> for Vec<Box<Solid>> {
|
||||
fn from(eg: &Solid) -> Self {
|
||||
vec![Box::new(eg.clone())]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<Solid>> for Vec<Box<Solid>> {
|
||||
fn from(eg: Box<Solid>) -> Self {
|
||||
vec![eg]
|
||||
}
|
||||
}
|
||||
|
||||
/// Data for an imported geometry.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
@ -219,7 +74,7 @@ pub struct ImportedGeometry {
|
||||
pub id: uuid::Uuid,
|
||||
/// The original file paths.
|
||||
pub value: Vec<String>,
|
||||
#[serde(rename = "__meta")]
|
||||
#[serde(skip)]
|
||||
pub meta: Vec<Metadata>,
|
||||
}
|
||||
|
||||
@ -227,25 +82,40 @@ pub struct ImportedGeometry {
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[allow(clippy::vec_box)]
|
||||
pub enum SolidOrImportedGeometry {
|
||||
Solid(Box<Solid>),
|
||||
ImportedGeometry(Box<ImportedGeometry>),
|
||||
}
|
||||
|
||||
impl SolidOrImportedGeometry {
|
||||
pub fn id(&self) -> uuid::Uuid {
|
||||
match self {
|
||||
SolidOrImportedGeometry::Solid(s) => s.id,
|
||||
SolidOrImportedGeometry::ImportedGeometry(s) => s.id,
|
||||
}
|
||||
}
|
||||
SolidSet(Vec<Solid>),
|
||||
}
|
||||
|
||||
impl From<SolidOrImportedGeometry> for crate::execution::KclValue {
|
||||
fn from(value: SolidOrImportedGeometry) -> Self {
|
||||
match value {
|
||||
SolidOrImportedGeometry::Solid(s) => crate::execution::KclValue::Solid { value: s },
|
||||
SolidOrImportedGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s),
|
||||
SolidOrImportedGeometry::SolidSet(mut s) => {
|
||||
if s.len() == 1 {
|
||||
crate::execution::KclValue::Solid {
|
||||
value: Box::new(s.pop().unwrap()),
|
||||
}
|
||||
} else {
|
||||
crate::execution::KclValue::HomArray {
|
||||
value: s
|
||||
.into_iter()
|
||||
.map(|s| crate::execution::KclValue::Solid { value: Box::new(s) })
|
||||
.collect(),
|
||||
ty: crate::execution::PrimitiveType::Solid,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SolidOrImportedGeometry {
|
||||
pub(crate) fn ids(&self) -> Vec<uuid::Uuid> {
|
||||
match self {
|
||||
SolidOrImportedGeometry::ImportedGeometry(s) => vec![s.id],
|
||||
SolidOrImportedGeometry::SolidSet(s) => s.iter().map(|s| s.id).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -266,7 +136,7 @@ pub struct Helix {
|
||||
/// Is the helix rotation counter clockwise?
|
||||
pub ccw: bool,
|
||||
pub units: UnitLen,
|
||||
#[serde(rename = "__meta")]
|
||||
#[serde(skip)]
|
||||
pub meta: Vec<Metadata>,
|
||||
}
|
||||
|
||||
@ -289,7 +159,7 @@ pub struct Plane {
|
||||
/// The z-axis (normal).
|
||||
pub z_axis: Point3d,
|
||||
pub units: UnitLen,
|
||||
#[serde(rename = "__meta")]
|
||||
#[serde(skip)]
|
||||
pub meta: Vec<Metadata>,
|
||||
}
|
||||
|
||||
@ -367,7 +237,7 @@ impl Plane {
|
||||
}
|
||||
|
||||
pub(crate) fn from_plane_data(value: PlaneData, exec_state: &mut ExecState) -> Self {
|
||||
let id = exec_state.global.id_generator.next_uuid();
|
||||
let id = exec_state.next_uuid();
|
||||
match value {
|
||||
PlaneData::XY => Plane {
|
||||
id,
|
||||
@ -440,17 +310,20 @@ impl Plane {
|
||||
x_axis,
|
||||
y_axis,
|
||||
z_axis,
|
||||
} => Plane {
|
||||
id,
|
||||
artifact_id: id.into(),
|
||||
origin,
|
||||
x_axis,
|
||||
y_axis,
|
||||
z_axis,
|
||||
value: PlaneType::Custom,
|
||||
units: exec_state.length_unit(),
|
||||
meta: vec![],
|
||||
},
|
||||
} => {
|
||||
let id = exec_state.next_uuid();
|
||||
Plane {
|
||||
id,
|
||||
artifact_id: id.into(),
|
||||
origin,
|
||||
x_axis,
|
||||
y_axis,
|
||||
z_axis,
|
||||
value: PlaneType::Custom,
|
||||
units: exec_state.length_unit(),
|
||||
meta: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -480,7 +353,7 @@ pub struct Face {
|
||||
/// The solid the face is on.
|
||||
pub solid: Box<Solid>,
|
||||
pub units: UnitLen,
|
||||
#[serde(rename = "__meta")]
|
||||
#[serde(skip)]
|
||||
pub meta: Vec<Metadata>,
|
||||
}
|
||||
|
||||
@ -528,10 +401,45 @@ pub struct Sketch {
|
||||
pub original_id: uuid::Uuid,
|
||||
pub units: UnitLen,
|
||||
/// Metadata.
|
||||
#[serde(rename = "__meta")]
|
||||
#[serde(skip)]
|
||||
pub meta: Vec<Metadata>,
|
||||
}
|
||||
|
||||
impl Sketch {
|
||||
// Tell the engine to enter sketch mode on the sketch.
|
||||
// Run a specific command, then exit sketch mode.
|
||||
pub(crate) fn build_sketch_mode_cmds(
|
||||
&self,
|
||||
exec_state: &mut ExecState,
|
||||
inner_cmd: ModelingCmdReq,
|
||||
) -> Vec<ModelingCmdReq> {
|
||||
vec![
|
||||
// Before we extrude, we need to enable the sketch mode.
|
||||
// We do this here in case extrude is called out of order.
|
||||
ModelingCmdReq {
|
||||
cmd: ModelingCmd::from(mcmd::EnableSketchMode {
|
||||
animated: false,
|
||||
ortho: false,
|
||||
entity_id: self.on.id(),
|
||||
adjust_camera: false,
|
||||
planar_normal: if let SketchSurface::Plane(plane) = &self.on {
|
||||
// We pass in the normal for the plane here.
|
||||
Some(plane.z_axis.into())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}),
|
||||
cmd_id: exec_state.next_uuid().into(),
|
||||
},
|
||||
inner_cmd,
|
||||
ModelingCmdReq {
|
||||
cmd: ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable::default()),
|
||||
cmd_id: exec_state.next_uuid().into(),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/// A sketch type.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
@ -598,19 +506,35 @@ impl GetTangentialInfoFromPathsResult {
|
||||
}
|
||||
|
||||
impl Sketch {
|
||||
pub(crate) fn add_tag(&mut self, tag: NodeRef<'_, TagDeclarator>, current_path: &Path) {
|
||||
pub(crate) fn add_tag(&mut self, tag: NodeRef<'_, TagDeclarator>, current_path: &Path, exec_state: &ExecState) {
|
||||
let mut tag_identifier: TagIdentifier = tag.into();
|
||||
let base = current_path.get_base();
|
||||
tag_identifier.info = Some(TagEngineInfo {
|
||||
id: base.geo_meta.id,
|
||||
sketch: self.id,
|
||||
path: Some(current_path.clone()),
|
||||
surface: None,
|
||||
});
|
||||
tag_identifier.info.push((
|
||||
exec_state.stack().current_epoch(),
|
||||
TagEngineInfo {
|
||||
id: base.geo_meta.id,
|
||||
sketch: self.id,
|
||||
path: Some(current_path.clone()),
|
||||
surface: None,
|
||||
},
|
||||
));
|
||||
|
||||
self.tags.insert(tag.name.to_string(), tag_identifier);
|
||||
}
|
||||
|
||||
pub(crate) fn merge_tags<'a>(&mut self, tags: impl Iterator<Item = &'a TagIdentifier>) {
|
||||
for t in tags {
|
||||
match self.tags.get_mut(&t.value) {
|
||||
Some(id) => {
|
||||
id.merge_info(t);
|
||||
}
|
||||
None => {
|
||||
self.tags.insert(t.value.clone(), t.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the path most recently sketched.
|
||||
pub(crate) fn latest_path(&self) -> Option<&Path> {
|
||||
self.paths.last()
|
||||
@ -659,7 +583,7 @@ pub struct Solid {
|
||||
pub edge_cuts: Vec<EdgeCut>,
|
||||
pub units: UnitLen,
|
||||
/// Metadata.
|
||||
#[serde(rename = "__meta")]
|
||||
#[serde(skip)]
|
||||
pub meta: Vec<Metadata>,
|
||||
}
|
||||
|
||||
@ -908,6 +832,19 @@ pub enum Path {
|
||||
#[ts(type = "[number, number]")]
|
||||
p3: [f64; 2],
|
||||
},
|
||||
ArcThreePoint {
|
||||
#[serde(flatten)]
|
||||
base: BasePath,
|
||||
/// Point 1 of the arc (base on the end of previous segment)
|
||||
#[ts(type = "[number, number]")]
|
||||
p1: [f64; 2],
|
||||
/// Point 2 of the arc (interior kwarg)
|
||||
#[ts(type = "[number, number]")]
|
||||
p2: [f64; 2],
|
||||
/// Point 3 of the arc (end kwarg)
|
||||
#[ts(type = "[number, number]")]
|
||||
p3: [f64; 2],
|
||||
},
|
||||
/// A path that is horizontal.
|
||||
Horizontal {
|
||||
#[serde(flatten)]
|
||||
@ -968,6 +905,7 @@ impl From<&Path> for PathType {
|
||||
Path::AngledLineTo { .. } => Self::AngledLineTo,
|
||||
Path::Base { .. } => Self::Base,
|
||||
Path::Arc { .. } => Self::Arc,
|
||||
Path::ArcThreePoint { .. } => Self::Arc,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -984,6 +922,7 @@ impl Path {
|
||||
Path::Circle { base, .. } => base.geo_meta.id,
|
||||
Path::CircleThreePoint { base, .. } => base.geo_meta.id,
|
||||
Path::Arc { base, .. } => base.geo_meta.id,
|
||||
Path::ArcThreePoint { base, .. } => base.geo_meta.id,
|
||||
}
|
||||
}
|
||||
|
||||
@ -998,6 +937,7 @@ impl Path {
|
||||
Path::Circle { base, .. } => base.tag.clone(),
|
||||
Path::CircleThreePoint { base, .. } => base.tag.clone(),
|
||||
Path::Arc { base, .. } => base.tag.clone(),
|
||||
Path::ArcThreePoint { base, .. } => base.tag.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1012,6 +952,7 @@ impl Path {
|
||||
Path::Circle { base, .. } => base,
|
||||
Path::CircleThreePoint { base, .. } => base,
|
||||
Path::Arc { base, .. } => base,
|
||||
Path::ArcThreePoint { base, .. } => base,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1061,6 +1002,10 @@ impl Path {
|
||||
// TODO: Call engine utils to figure this out.
|
||||
linear_distance(self.get_from(), self.get_to())
|
||||
}
|
||||
Self::ArcThreePoint { .. } => {
|
||||
// TODO: Call engine utils to figure this out.
|
||||
linear_distance(self.get_from(), self.get_to())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1075,6 +1020,7 @@ impl Path {
|
||||
Path::Circle { base, .. } => Some(base),
|
||||
Path::CircleThreePoint { base, .. } => Some(base),
|
||||
Path::Arc { base, .. } => Some(base),
|
||||
Path::ArcThreePoint { base, .. } => Some(base),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1086,6 +1032,17 @@ impl Path {
|
||||
center: *center,
|
||||
ccw: *ccw,
|
||||
},
|
||||
Path::ArcThreePoint { p1, p2, p3, .. } => {
|
||||
let circle_center =
|
||||
crate::std::utils::calculate_circle_from_3_points([(*p1).into(), (*p2).into(), (*p3).into()]);
|
||||
let radius = linear_distance(&[circle_center.center.x, circle_center.center.y], p1);
|
||||
let center_point = [circle_center.center.x, circle_center.center.y];
|
||||
GetTangentialInfoFromPathsResult::Circle {
|
||||
center: center_point,
|
||||
ccw: true,
|
||||
radius,
|
||||
}
|
||||
}
|
||||
Path::Circle {
|
||||
center, ccw, radius, ..
|
||||
} => GetTangentialInfoFromPathsResult::Circle {
|
||||
|
83
rust/kcl-lib/src/execution/id_generator.rs
Normal file
83
rust/kcl-lib/src/execution/id_generator.rs
Normal file
@ -0,0 +1,83 @@
|
||||
//! A generator for ArtifactIds that can be stable across executions.
|
||||
|
||||
use crate::execution::ModuleId;
|
||||
|
||||
const NAMESPACE_KCL: uuid::Uuid = uuid::uuid!("efcd6508-4ce6-4a09-8317-e6a6994a3cd7");
|
||||
|
||||
/// A generator for ArtifactIds that can be stable across executions.
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
pub struct IdGenerator {
|
||||
module_id: Option<ModuleId>,
|
||||
next_id: u64,
|
||||
}
|
||||
|
||||
impl IdGenerator {
|
||||
pub fn new(module_id: Option<ModuleId>) -> Self {
|
||||
Self { module_id, next_id: 0 }
|
||||
}
|
||||
|
||||
pub fn next_uuid(&mut self) -> uuid::Uuid {
|
||||
let next_id = self.next_id;
|
||||
|
||||
let next = format!(
|
||||
"{} {}",
|
||||
self.module_id.map(|id| id.to_string()).unwrap_or("none".to_string()),
|
||||
next_id
|
||||
);
|
||||
let next_uuid = uuid::Uuid::new_v5(&NAMESPACE_KCL, next.as_bytes());
|
||||
|
||||
self.next_id += 1;
|
||||
|
||||
next_uuid
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_id_generator() {
|
||||
let mut generator = IdGenerator::new(Some(ModuleId::default()));
|
||||
|
||||
let uuid1 = generator.next_uuid();
|
||||
let uuid2 = generator.next_uuid();
|
||||
|
||||
assert_ne!(uuid1, uuid2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Test that the same generator produces the same UUIDs.
|
||||
fn test_id_generator_stable() {
|
||||
let mut generator = IdGenerator::new(Some(ModuleId::default()));
|
||||
|
||||
let uuid1 = generator.next_uuid();
|
||||
let uuid2 = generator.next_uuid();
|
||||
|
||||
let mut generator = IdGenerator::new(Some(ModuleId::default()));
|
||||
|
||||
let uuid3 = generator.next_uuid();
|
||||
let uuid4 = generator.next_uuid();
|
||||
|
||||
assert_eq!(uuid1, uuid3);
|
||||
assert_eq!(uuid2, uuid4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Generate 20 uuids and make sure all are unique.
|
||||
fn test_id_generator_unique() {
|
||||
let mut generator = IdGenerator::new(Some(ModuleId::default()));
|
||||
|
||||
let mut uuids = Vec::new();
|
||||
|
||||
for _ in 0..20 {
|
||||
uuids.push(generator.next_uuid());
|
||||
}
|
||||
|
||||
for i in 0..uuids.len() {
|
||||
for j in i + 1..uuids.len() {
|
||||
assert_ne!(uuids[i], uuids[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -6,13 +6,12 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{
|
||||
memory::{self, EnvironmentRef},
|
||||
MetaSettings,
|
||||
MetaSettings, Point3d,
|
||||
};
|
||||
use crate::{
|
||||
errors::KclErrorDetails,
|
||||
execution::{
|
||||
ExecState, ExecutorContext, Face, Helix, ImportedGeometry, Metadata, Plane, Sketch, SketchSet, Solid, SolidSet,
|
||||
TagIdentifier,
|
||||
ExecState, ExecutorContext, Face, Helix, ImportedGeometry, Metadata, Plane, Sketch, Solid, TagIdentifier,
|
||||
},
|
||||
parsing::{
|
||||
ast::types::{
|
||||
@ -21,7 +20,10 @@ use crate::{
|
||||
},
|
||||
token::NumericSuffix,
|
||||
},
|
||||
std::{args::Arg, StdFnProps},
|
||||
std::{
|
||||
args::{Arg, FromKclValue},
|
||||
StdFnProps,
|
||||
},
|
||||
CompilationError, KclError, ModuleId, SourceRange,
|
||||
};
|
||||
|
||||
@ -34,33 +36,40 @@ pub type KclObjectFields = HashMap<String, KclValue>;
|
||||
pub enum KclValue {
|
||||
Uuid {
|
||||
value: ::uuid::Uuid,
|
||||
#[serde(rename = "__meta")]
|
||||
#[serde(skip)]
|
||||
meta: Vec<Metadata>,
|
||||
},
|
||||
Bool {
|
||||
value: bool,
|
||||
#[serde(rename = "__meta")]
|
||||
#[serde(skip)]
|
||||
meta: Vec<Metadata>,
|
||||
},
|
||||
Number {
|
||||
value: f64,
|
||||
ty: NumericType,
|
||||
#[serde(rename = "__meta")]
|
||||
#[serde(skip)]
|
||||
meta: Vec<Metadata>,
|
||||
},
|
||||
String {
|
||||
value: String,
|
||||
#[serde(rename = "__meta")]
|
||||
#[serde(skip)]
|
||||
meta: Vec<Metadata>,
|
||||
},
|
||||
MixedArray {
|
||||
value: Vec<KclValue>,
|
||||
#[serde(rename = "__meta")]
|
||||
#[serde(skip)]
|
||||
meta: Vec<Metadata>,
|
||||
},
|
||||
// An array where all values have a shared type (not necessarily the same principal type).
|
||||
HomArray {
|
||||
value: Vec<KclValue>,
|
||||
// The type of values, not the array type.
|
||||
#[serde(skip)]
|
||||
ty: PrimitiveType,
|
||||
},
|
||||
Object {
|
||||
value: KclObjectFields,
|
||||
#[serde(rename = "__meta")]
|
||||
#[serde(skip)]
|
||||
meta: Vec<Metadata>,
|
||||
},
|
||||
TagIdentifier(Box<TagIdentifier>),
|
||||
@ -74,15 +83,9 @@ pub enum KclValue {
|
||||
Sketch {
|
||||
value: Box<Sketch>,
|
||||
},
|
||||
Sketches {
|
||||
value: Vec<Box<Sketch>>,
|
||||
},
|
||||
Solid {
|
||||
value: Box<Solid>,
|
||||
},
|
||||
Solids {
|
||||
value: Vec<Box<Solid>>,
|
||||
},
|
||||
Helix {
|
||||
value: Box<Helix>,
|
||||
},
|
||||
@ -91,30 +94,24 @@ pub enum KclValue {
|
||||
Function {
|
||||
#[serde(skip)]
|
||||
value: FunctionSource,
|
||||
#[serde(rename = "__meta")]
|
||||
#[serde(skip)]
|
||||
meta: Vec<Metadata>,
|
||||
},
|
||||
Module {
|
||||
value: ModuleId,
|
||||
#[serde(rename = "__meta")]
|
||||
#[serde(skip)]
|
||||
meta: Vec<Metadata>,
|
||||
},
|
||||
#[ts(skip)]
|
||||
Type {
|
||||
#[serde(skip)]
|
||||
value: Option<(PrimitiveType, StdFnProps)>,
|
||||
#[serde(rename = "__meta")]
|
||||
#[serde(skip)]
|
||||
meta: Vec<Metadata>,
|
||||
},
|
||||
KclNone {
|
||||
value: KclNone,
|
||||
#[serde(rename = "__meta")]
|
||||
meta: Vec<Metadata>,
|
||||
},
|
||||
// Only used for memory management. Should never be visible outside of the memory module.
|
||||
Tombstone {
|
||||
value: (),
|
||||
#[serde(rename = "__meta")]
|
||||
#[serde(skip)]
|
||||
meta: Vec<Metadata>,
|
||||
},
|
||||
}
|
||||
@ -145,48 +142,46 @@ impl JsonSchema for FunctionSource {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SketchSet> for KclValue {
|
||||
fn from(sg: SketchSet) -> Self {
|
||||
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 {
|
||||
KclValue::Sketches { value: sg }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SolidSet> for KclValue {
|
||||
fn from(eg: SolidSet) -> Self {
|
||||
match eg {
|
||||
SolidSet::Solid(eg) => KclValue::Solid { value: eg },
|
||||
SolidSet::Solids(egs) => KclValue::Solids { value: egs },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Box<Solid>>> for KclValue {
|
||||
fn from(eg: Vec<Box<Solid>>) -> Self {
|
||||
impl From<Vec<Sketch>> for KclValue {
|
||||
fn from(mut eg: Vec<Sketch>) -> Self {
|
||||
if eg.len() == 1 {
|
||||
KclValue::Solid { value: eg[0].clone() }
|
||||
KclValue::Sketch {
|
||||
value: Box::new(eg.pop().unwrap()),
|
||||
}
|
||||
} else {
|
||||
KclValue::Solids { value: eg }
|
||||
KclValue::HomArray {
|
||||
value: eg
|
||||
.into_iter()
|
||||
.map(|s| KclValue::Sketch { value: Box::new(s) })
|
||||
.collect(),
|
||||
ty: crate::execution::PrimitiveType::Sketch,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Solid>> for KclValue {
|
||||
fn from(mut eg: Vec<Solid>) -> Self {
|
||||
if eg.len() == 1 {
|
||||
KclValue::Solid {
|
||||
value: Box::new(eg.pop().unwrap()),
|
||||
}
|
||||
} else {
|
||||
KclValue::HomArray {
|
||||
value: eg.into_iter().map(|s| KclValue::Solid { value: Box::new(s) }).collect(),
|
||||
ty: crate::execution::PrimitiveType::Solid,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KclValue> for Vec<SourceRange> {
|
||||
fn from(item: KclValue) -> Self {
|
||||
match item {
|
||||
KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)],
|
||||
KclValue::TagIdentifier(t) => to_vec_sr(&t.meta),
|
||||
KclValue::Solid { value } => to_vec_sr(&value.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::Helix { value } => to_vec_sr(&value.meta),
|
||||
KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta),
|
||||
KclValue::Function { meta, .. } => to_vec_sr(&meta),
|
||||
@ -196,12 +191,12 @@ impl From<KclValue> for Vec<SourceRange> {
|
||||
KclValue::Number { meta, .. } => to_vec_sr(&meta),
|
||||
KclValue::String { meta, .. } => to_vec_sr(&meta),
|
||||
KclValue::MixedArray { meta, .. } => to_vec_sr(&meta),
|
||||
KclValue::HomArray { value, .. } => value.iter().flat_map(Into::<Vec<SourceRange>>::into).collect(),
|
||||
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,9 +211,7 @@ impl From<&KclValue> for Vec<SourceRange> {
|
||||
KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)],
|
||||
KclValue::TagIdentifier(t) => to_vec_sr(&t.meta),
|
||||
KclValue::Solid { value } => to_vec_sr(&value.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::Helix { value } => to_vec_sr(&value.meta),
|
||||
KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta),
|
||||
KclValue::Function { meta, .. } => to_vec_sr(meta),
|
||||
@ -229,11 +222,11 @@ impl From<&KclValue> for Vec<SourceRange> {
|
||||
KclValue::String { meta, .. } => to_vec_sr(meta),
|
||||
KclValue::Uuid { meta, .. } => to_vec_sr(meta),
|
||||
KclValue::MixedArray { meta, .. } => to_vec_sr(meta),
|
||||
KclValue::HomArray { value, .. } => value.iter().flat_map(Into::<Vec<SourceRange>>::into).collect(),
|
||||
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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -253,22 +246,20 @@ impl KclValue {
|
||||
KclValue::Number { meta, .. } => meta.clone(),
|
||||
KclValue::String { value: _, meta } => meta.clone(),
|
||||
KclValue::MixedArray { value: _, meta } => meta.clone(),
|
||||
KclValue::HomArray { value, .. } => value.iter().flat_map(|v| v.metadata()).collect(),
|
||||
KclValue::Object { value: _, meta } => meta.clone(),
|
||||
KclValue::TagIdentifier(x) => x.meta.clone(),
|
||||
KclValue::TagDeclarator(x) => vec![x.metadata()],
|
||||
KclValue::Plane { value } => value.meta.clone(),
|
||||
KclValue::Face { value } => value.meta.clone(),
|
||||
KclValue::Sketch { value } => value.meta.clone(),
|
||||
KclValue::Sketches { value } => value.iter().flat_map(|sketch| &sketch.meta).copied().collect(),
|
||||
KclValue::Solid { value } => value.meta.clone(),
|
||||
KclValue::Solids { value } => value.iter().flat_map(|sketch| &sketch.meta).copied().collect(),
|
||||
KclValue::Helix { value } => value.meta.clone(),
|
||||
KclValue::ImportedGeometry(x) => x.meta.clone(),
|
||||
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"),
|
||||
}
|
||||
}
|
||||
|
||||
@ -285,29 +276,6 @@ impl KclValue {
|
||||
Some(ast.as_source_range())
|
||||
}
|
||||
|
||||
pub(crate) fn get_solid_set(&self) -> Result<SolidSet> {
|
||||
match self {
|
||||
KclValue::Solid { value } => Ok(SolidSet::Solid(value.clone())),
|
||||
KclValue::Solids { value } => Ok(SolidSet::Solids(value.clone())),
|
||||
KclValue::MixedArray { 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),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) fn none() -> Self {
|
||||
Self::KclNone {
|
||||
@ -324,9 +292,7 @@ impl KclValue {
|
||||
KclValue::TagDeclarator(_) => "TagDeclarator",
|
||||
KclValue::TagIdentifier(_) => "TagIdentifier",
|
||||
KclValue::Solid { .. } => "Solid",
|
||||
KclValue::Solids { .. } => "Solids",
|
||||
KclValue::Sketch { .. } => "Sketch",
|
||||
KclValue::Sketches { .. } => "Sketches",
|
||||
KclValue::Helix { .. } => "Helix",
|
||||
KclValue::ImportedGeometry(_) => "ImportedGeometry",
|
||||
KclValue::Function { .. } => "Function",
|
||||
@ -336,11 +302,11 @@ impl KclValue {
|
||||
KclValue::Number { .. } => "number",
|
||||
KclValue::String { .. } => "string (text)",
|
||||
KclValue::MixedArray { .. } => "array (list)",
|
||||
KclValue::HomArray { .. } => "array (list)",
|
||||
KclValue::Object { .. } => "object",
|
||||
KclValue::Module { .. } => "module",
|
||||
KclValue::Type { .. } => "type",
|
||||
KclValue::KclNone { .. } => "None",
|
||||
KclValue::Tombstone { .. } => "TOMBSTONE",
|
||||
}
|
||||
}
|
||||
|
||||
@ -367,16 +333,14 @@ impl KclValue {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_env_ref(&self, env_map: &HashMap<EnvironmentRef, EnvironmentRef>) -> Self {
|
||||
pub(crate) fn map_env_ref(&self, old_env: usize, new_env: usize) -> Self {
|
||||
let mut result = self.clone();
|
||||
if let KclValue::Function {
|
||||
value: FunctionSource::User { ref mut memory, .. },
|
||||
..
|
||||
} = result
|
||||
{
|
||||
if let Some(new) = env_map.get(memory) {
|
||||
*memory = *new;
|
||||
}
|
||||
memory.replace_env(old_env, new_env);
|
||||
}
|
||||
result
|
||||
}
|
||||
@ -501,6 +465,21 @@ impl KclValue {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_mut_sketch(&mut self) -> Option<&mut Sketch> {
|
||||
if let KclValue::Sketch { value } = self {
|
||||
Some(value)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_mut_tag(&mut self) -> Option<&mut TagIdentifier> {
|
||||
if let KclValue::TagIdentifier(value) = self {
|
||||
Some(value)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
pub fn as_f64(&self) -> Option<f64> {
|
||||
if let KclValue::Number { value, .. } = &self {
|
||||
Some(*value)
|
||||
@ -563,17 +542,6 @@ impl KclValue {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get an optional tag from a memory item.
|
||||
pub fn get_tag_declarator_opt(&self) -> Result<Option<TagNode>, KclError> {
|
||||
match self {
|
||||
KclValue::TagDeclarator(t) => Ok(Some((**t).clone())),
|
||||
_ => Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!("Not a tag declarator: {:?}", self),
|
||||
source_ranges: self.clone().into(),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
/// If this KCL value is a bool, retrieve it.
|
||||
pub fn get_bool(&self) -> Result<bool, KclError> {
|
||||
let Self::Bool { value: b, .. } = self else {
|
||||
@ -594,6 +562,215 @@ impl KclValue {
|
||||
self_ty.subtype(ty)
|
||||
}
|
||||
|
||||
/// Coerce `self` to a new value which has `ty` as it's closest supertype.
|
||||
///
|
||||
/// If the result is Some, then:
|
||||
/// - result.principal_type().unwrap().subtype(ty)
|
||||
///
|
||||
/// If self.principal_type() == ty then result == self
|
||||
pub fn coerce(&self, ty: &RuntimeType, exec_state: &mut ExecState) -> Option<KclValue> {
|
||||
match ty {
|
||||
RuntimeType::Primitive(ty) => self.coerce_to_primitive_type(ty, exec_state),
|
||||
RuntimeType::Array(ty, len) => self.coerce_to_array_type(ty, *len, exec_state),
|
||||
RuntimeType::Tuple(tys) => self.coerce_to_tuple_type(tys, exec_state),
|
||||
RuntimeType::Union(tys) => self.coerce_to_union_type(tys, exec_state),
|
||||
RuntimeType::Object(tys) => self.coerce_to_object_type(tys, exec_state),
|
||||
}
|
||||
}
|
||||
|
||||
fn coerce_to_primitive_type(&self, ty: &PrimitiveType, exec_state: &mut ExecState) -> Option<KclValue> {
|
||||
let value = match self {
|
||||
KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } if value.len() == 1 => &value[0],
|
||||
_ => self,
|
||||
};
|
||||
match ty {
|
||||
// TODO numeric type coercions
|
||||
PrimitiveType::Number(_ty) => match value {
|
||||
KclValue::Number { .. } => Some(value.clone()),
|
||||
_ => None,
|
||||
},
|
||||
PrimitiveType::String => match value {
|
||||
KclValue::String { .. } => Some(value.clone()),
|
||||
_ => None,
|
||||
},
|
||||
PrimitiveType::Boolean => match value {
|
||||
KclValue::Bool { .. } => Some(value.clone()),
|
||||
_ => None,
|
||||
},
|
||||
PrimitiveType::Sketch => match value {
|
||||
KclValue::Sketch { .. } => Some(value.clone()),
|
||||
_ => None,
|
||||
},
|
||||
PrimitiveType::Solid => match value {
|
||||
KclValue::Solid { .. } => Some(value.clone()),
|
||||
_ => None,
|
||||
},
|
||||
PrimitiveType::Plane => match value {
|
||||
KclValue::Plane { .. } => Some(value.clone()),
|
||||
KclValue::Object { value, meta } => {
|
||||
let origin = value.get("origin").and_then(Point3d::from_kcl_val)?;
|
||||
let x_axis = value.get("xAxis").and_then(Point3d::from_kcl_val)?;
|
||||
let y_axis = value.get("yAxis").and_then(Point3d::from_kcl_val)?;
|
||||
let z_axis = value.get("zAxis").and_then(Point3d::from_kcl_val)?;
|
||||
|
||||
let id = exec_state.mod_local.id_generator.next_uuid();
|
||||
let plane = Plane {
|
||||
id,
|
||||
artifact_id: id.into(),
|
||||
origin,
|
||||
x_axis,
|
||||
y_axis,
|
||||
z_axis,
|
||||
value: super::PlaneType::Uninit,
|
||||
// TODO use length unit from origin
|
||||
units: exec_state.length_unit(),
|
||||
meta: meta.clone(),
|
||||
};
|
||||
|
||||
Some(KclValue::Plane { value: Box::new(plane) })
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
PrimitiveType::ImportedGeometry => match value {
|
||||
KclValue::ImportedGeometry { .. } => Some(value.clone()),
|
||||
_ => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn coerce_to_array_type(&self, ty: &PrimitiveType, len: ArrayLen, exec_state: &mut ExecState) -> Option<KclValue> {
|
||||
match self {
|
||||
KclValue::HomArray { value, ty: aty } => {
|
||||
// TODO could check types of values individually
|
||||
if aty != ty {
|
||||
return None;
|
||||
}
|
||||
|
||||
let value = match len {
|
||||
ArrayLen::None => value.clone(),
|
||||
ArrayLen::NonEmpty => {
|
||||
if value.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
value.clone()
|
||||
}
|
||||
ArrayLen::Known(n) => {
|
||||
if n != value.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
value[..n].to_vec()
|
||||
}
|
||||
};
|
||||
|
||||
Some(KclValue::HomArray { value, ty: ty.clone() })
|
||||
}
|
||||
KclValue::MixedArray { value, .. } => {
|
||||
let value = match len {
|
||||
ArrayLen::None => value.clone(),
|
||||
ArrayLen::NonEmpty => {
|
||||
if value.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
value.clone()
|
||||
}
|
||||
ArrayLen::Known(n) => {
|
||||
if n != value.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
value[..n].to_vec()
|
||||
}
|
||||
};
|
||||
|
||||
let rt = RuntimeType::Primitive(ty.clone());
|
||||
let value = value
|
||||
.iter()
|
||||
.map(|v| v.coerce(&rt, exec_state))
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
|
||||
Some(KclValue::HomArray { value, ty: ty.clone() })
|
||||
}
|
||||
KclValue::KclNone { .. } if len.satisfied(0) => Some(KclValue::HomArray {
|
||||
value: Vec::new(),
|
||||
ty: ty.clone(),
|
||||
}),
|
||||
value if len.satisfied(1) => {
|
||||
if value.has_type(&RuntimeType::Primitive(ty.clone())) {
|
||||
Some(KclValue::HomArray {
|
||||
value: vec![value.clone()],
|
||||
ty: ty.clone(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn coerce_to_tuple_type(&self, tys: &[PrimitiveType], exec_state: &mut ExecState) -> Option<KclValue> {
|
||||
match self {
|
||||
KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } => {
|
||||
if value.len() < tys.len() {
|
||||
return None;
|
||||
}
|
||||
let mut result = Vec::new();
|
||||
for (i, t) in tys.iter().enumerate() {
|
||||
result.push(value[i].coerce_to_primitive_type(t, exec_state)?);
|
||||
}
|
||||
|
||||
Some(KclValue::MixedArray {
|
||||
value: result,
|
||||
meta: Vec::new(),
|
||||
})
|
||||
}
|
||||
KclValue::KclNone { meta, .. } if tys.is_empty() => Some(KclValue::MixedArray {
|
||||
value: Vec::new(),
|
||||
meta: meta.clone(),
|
||||
}),
|
||||
value if tys.len() == 1 => {
|
||||
if value.has_type(&RuntimeType::Primitive(tys[0].clone())) {
|
||||
Some(KclValue::MixedArray {
|
||||
value: vec![value.clone()],
|
||||
meta: Vec::new(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn coerce_to_union_type(&self, tys: &[RuntimeType], exec_state: &mut ExecState) -> Option<KclValue> {
|
||||
for t in tys {
|
||||
if let Some(v) = self.coerce(t, exec_state) {
|
||||
return Some(v);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn coerce_to_object_type(&self, tys: &[(String, RuntimeType)], _exec_state: &mut ExecState) -> Option<KclValue> {
|
||||
match self {
|
||||
KclValue::Object { value, .. } => {
|
||||
for (s, t) in tys {
|
||||
// TODO coerce fields
|
||||
if !value.get(s)?.has_type(t) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
// TODO remove non-required fields
|
||||
Some(self.clone())
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn principal_type(&self) -> Option<RuntimeType> {
|
||||
match self {
|
||||
KclValue::Bool { .. } => Some(RuntimeType::Primitive(PrimitiveType::Boolean)),
|
||||
@ -608,26 +785,24 @@ impl KclValue {
|
||||
}
|
||||
KclValue::Plane { .. } => Some(RuntimeType::Primitive(PrimitiveType::Plane)),
|
||||
KclValue::Sketch { .. } => Some(RuntimeType::Primitive(PrimitiveType::Sketch)),
|
||||
KclValue::Sketches { .. } => Some(RuntimeType::Array(PrimitiveType::Sketch)),
|
||||
KclValue::Solid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Solid)),
|
||||
KclValue::Solids { .. } => Some(RuntimeType::Array(PrimitiveType::Solid)),
|
||||
KclValue::ImportedGeometry(..) => Some(RuntimeType::Primitive(PrimitiveType::ImportedGeometry)),
|
||||
KclValue::MixedArray { value, .. } => Some(RuntimeType::Tuple(
|
||||
value
|
||||
.iter()
|
||||
.map(|v| v.principal_type().and_then(RuntimeType::primitive))
|
||||
.collect::<Option<Vec<_>>>()?,
|
||||
)),
|
||||
KclValue::HomArray { ty, value, .. } => Some(RuntimeType::Array(ty.clone(), ArrayLen::Known(value.len()))),
|
||||
KclValue::Face { .. } => None,
|
||||
KclValue::Helix { .. }
|
||||
| KclValue::ImportedGeometry(..)
|
||||
| KclValue::Function { .. }
|
||||
| KclValue::Module { .. }
|
||||
| KclValue::TagIdentifier(_)
|
||||
| KclValue::TagDeclarator(_)
|
||||
| KclValue::KclNone { .. }
|
||||
| KclValue::Type { .. }
|
||||
| KclValue::Uuid { .. }
|
||||
| KclValue::Tombstone { .. } => None,
|
||||
| KclValue::Uuid { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -729,20 +904,18 @@ impl KclValue {
|
||||
KclValue::TagIdentifier(tag) => Some(format!("${}", tag.value)),
|
||||
// TODO better Array and Object stringification
|
||||
KclValue::MixedArray { .. } => Some("[...]".to_owned()),
|
||||
KclValue::HomArray { .. } => Some("[...]".to_owned()),
|
||||
KclValue::Object { .. } => Some("{ ... }".to_owned()),
|
||||
KclValue::Module { .. }
|
||||
| KclValue::Solid { .. }
|
||||
| KclValue::Solids { .. }
|
||||
| KclValue::Sketch { .. }
|
||||
| KclValue::Sketches { .. }
|
||||
| KclValue::Helix { .. }
|
||||
| KclValue::ImportedGeometry(_)
|
||||
| KclValue::Function { .. }
|
||||
| KclValue::Plane { .. }
|
||||
| KclValue::Face { .. }
|
||||
| KclValue::KclNone { .. }
|
||||
| KclValue::Type { .. }
|
||||
| KclValue::Tombstone { .. } => None,
|
||||
| KclValue::Type { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -750,7 +923,8 @@ impl KclValue {
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum RuntimeType {
|
||||
Primitive(PrimitiveType),
|
||||
Array(PrimitiveType),
|
||||
Array(PrimitiveType, ArrayLen),
|
||||
Union(Vec<RuntimeType>),
|
||||
Tuple(Vec<PrimitiveType>),
|
||||
Object(Vec<(String, RuntimeType)>),
|
||||
}
|
||||
@ -765,7 +939,9 @@ impl RuntimeType {
|
||||
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::Array(pt) => {
|
||||
PrimitiveType::from_parsed(pt, exec_state, source_range)?.map(|t| RuntimeType::Array(t, ArrayLen::None))
|
||||
}
|
||||
Type::Object { properties } => properties
|
||||
.into_iter()
|
||||
.map(|p| {
|
||||
@ -781,15 +957,37 @@ impl RuntimeType {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn human_friendly_type(&self) -> String {
|
||||
match self {
|
||||
RuntimeType::Primitive(ty) => ty.to_string(),
|
||||
RuntimeType::Array(ty, ArrayLen::None) => format!("an array of {}", ty.display_multiple()),
|
||||
RuntimeType::Array(ty, ArrayLen::NonEmpty) => format!("one or more {}", ty.display_multiple()),
|
||||
RuntimeType::Array(ty, ArrayLen::Known(n)) => format!("an array of {n} {}", ty.display_multiple()),
|
||||
RuntimeType::Union(tys) => tys
|
||||
.iter()
|
||||
.map(Self::human_friendly_type)
|
||||
.collect::<Vec<_>>()
|
||||
.join(" or "),
|
||||
RuntimeType::Tuple(tys) => format!(
|
||||
"an array with values of types ({})",
|
||||
tys.iter().map(PrimitiveType::to_string).collect::<Vec<_>>().join(", ")
|
||||
),
|
||||
RuntimeType::Object(_) => format!("an object with fields {}", self),
|
||||
}
|
||||
}
|
||||
|
||||
// Subtype with no coercion, including refining numeric types.
|
||||
fn subtype(&self, sup: &RuntimeType) -> bool {
|
||||
use RuntimeType::*;
|
||||
|
||||
match (self, sup) {
|
||||
(Primitive(t1), Primitive(t2)) => t1 == t2,
|
||||
// TODO arrays could be covariant
|
||||
(Primitive(t1), Primitive(t2)) | (Array(t1), Array(t2)) => t1 == t2,
|
||||
(Array(t1, l1), Array(t2, l2)) => t1 == t2 && l1.subtype(*l2),
|
||||
(Tuple(t1), Tuple(t2)) => t1 == t2,
|
||||
(Tuple(t1), Array(t2)) => t1.iter().all(|t| t == t2),
|
||||
(Tuple(t1), Array(t2, l2)) => (l2.satisfied(t1.len())) && t1.iter().all(|t| t == t2),
|
||||
(Union(ts1), Union(ts2)) => ts1.iter().all(|t| ts2.contains(t)),
|
||||
(t1, Union(ts2)) => ts2.contains(t1),
|
||||
// TODO record subtyping - subtype can be larger, fields can be covariant.
|
||||
(Object(t1), Object(t2)) => t1 == t2,
|
||||
_ => false,
|
||||
@ -808,12 +1006,21 @@ impl fmt::Display for RuntimeType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
RuntimeType::Primitive(t) => t.fmt(f),
|
||||
RuntimeType::Array(t) => write!(f, "[{t}]"),
|
||||
RuntimeType::Array(t, l) => match l {
|
||||
ArrayLen::None => write!(f, "[{t}]"),
|
||||
ArrayLen::NonEmpty => write!(f, "[{t}; 1+]"),
|
||||
ArrayLen::Known(n) => write!(f, "[{t}; {n}]"),
|
||||
},
|
||||
RuntimeType::Tuple(ts) => write!(
|
||||
f,
|
||||
"[{}]",
|
||||
ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(", ")
|
||||
),
|
||||
RuntimeType::Union(ts) => write!(
|
||||
f,
|
||||
"{}",
|
||||
ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(" | ")
|
||||
),
|
||||
RuntimeType::Object(items) => write!(
|
||||
f,
|
||||
"{{ {} }}",
|
||||
@ -827,6 +1034,34 @@ impl fmt::Display for RuntimeType {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum ArrayLen {
|
||||
None,
|
||||
NonEmpty,
|
||||
Known(usize),
|
||||
}
|
||||
|
||||
impl ArrayLen {
|
||||
pub fn subtype(self, other: ArrayLen) -> bool {
|
||||
match (self, other) {
|
||||
(_, ArrayLen::None) => true,
|
||||
(ArrayLen::NonEmpty, ArrayLen::NonEmpty) => true,
|
||||
(ArrayLen::Known(size), ArrayLen::NonEmpty) if size > 0 => true,
|
||||
(ArrayLen::Known(s1), ArrayLen::Known(s2)) if s1 == s2 => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// True if the length constraint is satisfied by the supplied length.
|
||||
fn satisfied(self, len: usize) -> bool {
|
||||
match self {
|
||||
ArrayLen::None => true,
|
||||
ArrayLen::NonEmpty => len > 0,
|
||||
ArrayLen::Known(s) => len == s,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum PrimitiveType {
|
||||
Number(NumericType),
|
||||
@ -835,6 +1070,7 @@ pub enum PrimitiveType {
|
||||
Sketch,
|
||||
Solid,
|
||||
Plane,
|
||||
ImportedGeometry,
|
||||
}
|
||||
|
||||
impl PrimitiveType {
|
||||
@ -866,6 +1102,19 @@ impl PrimitiveType {
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
fn display_multiple(&self) -> String {
|
||||
match self {
|
||||
PrimitiveType::Number(NumericType::Known(unit)) => format!("numbers({unit})"),
|
||||
PrimitiveType::Number(_) => "numbers".to_owned(),
|
||||
PrimitiveType::String => "strings".to_owned(),
|
||||
PrimitiveType::Boolean => "bools".to_owned(),
|
||||
PrimitiveType::Sketch => "Sketches".to_owned(),
|
||||
PrimitiveType::Solid => "Solids".to_owned(),
|
||||
PrimitiveType::Plane => "Planes".to_owned(),
|
||||
PrimitiveType::ImportedGeometry => "imported geometries".to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PrimitiveType {
|
||||
@ -878,6 +1127,7 @@ impl fmt::Display for PrimitiveType {
|
||||
PrimitiveType::Sketch => write!(f, "Sketch"),
|
||||
PrimitiveType::Solid => write!(f, "Solid"),
|
||||
PrimitiveType::Plane => write!(f, "Plane"),
|
||||
PrimitiveType::ImportedGeometry => write!(f, "imported geometry"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,18 +6,18 @@
|
||||
//! one per execution. It has no explicit support for caching between executions.
|
||||
//!
|
||||
//! Memory is mostly immutable (since KCL does not support mutation or reassignment). However, tags
|
||||
//! may change as code is executed and that mutates memory. Therefore,
|
||||
//! may change as code is executed and that mutates memory. Therefore to some extent,
|
||||
//! ProgramMemory supports mutability and does not rely on KCL's (mostly) immutable nature.
|
||||
//!
|
||||
//! ProgramMemory is observably monotonic, i.e., it only grows and even when we pop a stack frame,
|
||||
//! the frame is retained unless we can prove it is unreferenced. We remove some values which we
|
||||
//! know cannot be referenced, but we should in the future do better garbage collection (of values
|
||||
//! and envs).
|
||||
//! and envs).
|
||||
//!
|
||||
//! ## Concepts
|
||||
//!
|
||||
//! There are three main moving parts for ProgramMemory: environments, snapshots, and stacks. I'll
|
||||
//! cover environments (and the call stack) first as if snapshots didn't exist, then describe snapshots.
|
||||
//! There are three main moving parts for ProgramMemory: environments, epochs, and stacks. I'll
|
||||
//! cover environments (and the call stack) first as if epochs didn't exist, then describe epochs.
|
||||
//!
|
||||
//! An environment is a set of bindings (i.e., a map from names to values). Environments handle
|
||||
//! both scoping and context switching. A new lexical scope means a new environment. Nesting of scopes
|
||||
@ -81,12 +81,25 @@
|
||||
//! temporally) the definition of `c`. (Note that although KCL does not permit mutation, objects
|
||||
//! can change due to the way tags are implemented).
|
||||
//!
|
||||
//! To make this work, when we save a reference to an enclosing scope we take a snapshot of memory at
|
||||
//! that point and save a reference to that snapshot. When we call a function, the parent of the new
|
||||
//! callee env is that snapshot, not the current version of the enclosing scope.
|
||||
//! To make this work, we have the concept of an epoch. An epoch is a simple, global, monotonic counter
|
||||
//! which is incremented at any significant moment in execution (we use the term snapshot). When a
|
||||
//! value is saved in memory we also save the epoch at which it was stored.
|
||||
//!
|
||||
//! Entering an inline scope (e.g., the body of an `if` statement) means pushing an env whose parent
|
||||
//! is the current env. We don't need to snapshot in this case.
|
||||
//! When we save a reference to an enclosing scope we take a snapshot and save that epoch as part of
|
||||
//! the reference. When we call a function, we use the epoch when it was defined to look up variables,
|
||||
//! ignoring any variables which have a creation time later than the saved epoch.
|
||||
//!
|
||||
//! Because the callee could create new variables (with a creation time of the current epoch) which
|
||||
//! the callee should be able to read, we can't simply check the epoch with the callees (and we'd need
|
||||
//! to maintain a stack of callee epochs for further calls, etc.). Instead a stack frame consists of
|
||||
//! a reference to an environment and an epoch at which reads should take place. When we call a function
|
||||
//! this creates a new env using the current epoch, and it's parent env (which is the enclosing scope
|
||||
//! of the function declaration) includes the epoch at which the function was declared.
|
||||
//!
|
||||
//! So far, this handles variables created after a function is declared, but does not handle mutation.
|
||||
//! Mutation must be handled internally in values, see for example `TagIdentifier`. It is suggested
|
||||
//! that objects rely on epochs for this. Since epochs are linked to the stack frame, only objects in
|
||||
//! the current stack frame should be mutated.
|
||||
//!
|
||||
//! ### Std
|
||||
//!
|
||||
@ -107,53 +120,17 @@
|
||||
//! Pushing and popping stack frames is straightforward. Most get/set/update operations don't touch
|
||||
//! the call stack other than the current env (updating tags on function return is the exception).
|
||||
//!
|
||||
//! Snapshots are maintained within an environment and are always specific to an environment. Snapshots
|
||||
//! must also have a parent reference (since they are logically a snapshot of all memory). This parent
|
||||
//! refers to a snapshot within the parent env. When a snapshot is created, we must create a snapshot
|
||||
//! object for each parent env. When using a snapshot we must check the parent snapshot whenever
|
||||
//! we check the parent env (and not the current version of the parent env).
|
||||
//!
|
||||
//! An environment will have many snapshots, they are kept in time order, and do not reference each
|
||||
//! other. (The parent of a snapshot is always in another env).
|
||||
//!
|
||||
//! A snapshot is created empty (we don't copy memory) and we use a copy-on-write design: when a
|
||||
//! value in an environment is modified, we copy the old version into the most recent snapshot (note
|
||||
//! that we never overwrite a value in the snapshot, if a value is modified multiple times, we want
|
||||
//! to keep the original version, not an intermediate one). Likewise, if we insert a new variable,
|
||||
//! we put a tombstone value in the snapshot.
|
||||
//!
|
||||
//! When we read from the current version of an environment, we simply read from the bindings in the
|
||||
//! env and ignore the snapshots. When we read from a snapshot, we first check the specific snapshot
|
||||
//! for the key, then check any newer snapshots, then finally check the env bindings.
|
||||
//!
|
||||
//! A minor optimisation is that when creating a snapshot, if the previous one is empty, then
|
||||
//! we can reuse that rather than creating a new one. Since we only create a snapshot when a function
|
||||
//! is declared and the function decl is immediately saved into the new snapshot, the empty snapshot
|
||||
//! optimisation only happens with parent snapshots (though if the env tree is deep this means we
|
||||
//! can save a lot of snapshots).
|
||||
//!
|
||||
//! ## Invariants
|
||||
//!
|
||||
//! There's obviously a bunch of invariants in this design, some are kinda obvious, some are limited
|
||||
//! in scope and are documented inline, here are some others:
|
||||
//!
|
||||
//! - The current env and all envs in the call stack are 'just envs', never a snapshot (we could
|
||||
//! use just a ref to an env, rather than to a snapshot but this is pretty inconvenient, so just
|
||||
//! know that the snapshot ref is always to the current version). Only the parent envs or saved refs
|
||||
//! can be refs to snapshots.
|
||||
//! - We only ever write into the current env, never into any parent envs (though we can read from
|
||||
//! both).
|
||||
//! - Therefore, there is no concept of writing into a snapshot, only reading from one.
|
||||
//! - The env ref saved with a function decl is always to a snapshot, never to the current version.
|
||||
//! - If there are no snapshots in an environment and it is no longer in the call stack, then there
|
||||
//! are no references from function decls to the env (if it is the parent of an env with extant refs
|
||||
//! then there would be snapshots in the child env and that implies there must be a snapshot in the
|
||||
//! parent to be the parent of that snapshot).
|
||||
//! - We only ever write (or mutate) at the most recent epoch, never at an older one.
|
||||
//! - The env ref saved with a function decl is always to an historic epoch, never to the current one.
|
||||
//! - Since KCL does not have submodules and decls are not visible outside of a nested scope, all
|
||||
//! references to variables in other modules must be in the root scope of a module.
|
||||
//! - Therefore, an active env must either be on the call stack, have snapshots, or be a root env. This
|
||||
//! is however a conservative approximation since snapshots may exist even if there are no live
|
||||
//! references to an env.
|
||||
//!
|
||||
//! ## Concurrency and thread-safety
|
||||
//!
|
||||
@ -227,7 +204,6 @@
|
||||
|
||||
use std::{
|
||||
cell::UnsafeCell,
|
||||
collections::HashMap,
|
||||
fmt,
|
||||
pin::Pin,
|
||||
sync::{
|
||||
@ -267,6 +243,7 @@ pub(crate) struct ProgramMemory {
|
||||
/// Statistics about the memory, should not be used for anything other than meta-info.
|
||||
pub(crate) stats: MemoryStats,
|
||||
next_stack_id: AtomicUsize,
|
||||
epoch: AtomicUsize,
|
||||
write_lock: AtomicBool,
|
||||
}
|
||||
|
||||
@ -307,7 +284,7 @@ impl fmt::Display for Stack {
|
||||
.call_stack
|
||||
.iter()
|
||||
.chain(Some(&self.current_env))
|
||||
.map(|e| format!("EnvRef({}, {})", e.0, e.1 .0))
|
||||
.map(|e| format!("EnvRef({}, {})", e.0, e.1))
|
||||
.collect();
|
||||
write!(f, "Stack {}\nstack frames:\n{}", self.id, stack.join("\n"))
|
||||
}
|
||||
@ -322,6 +299,7 @@ impl ProgramMemory {
|
||||
std: None,
|
||||
stats: MemoryStats::default(),
|
||||
next_stack_id: AtomicUsize::new(1),
|
||||
epoch: AtomicUsize::new(1),
|
||||
write_lock: AtomicBool::new(false),
|
||||
})
|
||||
}
|
||||
@ -340,10 +318,12 @@ impl ProgramMemory {
|
||||
std: self.std,
|
||||
stats: MemoryStats::default(),
|
||||
next_stack_id: AtomicUsize::new(self.next_stack_id.load(Ordering::Relaxed)),
|
||||
epoch: AtomicUsize::new(self.epoch.load(Ordering::Relaxed)),
|
||||
write_lock: AtomicBool::new(false),
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new stack object referencing this `ProgramMemory`.
|
||||
pub fn new_stack(self: Arc<Self>) -> Stack {
|
||||
let id = self.next_stack_id.fetch_add(1, Ordering::Relaxed);
|
||||
assert!(id > 0);
|
||||
@ -367,7 +347,7 @@ impl ProgramMemory {
|
||||
self.std.is_none()
|
||||
}
|
||||
|
||||
/// Get a value from a specific snapshot of the memory.
|
||||
/// Get a value from a specific environment of the memory at a specific point in time.
|
||||
pub fn get_from(
|
||||
&self,
|
||||
var: &str,
|
||||
@ -438,7 +418,7 @@ impl ProgramMemory {
|
||||
|
||||
let new_env = Environment::new(parent, is_root_env, owner);
|
||||
self.with_envs(|envs| {
|
||||
let result = EnvironmentRef(envs.len(), SnapshotRef::none());
|
||||
let result = EnvironmentRef(envs.len(), usize::MAX);
|
||||
// Note this might reallocate, which would hold the `with_envs` spin lock for way too long
|
||||
// so somehow we should make sure we don't do that (though honestly the chance of that
|
||||
// happening while another thread is waiting for the lock is pretty small).
|
||||
@ -490,23 +470,12 @@ impl ProgramMemory {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn update_with_env(&self, key: &str, value: KclValue, env: usize, owner: usize) {
|
||||
self.stats.mutation_count.fetch_add(1, Ordering::Relaxed);
|
||||
self.get_env(env).insert_or_update(key.to_owned(), value, owner);
|
||||
}
|
||||
|
||||
/// Get a value from memory without checking for ownership of the env.
|
||||
///
|
||||
/// This is not safe to use in general and should only be used if you have unique access to
|
||||
/// the `self` which is generally only true during testing.
|
||||
#[cfg(test)]
|
||||
pub fn get_from_unchecked(
|
||||
&self,
|
||||
var: &str,
|
||||
mut env_ref: EnvironmentRef,
|
||||
source_range: SourceRange,
|
||||
) -> Result<&KclValue, KclError> {
|
||||
pub fn get_from_unchecked(&self, var: &str, mut env_ref: EnvironmentRef) -> Result<&KclValue, KclError> {
|
||||
loop {
|
||||
let env = self.get_env(env_ref.index());
|
||||
env_ref = match env.get_unchecked(var, env_ref.1) {
|
||||
@ -518,7 +487,7 @@ impl ProgramMemory {
|
||||
|
||||
Err(KclError::UndefinedValue(KclErrorDetails {
|
||||
message: format!("memory item key `{}` is not defined", var),
|
||||
source_ranges: vec![source_range],
|
||||
source_ranges: vec![],
|
||||
}))
|
||||
}
|
||||
}
|
||||
@ -544,6 +513,11 @@ impl Stack {
|
||||
stack
|
||||
}
|
||||
|
||||
/// Get the current (globally most recent) epoch.
|
||||
pub fn current_epoch(&self) -> usize {
|
||||
self.memory.epoch.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Push a new (standard KCL) stack frame on to the call stack.
|
||||
///
|
||||
/// `parent` is the environment where the function being called is declared (not the caller's
|
||||
@ -577,7 +551,7 @@ impl Stack {
|
||||
// Rust functions shouldn't try to set or access anything in their environment, so don't
|
||||
// waste time and space on a new env. Using usize::MAX means we'll get an overflow if we
|
||||
// try to access anything rather than a silent error.
|
||||
self.current_env = EnvironmentRef(usize::MAX, SnapshotRef::none());
|
||||
self.current_env = EnvironmentRef(usize::MAX, 0);
|
||||
}
|
||||
|
||||
/// Push a new stack frame on to the call stack with no connection to a parent environment.
|
||||
@ -596,7 +570,6 @@ impl Stack {
|
||||
/// SAFETY: the env must not be being used by another `Stack` since we'll move the env from
|
||||
/// read-only to owned.
|
||||
pub fn restore_env(&mut self, env: EnvironmentRef) {
|
||||
assert!(env.1.is_none());
|
||||
self.call_stack.push(self.current_env);
|
||||
self.memory.get_env(env.index()).restore_owner(self.id);
|
||||
self.current_env = env;
|
||||
@ -642,25 +615,28 @@ impl Stack {
|
||||
}
|
||||
|
||||
let mut old_env = self.memory.take_env(old);
|
||||
if old_env.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Map of any old env refs to the current env.
|
||||
let snapshot_map: HashMap<_, _> = old_env
|
||||
.snapshot_parents()
|
||||
.map(|(s, p)| (EnvironmentRef(old.0, s), (EnvironmentRef(self.current_env.0, p))))
|
||||
.collect();
|
||||
|
||||
// Make a new scope so we override variables properly.
|
||||
self.push_new_env_for_scope();
|
||||
// Move the variables in the popped env into the current env.
|
||||
let env = self.memory.get_env(self.current_env.index());
|
||||
for (k, v) in old_env.as_mut().take_bindings() {
|
||||
env.insert_or_update(k.clone(), v.map_env_ref(&snapshot_map), self.id);
|
||||
for (k, (e, v)) in old_env.as_mut().take_bindings() {
|
||||
env.insert(k, e, v.map_env_ref(old.0, self.current_env.0), self.id);
|
||||
}
|
||||
}
|
||||
|
||||
/// Snapshot the current state of the memory.
|
||||
pub fn snapshot(&mut self) -> EnvironmentRef {
|
||||
self.memory.stats.snapshot_count.fetch_add(1, Ordering::Relaxed);
|
||||
let snapshot = env::snapshot(&self.memory, self.current_env, self.id);
|
||||
EnvironmentRef(self.current_env.0, snapshot)
|
||||
self.memory.stats.epoch_count.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
let env = self.memory.get_env(self.current_env.index());
|
||||
env.mark_as_refed();
|
||||
|
||||
let prev_epoch = self.memory.epoch.fetch_add(1, Ordering::Relaxed);
|
||||
EnvironmentRef(self.current_env.0, prev_epoch)
|
||||
}
|
||||
|
||||
/// Add a value to the program memory (in the current scope). The value must not already exist.
|
||||
@ -675,16 +651,21 @@ impl Stack {
|
||||
|
||||
self.memory.stats.mutation_count.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
env.insert(key, value, self.id);
|
||||
env.insert(key, self.memory.epoch.load(Ordering::Relaxed), value, self.id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn insert_or_update(&mut self, key: String, value: KclValue) {
|
||||
/// Update a variable in memory. `key` must exist in memory. If it doesn't, this function will panic
|
||||
/// in debug builds and do nothing in release builds.
|
||||
pub fn update(&mut self, key: &str, f: impl Fn(&mut KclValue, usize)) {
|
||||
self.memory.stats.mutation_count.fetch_add(1, Ordering::Relaxed);
|
||||
self.memory
|
||||
.get_env(self.current_env.index())
|
||||
.insert_or_update(key, value, self.id);
|
||||
self.memory.get_env(self.current_env.index()).update(
|
||||
key,
|
||||
f,
|
||||
self.memory.epoch.load(Ordering::Relaxed),
|
||||
self.id,
|
||||
);
|
||||
}
|
||||
|
||||
/// Get a value from the program memory.
|
||||
@ -693,38 +674,41 @@ impl Stack {
|
||||
self.memory.get_from(var, self.current_env, source_range, self.id)
|
||||
}
|
||||
|
||||
/// Whether the current frame of the stack contains a variable with the given name.
|
||||
pub fn cur_frame_contains(&self, var: &str) -> bool {
|
||||
let env = self.memory.get_env(self.current_env.index());
|
||||
env.contains_key(var)
|
||||
}
|
||||
|
||||
/// Get a key from the first KCL (i.e., non-Rust) stack frame on the call stack.
|
||||
pub fn get_from_call_stack(&self, key: &str, source_range: SourceRange) -> Result<&KclValue, KclError> {
|
||||
pub fn get_from_call_stack(&self, key: &str, source_range: SourceRange) -> Result<(usize, &KclValue), KclError> {
|
||||
if !self.current_env.skip_env() {
|
||||
return self.get(key, source_range);
|
||||
return Ok((self.current_env.1, self.get(key, source_range)?));
|
||||
}
|
||||
|
||||
for env in self.call_stack.iter().rev() {
|
||||
if !env.skip_env() {
|
||||
return self.memory.get_from(key, *env, source_range, self.id);
|
||||
return Ok((env.1, self.memory.get_from(key, *env, source_range, self.id)?));
|
||||
}
|
||||
}
|
||||
|
||||
unreachable!("It can't be Rust frames all the way down");
|
||||
}
|
||||
|
||||
/// Iterate over all key/value pairs in the current environment which satisfy the provided
|
||||
/// predicate.
|
||||
pub fn find_all_in_current_env<'a>(
|
||||
/// Iterate over all keys in the current environment which satisfy the provided predicate.
|
||||
pub fn find_keys_in_current_env<'a>(
|
||||
&'a self,
|
||||
pred: impl Fn(&KclValue) -> bool + 'a,
|
||||
) -> impl Iterator<Item = (&'a String, &'a KclValue)> {
|
||||
self.memory.find_all_in_env(self.current_env, pred, self.id)
|
||||
) -> impl Iterator<Item = &'a String> {
|
||||
self.memory
|
||||
.find_all_in_env(self.current_env, pred, self.id)
|
||||
.map(|(k, _)| k)
|
||||
}
|
||||
|
||||
/// Iterate over all key/value pairs in the specified environment which satisfy the provided
|
||||
/// predicate. `env` must either be read-only or owned by `self`.
|
||||
pub fn find_all_in_env<'a>(
|
||||
&'a self,
|
||||
env: EnvironmentRef,
|
||||
pred: impl Fn(&KclValue) -> bool + 'a,
|
||||
) -> impl Iterator<Item = (&'a String, &'a KclValue)> {
|
||||
self.memory.find_all_in_env(env, pred, self.id)
|
||||
pub fn find_all_in_env(&self, env: EnvironmentRef) -> impl Iterator<Item = (&String, &KclValue)> {
|
||||
self.memory.find_all_in_env(env, |_| true, self.id)
|
||||
}
|
||||
|
||||
/// Walk all values accessible from any environment in the call stack.
|
||||
@ -781,7 +765,7 @@ impl<'a> Iterator for CallStackIterator<'a> {
|
||||
return next;
|
||||
}
|
||||
|
||||
if let Some(env_ref) = self.stack.memory.get_env(self.cur_env.index()).parent(self.cur_env.1) {
|
||||
if let Some(env_ref) = self.stack.memory.get_env(self.cur_env.index()).parent() {
|
||||
self.cur_env = env_ref;
|
||||
self.init_iter();
|
||||
} else {
|
||||
@ -816,23 +800,32 @@ impl<'a> Iterator for CallStackIterator<'a> {
|
||||
#[cfg(test)]
|
||||
impl PartialEq for Stack {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
let vars: Vec<_> = self.find_all_in_current_env(|_| true).collect();
|
||||
let vars_other: Vec<_> = other.find_all_in_current_env(|_| true).collect();
|
||||
vars == vars_other
|
||||
let vars: Vec<_> = self.find_keys_in_current_env(|_| true).collect();
|
||||
let vars_other: Vec<_> = other.find_keys_in_current_env(|_| true).collect();
|
||||
if vars != vars_other {
|
||||
return false;
|
||||
}
|
||||
|
||||
vars.iter()
|
||||
.all(|k| self.get(k, SourceRange::default()).unwrap() == other.get(k, SourceRange::default()).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
/// An index pointing to an environment at a point in time (either a snapshot or the current version, see the module docs).
|
||||
/// An index pointing to an environment at a point in time.
|
||||
///
|
||||
/// The first field indexes an environment, the second field is an epoch. An epoch of 0 is indicates
|
||||
/// a dummy, error, or placeholder env ref, an epoch of `usize::MAX` represents the current most
|
||||
/// recent epoch.
|
||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Hash, Eq, ts_rs::TS, JsonSchema)]
|
||||
pub struct EnvironmentRef(usize, SnapshotRef);
|
||||
pub struct EnvironmentRef(usize, usize);
|
||||
|
||||
impl EnvironmentRef {
|
||||
fn dummy() -> Self {
|
||||
Self(usize::MAX, SnapshotRef(usize::MAX))
|
||||
Self(usize::MAX, 0)
|
||||
}
|
||||
|
||||
fn is_regular(&self) -> bool {
|
||||
self.0 < usize::MAX && self.1 .0 < usize::MAX
|
||||
self.0 < usize::MAX && self.1 > 0
|
||||
}
|
||||
|
||||
fn index(&self) -> usize {
|
||||
@ -842,33 +835,11 @@ impl EnvironmentRef {
|
||||
fn skip_env(&self) -> bool {
|
||||
self.0 == usize::MAX
|
||||
}
|
||||
}
|
||||
|
||||
/// An index pointing to a snapshot within a specific (unspecified) environment.
|
||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Hash, Eq, ts_rs::TS, JsonSchema)]
|
||||
struct SnapshotRef(usize);
|
||||
|
||||
impl SnapshotRef {
|
||||
/// Represents no snapshot, use the current version of the environment.
|
||||
fn none() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
|
||||
/// `self` represents a snapshot.
|
||||
fn is_some(self) -> bool {
|
||||
self.0 > 0
|
||||
}
|
||||
|
||||
/// `self` represents the current version.
|
||||
fn is_none(self) -> bool {
|
||||
self.0 == 0
|
||||
}
|
||||
|
||||
// Precondition: self.is_some()
|
||||
fn index(&self) -> usize {
|
||||
// Note that `0` is a distinguished value meaning 'no snapshot', so the reference value
|
||||
// is one greater than the index into the list of snapshots.
|
||||
self.0 - 1
|
||||
pub fn replace_env(&mut self, old: usize, new: usize) {
|
||||
if self.0 == old {
|
||||
self.0 = new;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -877,8 +848,8 @@ impl SnapshotRef {
|
||||
pub(crate) struct MemoryStats {
|
||||
// Total number of environments created.
|
||||
env_count: AtomicUsize,
|
||||
// Total number of snapshots created.
|
||||
snapshot_count: AtomicUsize,
|
||||
// Total number of epochs.
|
||||
epoch_count: AtomicUsize,
|
||||
// Total number of values inserted or updated.
|
||||
mutation_count: AtomicUsize,
|
||||
// The number of envs we delete when popped from the call stack.
|
||||
@ -900,12 +871,10 @@ mod env {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct Environment {
|
||||
bindings: UnsafeCell<IndexMap<String, KclValue>>,
|
||||
// invariant: self.parent.is_none() => forall s in self.snapshots: s.parent_snapshot.is_none()
|
||||
snapshots: UnsafeCell<Vec<Snapshot>>,
|
||||
bindings: UnsafeCell<IndexMap<String, (usize, KclValue)>>,
|
||||
// An outer scope, if one exists.
|
||||
parent: Option<EnvironmentRef>,
|
||||
is_root_env: bool,
|
||||
might_be_refed: AtomicBool,
|
||||
// The id of the `Stack` if this `Environment` is on a call stack. If this is >0 then it may
|
||||
// only be read or written by that `Stack`; if 0 then the env is read-only.
|
||||
owner: AtomicUsize,
|
||||
@ -918,9 +887,8 @@ mod env {
|
||||
assert!(self.owner.load(Ordering::Acquire) == 0);
|
||||
Self {
|
||||
bindings: UnsafeCell::new(self.get_bindings().clone()),
|
||||
snapshots: UnsafeCell::new(self.iter_snapshots().cloned().collect()),
|
||||
parent: self.parent,
|
||||
is_root_env: self.is_root_env,
|
||||
might_be_refed: AtomicBool::new(self.might_be_refed.load(Ordering::Acquire)),
|
||||
owner: AtomicUsize::new(0),
|
||||
_unpin: PhantomPinned,
|
||||
}
|
||||
@ -931,45 +899,19 @@ mod env {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let parent = self
|
||||
.parent
|
||||
.map(|e| format!("EnvRef({}, {})", e.0, e.1 .0))
|
||||
.map(|e| format!("EnvRef({}, {})", e.0, e.1))
|
||||
.unwrap_or("_".to_owned());
|
||||
let data: Vec<String> = self
|
||||
.get_bindings()
|
||||
.iter()
|
||||
.map(|(k, v)| format!("{k}: {}", v.human_friendly_type()))
|
||||
.map(|(k, v)| format!("{k}: {}@{}", v.1.human_friendly_type(), v.0))
|
||||
.collect();
|
||||
let snapshots: Vec<String> = self.iter_snapshots().map(|s| s.to_string()).collect();
|
||||
write!(
|
||||
f,
|
||||
"Env {{\n parent: {parent},\n owner: {},\n is root: {},\n bindings:\n {},\n snapshots:\n {}\n}}",
|
||||
"Env {{\n parent: {parent},\n owner: {},\n ref'ed?: {},\n bindings:\n {}\n}}",
|
||||
self.owner.load(Ordering::Relaxed),
|
||||
self.is_root_env,
|
||||
self.might_be_refed.load(Ordering::Relaxed),
|
||||
data.join("\n "),
|
||||
snapshots.join("\n ")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
struct Snapshot {
|
||||
/// The version of the owning environment's parent environment corresponding to this snapshot.
|
||||
parent_snapshot: Option<SnapshotRef>,
|
||||
/// CoW'ed data from the environment.
|
||||
data: IndexMap<String, KclValue>,
|
||||
}
|
||||
|
||||
impl fmt::Display for Snapshot {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let parent = self.parent_snapshot.map(|s| s.0.to_string()).unwrap_or("_".to_owned());
|
||||
let data: Vec<String> = self
|
||||
.data
|
||||
.iter()
|
||||
.map(|(k, v)| format!("{k}: {}", v.human_friendly_type()))
|
||||
.collect();
|
||||
write!(
|
||||
f,
|
||||
"Snapshot {{\n parent: {parent},\n data: {},\n }}",
|
||||
data.join("\n ")
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -977,80 +919,47 @@ mod env {
|
||||
impl Environment {
|
||||
/// Create a new environment, parent points to it's surrounding lexical scope or the std
|
||||
/// env if it's a root scope.
|
||||
pub(super) fn new(parent: Option<EnvironmentRef>, is_root_env: bool, owner: usize) -> Self {
|
||||
pub(super) fn new(parent: Option<EnvironmentRef>, might_be_refed: bool, owner: usize) -> Self {
|
||||
assert!(parent.map(|p| p.is_regular()).unwrap_or(true));
|
||||
Self {
|
||||
bindings: UnsafeCell::new(IndexMap::new()),
|
||||
snapshots: UnsafeCell::new(Vec::new()),
|
||||
parent,
|
||||
is_root_env,
|
||||
might_be_refed: AtomicBool::new(might_be_refed),
|
||||
owner: AtomicUsize::new(owner),
|
||||
_unpin: PhantomPinned,
|
||||
}
|
||||
}
|
||||
|
||||
// Mark this env as read-only (see module docs).
|
||||
/// Mark this env as read-only (see module docs).
|
||||
pub(super) fn read_only(&self) {
|
||||
self.owner.store(0, Ordering::Release);
|
||||
}
|
||||
|
||||
// Mark this env as owned (see module docs).
|
||||
/// Mark this env as owned (see module docs).
|
||||
pub(super) fn restore_owner(&self, owner: usize) {
|
||||
self.owner.store(owner, Ordering::Release);
|
||||
}
|
||||
|
||||
// SAFETY: either the owner of the env is on the Rust stack or the env is read-only.
|
||||
fn snapshots_len(&self) -> usize {
|
||||
unsafe { self.snapshots.get().as_ref().unwrap().len() }
|
||||
/// Mark this environment as possibly having external references.
|
||||
pub(super) fn mark_as_refed(&self) {
|
||||
self.might_be_refed.store(true, Ordering::Release);
|
||||
}
|
||||
|
||||
// SAFETY: either the owner of the env is on the Rust stack or the env is read-only.
|
||||
fn get_shapshot(&self, index: usize) -> &Snapshot {
|
||||
unsafe { &self.snapshots.get().as_ref().unwrap()[index] }
|
||||
}
|
||||
|
||||
// SAFETY: either the owner of the env is on the Rust stack or the env is read-only.
|
||||
fn iter_snapshots(&self) -> impl Iterator<Item = &Snapshot> {
|
||||
unsafe { self.snapshots.get().as_ref().unwrap().iter() }
|
||||
}
|
||||
|
||||
fn cur_snapshot(&self, owner: usize) -> Option<&mut Snapshot> {
|
||||
assert!(owner > 0 && self.owner.load(Ordering::Acquire) == owner);
|
||||
unsafe { self.snapshots.get().as_mut().unwrap().last_mut() }
|
||||
}
|
||||
|
||||
// SAFETY: either the owner of the env is on the Rust stack or the env is read-only.
|
||||
fn get_bindings(&self) -> &IndexMap<String, KclValue> {
|
||||
fn get_bindings(&self) -> &IndexMap<String, (usize, KclValue)> {
|
||||
unsafe { self.bindings.get().as_ref().unwrap() }
|
||||
}
|
||||
|
||||
// SAFETY do not call this function while a previous mutable reference is live
|
||||
#[allow(clippy::mut_from_ref)]
|
||||
fn get_mut_bindings(&self, owner: usize) -> &mut IndexMap<String, KclValue> {
|
||||
fn get_mut_bindings(&self, owner: usize) -> &mut IndexMap<String, (usize, KclValue)> {
|
||||
assert!(owner > 0 && self.owner.load(Ordering::Acquire) == owner);
|
||||
unsafe { self.bindings.get().as_mut().unwrap() }
|
||||
}
|
||||
|
||||
// True if the env is empty and not a root env.
|
||||
// True if the env is empty and has no external references.
|
||||
pub(super) fn is_empty(&self) -> bool {
|
||||
self.snapshots_len() == 0 && self.get_bindings().is_empty() && !self.is_root_env
|
||||
}
|
||||
|
||||
fn push_snapshot(&self, parent: Option<SnapshotRef>, owner: usize) -> SnapshotRef {
|
||||
let env_owner = self.owner.load(Ordering::Acquire);
|
||||
// The env is read-only, no need to snapshot.
|
||||
if env_owner == 0 {
|
||||
return SnapshotRef::none();
|
||||
}
|
||||
assert!(
|
||||
owner > 0 && env_owner == owner,
|
||||
"mutating owner: {owner}, env: {self}({env_owner})"
|
||||
);
|
||||
unsafe {
|
||||
let snapshots = self.snapshots.get().as_mut().unwrap();
|
||||
snapshots.push(Snapshot::new(parent));
|
||||
SnapshotRef(snapshots.len())
|
||||
}
|
||||
self.get_bindings().is_empty() && !self.might_be_refed.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
/// Possibly compress this environment by deleting the memory.
|
||||
@ -1062,116 +971,61 @@ mod env {
|
||||
/// See module docs for more details.
|
||||
pub(super) fn compact(&self, owner: usize) {
|
||||
// Don't compress if there might be a closure or import referencing us.
|
||||
if self.snapshots_len() != 0 || self.is_root_env {
|
||||
if self.might_be_refed.load(Ordering::Acquire) {
|
||||
return;
|
||||
}
|
||||
|
||||
*self.get_mut_bindings(owner) = IndexMap::new();
|
||||
}
|
||||
|
||||
pub(super) fn get(
|
||||
&self,
|
||||
key: &str,
|
||||
snapshot: SnapshotRef,
|
||||
owner: usize,
|
||||
) -> Result<&KclValue, Option<EnvironmentRef>> {
|
||||
pub(super) fn get(&self, key: &str, epoch: usize, owner: usize) -> Result<&KclValue, Option<EnvironmentRef>> {
|
||||
let env_owner = self.owner.load(Ordering::Acquire);
|
||||
assert!(env_owner == 0 || env_owner == owner);
|
||||
|
||||
self.get_unchecked(key, snapshot)
|
||||
self.get_unchecked(key, epoch)
|
||||
}
|
||||
|
||||
/// Get a value from memory without checking the env's ownership invariant. Prefer to use `get`.
|
||||
pub(super) fn get_unchecked(
|
||||
&self,
|
||||
key: &str,
|
||||
snapshot: SnapshotRef,
|
||||
) -> Result<&KclValue, Option<EnvironmentRef>> {
|
||||
if snapshot.is_some() {
|
||||
for i in snapshot.index()..self.snapshots_len() {
|
||||
match self.get_shapshot(i).data.get(key) {
|
||||
Some(KclValue::Tombstone { .. }) => return Err(self.parent(snapshot)),
|
||||
Some(v) => return Ok(v),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn get_unchecked(&self, key: &str, epoch: usize) -> Result<&KclValue, Option<EnvironmentRef>> {
|
||||
self.get_bindings()
|
||||
.get(key)
|
||||
.and_then(|v| match v {
|
||||
KclValue::Tombstone { .. } => None,
|
||||
_ => Some(v),
|
||||
})
|
||||
.ok_or(self.parent(snapshot))
|
||||
.and_then(|(e, v)| if *e <= epoch { Some(v) } else { None })
|
||||
.ok_or(self.parent)
|
||||
}
|
||||
|
||||
/// Find the `EnvironmentRef` of the parent of this environment corresponding to the specified snapshot.
|
||||
pub(super) fn parent(&self, snapshot: SnapshotRef) -> Option<EnvironmentRef> {
|
||||
if snapshot.is_none() {
|
||||
return self.parent;
|
||||
}
|
||||
pub(super) fn update(&self, key: &str, f: impl Fn(&mut KclValue, usize), epoch: usize, owner: usize) {
|
||||
let Some((_, value)) = self.get_mut_bindings(owner).get_mut(key) else {
|
||||
debug_assert!(false, "Missing memory entry for {key}");
|
||||
return;
|
||||
};
|
||||
|
||||
match self.get_shapshot(snapshot.index()).parent_snapshot {
|
||||
Some(sr) => Some(EnvironmentRef(self.parent.unwrap().0, sr)),
|
||||
None => self.parent,
|
||||
}
|
||||
f(value, epoch);
|
||||
}
|
||||
|
||||
/// Iterate over all values in the environment at the specified snapshot.
|
||||
pub(super) fn values<'a>(&'a self, snapshot: SnapshotRef) -> Box<dyn Iterator<Item = &'a KclValue> + 'a> {
|
||||
if snapshot.is_none() {
|
||||
return Box::new(self.get_bindings().values());
|
||||
}
|
||||
pub(super) fn parent(&self) -> Option<EnvironmentRef> {
|
||||
self.parent
|
||||
}
|
||||
|
||||
/// Iterate over all values in the environment at the specified epoch.
|
||||
pub(super) fn values<'a>(&'a self, epoch: usize) -> Box<dyn Iterator<Item = &'a KclValue> + 'a> {
|
||||
Box::new(
|
||||
self.get_bindings()
|
||||
.iter()
|
||||
.filter_map(move |(k, v)| {
|
||||
(!self.snapshot_contains_key(k, snapshot) && !matches!(v, KclValue::Tombstone { .. }))
|
||||
.then_some(v)
|
||||
})
|
||||
.chain(
|
||||
self.iter_snapshots()
|
||||
.flat_map(|s| s.data.values().filter(|v| !matches!(v, KclValue::Tombstone { .. }))),
|
||||
),
|
||||
.values()
|
||||
.filter_map(move |(e, v)| (*e <= epoch).then_some(v)),
|
||||
)
|
||||
}
|
||||
|
||||
/// Pure insert, panics if `key` is already in this environment.
|
||||
///
|
||||
/// Precondition: !self.contains_key(key)
|
||||
pub(super) fn insert(&self, key: String, value: KclValue, owner: usize) {
|
||||
pub(super) fn insert(&self, key: String, epoch: usize, value: KclValue, owner: usize) {
|
||||
debug_assert!(!self.get_bindings().contains_key(&key));
|
||||
if let Some(s) = self.cur_snapshot(owner) {
|
||||
s.data.insert(key.clone(), tombstone());
|
||||
}
|
||||
self.get_mut_bindings(owner).insert(key, value);
|
||||
}
|
||||
|
||||
pub(super) fn insert_or_update(&self, key: String, value: KclValue, owner: usize) {
|
||||
if let Some(s) = self.cur_snapshot(owner) {
|
||||
if !s.data.contains_key(&key) {
|
||||
let old_value = self.get_bindings().get(&key).cloned().unwrap_or_else(tombstone);
|
||||
s.data.insert(key.clone(), old_value);
|
||||
}
|
||||
}
|
||||
self.get_mut_bindings(owner).insert(key, value);
|
||||
}
|
||||
|
||||
/// 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() {
|
||||
if self.get_shapshot(i).data.contains_key(key) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
self.get_mut_bindings(owner).insert(key, (epoch, value));
|
||||
}
|
||||
|
||||
/// Is the key currently contained in this environment.
|
||||
pub(super) fn contains_key(&self, key: &str) -> bool {
|
||||
!matches!(self.get_bindings().get(key), Some(KclValue::Tombstone { .. }) | None)
|
||||
self.get_bindings().contains_key(key)
|
||||
}
|
||||
|
||||
/// Iterate over all key/value pairs currently in this environment where the value satisfies
|
||||
@ -1186,61 +1040,14 @@ mod env {
|
||||
|
||||
self.get_bindings()
|
||||
.iter()
|
||||
.filter(move |(_, v)| f(v) && !matches!(v, KclValue::Tombstone { .. }))
|
||||
.filter_map(move |(k, (_, v))| f(v).then_some((k, v)))
|
||||
}
|
||||
|
||||
/// Take all bindings from the environment.
|
||||
pub(super) fn take_bindings(self: Pin<&mut Self>) -> impl Iterator<Item = (String, KclValue)> {
|
||||
pub(super) fn take_bindings(self: Pin<&mut Self>) -> impl Iterator<Item = (String, (usize, KclValue))> {
|
||||
// SAFETY: caller must have unique access since self is mut. We're not moving or invalidating `self`.
|
||||
let bindings = std::mem::take(unsafe { self.bindings.get().as_mut().unwrap() });
|
||||
bindings
|
||||
.into_iter()
|
||||
.filter(move |(_, v)| !matches!(v, KclValue::Tombstone { .. }))
|
||||
}
|
||||
|
||||
/// Returns an iterator over any snapshots in this environment, returning the ref to the
|
||||
/// snapshot and its parent.
|
||||
pub(super) fn snapshot_parents(&self) -> impl Iterator<Item = (SnapshotRef, SnapshotRef)> + '_ {
|
||||
self.iter_snapshots()
|
||||
.enumerate()
|
||||
.map(|(i, s)| (SnapshotRef(i + 1), s.parent_snapshot.unwrap()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Snapshot {
|
||||
fn new(parent_snapshot: Option<SnapshotRef>) -> Self {
|
||||
Snapshot {
|
||||
parent_snapshot,
|
||||
data: IndexMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a new snapshot of the specified environment at the current moment.
|
||||
///
|
||||
/// This is non-trival since we have to build the tree of parent snapshots.
|
||||
pub(super) fn snapshot(mem: &ProgramMemory, env_ref: EnvironmentRef, owner: usize) -> SnapshotRef {
|
||||
let env = mem.get_env(env_ref.index());
|
||||
let parent_snapshot = env.parent.map(|p| snapshot(mem, p, owner));
|
||||
|
||||
let env = mem.get_env(env_ref.index());
|
||||
if env.snapshots_len() == 0 {
|
||||
return env.push_snapshot(parent_snapshot, owner);
|
||||
}
|
||||
|
||||
let prev_snapshot = env.cur_snapshot(owner).unwrap();
|
||||
if prev_snapshot.data.is_empty() && prev_snapshot.parent_snapshot == parent_snapshot {
|
||||
// If the prev snapshot is empty, reuse it.
|
||||
return SnapshotRef(env.snapshots_len());
|
||||
}
|
||||
|
||||
env.push_snapshot(parent_snapshot, owner)
|
||||
}
|
||||
|
||||
fn tombstone() -> KclValue {
|
||||
KclValue::Tombstone {
|
||||
value: (),
|
||||
meta: Vec::new(),
|
||||
bindings.into_iter()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1270,16 +1077,9 @@ mod test {
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_small_number(value: &KclValue) -> Option<i64> {
|
||||
match value {
|
||||
KclValue::Number { value, .. } if value > &0.0 && value < &10.0 => Some(*value as i64),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_get_from(mem: &Stack, key: &str, n: i64, snapshot: EnvironmentRef) {
|
||||
match mem.memory.get_from_unchecked(key, snapshot, sr()).unwrap() {
|
||||
match mem.memory.get_from_unchecked(key, snapshot).unwrap() {
|
||||
KclValue::Number { value, .. } => assert_eq!(*value as i64, n),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
@ -1318,7 +1118,7 @@ mod test {
|
||||
assert_get(mem, "a", 1);
|
||||
mem.add("b".to_owned(), val(3), sr()).unwrap();
|
||||
assert_get(mem, "b", 3);
|
||||
mem.memory.get_from_unchecked("b", sn, sr()).unwrap_err();
|
||||
mem.memory.get_from_unchecked("b", sn).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1337,11 +1137,11 @@ mod test {
|
||||
assert_get(mem, "b", 3);
|
||||
assert_get(mem, "c", 6);
|
||||
assert_get_from(mem, "a", 1, sn1);
|
||||
mem.memory.get_from_unchecked("b", sn1, sr()).unwrap_err();
|
||||
mem.memory.get_from_unchecked("c", sn1, sr()).unwrap_err();
|
||||
mem.memory.get_from_unchecked("b", sn1).unwrap_err();
|
||||
mem.memory.get_from_unchecked("c", sn1).unwrap_err();
|
||||
assert_get_from(mem, "a", 1, sn2);
|
||||
assert_get_from(mem, "b", 3, sn2);
|
||||
mem.memory.get_from_unchecked("c", sn2, sr()).unwrap_err();
|
||||
mem.memory.get_from_unchecked("c", sn2).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1481,7 +1281,7 @@ mod test {
|
||||
|
||||
mem.pop_env();
|
||||
// old snapshot still untouched
|
||||
mem.memory.get_from_unchecked("b", sn, sr()).unwrap_err();
|
||||
mem.memory.get_from_unchecked("b", sn).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1503,62 +1303,22 @@ mod test {
|
||||
|
||||
mem.pop_env();
|
||||
// old snapshots still untouched
|
||||
mem.memory.get_from_unchecked("b", sn1, sr()).unwrap_err();
|
||||
mem.memory.get_from_unchecked("b", sn1).unwrap_err();
|
||||
assert_get_from(mem, "b", 3, sn2);
|
||||
mem.memory.get_from_unchecked("c", sn2, sr()).unwrap_err();
|
||||
mem.memory.get_from_unchecked("c", sn2).unwrap_err();
|
||||
assert_get_from(mem, "b", 4, sn3);
|
||||
mem.memory.get_from_unchecked("c", sn3, sr()).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snap_env_two_updates() {
|
||||
let mem = &mut Stack::new_for_tests();
|
||||
mem.add("a".to_owned(), val(1), sr()).unwrap();
|
||||
|
||||
let sn1 = mem.snapshot();
|
||||
mem.add("b".to_owned(), val(3), sr()).unwrap();
|
||||
let sn2 = mem.snapshot();
|
||||
|
||||
let callee_env = mem.current_env.0;
|
||||
mem.push_new_env_for_call(sn2);
|
||||
let sn3 = mem.snapshot();
|
||||
mem.add("b".to_owned(), val(4), sr()).unwrap();
|
||||
let sn4 = mem.snapshot();
|
||||
mem.insert_or_update("b".to_owned(), val(6));
|
||||
mem.memory.update_with_env("b", val(7), callee_env, mem.id);
|
||||
|
||||
assert_get(mem, "b", 6);
|
||||
assert_get_from(mem, "b", 3, sn3);
|
||||
assert_get_from(mem, "b", 4, sn4);
|
||||
|
||||
let vals: Vec<_> = mem.walk_call_stack().filter_map(expect_small_number).collect();
|
||||
let expected = [6, 1, 3, 1, 7];
|
||||
assert_eq!(vals, expected);
|
||||
|
||||
let popped = mem.pop_env();
|
||||
assert_get(mem, "b", 7);
|
||||
mem.memory.get_from_unchecked("b", sn1, sr()).unwrap_err();
|
||||
assert_get_from(mem, "b", 3, sn2);
|
||||
|
||||
let vals: Vec<_> = mem.walk_call_stack().filter_map(expect_small_number).collect();
|
||||
let expected = [1, 7];
|
||||
assert_eq!(vals, expected);
|
||||
|
||||
let popped_env = mem.memory.get_env(popped.index());
|
||||
let sp: Vec<_> = popped_env.snapshot_parents().collect();
|
||||
assert_eq!(
|
||||
sp,
|
||||
vec![(SnapshotRef(1), SnapshotRef(2)), (SnapshotRef(2), SnapshotRef(2))]
|
||||
);
|
||||
mem.memory.get_from_unchecked("c", sn3).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn squash_env() {
|
||||
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.push_new_env_for_call(sn1);
|
||||
mem.add("b".to_owned(), val(2), sr()).unwrap();
|
||||
|
||||
let sn2 = mem.snapshot();
|
||||
mem.add(
|
||||
"f".to_owned(),
|
||||
@ -1581,11 +1341,10 @@ mod test {
|
||||
KclValue::Function {
|
||||
value: FunctionSource::User { memory, .. },
|
||||
..
|
||||
} if memory == &sn1 => {}
|
||||
v => panic!("{v:#?}"),
|
||||
} if memory.0 == mem.current_env.0 => {}
|
||||
v => panic!("{v:#?}, expected {sn1:?}"),
|
||||
}
|
||||
assert_eq!(mem.memory.envs().len(), 1);
|
||||
assert_eq!(mem.current_env, EnvironmentRef(0, SnapshotRef(0)));
|
||||
assert_eq!(mem.memory.envs().len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -10,6 +10,7 @@ use cache::OldAstState;
|
||||
pub use cache::{bust_cache, clear_mem_cache};
|
||||
pub use cad_op::Operation;
|
||||
pub use geometry::*;
|
||||
pub use id_generator::IdGenerator;
|
||||
pub(crate) use import::{
|
||||
import_foreign, send_to_engine as send_import_to_engine, PreImportedGeometry, ZOO_COORD_SYSTEM,
|
||||
};
|
||||
@ -25,7 +26,7 @@ use kittycad_modeling_cmds as kcmc;
|
||||
pub use memory::EnvironmentRef;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
pub use state::{ExecState, IdGenerator, MetaSettings};
|
||||
pub use state::{ExecState, MetaSettings};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::{
|
||||
@ -50,6 +51,7 @@ pub(crate) mod cache;
|
||||
mod cad_op;
|
||||
mod exec_ast;
|
||||
mod geometry;
|
||||
mod id_generator;
|
||||
mod import;
|
||||
pub(crate) mod kcl_value;
|
||||
mod memory;
|
||||
@ -73,6 +75,8 @@ pub struct ExecOutcome {
|
||||
pub errors: Vec<CompilationError>,
|
||||
/// File Names in module Id array index order
|
||||
pub filenames: IndexMap<ModuleId, ModulePath>,
|
||||
/// The default planes.
|
||||
pub default_planes: Option<DefaultPlanes>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
@ -92,11 +96,46 @@ pub struct DefaultPlanes {
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
pub struct TagIdentifier {
|
||||
pub value: String,
|
||||
pub info: Option<TagEngineInfo>,
|
||||
#[serde(rename = "__meta")]
|
||||
// Multi-version representation of info about the tag. Kept ordered. The usize is the epoch at which the info
|
||||
// was written. Note that there might be multiple versions of tag info from the same epoch, the version with
|
||||
// the higher index will be the most recent.
|
||||
#[serde(skip)]
|
||||
pub info: Vec<(usize, TagEngineInfo)>,
|
||||
#[serde(skip)]
|
||||
pub meta: Vec<Metadata>,
|
||||
}
|
||||
|
||||
impl TagIdentifier {
|
||||
/// Get the tag info for this tag at a specified epoch.
|
||||
pub fn get_info(&self, at_epoch: usize) -> Option<&TagEngineInfo> {
|
||||
for (e, info) in self.info.iter().rev() {
|
||||
if *e <= at_epoch {
|
||||
return Some(info);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Get the most recent tag info for this tag.
|
||||
pub fn get_cur_info(&self) -> Option<&TagEngineInfo> {
|
||||
self.info.last().map(|i| &i.1)
|
||||
}
|
||||
|
||||
/// Add info from a different instance of this tag.
|
||||
pub fn merge_info(&mut self, other: &TagIdentifier) {
|
||||
assert_eq!(&self.value, &other.value);
|
||||
'new_info: for (oe, ot) in &other.info {
|
||||
for (e, _) in &self.info {
|
||||
if e > oe {
|
||||
continue 'new_info;
|
||||
}
|
||||
}
|
||||
self.info.push((*oe, ot.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for TagIdentifier {}
|
||||
|
||||
impl std::fmt::Display for TagIdentifier {
|
||||
@ -111,7 +150,7 @@ impl std::str::FromStr for TagIdentifier {
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self {
|
||||
value: s.to_string(),
|
||||
info: None,
|
||||
info: Vec::new(),
|
||||
meta: Default::default(),
|
||||
})
|
||||
}
|
||||
@ -500,7 +539,7 @@ impl ExecutorContext {
|
||||
source_range: crate::execution::SourceRange,
|
||||
) -> Result<(), KclError> {
|
||||
self.engine
|
||||
.clear_scene(&mut exec_state.global.id_generator, source_range)
|
||||
.clear_scene(&mut exec_state.mod_local.id_generator, source_range)
|
||||
.await
|
||||
}
|
||||
|
||||
@ -519,7 +558,7 @@ impl ExecutorContext {
|
||||
) -> Result<ExecOutcome, KclErrorWithOutputs> {
|
||||
assert!(self.is_mock());
|
||||
|
||||
let mut exec_state = ExecState::new(&self.settings);
|
||||
let mut exec_state = ExecState::new(self);
|
||||
if use_prev_memory {
|
||||
match cache::read_old_memory().await {
|
||||
Some(mem) => *exec_state.mut_stack() = mem,
|
||||
@ -540,7 +579,7 @@ impl ExecutorContext {
|
||||
// memory, not to the exec_state which is not cached for mock execution.
|
||||
|
||||
let mut mem = exec_state.stack().clone();
|
||||
let outcome = exec_state.to_mock_wasm_outcome(result.0);
|
||||
let outcome = exec_state.to_mock_wasm_outcome(result.0).await;
|
||||
|
||||
mem.squash_env(result.0);
|
||||
cache::write_old_memory(mem).await;
|
||||
@ -608,13 +647,13 @@ impl ExecutorContext {
|
||||
})
|
||||
.await;
|
||||
|
||||
let outcome = old_state.to_wasm_outcome(result_env);
|
||||
let outcome = old_state.to_wasm_outcome(result_env).await;
|
||||
return Ok(outcome);
|
||||
}
|
||||
(true, program)
|
||||
}
|
||||
CacheResult::NoAction(false) => {
|
||||
let outcome = old_state.to_wasm_outcome(result_env);
|
||||
let outcome = old_state.to_wasm_outcome(result_env).await;
|
||||
return Ok(outcome);
|
||||
}
|
||||
};
|
||||
@ -622,7 +661,7 @@ impl ExecutorContext {
|
||||
let (exec_state, preserve_mem) = if clear_scene {
|
||||
// Pop the execution state, since we are starting fresh.
|
||||
let mut exec_state = old_state;
|
||||
exec_state.reset(&self.settings);
|
||||
exec_state.reset(self);
|
||||
|
||||
// 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.
|
||||
@ -639,7 +678,7 @@ impl ExecutorContext {
|
||||
|
||||
(program, exec_state, preserve_mem)
|
||||
} else {
|
||||
let mut exec_state = ExecState::new(&self.settings);
|
||||
let mut exec_state = ExecState::new(self);
|
||||
self.send_clear_scene(&mut exec_state, Default::default())
|
||||
.await
|
||||
.map_err(KclErrorWithOutputs::no_outputs)?;
|
||||
@ -664,7 +703,7 @@ impl ExecutorContext {
|
||||
})
|
||||
.await;
|
||||
|
||||
let outcome = exec_state.to_wasm_outcome(result.0);
|
||||
let outcome = exec_state.to_wasm_outcome(result.0).await;
|
||||
Ok(outcome)
|
||||
}
|
||||
|
||||
@ -700,6 +739,7 @@ impl ExecutorContext {
|
||||
.await
|
||||
.map_err(KclErrorWithOutputs::no_outputs)?;
|
||||
|
||||
let default_planes = self.engine.get_default_planes().read().await.clone();
|
||||
let env_ref = self
|
||||
.execute_and_build_graph(&program.ast, exec_state, preserve_mem)
|
||||
.await
|
||||
@ -718,6 +758,7 @@ impl ExecutorContext {
|
||||
exec_state.global.artifact_graph.clone(),
|
||||
module_id_to_module_path,
|
||||
exec_state.global.id_to_source.clone(),
|
||||
default_planes,
|
||||
)
|
||||
})?;
|
||||
|
||||
@ -725,6 +766,7 @@ impl ExecutorContext {
|
||||
"Post interpretation KCL memory stats: {:#?}",
|
||||
exec_state.stack().memory.stats
|
||||
));
|
||||
crate::log::log(format!("Engine stats: {:?}", self.engine.stats()));
|
||||
|
||||
if !self.is_mock() {
|
||||
let mut mem = exec_state.stack().deep_clone();
|
||||
@ -755,6 +797,7 @@ impl ExecutorContext {
|
||||
exec_state,
|
||||
ExecutionKind::Normal,
|
||||
preserve_mem,
|
||||
ModuleId::default(),
|
||||
&ModulePath::Main,
|
||||
)
|
||||
.await;
|
||||
@ -890,38 +933,24 @@ impl ExecutorContext {
|
||||
&self,
|
||||
deterministic_time: bool,
|
||||
) -> Result<Vec<kittycad_modeling_cmds::websocket::RawFile>, KclError> {
|
||||
let mut files = self
|
||||
let files = self
|
||||
.export(kittycad_modeling_cmds::format::OutputFormat3d::Step(
|
||||
kittycad_modeling_cmds::format::step::export::Options {
|
||||
coords: *kittycad_modeling_cmds::coord::KITTYCAD,
|
||||
created: None,
|
||||
created: if deterministic_time {
|
||||
Some("2021-01-01T00:00:00Z".parse().map_err(|e| {
|
||||
KclError::Internal(crate::errors::KclErrorDetails {
|
||||
message: format!("Failed to parse date: {}", e),
|
||||
source_ranges: vec![SourceRange::default()],
|
||||
})
|
||||
})?)
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
|
||||
@ -948,7 +977,7 @@ pub(crate) async fn parse_execute(code: &str) -> Result<ExecTestResults, KclErro
|
||||
settings: Default::default(),
|
||||
context_type: ContextType::Mock,
|
||||
};
|
||||
let mut exec_state = ExecState::new(&exec_ctxt.settings);
|
||||
let mut exec_state = ExecState::new(&exec_ctxt);
|
||||
let result = exec_ctxt.run(&program, &mut exec_state).await?;
|
||||
|
||||
Ok(ExecTestResults {
|
||||
@ -978,11 +1007,7 @@ mod tests {
|
||||
/// Convenience function to get a JSON value from memory and unwrap.
|
||||
#[track_caller]
|
||||
fn mem_get_json(memory: &Stack, env: EnvironmentRef, name: &str) -> KclValue {
|
||||
memory
|
||||
.memory
|
||||
.get_from_unchecked(name, env, SourceRange::default())
|
||||
.unwrap()
|
||||
.to_owned()
|
||||
memory.memory.get_from_unchecked(name, env).unwrap().to_owned()
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1864,15 +1889,6 @@ let w = f() + f()
|
||||
parse_execute(ast).await.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serialize_memory_item() {
|
||||
let mem = KclValue::Solids {
|
||||
value: Default::default(),
|
||||
};
|
||||
let json = serde_json::to_string(&mem).unwrap();
|
||||
assert_eq!(json, r#"{"type":"Solids","value":[]}"#);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn kcl_test_ids_stable_between_executions() {
|
||||
let code = r#"sketch001 = startSketchOn(XZ)
|
||||
@ -1895,10 +1911,14 @@ let w = f() + f()
|
||||
let old_program = crate::Program::parse_no_errs(code).unwrap();
|
||||
|
||||
// Execute the program.
|
||||
ctx.run_with_caching(old_program).await.unwrap();
|
||||
if let Err(err) = ctx.run_with_caching(old_program).await {
|
||||
let report = err.into_miette_report_with_outputs(code).unwrap();
|
||||
let report = miette::Report::new(report);
|
||||
panic!("Error executing program: {:?}", report);
|
||||
}
|
||||
|
||||
// Get the id_generator from the first execution.
|
||||
let id_generator = cache::read_old_ast().await.unwrap().exec_state.global.id_generator;
|
||||
let id_generator = cache::read_old_ast().await.unwrap().exec_state.mod_local.id_generator;
|
||||
|
||||
let code = r#"sketch001 = startSketchOn(XZ)
|
||||
|> startProfileAt([62.74, 206.13], %)
|
||||
@ -1919,7 +1939,7 @@ let w = f() + f()
|
||||
// Execute the program.
|
||||
ctx.run_with_caching(program).await.unwrap();
|
||||
|
||||
let new_id_generator = cache::read_old_ast().await.unwrap().exec_state.global.id_generator;
|
||||
let new_id_generator = cache::read_old_ast().await.unwrap().exec_state.mod_local.id_generator;
|
||||
|
||||
assert_eq!(id_generator, new_id_generator);
|
||||
}
|
||||
@ -1948,7 +1968,6 @@ let w = f() + f()
|
||||
// Execute the program.
|
||||
ctx.run_with_caching(old_program.clone()).await.unwrap();
|
||||
|
||||
// Get the id_generator from the first execution.
|
||||
let settings_state = cache::read_old_ast().await.unwrap().settings;
|
||||
|
||||
// Ensure the settings are as expected.
|
||||
@ -1960,7 +1979,6 @@ let w = f() + f()
|
||||
// Execute the program.
|
||||
ctx.run_with_caching(old_program.clone()).await.unwrap();
|
||||
|
||||
// Get the id_generator from the first execution.
|
||||
let settings_state = cache::read_old_ast().await.unwrap().settings;
|
||||
|
||||
// Ensure the settings are as expected.
|
||||
@ -1972,7 +1990,6 @@ let w = f() + f()
|
||||
// Execute the program.
|
||||
ctx.run_with_caching(old_program).await.unwrap();
|
||||
|
||||
// Get the id_generator from the first execution.
|
||||
let settings_state = cache::read_old_ast().await.unwrap().settings;
|
||||
|
||||
// Ensure the settings are as expected.
|
||||
@ -1991,4 +2008,41 @@ let w = f() + f()
|
||||
let result = ctx2.run_mock(program2, true).await.unwrap();
|
||||
assert_eq!(result.variables.get("z").unwrap().as_f64().unwrap(), 3.0);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn read_tag_version() {
|
||||
let ast = r#"fn bar(t) {
|
||||
return startSketchOn(XY)
|
||||
|> startProfileAt([0,0], %)
|
||||
|> angledLine({
|
||||
angle = -60,
|
||||
length = segLen(t),
|
||||
}, %)
|
||||
|> line(end = [0, 0])
|
||||
|> close()
|
||||
}
|
||||
|
||||
sketch = startSketchOn(XY)
|
||||
|> startProfileAt([0,0], %)
|
||||
|> line(end = [0, 10])
|
||||
|> line(end = [10, 0], tag = $tag0)
|
||||
|> line(end = [0, 0])
|
||||
|
||||
fn foo() {
|
||||
// tag0 tags an edge
|
||||
return bar(tag0)
|
||||
}
|
||||
|
||||
solid = sketch |> extrude(length = 10)
|
||||
// tag0 tags a face
|
||||
sketch2 = startSketchOn(solid, tag0)
|
||||
|> startProfileAt([0,0], %)
|
||||
|> line(end = [0, 1])
|
||||
|> line(end = [1, 0])
|
||||
|> line(end = [0, 0])
|
||||
|
||||
foo() |> extrude(length = 1)
|
||||
"#;
|
||||
parse_execute(ast).await.unwrap();
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,9 @@ use uuid::Uuid;
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails, Severity},
|
||||
execution::{
|
||||
annotations, kcl_value,
|
||||
annotations,
|
||||
id_generator::IdGenerator,
|
||||
kcl_value,
|
||||
memory::{ProgramMemory, Stack},
|
||||
Artifact, ArtifactCommand, ArtifactGraph, ArtifactId, EnvironmentRef, ExecOutcome, ExecutorSettings, KclValue,
|
||||
Operation, UnitAngle, UnitLen,
|
||||
@ -26,12 +28,11 @@ use crate::{
|
||||
pub struct ExecState {
|
||||
pub(super) global: GlobalState,
|
||||
pub(super) mod_local: ModuleState,
|
||||
pub(super) exec_context: Option<super::ExecutorContext>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct GlobalState {
|
||||
/// The stable artifact ID generator.
|
||||
pub id_generator: IdGenerator,
|
||||
/// Map from source file absolute path to module ID.
|
||||
pub path_to_source_id: IndexMap<ModulePath, ModuleId>,
|
||||
/// Map from module ID to source file.
|
||||
@ -62,6 +63,8 @@ pub(super) struct GlobalState {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct ModuleState {
|
||||
/// The id generator for this module.
|
||||
pub id_generator: IdGenerator,
|
||||
pub stack: Stack,
|
||||
/// The current value of the pipe operator returned from the previous
|
||||
/// expression. If we're not currently in a pipeline, this will be None.
|
||||
@ -73,25 +76,21 @@ pub(super) struct ModuleState {
|
||||
}
|
||||
|
||||
impl ExecState {
|
||||
pub fn new(exec_settings: &ExecutorSettings) -> Self {
|
||||
pub fn new(exec_context: &super::ExecutorContext) -> Self {
|
||||
ExecState {
|
||||
global: GlobalState::new(exec_settings),
|
||||
mod_local: ModuleState::new(exec_settings, None, ProgramMemory::new()),
|
||||
global: GlobalState::new(&exec_context.settings),
|
||||
mod_local: ModuleState::new(&exec_context.settings, None, ProgramMemory::new(), Default::default()),
|
||||
exec_context: Some(exec_context.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn reset(&mut self, exec_settings: &ExecutorSettings) {
|
||||
let mut id_generator = self.global.id_generator.clone();
|
||||
// We do not pop the ids, since we want to keep the same id generator.
|
||||
// This is for the front end to keep track of the ids.
|
||||
id_generator.next_id = 0;
|
||||
|
||||
let mut global = GlobalState::new(exec_settings);
|
||||
global.id_generator = id_generator;
|
||||
pub(super) fn reset(&mut self, exec_context: &super::ExecutorContext) {
|
||||
let global = GlobalState::new(&exec_context.settings);
|
||||
|
||||
*self = ExecState {
|
||||
global,
|
||||
mod_local: ModuleState::new(exec_settings, None, ProgramMemory::new()),
|
||||
mod_local: ModuleState::new(&exec_context.settings, None, ProgramMemory::new(), Default::default()),
|
||||
exec_context: Some(exec_context.clone()),
|
||||
};
|
||||
}
|
||||
|
||||
@ -113,13 +112,13 @@ impl ExecState {
|
||||
/// Convert to execution outcome when running in WebAssembly. We want to
|
||||
/// reduce the amount of data that crosses the WASM boundary as much as
|
||||
/// possible.
|
||||
pub fn to_wasm_outcome(self, main_ref: EnvironmentRef) -> ExecOutcome {
|
||||
pub async fn to_wasm_outcome(self, main_ref: EnvironmentRef) -> ExecOutcome {
|
||||
// Fields are opt-in so that we don't accidentally leak private internal
|
||||
// state when we add more to ExecState.
|
||||
ExecOutcome {
|
||||
variables: self
|
||||
.stack()
|
||||
.find_all_in_env(main_ref, |_| true)
|
||||
.find_all_in_env(main_ref)
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect(),
|
||||
operations: self.global.operations,
|
||||
@ -132,16 +131,21 @@ impl ExecState {
|
||||
.iter()
|
||||
.map(|(k, v)| ((*v), k.clone()))
|
||||
.collect(),
|
||||
default_planes: if let Some(ctx) = &self.exec_context {
|
||||
ctx.engine.get_default_planes().read().await.clone()
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_mock_wasm_outcome(self, main_ref: EnvironmentRef) -> ExecOutcome {
|
||||
pub async fn to_mock_wasm_outcome(self, main_ref: EnvironmentRef) -> ExecOutcome {
|
||||
// Fields are opt-in so that we don't accidentally leak private internal
|
||||
// state when we add more to ExecState.
|
||||
ExecOutcome {
|
||||
variables: self
|
||||
.stack()
|
||||
.find_all_in_env(main_ref, |_| true)
|
||||
.find_all_in_env(main_ref)
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect(),
|
||||
operations: Default::default(),
|
||||
@ -149,6 +153,11 @@ impl ExecState {
|
||||
artifact_graph: Default::default(),
|
||||
errors: self.global.errors,
|
||||
filenames: Default::default(),
|
||||
default_planes: if let Some(ctx) = &self.exec_context {
|
||||
ctx.engine.get_default_planes().read().await.clone()
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -160,8 +169,12 @@ impl ExecState {
|
||||
&mut self.mod_local.stack
|
||||
}
|
||||
|
||||
pub(crate) fn next_uuid(&mut self) -> Uuid {
|
||||
self.global.id_generator.next_uuid()
|
||||
pub fn next_uuid(&mut self) -> Uuid {
|
||||
self.mod_local.id_generator.next_uuid()
|
||||
}
|
||||
|
||||
pub fn id_generator(&mut self) -> &mut IdGenerator {
|
||||
&mut self.mod_local.id_generator
|
||||
}
|
||||
|
||||
pub(crate) fn add_artifact(&mut self, artifact: Artifact) {
|
||||
@ -245,7 +258,6 @@ impl ExecState {
|
||||
impl GlobalState {
|
||||
fn new(settings: &ExecutorSettings) -> Self {
|
||||
let mut global = GlobalState {
|
||||
id_generator: Default::default(),
|
||||
path_to_source_id: Default::default(),
|
||||
module_infos: Default::default(),
|
||||
artifacts: Default::default(),
|
||||
@ -278,8 +290,14 @@ impl GlobalState {
|
||||
}
|
||||
|
||||
impl ModuleState {
|
||||
pub(super) fn new(exec_settings: &ExecutorSettings, std_path: Option<String>, memory: Arc<ProgramMemory>) -> Self {
|
||||
pub(super) fn new(
|
||||
exec_settings: &ExecutorSettings,
|
||||
std_path: Option<String>,
|
||||
memory: Arc<ProgramMemory>,
|
||||
module_id: Option<ModuleId>,
|
||||
) -> Self {
|
||||
ModuleState {
|
||||
id_generator: IdGenerator::new(module_id),
|
||||
stack: memory.new_stack(),
|
||||
pipe_value: Default::default(),
|
||||
module_exports: Default::default(),
|
||||
@ -336,29 +354,3 @@ impl MetaSettings {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A generator for ArtifactIds that can be stable across executions.
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct IdGenerator {
|
||||
pub(super) next_id: usize,
|
||||
ids: Vec<uuid::Uuid>,
|
||||
}
|
||||
|
||||
impl IdGenerator {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn next_uuid(&mut self) -> uuid::Uuid {
|
||||
if let Some(id) = self.ids.get(self.next_id) {
|
||||
self.next_id += 1;
|
||||
*id
|
||||
} else {
|
||||
let id = uuid::Uuid::new_v4();
|
||||
self.ids.push(id);
|
||||
self.next_id += 1;
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user