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