1#![allow(clippy::missing_errors_doc)]
2pub mod closure;
3
4use crate::diagnostic::{DiagnosticMessage, Label, Note};
5use crate::parser::ast::Ident;
6use crate::path::OwnedTargetPath;
7use crate::value::{KeyString, Value, kind::Collection};
8use std::{
9 collections::{BTreeMap, HashMap},
10 fmt,
11};
12
13use super::{
14 CompileConfig, Span, TypeDef,
15 expression::{Block, Container, Expr, Expression, container::Variant},
16 state::TypeState,
17 value::{Kind, kind},
18};
19
20pub type Compiled = Result<Box<dyn Expression>, Box<dyn DiagnosticMessage>>;
21pub type CompiledArgument =
22 Result<Option<Box<dyn std::any::Any + Send + Sync>>, Box<dyn DiagnosticMessage>>;
23
24pub trait Function: Send + Sync + fmt::Debug {
25 fn identifier(&self) -> &'static str;
27
28 fn summary(&self) -> &'static str {
30 "TODO"
31 }
32
33 fn usage(&self) -> &'static str {
35 "TODO"
36 }
37
38 fn examples(&self) -> &'static [Example];
41
42 fn compile(
51 &self,
52 state: &TypeState,
53 ctx: &mut FunctionCompileContext,
54 arguments: ArgumentList,
55 ) -> Compiled;
56
57 fn parameters(&self) -> &'static [Parameter] {
62 &[]
63 }
64
65 fn closure(&self) -> Option<closure::Definition> {
70 None
71 }
72}
73
74#[derive(Debug, Copy, Clone, PartialEq, Eq)]
77pub struct Example {
78 pub title: &'static str,
79 pub source: &'static str,
80 pub result: Result<&'static str, &'static str>,
81}
82
83#[allow(clippy::module_name_repetitions)]
85pub struct FunctionCompileContext {
86 span: Span,
87 config: CompileConfig,
88}
89
90impl FunctionCompileContext {
91 #[must_use]
92 pub fn new(span: Span, config: CompileConfig) -> Self {
93 Self { span, config }
94 }
95
96 #[must_use]
98 pub fn span(&self) -> Span {
99 self.span
100 }
101
102 #[must_use]
104 pub fn get_external_context<T: 'static>(&self) -> Option<&T> {
105 self.config.get_custom()
106 }
107
108 pub fn get_external_context_mut<T: 'static>(&mut self) -> Option<&mut T> {
110 self.config.get_custom_mut()
111 }
112
113 #[must_use]
114 pub fn is_read_only_path(&self, path: &OwnedTargetPath) -> bool {
115 self.config.is_read_only_path(path)
116 }
117
118 #[must_use]
120 pub fn into_config(self) -> CompileConfig {
121 self.config
122 }
123}
124
125#[derive(Debug, Copy, Clone, PartialEq, Eq)]
128pub struct Parameter {
129 pub keyword: &'static str,
134
135 pub kind: u16,
140
141 pub required: bool,
146}
147
148impl Parameter {
149 #[allow(arithmetic_overflow)]
150 #[must_use]
151 pub fn kind(&self) -> Kind {
152 let mut kind = Kind::never();
153
154 let n = self.kind;
155
156 if (n & kind::BYTES) == kind::BYTES {
157 kind.add_bytes();
158 }
159
160 if (n & kind::INTEGER) == kind::INTEGER {
161 kind.add_integer();
162 }
163
164 if (n & kind::FLOAT) == kind::FLOAT {
165 kind.add_float();
166 }
167
168 if (n & kind::BOOLEAN) == kind::BOOLEAN {
169 kind.add_boolean();
170 }
171
172 if (n & kind::OBJECT) == kind::OBJECT {
173 kind.add_object(Collection::any());
174 }
175
176 if (n & kind::ARRAY) == kind::ARRAY {
177 kind.add_array(Collection::any());
178 }
179
180 if (n & kind::TIMESTAMP) == kind::TIMESTAMP {
181 kind.add_timestamp();
182 }
183
184 if (n & kind::REGEX) == kind::REGEX {
185 kind.add_regex();
186 }
187
188 if (n & kind::NULL) == kind::NULL {
189 kind.add_null();
190 }
191
192 if (n & kind::UNDEFINED) == kind::UNDEFINED {
193 kind.add_undefined();
194 }
195
196 kind
197 }
198}
199
200#[derive(Debug, Default, Clone)]
203pub struct ArgumentList {
204 pub(crate) arguments: HashMap<&'static str, Expr>,
205
206 closure: Option<Closure>,
213}
214
215impl ArgumentList {
216 #[must_use]
217 pub fn optional(&self, keyword: &'static str) -> Option<Box<dyn Expression>> {
218 self.optional_expr(keyword).map(|v| Box::new(v) as _)
219 }
220
221 #[must_use]
222 pub fn required(&self, keyword: &'static str) -> Box<dyn Expression> {
223 Box::new(self.required_expr(keyword)) as _
224 }
225
226 pub fn optional_literal(
227 &self,
228 keyword: &'static str,
229 state: &TypeState,
230 ) -> Result<Option<Value>, Error> {
231 self.optional_expr(keyword)
232 .map(|expr| match expr.resolve_constant(state) {
233 Some(value) => Ok(value),
234 _ => Err(Error::UnexpectedExpression {
235 keyword,
236 expected: "literal",
237 expr,
238 }),
239 })
240 .transpose()
241 }
242
243 pub fn required_literal(
244 &self,
245 keyword: &'static str,
246 state: &TypeState,
247 ) -> Result<Value, Error> {
248 Ok(required(self.optional_literal(keyword, state)?))
249 }
250
251 pub fn optional_enum(
252 &self,
253 keyword: &'static str,
254 variants: &[Value],
255 state: &TypeState,
256 ) -> Result<Option<Value>, Error> {
257 self.optional_literal(keyword, state)?
258 .map(|value| {
259 variants
260 .iter()
261 .find(|v| *v == &value)
262 .cloned()
263 .ok_or(Error::InvalidEnumVariant {
264 keyword,
265 value,
266 variants: variants.to_vec(),
267 })
268 })
269 .transpose()
270 }
271
272 pub fn required_enum(
273 &self,
274 keyword: &'static str,
275 variants: &[Value],
276 state: &TypeState,
277 ) -> Result<Value, Error> {
278 Ok(required(self.optional_enum(keyword, variants, state)?))
279 }
280
281 pub fn optional_query(
282 &self,
283 keyword: &'static str,
284 ) -> Result<Option<crate::compiler::expression::Query>, Error> {
285 self.optional_expr(keyword)
286 .map(|expr| match expr {
287 Expr::Query(query) => Ok(query),
288 expr => Err(Error::UnexpectedExpression {
289 keyword,
290 expected: "query",
291 expr,
292 }),
293 })
294 .transpose()
295 }
296
297 pub fn required_query(
298 &self,
299 keyword: &'static str,
300 ) -> Result<crate::compiler::expression::Query, Error> {
301 Ok(required(self.optional_query(keyword)?))
302 }
303
304 pub fn optional_regex(
305 &self,
306 keyword: &'static str,
307 state: &TypeState,
308 ) -> Result<Option<regex::Regex>, Error> {
309 self.optional_expr(keyword)
310 .map(|expr| match expr.resolve_constant(state) {
311 Some(Value::Regex(regex)) => Ok((*regex).clone()),
312 _ => Err(Error::UnexpectedExpression {
313 keyword,
314 expected: "regex",
315 expr,
316 }),
317 })
318 .transpose()
319 }
320
321 pub fn required_regex(
322 &self,
323 keyword: &'static str,
324 state: &TypeState,
325 ) -> Result<regex::Regex, Error> {
326 Ok(required(self.optional_regex(keyword, state)?))
327 }
328
329 pub fn optional_object(
330 &self,
331 keyword: &'static str,
332 ) -> Result<Option<BTreeMap<KeyString, Expr>>, Error> {
333 self.optional_expr(keyword)
334 .map(|expr| match expr {
335 Expr::Container(Container {
336 variant: Variant::Object(object),
337 }) => Ok((*object).clone()),
338 expr => Err(Error::UnexpectedExpression {
339 keyword,
340 expected: "object",
341 expr,
342 }),
343 })
344 .transpose()
345 }
346
347 pub fn required_object(
348 &self,
349 keyword: &'static str,
350 ) -> Result<BTreeMap<KeyString, Expr>, Error> {
351 Ok(required(self.optional_object(keyword)?))
352 }
353
354 pub fn optional_array(&self, keyword: &'static str) -> Result<Option<Vec<Expr>>, Error> {
355 self.optional_expr(keyword)
356 .map(|expr| match expr {
357 Expr::Container(Container {
358 variant: Variant::Array(array),
359 }) => Ok((*array).clone()),
360 expr => Err(Error::UnexpectedExpression {
361 keyword,
362 expected: "array",
363 expr,
364 }),
365 })
366 .transpose()
367 }
368
369 pub fn required_array(&self, keyword: &'static str) -> Result<Vec<Expr>, Error> {
370 Ok(required(self.optional_array(keyword)?))
371 }
372
373 #[must_use]
374 pub fn optional_closure(&self) -> Option<&Closure> {
375 self.closure.as_ref()
376 }
377
378 pub fn required_closure(&self) -> Result<Closure, Error> {
379 self.optional_closure()
380 .cloned()
381 .ok_or(Error::ExpectedFunctionClosure)
382 }
383
384 pub(crate) fn keywords(&self) -> Vec<&'static str> {
385 self.arguments.keys().copied().collect::<Vec<_>>()
386 }
387
388 pub(crate) fn insert(&mut self, k: &'static str, v: Expr) {
389 self.arguments.insert(k, v);
390 }
391
392 pub(crate) fn set_closure(&mut self, closure: Closure) {
393 self.closure = Some(closure);
394 }
395
396 pub(crate) fn optional_expr(&self, keyword: &'static str) -> Option<Expr> {
397 self.arguments.get(keyword).cloned()
398 }
399
400 #[must_use]
401 pub fn required_expr(&self, keyword: &'static str) -> Expr {
402 required(self.optional_expr(keyword))
403 }
404}
405
406fn required<T>(argument: Option<T>) -> T {
407 argument.expect("invalid function signature")
408}
409
410#[cfg(any(test, feature = "test"))]
411mod test_impls {
412 use super::{ArgumentList, HashMap, Span, Value};
413 use crate::compiler::expression::FunctionArgument;
414 use crate::compiler::parser::Node;
415
416 impl From<HashMap<&'static str, Value>> for ArgumentList {
417 fn from(map: HashMap<&'static str, Value>) -> Self {
418 Self {
419 arguments: map
420 .into_iter()
421 .map(|(k, v)| (k, v.into()))
422 .collect::<HashMap<_, _>>(),
423 closure: None,
424 }
425 }
426 }
427
428 impl From<ArgumentList> for Vec<(&'static str, Option<FunctionArgument>)> {
429 fn from(args: ArgumentList) -> Self {
430 args.arguments
431 .iter()
432 .map(|(key, expr)| {
433 (
434 *key,
435 Some(FunctionArgument::new(
436 None,
437 Node::new(Span::default(), expr.clone()),
438 )),
439 )
440 })
441 .collect()
442 }
443 }
444}
445
446#[derive(Debug, Clone, PartialEq)]
449pub struct Closure {
450 pub variables: Vec<Ident>,
451 pub block: Block,
452 pub block_type_def: TypeDef,
453}
454
455impl Closure {
456 #[must_use]
457 pub fn new<T: Into<Ident>>(variables: Vec<T>, block: Block, block_type_def: TypeDef) -> Self {
458 Self {
459 variables: variables.into_iter().map(Into::into).collect(),
460 block,
461 block_type_def,
462 }
463 }
464}
465
466#[derive(thiserror::Error, Debug, PartialEq)]
469pub enum Error {
470 #[error("unexpected expression type")]
471 UnexpectedExpression {
472 keyword: &'static str,
473 expected: &'static str,
474 expr: Expr,
475 },
476
477 #[error(r#"invalid enum variant""#)]
478 InvalidEnumVariant {
479 keyword: &'static str,
480 value: Value,
481 variants: Vec<Value>,
482 },
483
484 #[error("this argument must be a static expression")]
485 ExpectedStaticExpression { keyword: &'static str, expr: Expr },
486
487 #[error("invalid argument")]
488 InvalidArgument {
489 keyword: &'static str,
490 value: Value,
491 error: &'static str,
492 },
493
494 #[error("missing function closure")]
495 ExpectedFunctionClosure,
496
497 #[error("mutation of read-only value")]
498 ReadOnlyMutation { context: String },
499}
500
501impl crate::diagnostic::DiagnosticMessage for Error {
502 fn code(&self) -> usize {
503 use Error::{
504 ExpectedFunctionClosure, ExpectedStaticExpression, InvalidArgument, InvalidEnumVariant,
505 ReadOnlyMutation, UnexpectedExpression,
506 };
507
508 match self {
509 UnexpectedExpression { .. } => 400,
510 InvalidEnumVariant { .. } => 401,
511 ExpectedStaticExpression { .. } => 402,
512 InvalidArgument { .. } => 403,
513 ExpectedFunctionClosure => 420,
514 ReadOnlyMutation { .. } => 315,
515 }
516 }
517
518 fn labels(&self) -> Vec<Label> {
519 use Error::{
520 ExpectedFunctionClosure, ExpectedStaticExpression, InvalidArgument, InvalidEnumVariant,
521 ReadOnlyMutation, UnexpectedExpression,
522 };
523
524 match self {
525 UnexpectedExpression {
526 keyword,
527 expected,
528 expr,
529 } => vec![
530 Label::primary(
531 format!(r#"unexpected expression for argument "{keyword}""#),
532 Span::default(),
533 ),
534 Label::context(format!("received: {}", expr.as_str()), Span::default()),
535 Label::context(format!("expected: {expected}"), Span::default()),
536 ],
537
538 InvalidEnumVariant {
539 keyword,
540 value,
541 variants,
542 } => vec![
543 Label::primary(
544 format!(r#"invalid enum variant for argument "{keyword}""#),
545 Span::default(),
546 ),
547 Label::context(format!("received: {value}"), Span::default()),
548 Label::context(
549 format!(
550 "expected one of: {}",
551 variants
552 .iter()
553 .map(std::string::ToString::to_string)
554 .collect::<Vec<_>>()
555 .join(", ")
556 ),
557 Span::default(),
558 ),
559 ],
560
561 ExpectedStaticExpression { keyword, expr } => vec![
562 Label::primary(
563 format!(r#"expected static expression for argument "{keyword}""#),
564 Span::default(),
565 ),
566 Label::context(format!("received: {}", expr.as_str()), Span::default()),
567 ],
568
569 InvalidArgument {
570 keyword,
571 value,
572 error,
573 } => vec![
574 Label::primary(format!(r#"invalid argument "{keyword}""#), Span::default()),
575 Label::context(format!("received: {value}"), Span::default()),
576 Label::context(format!("error: {error}"), Span::default()),
577 ],
578
579 ExpectedFunctionClosure => vec![],
580 ReadOnlyMutation { context } => vec![
581 Label::primary("mutation of read-only value", Span::default()),
582 Label::context(context, Span::default()),
583 ],
584 }
585 }
586
587 fn notes(&self) -> Vec<Note> {
588 vec![Note::SeeCodeDocs(self.code())]
589 }
590}
591
592impl From<Error> for Box<dyn crate::diagnostic::DiagnosticMessage> {
593 fn from(error: Error) -> Self {
594 Box::new(error) as _
595 }
596}
597
598#[cfg(test)]
599mod tests {
600 use super::*;
601
602 #[test]
603 fn test_parameter_kind() {
604 struct TestCase {
605 parameter_kind: u16,
606 kind: Kind,
607 }
608
609 for (
610 title,
611 TestCase {
612 parameter_kind,
613 kind,
614 },
615 ) in HashMap::from([
616 (
617 "bytes",
618 TestCase {
619 parameter_kind: kind::BYTES,
620 kind: Kind::bytes(),
621 },
622 ),
623 (
624 "integer",
625 TestCase {
626 parameter_kind: kind::INTEGER,
627 kind: Kind::integer(),
628 },
629 ),
630 ]) {
631 let parameter = Parameter {
632 keyword: "",
633 kind: parameter_kind,
634 required: false,
635 };
636
637 assert_eq!(parameter.kind(), kind, "{title}");
638 }
639 }
640}