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