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