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