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
36 fn category(&self) -> &'static str;
40
41 fn internal_failure_reasons(&self) -> &'static [&'static str] {
46 &[]
47 }
48
49 fn return_kind(&self) -> u16;
51
52 fn return_rules(&self) -> &'static [&'static str] {
57 &[]
58 }
59
60 fn notices(&self) -> &'static [&'static str] {
65 &[]
66 }
67
68 fn pure(&self) -> bool {
71 true
72 }
73
74 fn examples(&self) -> &'static [Example];
77
78 fn compile(
87 &self,
88 state: &TypeState,
89 ctx: &mut FunctionCompileContext,
90 arguments: ArgumentList,
91 ) -> Compiled;
92
93 fn parameters(&self) -> &'static [Parameter] {
98 &[]
99 }
100
101 fn closure(&self) -> Option<closure::Definition> {
106 None
107 }
108}
109
110#[derive(Debug, Copy, Clone, PartialEq, Eq)]
113pub struct Example {
114 pub title: &'static str,
115 pub source: &'static str,
116 pub input: Option<&'static str>,
117 pub result: Result<&'static str, &'static str>,
118 pub file: &'static str,
119 pub line: u32,
120 pub deterministic: bool,
123}
124
125#[macro_export]
127macro_rules! example {
128 (
129 title: $title:expr,
130 source: $source:expr,
131 input: $input:expr,
132 result: $result:expr $(,)?
133 ) => {
134 $crate::compiler::function::Example {
135 title: $title,
136 source: $source,
137 input: Some($input),
138 result: $result,
139 file: file!(),
140 line: line!(),
141 deterministic: true,
142 }
143 };
144 (
145 title: $title:expr,
146 source: $source:expr,
147 result: $result:expr $(,)?
148 ) => {
149 $crate::compiler::function::Example {
150 title: $title,
151 source: $source,
152 input: None,
153 result: $result,
154 file: file!(),
155 line: line!(),
156 deterministic: true,
157 }
158 };
159 (
160 title: $title:expr,
161 source: $source:expr,
162 result: $result:expr,
163 deterministic: $det:expr $(,)?
164 ) => {
165 $crate::compiler::function::Example {
166 title: $title,
167 source: $source,
168 input: None,
169 result: $result,
170 file: file!(),
171 line: line!(),
172 deterministic: $det,
173 }
174 };
175}
176
177#[allow(clippy::module_name_repetitions)]
179pub struct FunctionCompileContext {
180 span: Span,
181 config: CompileConfig,
182}
183
184impl FunctionCompileContext {
185 #[must_use]
186 pub fn new(span: Span, config: CompileConfig) -> Self {
187 Self { span, config }
188 }
189
190 #[must_use]
192 pub fn span(&self) -> Span {
193 self.span
194 }
195
196 #[must_use]
198 pub fn get_external_context<T: 'static>(&self) -> Option<&T> {
199 self.config.get_custom()
200 }
201
202 pub fn get_external_context_mut<T: 'static>(&mut self) -> Option<&mut T> {
204 self.config.get_custom_mut()
205 }
206
207 #[must_use]
208 pub fn is_read_only_path(&self, path: &OwnedTargetPath) -> bool {
209 self.config.is_read_only_path(path)
210 }
211
212 #[must_use]
214 pub fn into_config(self) -> CompileConfig {
215 self.config
216 }
217}
218
219#[derive(Debug, Copy, Clone, PartialEq, Eq)]
222pub struct EnumVariant {
223 pub value: &'static str,
224 pub description: &'static str,
225}
226
227#[derive(Debug, Copy, Clone, PartialEq, Eq)]
228pub struct Parameter {
229 pub keyword: &'static str,
234
235 pub kind: u16,
240
241 pub required: bool,
246
247 pub description: &'static str,
249
250 pub default: Option<&'static Value>,
259
260 pub enum_variants: Option<&'static [EnumVariant]>,
262}
263
264impl Parameter {
265 #[must_use]
267 pub const fn required(keyword: &'static str, kind: u16, description: &'static str) -> Self {
268 Self {
269 keyword,
270 kind,
271 required: true,
272 description,
273 default: None,
274 enum_variants: None,
275 }
276 }
277
278 #[must_use]
280 pub const fn optional(keyword: &'static str, kind: u16, description: &'static str) -> Self {
281 Self {
282 keyword,
283 kind,
284 required: false,
285 description,
286 default: None,
287 enum_variants: None,
288 }
289 }
290
291 #[must_use]
293 pub const fn default(mut self, value: &'static Value) -> Self {
294 self.default = Some(value);
295 self
296 }
297
298 #[must_use]
300 pub const fn enum_variants(mut self, variants: &'static [EnumVariant]) -> Self {
301 self.enum_variants = Some(variants);
302 self
303 }
304
305 #[allow(arithmetic_overflow)]
306 #[must_use]
307 pub fn kind(&self) -> Kind {
308 let mut kind = Kind::never();
309
310 let n = self.kind;
311
312 if (n & kind::BYTES) == kind::BYTES {
313 kind.add_bytes();
314 }
315
316 if (n & kind::INTEGER) == kind::INTEGER {
317 kind.add_integer();
318 }
319
320 if (n & kind::FLOAT) == kind::FLOAT {
321 kind.add_float();
322 }
323
324 if (n & kind::BOOLEAN) == kind::BOOLEAN {
325 kind.add_boolean();
326 }
327
328 if (n & kind::OBJECT) == kind::OBJECT {
329 kind.add_object(Collection::any());
330 }
331
332 if (n & kind::ARRAY) == kind::ARRAY {
333 kind.add_array(Collection::any());
334 }
335
336 if (n & kind::TIMESTAMP) == kind::TIMESTAMP {
337 kind.add_timestamp();
338 }
339
340 if (n & kind::REGEX) == kind::REGEX {
341 kind.add_regex();
342 }
343
344 if (n & kind::NULL) == kind::NULL {
345 kind.add_null();
346 }
347
348 if (n & kind::UNDEFINED) == kind::UNDEFINED {
349 kind.add_undefined();
350 }
351
352 kind
353 }
354}
355
356#[derive(Debug, Default, Clone)]
359pub struct ArgumentList {
360 pub(crate) arguments: HashMap<&'static str, Expr>,
361
362 closure: Option<Closure>,
369}
370
371impl ArgumentList {
372 #[must_use]
373 pub fn optional(&self, keyword: &'static str) -> Option<Box<dyn Expression>> {
374 self.optional_expr(keyword).map(|v| Box::new(v) as _)
375 }
376
377 #[must_use]
378 pub fn required(&self, keyword: &'static str) -> Box<dyn Expression> {
379 Box::new(self.required_expr(keyword)) as _
380 }
381
382 pub fn optional_literal(
383 &self,
384 keyword: &'static str,
385 state: &TypeState,
386 ) -> Result<Option<Value>, Error> {
387 self.optional_expr(keyword)
388 .map(|expr| match expr.resolve_constant(state) {
389 Some(value) => Ok(value),
390 _ => Err(Error::UnexpectedExpression {
391 keyword,
392 expected: "literal",
393 expr,
394 }),
395 })
396 .transpose()
397 }
398
399 pub fn required_literal(
400 &self,
401 keyword: &'static str,
402 state: &TypeState,
403 ) -> Result<Value, Error> {
404 Ok(required(self.optional_literal(keyword, state)?))
405 }
406
407 pub fn optional_enum(
408 &self,
409 keyword: &'static str,
410 variants: &[Value],
411 state: &TypeState,
412 ) -> Result<Option<Value>, Error> {
413 self.optional_literal(keyword, state)?
414 .map(|value| {
415 variants
416 .iter()
417 .find(|v| *v == &value)
418 .cloned()
419 .ok_or(Error::InvalidEnumVariant {
420 keyword,
421 value,
422 variants: variants.to_vec(),
423 })
424 })
425 .transpose()
426 }
427
428 pub fn required_enum(
429 &self,
430 keyword: &'static str,
431 variants: &[Value],
432 state: &TypeState,
433 ) -> Result<Value, Error> {
434 Ok(required(self.optional_enum(keyword, variants, state)?))
435 }
436
437 pub fn optional_query(
438 &self,
439 keyword: &'static str,
440 ) -> Result<Option<crate::compiler::expression::Query>, Error> {
441 self.optional_expr(keyword)
442 .map(|expr| match expr {
443 Expr::Query(query) => Ok(query),
444 expr => Err(Error::UnexpectedExpression {
445 keyword,
446 expected: "query",
447 expr,
448 }),
449 })
450 .transpose()
451 }
452
453 pub fn required_query(
454 &self,
455 keyword: &'static str,
456 ) -> Result<crate::compiler::expression::Query, Error> {
457 Ok(required(self.optional_query(keyword)?))
458 }
459
460 pub fn optional_regex(
461 &self,
462 keyword: &'static str,
463 state: &TypeState,
464 ) -> Result<Option<regex::Regex>, Error> {
465 self.optional_expr(keyword)
466 .map(|expr| match expr.resolve_constant(state) {
467 Some(Value::Regex(regex)) => Ok((*regex).clone()),
468 _ => Err(Error::UnexpectedExpression {
469 keyword,
470 expected: "regex",
471 expr,
472 }),
473 })
474 .transpose()
475 }
476
477 pub fn required_regex(
478 &self,
479 keyword: &'static str,
480 state: &TypeState,
481 ) -> Result<regex::Regex, Error> {
482 Ok(required(self.optional_regex(keyword, state)?))
483 }
484
485 pub fn optional_object(
486 &self,
487 keyword: &'static str,
488 ) -> Result<Option<BTreeMap<KeyString, Expr>>, Error> {
489 self.optional_expr(keyword)
490 .map(|expr| match expr {
491 Expr::Container(Container {
492 variant: Variant::Object(object),
493 }) => Ok((*object).clone()),
494 expr => Err(Error::UnexpectedExpression {
495 keyword,
496 expected: "object",
497 expr,
498 }),
499 })
500 .transpose()
501 }
502
503 pub fn required_object(
504 &self,
505 keyword: &'static str,
506 ) -> Result<BTreeMap<KeyString, Expr>, Error> {
507 Ok(required(self.optional_object(keyword)?))
508 }
509
510 pub fn optional_array(&self, keyword: &'static str) -> Result<Option<Vec<Expr>>, Error> {
511 self.optional_expr(keyword)
512 .map(|expr| match expr {
513 Expr::Container(Container {
514 variant: Variant::Array(array),
515 }) => Ok((*array).clone()),
516 expr => Err(Error::UnexpectedExpression {
517 keyword,
518 expected: "array",
519 expr,
520 }),
521 })
522 .transpose()
523 }
524
525 pub fn required_array(&self, keyword: &'static str) -> Result<Vec<Expr>, Error> {
526 Ok(required(self.optional_array(keyword)?))
527 }
528
529 #[must_use]
530 pub fn optional_closure(&self) -> Option<&Closure> {
531 self.closure.as_ref()
532 }
533
534 pub fn required_closure(&self) -> Result<Closure, Error> {
535 self.optional_closure()
536 .cloned()
537 .ok_or(Error::ExpectedFunctionClosure)
538 }
539
540 pub(crate) fn keywords(&self) -> Vec<&'static str> {
541 self.arguments.keys().copied().collect::<Vec<_>>()
542 }
543
544 pub(crate) fn insert(&mut self, k: &'static str, v: Expr) {
545 self.arguments.insert(k, v);
546 }
547
548 pub(crate) fn set_closure(&mut self, closure: Closure) {
549 self.closure = Some(closure);
550 }
551
552 pub(crate) fn optional_expr(&self, keyword: &'static str) -> Option<Expr> {
553 self.arguments.get(keyword).cloned()
554 }
555
556 #[must_use]
557 pub fn required_expr(&self, keyword: &'static str) -> Expr {
558 required(self.optional_expr(keyword))
559 }
560}
561
562fn required<T>(argument: Option<T>) -> T {
563 argument.expect("invalid function signature")
564}
565
566#[cfg(any(test, feature = "test"))]
567mod test_impls {
568 use super::{ArgumentList, HashMap, Span, Value};
569 use crate::compiler::expression::FunctionArgument;
570 use crate::compiler::parser::Node;
571
572 impl From<HashMap<&'static str, Value>> for ArgumentList {
573 fn from(map: HashMap<&'static str, Value>) -> Self {
574 Self {
575 arguments: map
576 .into_iter()
577 .map(|(k, v)| (k, v.into()))
578 .collect::<HashMap<_, _>>(),
579 closure: None,
580 }
581 }
582 }
583
584 impl From<ArgumentList> for Vec<(&'static str, Option<FunctionArgument>)> {
585 fn from(args: ArgumentList) -> Self {
586 args.arguments
587 .iter()
588 .map(|(key, expr)| {
589 (
590 *key,
591 Some(FunctionArgument::new(
592 None,
593 Node::new(Span::default(), expr.clone()),
594 )),
595 )
596 })
597 .collect()
598 }
599 }
600}
601
602#[derive(Debug, Clone, PartialEq)]
605pub struct Closure {
606 pub variables: Vec<Ident>,
607 pub block: Block,
608 pub block_type_def: TypeDef,
609}
610
611impl Closure {
612 #[must_use]
613 pub fn new<T: Into<Ident>>(variables: Vec<T>, block: Block, block_type_def: TypeDef) -> Self {
614 Self {
615 variables: variables.into_iter().map(Into::into).collect(),
616 block,
617 block_type_def,
618 }
619 }
620}
621
622#[derive(thiserror::Error, Debug, PartialEq)]
625pub enum Error {
626 #[error("unexpected expression type")]
627 UnexpectedExpression {
628 keyword: &'static str,
629 expected: &'static str,
630 expr: Expr,
631 },
632
633 #[error(r#"invalid enum variant""#)]
634 InvalidEnumVariant {
635 keyword: &'static str,
636 value: Value,
637 variants: Vec<Value>,
638 },
639
640 #[error("this argument must be a static expression")]
641 ExpectedStaticExpression { keyword: &'static str, expr: Expr },
642
643 #[error("invalid argument")]
644 InvalidArgument {
645 keyword: &'static str,
646 value: Value,
647 error: &'static str,
648 },
649
650 #[error("missing function closure")]
651 ExpectedFunctionClosure,
652
653 #[error("mutation of read-only value")]
654 ReadOnlyMutation { context: String },
655}
656
657impl crate::diagnostic::DiagnosticMessage for Error {
658 fn code(&self) -> usize {
659 use Error::{
660 ExpectedFunctionClosure, ExpectedStaticExpression, InvalidArgument, InvalidEnumVariant,
661 ReadOnlyMutation, UnexpectedExpression,
662 };
663
664 match self {
665 UnexpectedExpression { .. } => 400,
666 InvalidEnumVariant { .. } => 401,
667 ExpectedStaticExpression { .. } => 402,
668 InvalidArgument { .. } => 403,
669 ExpectedFunctionClosure => 420,
670 ReadOnlyMutation { .. } => 315,
671 }
672 }
673
674 fn labels(&self) -> Vec<Label> {
675 use Error::{
676 ExpectedFunctionClosure, ExpectedStaticExpression, InvalidArgument, InvalidEnumVariant,
677 ReadOnlyMutation, UnexpectedExpression,
678 };
679
680 match self {
681 UnexpectedExpression {
682 keyword,
683 expected,
684 expr,
685 } => vec![
686 Label::primary(
687 format!(r#"unexpected expression for argument "{keyword}""#),
688 Span::default(),
689 ),
690 Label::context(format!("received: {}", expr.as_str()), Span::default()),
691 Label::context(format!("expected: {expected}"), Span::default()),
692 ],
693
694 InvalidEnumVariant {
695 keyword,
696 value,
697 variants,
698 } => vec![
699 Label::primary(
700 format!(r#"invalid enum variant for argument "{keyword}""#),
701 Span::default(),
702 ),
703 Label::context(format!("received: {value}"), Span::default()),
704 Label::context(
705 format!(
706 "expected one of: {}",
707 variants
708 .iter()
709 .map(std::string::ToString::to_string)
710 .collect::<Vec<_>>()
711 .join(", ")
712 ),
713 Span::default(),
714 ),
715 ],
716
717 ExpectedStaticExpression { keyword, expr } => vec![
718 Label::primary(
719 format!(r#"expected static expression for argument "{keyword}""#),
720 Span::default(),
721 ),
722 Label::context(format!("received: {}", expr.as_str()), Span::default()),
723 ],
724
725 InvalidArgument {
726 keyword,
727 value,
728 error,
729 } => vec![
730 Label::primary(format!(r#"invalid argument "{keyword}""#), Span::default()),
731 Label::context(format!("received: {value}"), Span::default()),
732 Label::context(format!("error: {error}"), Span::default()),
733 ],
734
735 ExpectedFunctionClosure => vec![],
736 ReadOnlyMutation { context } => vec![
737 Label::primary("mutation of read-only value", Span::default()),
738 Label::context(context, Span::default()),
739 ],
740 }
741 }
742
743 fn notes(&self) -> Vec<Note> {
744 vec![Note::SeeCodeDocs(self.code())]
745 }
746}
747
748impl From<Error> for Box<dyn crate::diagnostic::DiagnosticMessage> {
749 fn from(error: Error) -> Self {
750 Box::new(error) as _
751 }
752}
753
754#[cfg(test)]
755mod tests {
756 use super::*;
757
758 #[test]
759 fn test_parameter_kind() {
760 struct TestCase {
761 parameter_kind: u16,
762 kind: Kind,
763 }
764
765 for (
766 title,
767 TestCase {
768 parameter_kind,
769 kind,
770 },
771 ) in HashMap::from([
772 (
773 "bytes",
774 TestCase {
775 parameter_kind: kind::BYTES,
776 kind: Kind::bytes(),
777 },
778 ),
779 (
780 "integer",
781 TestCase {
782 parameter_kind: kind::INTEGER,
783 kind: Kind::integer(),
784 },
785 ),
786 ]) {
787 let parameter = Parameter::optional("", parameter_kind, "");
788
789 assert_eq!(parameter.kind(), kind, "{title}");
790 }
791 }
792}