Browse Source

Add @examples decorator

Bzero 1 year ago
parent
commit
efe59213a7

+ 1 - 1
numbat/examples/inspect.rs

@@ -41,7 +41,7 @@ and — where sensible — units allow for [binary prefixes](https://en.wikipedi
 }
 
 fn inspect_functions_in_module(ctx: &Context, module: String) {
-    for (fn_name, name, signature, description, url, code_source) in ctx.functions() {
+    for (fn_name, name, signature, description, url, examples, code_source) in ctx.functions() {
         let CodeSource::Module(module_path, _) = code_source else {
             unreachable!();
         };

+ 21 - 0
numbat/src/decorator.rs

@@ -8,6 +8,7 @@ pub enum Decorator {
     Url(String),
     Name(String),
     Description(String),
+    Example(String, Option<String>),
 }
 
 pub fn name_and_aliases<'a>(
@@ -89,6 +90,16 @@ pub fn description(decorators: &[Decorator]) -> Option<String> {
     }
 }
 
+pub fn examples(decorators: &[Decorator]) -> Vec<(String, Option<String>)> {
+    let mut examples = Vec::new();
+    for decorator in decorators {
+        if let Decorator::Example(example_code, example_description) = decorator {
+            examples.push((example_code.clone(), example_description.clone()));
+        }
+    }
+    return examples;
+}
+
 pub fn contains_aliases_with_prefixes(decorates: &[Decorator]) -> bool {
     for decorator in decorates {
         if let Decorator::Aliases(aliases) = decorator {
@@ -110,3 +121,13 @@ pub fn contains_aliases(decorators: &[Decorator]) -> bool {
 
     false
 }
+
+pub fn contains_examples(decorators: &[Decorator]) -> bool {
+    for decorator in decorators {
+        if let Decorator::Example(_, _) = decorator {
+            return true;
+        }
+    }
+
+    false
+}

+ 2 - 0
numbat/src/lib.rs

@@ -168,6 +168,7 @@ impl Context {
             String,
             Option<String>,
             Option<String>,
+            Vec<(String, Option<String>)>,
             CodeSource,
         ),
     > + '_ {
@@ -185,6 +186,7 @@ impl Context {
                         .to_string(),
                     meta.description.clone(),
                     meta.url.clone(),
+                    meta.examples.clone(),
                     self.resolver
                         .get_code_source(signature.definition_span.code_source_id),
                 )

+ 85 - 0
numbat/src/parser.rs

@@ -198,6 +198,9 @@ pub enum ParseErrorKind {
     #[error("Aliases cannot be used on functions.")]
     AliasUsedOnFunction,
 
+    #[error("Example decorators can only be used on functions.")]
+    ExampleUsedOnUnsuitableKind,
+
     #[error("Numerical overflow in dimension exponent")]
     OverflowInDimensionExponent,
 
@@ -456,6 +459,14 @@ impl Parser {
                             span: self.peek(tokens).span,
                         });
                     }
+
+                    if decorator::contains_examples(&self.decorator_stack) {
+                        return Err(ParseError {
+                            kind: ParseErrorKind::ExampleUsedOnUnsuitableKind,
+                            span: self.peek(tokens).span,
+                        });
+                    }
+
                     std::mem::swap(&mut decorators, &mut self.decorator_stack);
                 }
 
@@ -735,6 +746,55 @@ impl Parser {
                         });
                     }
                 }
