Skip to main content

kcl_lib/execution/
annotations.rs

1//! Data on available annotations.
2
3use std::str::FromStr;
4
5use kittycad_modeling_cmds::coord::KITTYCAD;
6use kittycad_modeling_cmds::coord::OPENGL;
7use kittycad_modeling_cmds::coord::System;
8use kittycad_modeling_cmds::coord::VULKAN;
9use serde::Deserialize;
10use serde::Serialize;
11
12use crate::KclError;
13use crate::SourceRange;
14use crate::errors::KclErrorDetails;
15use crate::errors::Severity;
16use crate::parsing::ast::types::Annotation;
17use crate::parsing::ast::types::Expr;
18use crate::parsing::ast::types::LiteralValue;
19use crate::parsing::ast::types::Node;
20use crate::parsing::ast::types::ObjectProperty;
21
22/// Annotations which should cause re-execution if they change.
23pub(super) const SIGNIFICANT_ATTRS: [&str; 3] = [SETTINGS, NO_PRELUDE, WARNINGS];
24
25pub(crate) const SETTINGS: &str = "settings";
26pub(crate) const SETTINGS_UNIT_LENGTH: &str = "defaultLengthUnit";
27pub(crate) const SETTINGS_UNIT_ANGLE: &str = "defaultAngleUnit";
28pub(crate) const SETTINGS_VERSION: &str = "kclVersion";
29pub(crate) const SETTINGS_EXPERIMENTAL_FEATURES: &str = "experimentalFeatures";
30
31pub(super) const NO_PRELUDE: &str = "no_std";
32pub(crate) const DEPRECATED: &str = "deprecated";
33pub(crate) const DOC_CATEGORY: &str = "doc_category";
34pub(crate) const EXPERIMENTAL: &str = "experimental";
35pub(crate) const INCLUDE_IN_FEATURE_TREE: &str = "feature_tree";
36
37pub(super) const IMPORT_FORMAT: &str = "format";
38pub(super) const IMPORT_COORDS: &str = "coords";
39pub(super) const IMPORT_COORDS_VALUES: [(&str, &System); 3] =
40    [("zoo", KITTYCAD), ("opengl", OPENGL), ("vulkan", VULKAN)];
41pub(super) const IMPORT_LENGTH_UNIT: &str = "lengthUnit";
42
43pub(crate) const IMPL: &str = "impl";
44pub(crate) const IMPL_RUST: &str = "std_rust";
45pub(crate) const IMPL_CONSTRAINT: &str = "std_rust_constraint";
46pub(crate) const IMPL_CONSTRAINABLE: &str = "std_constrainable";
47pub(crate) const IMPL_RUST_CONSTRAINABLE: &str = "std_rust_constrainable";
48pub(crate) const IMPL_KCL: &str = "kcl";
49pub(crate) const IMPL_PRIMITIVE: &str = "primitive";
50pub(super) const IMPL_VALUES: [&str; 6] = [
51    IMPL_RUST,
52    IMPL_KCL,
53    IMPL_PRIMITIVE,
54    IMPL_CONSTRAINT,
55    IMPL_CONSTRAINABLE,
56    IMPL_RUST_CONSTRAINABLE,
57];
58
59pub(crate) const WARNINGS: &str = "warnings";
60pub(crate) const WARN_ALLOW: &str = "allow";
61pub(crate) const WARN_DENY: &str = "deny";
62pub(crate) const WARN_WARN: &str = "warn";
63pub(super) const WARN_LEVELS: [&str; 3] = [WARN_ALLOW, WARN_DENY, WARN_WARN];
64pub(crate) const WARN_UNKNOWN_UNITS: &str = "unknownUnits";
65pub(crate) const WARN_ANGLE_UNITS: &str = "angleUnits";
66pub(crate) const WARN_UNKNOWN_ATTR: &str = "unknownAttribute";
67pub(crate) const WARN_MOD_RETURN_VALUE: &str = "moduleReturnValue";
68pub(crate) const WARN_DEPRECATED: &str = "deprecated";
69pub(crate) const WARN_IGNORED_Z_AXIS: &str = "ignoredZAxis";
70pub(crate) const WARN_SOLVER: &str = "solver";
71pub(crate) const WARN_SHOULD_BE_PERCENTAGE: &str = "shouldBePercentage";
72pub(crate) const WARN_INVALID_MATH: &str = "invalidMath";
73pub(crate) const WARN_UNNECESSARY_CLOSE: &str = "unnecessaryClose";
74pub(crate) const WARN_UNUSED_TAGS: &str = "unusedTags";
75pub(crate) const WARN_NOT_YET_SUPPORTED: &str = "notYetSupported";
76pub(super) const WARN_VALUES: [&str; 11] = [
77    WARN_UNKNOWN_UNITS,
78    WARN_ANGLE_UNITS,
79    WARN_UNKNOWN_ATTR,
80    WARN_MOD_RETURN_VALUE,
81    WARN_DEPRECATED,
82    WARN_IGNORED_Z_AXIS,
83    WARN_SOLVER,
84    WARN_SHOULD_BE_PERCENTAGE,
85    WARN_INVALID_MATH,
86    WARN_UNNECESSARY_CLOSE,
87    WARN_NOT_YET_SUPPORTED,
88];
89
90#[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize, ts_rs::TS)]
91#[ts(export)]
92#[serde(tag = "type")]
93pub enum WarningLevel {
94    Allow,
95    Warn,
96    Deny,
97}
98
99impl WarningLevel {
100    pub(crate) fn severity(self) -> Option<Severity> {
101        match self {
102            WarningLevel::Allow => None,
103            WarningLevel::Warn => Some(Severity::Warning),
104            WarningLevel::Deny => Some(Severity::Error),
105        }
106    }
107
108    pub(crate) fn as_str(self) -> &'static str {
109        match self {
110            WarningLevel::Allow => WARN_ALLOW,
111            WarningLevel::Warn => WARN_WARN,
112            WarningLevel::Deny => WARN_DENY,
113        }
114    }
115}
116
117impl FromStr for WarningLevel {
118    type Err = ();
119
120    fn from_str(s: &str) -> Result<Self, Self::Err> {
121        match s {
122            WARN_ALLOW => Ok(Self::Allow),
123            WARN_WARN => Ok(Self::Warn),
124            WARN_DENY => Ok(Self::Deny),
125            _ => Err(()),
126        }
127    }
128}
129
130#[derive(Clone, Copy, Eq, PartialEq, Debug, Default)]
131pub enum Impl {
132    #[default]
133    Kcl,
134    KclConstrainable,
135    Rust,
136    RustConstrainable,
137    RustConstraint,
138    Primitive,
139}
140
141impl FromStr for Impl {
142    type Err = ();
143
144    fn from_str(s: &str) -> Result<Self, Self::Err> {
145        match s {
146            IMPL_RUST => Ok(Self::Rust),
147            IMPL_CONSTRAINT => Ok(Self::RustConstraint),
148            IMPL_CONSTRAINABLE => Ok(Self::KclConstrainable),
149            IMPL_RUST_CONSTRAINABLE => Ok(Self::RustConstrainable),
150            IMPL_KCL => Ok(Self::Kcl),
151            IMPL_PRIMITIVE => Ok(Self::Primitive),
152            _ => Err(()),
153        }
154    }
155}
156
157pub(crate) fn settings_completion_text() -> String {
158    format!("@{SETTINGS}({SETTINGS_UNIT_LENGTH} = mm, {SETTINGS_VERSION} = 1.0)")
159}
160
161pub(super) fn is_significant(attr: &&Node<Annotation>) -> bool {
162    match attr.name() {
163        Some(name) => SIGNIFICANT_ATTRS.contains(&name),
164        None => true,
165    }
166}
167
168pub(super) fn expect_properties<'a>(
169    for_key: &'static str,
170    annotation: &'a Node<Annotation>,
171) -> Result<&'a [Node<ObjectProperty>], KclError> {
172    assert_eq!(annotation.name().unwrap(), for_key);
173    Ok(&**annotation.properties.as_ref().ok_or_else(|| {
174        KclError::new_semantic(KclErrorDetails::new(
175            format!("Empty `{for_key}` annotation"),
176            vec![annotation.as_source_range()],
177        ))
178    })?)
179}
180
181pub(super) fn expect_ident(expr: &Expr) -> Result<&str, KclError> {
182    if let Expr::Name(name) = expr
183        && let Some(name) = name.local_ident()
184    {
185        return Ok(*name);
186    }
187
188    Err(KclError::new_semantic(KclErrorDetails::new(
189        "Unexpected settings value, expected a simple name, e.g., `mm`".to_owned(),
190        vec![expr.into()],
191    )))
192}
193
194pub(super) fn many_of(
195    expr: &Expr,
196    of: &[&'static str],
197    source_range: SourceRange,
198) -> Result<Vec<&'static str>, KclError> {
199    const UNEXPECTED_MSG: &str = "Unexpected warnings value, expected a name or array of names, e.g., `unknownUnits` or `[unknownUnits, deprecated]`";
200
201    let values = match expr {
202        Expr::Name(name) => {
203            if let Some(name) = name.local_ident() {
204                vec![*name]
205            } else {
206                return Err(KclError::new_semantic(KclErrorDetails::new(
207                    UNEXPECTED_MSG.to_owned(),
208                    vec![expr.into()],
209                )));
210            }
211        }
212        Expr::ArrayExpression(e) => {
213            let mut result = Vec::new();
214            for e in &e.elements {
215                if let Expr::Name(name) = e
216                    && let Some(name) = name.local_ident()
217                {
218                    result.push(*name);
219                    continue;
220                }
221                return Err(KclError::new_semantic(KclErrorDetails::new(
222                    UNEXPECTED_MSG.to_owned(),
223                    vec![e.into()],
224                )));
225            }
226            result
227        }
228        _ => {
229            return Err(KclError::new_semantic(KclErrorDetails::new(
230                UNEXPECTED_MSG.to_owned(),
231                vec![expr.into()],
232            )));
233        }
234    };
235
236    values
237        .into_iter()
238        .map(|v| {
239            of.iter()
240                .find(|vv| **vv == v)
241                .ok_or_else(|| {
242                    KclError::new_semantic(KclErrorDetails::new(
243                        format!("Unexpected warning value: `{v}`; accepted values: {}", of.join(", "),),
244                        vec![source_range],
245                    ))
246                })
247                .copied()
248        })
249        .collect::<Result<Vec<&str>, KclError>>()
250}
251
252// Returns the unparsed number literal.
253pub(super) fn expect_number(expr: &Expr) -> Result<String, KclError> {
254    if let Expr::Literal(lit) = expr
255        && let LiteralValue::Number { .. } = &lit.value
256    {
257        return Ok(lit.raw.clone());
258    }
259
260    Err(KclError::new_semantic(KclErrorDetails::new(
261        "Unexpected settings value, expected a number, e.g., `1.0`".to_owned(),
262        vec![expr.into()],
263    )))
264}
265
266#[derive(Debug, Clone, Copy, Eq, PartialEq)]
267pub struct FnAttrs {
268    pub impl_: Impl,
269    pub deprecated: bool,
270    pub experimental: bool,
271    pub include_in_feature_tree: bool,
272}
273
274impl Default for FnAttrs {
275    fn default() -> Self {
276        Self {
277            impl_: Impl::default(),
278            deprecated: false,
279            experimental: false,
280            include_in_feature_tree: true,
281        }
282    }
283}
284
285pub(super) fn get_fn_attrs(
286    annotations: &[Node<Annotation>],
287    source_range: SourceRange,
288) -> Result<Option<FnAttrs>, KclError> {
289    let mut result = None;
290    for attr in annotations {
291        if attr.name.is_some() || attr.properties.is_none() {
292            continue;
293        }
294        for p in attr.properties.as_ref().unwrap() {
295            if &*p.key.name == IMPL
296                && let Some(s) = p.value.ident_name()
297            {
298                if result.is_none() {
299                    result = Some(FnAttrs::default());
300                }
301
302                result.as_mut().unwrap().impl_ = Impl::from_str(s).map_err(|_| {
303                    KclError::new_semantic(KclErrorDetails::new(
304                        format!(
305                            "Invalid value for {} attribute, expected one of: {}",
306                            IMPL,
307                            IMPL_VALUES.join(", ")
308                        ),
309                        vec![source_range],
310                    ))
311                })?;
312                continue;
313            }
314
315            if &*p.key.name == DEPRECATED
316                && let Some(b) = p.value.literal_bool()
317            {
318                if result.is_none() {
319                    result = Some(FnAttrs::default());
320                }
321                result.as_mut().unwrap().deprecated = b;
322                continue;
323            }
324
325            // doc_category is handled by the docs generator, not execution.
326            if &*p.key.name == DOC_CATEGORY {
327                continue;
328            }
329
330            if &*p.key.name == EXPERIMENTAL
331                && let Some(b) = p.value.literal_bool()
332            {
333                if result.is_none() {
334                    result = Some(FnAttrs::default());
335                }
336                result.as_mut().unwrap().experimental = b;
337                continue;
338            }
339
340            if &*p.key.name == INCLUDE_IN_FEATURE_TREE
341                && let Some(b) = p.value.literal_bool()
342            {
343                if result.is_none() {
344                    result = Some(FnAttrs::default());
345                }
346                result.as_mut().unwrap().include_in_feature_tree = b;
347                continue;
348            }
349
350            return Err(KclError::new_semantic(KclErrorDetails::new(
351                format!(
352                    "Invalid attribute, expected one of: {IMPL}, {DEPRECATED}, {DOC_CATEGORY}, {EXPERIMENTAL}, {INCLUDE_IN_FEATURE_TREE}, found `{}`",
353                    &*p.key.name,
354                ),
355                vec![source_range],
356            )));
357        }
358    }
359
360    Ok(result)
361}