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