+                "example" => {
+                    if self.match_exact(tokens, TokenKind::LeftParen).is_some() {
+                        if let Some(token_code) = self.match_exact(tokens, TokenKind::StringFixed) {
+                            if self.match_exact(tokens, TokenKind::Comma).is_some() {
+                                //Code and description
+                                if let Some(token_description) =
+                                    self.match_exact(tokens, TokenKind::StringFixed)
+                                {
+                                    if self.match_exact(tokens, TokenKind::RightParen).is_none() {
+                                        return Err(ParseError::new(
+                                            ParseErrorKind::MissingClosingParen,
+                                            self.peek(tokens).span,
+                                        ));
+                                    }
+
+                                    Decorator::Example(
+                                        strip_and_escape(&token_code.lexeme),
+                                        Some(strip_and_escape(&token_description.lexeme)),
+                                    )
+                                } else {
+                                    return Err(ParseError {
+                                        kind: ParseErrorKind::ExpectedString,
+                                        span: self.peek(tokens).span,
+                                    });
+                                }
+                            } else {
+                                //Code but no description
+                                if self.match_exact(tokens, TokenKind::RightParen).is_none() {
+                                    return Err(ParseError::new(
+                                        ParseErrorKind::MissingClosingParen,
+                                        self.peek(tokens).span,
+                                    ));
+                                }
+
+                                Decorator::Example(strip_and_escape(&token_code.lexeme), None)
+                            }
+                        } else {
+                            return Err(ParseError {
+                                kind: ParseErrorKind::ExpectedString,
+                                span: self.peek(tokens).span,
+                            });
+                        }
+                    } else {
+                        return Err(ParseError {
+                            kind: ParseErrorKind::ExpectedLeftParenAfterDecorator,
+                            span: self.peek(tokens).span,
+                        });
+                    }
+                }
                 _ => {
                     return Err(ParseError {
                         kind: ParseErrorKind::UnknownDecorator,
@@ -769,6 +829,13 @@ impl Parser {
 
             let unit_name = identifier.lexeme.to_owned();
 
+            if decorator::contains_examples(&self.decorator_stack) {
+                return Err(ParseError {
+                    kind: ParseErrorKind::ExampleUsedOnUnsuitableKind,
+                    span: self.peek(tokens).span,
+                });
+            }
+
             let mut decorators = vec![];
             std::mem::swap(&mut decorators, &mut self.decorator_stack);
 
@@ -2795,6 +2862,24 @@ mod tests {
             },
         );
 
+        parse_as(
+            &["@name(\"Some function\") @example(\"some_function(2)\", \"Use this function:\") @example(\"let some_var = some_function(0)\") fn some_function(x) = 1"],
+            Statement::DefineFunction {
+                function_name_span: Span::dummy(),
+                function_name: "some_function".into(),
+                type_parameters: vec![],
+                parameters: vec![(Span::dummy(), "x".into(), None)],
+                body: Some(scalar!(1.0)),
+                local_variables: vec![],
+                return_type_annotation: None,
+                decorators: vec![
+                    decorator::Decorator::Name("Some function".into()),
+                    decorator::Decorator::Example("some_function(2)".into(), Some("Use this function:".into())),
+                    decorator::Decorator::Example("let some_var = some_function(0)".into(), None),
+                ],
+            },
+        );
+
         parse_as(
             &["fn double_kef(x) = y where y = x * 2"],
             Statement::DefineFunction {

+ 1 - 0
numbat/src/typechecker/environment.rs

@@ -68,6 +68,7 @@ pub struct FunctionMetadata {
     pub name: Option<String>,
     pub url: Option<String>,
     pub description: Option<String>,
+    pub examples: Vec<(String, Option<String>)>,
 }
 
 #[derive(Clone, Debug)]

+ 1 - 0
numbat/src/typechecker/mod.rs

@@ -1420,6 +1420,7 @@ impl TypeChecker {
                         name: crate::decorator::name(decorators),
                         url: crate::decorator::url(decorators),
                         description: crate::decorator::description(decorators),
+                        examples: crate::decorator::examples(decorators),
                     },
                 );
 

+ 11 - 0
numbat/src/typed_ast.rs

@@ -857,6 +857,17 @@ fn decorator_markup(decorators: &Vec<Decorator>) -> Markup {
                         + m::string(description)
                         + m::operator(")")
                 }
+                Decorator::Example(example_code, example_description) => {
+                    m::decorator("@example")
+                        + m::operator("(")
+                        + m::string(example_code)
+                        + if let Some(example_description) = example_description {
+                            m::operator(", ") + m::string(example_description)
+                        } else {
+                            m::empty()
+                        }
+                        + m::operator(")")
+                }
             }
             + m::nl();
     }