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