Support types in the standard library (#5651)
* Parse an unparse type decls (and refactor impl attributes slightly) Signed-off-by: Nick Cameron <nrc@ncameron.org> * Remove special treatment of geometric types from parser and executor Signed-off-by: Nick Cameron <nrc@ncameron.org> * Generate docs for std types Signed-off-by: Nick Cameron <nrc@ncameron.org> * Hover tool-tips for types and fixup the frontend Signed-off-by: Nick Cameron <nrc@ncameron.org> * Fixes Signed-off-by: Nick Cameron <nrc@ncameron.org> --------- Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
@ -1,5 +1,7 @@
|
||||
//! Data on available annotations.
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use kittycad_modeling_cmds::coord::{System, KITTYCAD, OPENGL, VULKAN};
|
||||
|
||||
use crate::{
|
||||
@ -24,6 +26,33 @@ pub(super) const IMPORT_COORDS_VALUES: [(&str, &System); 3] =
|
||||
[("zoo", KITTYCAD), ("opengl", OPENGL), ("vulkan", VULKAN)];
|
||||
pub(super) const IMPORT_LENGTH_UNIT: &str = "lengthUnit";
|
||||
|
||||
pub(crate) const IMPL: &str = "impl";
|
||||
pub(crate) const IMPL_RUST: &str = "std_rust";
|
||||
pub(crate) const IMPL_KCL: &str = "kcl";
|
||||
pub(crate) const IMPL_PRIMITIVE: &str = "primitive";
|
||||
pub(super) const IMPL_VALUES: [&str; 3] = [IMPL_RUST, IMPL_KCL, IMPL_PRIMITIVE];
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug, Default)]
|
||||
pub enum Impl {
|
||||
#[default]
|
||||
Kcl,
|
||||
Rust,
|
||||
Primitive,
|
||||
}
|
||||
|
||||
impl FromStr for Impl {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
IMPL_RUST => Ok(Self::Rust),
|
||||
IMPL_KCL => Ok(Self::Kcl),
|
||||
IMPL_PRIMITIVE => Ok(Self::Primitive),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn settings_completion_text() -> String {
|
||||
format!("@{SETTINGS}({SETTINGS_UNIT_LENGTH} = mm, {SETTINGS_UNIT_ANGLE} = deg)")
|
||||
}
|
||||
@ -58,6 +87,32 @@ pub(super) fn expect_ident(expr: &Expr) -> Result<&str, KclError> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn get_impl(annotations: &[Node<Annotation>], source_range: SourceRange) -> Result<Option<Impl>, KclError> {
|
||||
for attr in annotations {
|
||||
if attr.name.is_some() || attr.properties.is_none() {
|
||||
continue;
|
||||
}
|
||||
for p in attr.properties.as_ref().unwrap() {
|
||||
if &*p.key.name == IMPL {
|
||||
if let Some(s) = p.value.ident_name() {
|
||||
return Impl::from_str(s).map(Some).map_err(|_| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"Invalid value for {} attribute, expected one of: {}",
|
||||
IMPL,
|
||||
IMPL_VALUES.join(", ")
|
||||
),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
impl UnitLen {
|
||||
pub(super) fn from_str(s: &str, source_range: SourceRange) -> Result<Self, KclError> {
|
||||
match s {
|
||||
|
@ -197,6 +197,7 @@ pub enum OpKclValue {
|
||||
},
|
||||
Function {},
|
||||
Module {},
|
||||
Type {},
|
||||
KclNone {},
|
||||
}
|
||||
|
||||
@ -293,6 +294,7 @@ impl From<&KclValue> for OpKclValue {
|
||||
KclValue::Function { .. } => Self::Function {},
|
||||
KclValue::Module { .. } => Self::Module {},
|
||||
KclValue::KclNone { .. } => Self::KclNone {},
|
||||
KclValue::Type { .. } => Self::Type {},
|
||||
KclValue::Tombstone { .. } => unreachable!("Tombstone OpKclValue"),
|
||||
}
|
||||
}
|
||||
|
@ -285,6 +285,50 @@ impl ExecutorContext {
|
||||
}
|
||||
last_expr = None;
|
||||
}
|
||||
BodyItem::TypeDeclaration(ty) => {
|
||||
let metadata = Metadata::from(&**ty);
|
||||
let impl_kind = annotations::get_impl(&ty.outer_attrs, metadata.source_range)?.unwrap_or_default();
|
||||
match impl_kind {
|
||||
annotations::Impl::Rust => {
|
||||
let std_path = match &exec_state.mod_local.settings.std_path {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: "User-defined types are not yet supported.".to_owned(),
|
||||
source_ranges: vec![metadata.source_range],
|
||||
}));
|
||||
}
|
||||
};
|
||||
let value = KclValue::Type {
|
||||
value: Some(crate::std::std_ty(std_path, &ty.name.name)),
|
||||
meta: vec![metadata],
|
||||
};
|
||||
exec_state
|
||||
.mut_stack()
|
||||
.add(
|
||||
format!("{}{}", memory::TYPE_PREFIX, ty.name.name),
|
||||
value,
|
||||
metadata.source_range,
|
||||
)
|
||||
.map_err(|_| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: format!("Redefinition of type {}.", ty.name.name),
|
||||
source_ranges: vec![metadata.source_range],
|
||||
})
|
||||
})?;
|
||||
}
|
||||
// Do nothing for primitive types, they get special treatment and their declarations are just for documentation.
|
||||
annotations::Impl::Primitive => {}
|
||||
annotations::Impl::Kcl => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: "User-defined types are not yet supported.".to_owned(),
|
||||
source_ranges: vec![metadata.source_range],
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
last_expr = None;
|
||||
}
|
||||
BodyItem::ReturnStatement(return_statement) => {
|
||||
let metadata = Metadata::from(return_statement);
|
||||
|
||||
@ -519,21 +563,9 @@ impl ExecutorContext {
|
||||
}
|
||||
Expr::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, self).await?,
|
||||
Expr::FunctionExpression(function_expression) => {
|
||||
let mut rust_impl = false;
|
||||
for attr in annotations {
|
||||
if attr.name.is_some() || attr.properties.is_none() {
|
||||
continue;
|
||||
}
|
||||
for p in attr.properties.as_ref().unwrap() {
|
||||
if &*p.key.name == "impl" {
|
||||
if let Some(s) = p.value.ident_name() {
|
||||
if s == "std_rust" {
|
||||
rust_impl = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let rust_impl = annotations::get_impl(annotations, metadata.source_range)?
|
||||
.map(|s| s == annotations::Impl::Rust)
|
||||
.unwrap_or(false);
|
||||
|
||||
if rust_impl {
|
||||
if let Some(std_path) = &exec_state.mod_local.settings.std_path {
|
||||
@ -623,7 +655,12 @@ impl ExecutorContext {
|
||||
}
|
||||
|
||||
fn coerce(value: KclValue, ty: &Node<Type>, exec_state: &mut ExecState) -> Result<KclValue, KclValue> {
|
||||
let ty = RuntimeType::from_parsed(ty.inner.clone(), &exec_state.mod_local.settings).ok_or_else(|| value.clone())?;
|
||||
let ty = RuntimeType::from_parsed(ty.inner.clone(), exec_state, (&value).into())
|
||||
.map_err(|e| {
|
||||
exec_state.err(e);
|
||||
value.clone()
|
||||
})?
|
||||
.ok_or_else(|| value.clone())?;
|
||||
if value.has_type(&ty) {
|
||||
return Ok(value);
|
||||
}
|
||||
|
@ -243,7 +243,6 @@ pub struct Helix {
|
||||
pub meta: Vec<Metadata>,
|
||||
}
|
||||
|
||||
/// A plane.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@ -480,47 +479,6 @@ pub enum PlaneType {
|
||||
Uninit,
|
||||
}
|
||||
|
||||
/// A sketch is a collection of paths.
|
||||
///
|
||||
/// When you define a sketch to a variable like:
|
||||
///
|
||||
/// ```kcl
|
||||
/// mySketch = startSketchOn('XY')
|
||||
/// |> startProfileAt([-12, 12], %)
|
||||
/// |> line(end = [24, 0])
|
||||
/// |> line(end = [0, -24])
|
||||
/// |> line(end = [-24, 0])
|
||||
/// |> close()
|
||||
/// ```
|
||||
///
|
||||
/// The `mySketch` variable will be an executed `Sketch` object. Executed being past
|
||||
/// tense, because the engine has already executed the commands to create the sketch.
|
||||
///
|
||||
/// The previous sketch commands will never be executed again, in this case.
|
||||
///
|
||||
/// If you would like to encapsulate the commands to create the sketch any time you call it,
|
||||
/// you can use a function.
|
||||
///
|
||||
/// ```kcl
|
||||
/// fn createSketch() {
|
||||
/// return startSketchOn('XY')
|
||||
/// |> startProfileAt([-12, 12], %)
|
||||
/// |> line(end = [24, 0])
|
||||
/// |> line(end = [0, -24])
|
||||
/// |> line(end = [-24, 0])
|
||||
/// |> close()
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Now, every time you call `createSketch()`, the commands will be
|
||||
/// executed and a new sketch will be created.
|
||||
///
|
||||
/// When you assign the result of `createSketch()` to a variable (`mySketch = createSketch()`), you are assigning
|
||||
/// the executed sketch to that variable. Meaning that the sketch `mySketch` will not be executed
|
||||
/// again.
|
||||
///
|
||||
/// You can still execute _new_ commands on the sketch like `extrude`, `revolve`, `loft`, etc. and
|
||||
/// the sketch will be updated.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
@ -651,49 +609,6 @@ impl Sketch {
|
||||
}
|
||||
}
|
||||
|
||||
/// A solid is a collection of extrude surfaces.
|
||||
///
|
||||
/// When you define a solid to a variable like:
|
||||
///
|
||||
/// ```kcl
|
||||
/// myPart = startSketchOn('XY')
|
||||
/// |> startProfileAt([-12, 12], %)
|
||||
/// |> line(end = [24, 0])
|
||||
/// |> line(end = [0, -24])
|
||||
/// |> line(end = [-24, 0])
|
||||
/// |> close()
|
||||
/// |> extrude(length = 6)
|
||||
/// ```
|
||||
///
|
||||
/// The `myPart` variable will be an executed `Solid` object. Executed being past
|
||||
/// tense, because the engine has already executed the commands to create the solid.
|
||||
///
|
||||
/// The previous solid commands will never be executed again, in this case.
|
||||
///
|
||||
/// If you would like to encapsulate the commands to create the solid any time you call it,
|
||||
/// you can use a function.
|
||||
///
|
||||
/// ```kcl
|
||||
/// fn createPart() {
|
||||
/// return startSketchOn('XY')
|
||||
/// |> startProfileAt([-12, 12], %)
|
||||
/// |> line(end = [24, 0])
|
||||
/// |> line(end = [0, -24])
|
||||
/// |> line(end = [-24, 0])
|
||||
/// |> close()
|
||||
/// |> extrude(length = 6)
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Now, every time you call `createPart()`, the commands will be
|
||||
/// executed and a new solid will be created.
|
||||
///
|
||||
/// When you assign the result of `createPart()` to a variable (`myPart = createPart()`), you are assigning
|
||||
/// the executed solid to that variable. Meaning that the solid `myPart` will not be executed
|
||||
/// again.
|
||||
///
|
||||
/// You can still execute _new_ commands on the solid like `shell`, `fillet`, `chamfer`, etc.
|
||||
/// and the solid will be updated.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
|
@ -4,7 +4,10 @@ use anyhow::Result;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{memory::EnvironmentRef, MetaSettings};
|
||||
use super::{
|
||||
memory::{self, EnvironmentRef},
|
||||
MetaSettings,
|
||||
};
|
||||
use crate::{
|
||||
errors::KclErrorDetails,
|
||||
execution::{
|
||||
@ -96,6 +99,13 @@ pub enum KclValue {
|
||||
#[serde(rename = "__meta")]
|
||||
meta: Vec<Metadata>,
|
||||
},
|
||||
#[ts(skip)]
|
||||
Type {
|
||||
#[serde(skip)]
|
||||
value: Option<(PrimitiveType, StdFnProps)>,
|
||||
#[serde(rename = "__meta")]
|
||||
meta: Vec<Metadata>,
|
||||
},
|
||||
KclNone {
|
||||
value: KclNone,
|
||||
#[serde(rename = "__meta")]
|
||||
@ -188,6 +198,7 @@ impl From<KclValue> for Vec<SourceRange> {
|
||||
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"),
|
||||
}
|
||||
@ -220,11 +231,19 @@ impl From<&KclValue> for Vec<SourceRange> {
|
||||
KclValue::Object { meta, .. } => to_vec_sr(meta),
|
||||
KclValue::Module { meta, .. } => to_vec_sr(meta),
|
||||
KclValue::KclNone { meta, .. } => to_vec_sr(meta),
|
||||
KclValue::Type { meta, .. } => to_vec_sr(meta),
|
||||
KclValue::Tombstone { .. } => unreachable!("Tombstone &SourceRange"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&KclValue> for SourceRange {
|
||||
fn from(item: &KclValue) -> Self {
|
||||
let v: Vec<_> = item.into();
|
||||
v.into_iter().next().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
impl KclValue {
|
||||
pub(crate) fn metadata(&self) -> Vec<Metadata> {
|
||||
match self {
|
||||
@ -247,6 +266,7 @@ impl KclValue {
|
||||
KclValue::Function { meta, .. } => meta.clone(),
|
||||
KclValue::Module { meta, .. } => meta.clone(),
|
||||
KclValue::KclNone { meta, .. } => meta.clone(),
|
||||
KclValue::Type { meta, .. } => meta.clone(),
|
||||
KclValue::Tombstone { .. } => unreachable!("Tombstone Metadata"),
|
||||
}
|
||||
}
|
||||
@ -317,6 +337,7 @@ impl KclValue {
|
||||
KclValue::Array { .. } => "array (list)",
|
||||
KclValue::Object { .. } => "object",
|
||||
KclValue::Module { .. } => "module",
|
||||
KclValue::Type { .. } => "type",
|
||||
KclValue::KclNone { .. } => "None",
|
||||
KclValue::Tombstone { .. } => "TOMBSTONE",
|
||||
}
|
||||
@ -596,15 +617,16 @@ impl KclValue {
|
||||
.collect::<Option<Vec<_>>>()?,
|
||||
)),
|
||||
KclValue::Face { .. } => None,
|
||||
KclValue::Helix { .. } => None,
|
||||
KclValue::ImportedGeometry(..) => None,
|
||||
KclValue::Function { .. } => None,
|
||||
KclValue::Module { .. } => None,
|
||||
KclValue::TagIdentifier(_) => None,
|
||||
KclValue::TagDeclarator(_) => None,
|
||||
KclValue::KclNone { .. } => None,
|
||||
KclValue::Uuid { .. } => None,
|
||||
KclValue::Tombstone { .. } => None,
|
||||
KclValue::Helix { .. }
|
||||
| KclValue::ImportedGeometry(..)
|
||||
| KclValue::Function { .. }
|
||||
| KclValue::Module { .. }
|
||||
| KclValue::TagIdentifier(_)
|
||||
| KclValue::TagDeclarator(_)
|
||||
| KclValue::KclNone { .. }
|
||||
| KclValue::Type { .. }
|
||||
| KclValue::Uuid { .. }
|
||||
| KclValue::Tombstone { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -714,6 +736,7 @@ impl KclValue {
|
||||
| KclValue::Plane { .. }
|
||||
| KclValue::Face { .. }
|
||||
| KclValue::KclNone { .. }
|
||||
| KclValue::Type { .. }
|
||||
| KclValue::Tombstone { .. } => None,
|
||||
}
|
||||
}
|
||||
@ -728,21 +751,29 @@ pub enum RuntimeType {
|
||||
}
|
||||
|
||||
impl RuntimeType {
|
||||
pub fn from_parsed(value: Type, settings: &super::MetaSettings) -> Option<Self> {
|
||||
match value {
|
||||
Type::Primitive(pt) => Some(RuntimeType::Primitive(PrimitiveType::from_parsed(pt, settings)?)),
|
||||
Type::Array(pt) => Some(RuntimeType::Array(PrimitiveType::from_parsed(pt, settings)?)),
|
||||
Type::Object { properties } => Some(RuntimeType::Object(
|
||||
properties
|
||||
.into_iter()
|
||||
.map(|p| {
|
||||
p.type_.and_then(|t| {
|
||||
RuntimeType::from_parsed(t.inner, settings).map(|ty| (p.identifier.inner.name, ty))
|
||||
})
|
||||
})
|
||||
.collect::<Option<Vec<_>>>()?,
|
||||
)),
|
||||
}
|
||||
pub fn from_parsed(
|
||||
value: Type,
|
||||
exec_state: &mut ExecState,
|
||||
source_range: SourceRange,
|
||||
) -> Result<Option<Self>, CompilationError> {
|
||||
Ok(match value {
|
||||
Type::Primitive(pt) => {
|
||||
PrimitiveType::from_parsed(pt, exec_state, source_range)?.map(RuntimeType::Primitive)
|
||||
}
|
||||
Type::Array(pt) => PrimitiveType::from_parsed(pt, exec_state, source_range)?.map(RuntimeType::Array),
|
||||
Type::Object { properties } => properties
|
||||
.into_iter()
|
||||
.map(|p| {
|
||||
let pt = match p.type_ {
|
||||
Some(t) => t,
|
||||
None => return Ok(None),
|
||||
};
|
||||
Ok(RuntimeType::from_parsed(pt.inner, exec_state, source_range)?
|
||||
.map(|ty| (p.identifier.inner.name, ty)))
|
||||
})
|
||||
.collect::<Result<Option<Vec<_>>, CompilationError>>()?
|
||||
.map(RuntimeType::Object),
|
||||
})
|
||||
}
|
||||
|
||||
// Subtype with no coercion, including refining numeric types.
|
||||
@ -802,16 +833,33 @@ pub enum PrimitiveType {
|
||||
}
|
||||
|
||||
impl PrimitiveType {
|
||||
fn from_parsed(value: AstPrimitiveType, settings: &super::MetaSettings) -> Option<Self> {
|
||||
match value {
|
||||
fn from_parsed(
|
||||
value: AstPrimitiveType,
|
||||
exec_state: &mut ExecState,
|
||||
source_range: SourceRange,
|
||||
) -> Result<Option<Self>, CompilationError> {
|
||||
Ok(match value {
|
||||
AstPrimitiveType::String => Some(PrimitiveType::String),
|
||||
AstPrimitiveType::Boolean => Some(PrimitiveType::Boolean),
|
||||
AstPrimitiveType::Number(suffix) => Some(PrimitiveType::Number(NumericType::from_parsed(suffix, settings))),
|
||||
AstPrimitiveType::Sketch => Some(PrimitiveType::Sketch),
|
||||
AstPrimitiveType::Solid => Some(PrimitiveType::Solid),
|
||||
AstPrimitiveType::Plane => Some(PrimitiveType::Plane),
|
||||
AstPrimitiveType::Number(suffix) => Some(PrimitiveType::Number(NumericType::from_parsed(
|
||||
suffix,
|
||||
&exec_state.mod_local.settings,
|
||||
))),
|
||||
AstPrimitiveType::Named(name) => {
|
||||
let ty_val = exec_state
|
||||
.stack()
|
||||
.get(&format!("{}{}", memory::TYPE_PREFIX, name.name), source_range)
|
||||
.map_err(|_| CompilationError::err(source_range, format!("Unknown type: {}", name.name)))?;
|
||||
|
||||
let (ty, _) = match ty_val {
|
||||
KclValue::Type { value: Some(ty), .. } => ty,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
Some(ty.clone())
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -250,6 +250,8 @@ use crate::{
|
||||
|
||||
/// The distinguished name of the return value of a function.
|
||||
pub(crate) const RETURN_NAME: &str = "__return";
|
||||
/// Low-budget namespacing for types.
|
||||
pub(crate) const TYPE_PREFIX: &str = "__ty_";
|
||||
|
||||
/// KCL memory. There should be only one ProgramMemory for the interpretation of a program (
|
||||
/// including other modules). Multiple interpretation runs should have fresh instances.
|
||||
|
@ -14,7 +14,7 @@ pub(crate) use import::{
|
||||
import_foreign, send_to_engine as send_import_to_engine, PreImportedGeometry, ZOO_COORD_SYSTEM,
|
||||
};
|
||||
use indexmap::IndexMap;
|
||||
pub use kcl_value::{KclObjectFields, KclValue, UnitAngle, UnitLen};
|
||||
pub use kcl_value::{KclObjectFields, KclValue, PrimitiveType, UnitAngle, UnitLen};
|
||||
use kcmc::{
|
||||
each_cmd as mcmd,
|
||||
ok_response::{output::TakeSnapshot, OkModelingCmdResponse},
|
||||
|
Reference in New Issue
Block a user