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