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