diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..427a343 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,108 @@ +use std::sync::Arc; + +use annotate_snippets::AnnotationKind; +use annotate_snippets::Level; +use annotate_snippets::Renderer; +use annotate_snippets::Snippet; +use thiserror::Error; + +use crate::input::NomoInput; +use crate::lexer::ParseError; +use crate::parser::AstError; + +#[derive(Debug, Error)] +pub struct AstFailure { + errors: Vec, +} + +impl std::fmt::Display for AstFailure { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("TODO") + } +} + +impl AstFailure { + pub(crate) fn from_errors(errors: Vec) -> AstFailure { + AstFailure { errors } + } + + pub fn to_report(&self, source: &str) -> String { + let reports = self + .errors + .iter() + .map(|error| { + annotate_snippets::Level::ERROR + .primary_title( + error + .message + .as_deref() + .unwrap_or("An error occurred while producing an Ast"), + ) + .element( + annotate_snippets::Snippet::source(source).annotation( + annotate_snippets::AnnotationKind::Primary + .span(error.span.clone().map(|s| s.range).unwrap_or_else(|| 0..0)), + ), + ) + .elements( + error + .help + .as_ref() + .map(|help| annotate_snippets::Level::HELP.message(help)), + ) + }) + .collect::>(); + + let renderer = annotate_snippets::Renderer::styled() + .decor_style(annotate_snippets::renderer::DecorStyle::Unicode); + renderer.render(&reports) + } +} + +#[derive(Debug, Error)] +pub struct ParseFailure { + input: Arc, + errors: Vec, +} + +impl std::fmt::Display for ParseFailure { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.to_report()) + } +} + +impl ParseFailure { + pub(crate) fn from_errors(errors: Vec, input: NomoInput) -> ParseFailure { + ParseFailure { + input: Arc::from(input.to_string()), + errors, + } + } + + pub fn to_report(&self) -> String { + let reports = self + .errors + .iter() + .map(|error| { + Level::ERROR + .primary_title( + error + .message + .as_deref() + .unwrap_or("An error occurred while parsing"), + ) + .element( + Snippet::source(self.input.as_ref()).annotation( + AnnotationKind::Primary + .span(error.span.clone().map(|s| s.range).unwrap_or_else(|| 0..0)), + ), + ) + .elements(error.help.as_ref().map(|help| Level::HELP.message(help))) + }) + .collect::>(); + + let renderer = + Renderer::styled().decor_style(annotate_snippets::renderer::DecorStyle::Unicode); + renderer.render(&reports) + } +} diff --git a/src/lexer/mod.rs b/src/lexer/mod.rs index 277f3e3..65872b7 100644 --- a/src/lexer/mod.rs +++ b/src/lexer/mod.rs @@ -39,60 +39,13 @@ use winnow::token::take_until; use winnow::token::take_while; use crate::SourceSpan; +use crate::errors::ParseFailure; use crate::input::NomoInput; use crate::resume_after_cut; type Input<'input> = Recoverable, ParseError>; type PResult<'input, T> = Result; -#[derive(Debug, Error)] -pub struct ParseFailure { - input: Arc, - errors: Vec, -} - -impl std::fmt::Display for ParseFailure { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.to_report()) - } -} - -impl ParseFailure { - fn from_errors(errors: Vec, input: NomoInput) -> ParseFailure { - ParseFailure { - input: Arc::from(input.to_string()), - errors, - } - } - - pub fn to_report(&self) -> String { - let reports = self - .errors - .iter() - .map(|error| { - Level::ERROR - .primary_title( - error - .message - .as_deref() - .unwrap_or("An error occurred while parsing"), - ) - .element( - Snippet::source(self.input.as_ref()).annotation( - AnnotationKind::Primary - .span(error.span.clone().map(|s| s.range).unwrap_or_else(|| 0..0)), - ), - ) - .elements(error.help.as_ref().map(|help| Level::HELP.message(help))) - }) - .collect::>(); - - let renderer = - Renderer::styled().decor_style(annotate_snippets::renderer::DecorStyle::Unicode); - renderer.render(&reports) - } -} - #[derive(Debug, Clone)] pub struct ParseError { pub(crate) message: Option, diff --git a/src/lib.rs b/src/lib.rs index 9311ac0..b16d474 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,76 @@ feature = "document-features", cfg_attr(doc, doc = ::document_features::document_features!()) )] -//! +//! +//! ## Syntax +//! +//! Nomo tries to keep a consistent syntax across its features. +//! The main concepts are: +//! +//! [`nomo`](crate) uses `{{ }}` as its identifiers. Each of them forms a 'block'. +//! +//! There are two kinds of blocks: +//! +//! - `{{= }}` Interpolations +//! - `{{ }}` everything else +//! +//! ### Expressions +//! +//! An expression in [`nomo`](crate) is anything that produces a value. Notably, control structures +//! _do not_ create values. This is different to rust. +//! +//! So for example this does not work: +//! +//! ```nomo +//! {{= if is_active "Active" else "Inactive" end }} +//! ``` +//! +//! If you wish to conditionally output something, you would write: +//! +//! ```nomo +//! {{ if is_active }} +//! Active +//! {{ else }} +//! Inactive +//! {{ end }} +//! ``` +//! +//! In expressions you can write: +//! - Mathematical expressions (`+-*/`) +//! - Logical/Binary expressions (`&&`, `||`, `>`, ...) +//! - Literals (`12`, `292.21`, `"Hello"`) +//! +//! ### Interpolations +//! +//! Interpolations is how one prints out data. A [`NomoValue`] can be printed if it is a: +//! +//! - String +//! - Integer/SignedInteger +//! - Bool +//! - Float +//! +//! All other values will result in an error. +//! +//! ### Control Structures +//! +//! [`Nomo`](crate) supports several control structures: +//! +//! **Conditions `if/else`**: +//! +//! ```nomo +//! {{ if }} +//! {{ else if }} +//! {{ else }} +//! {{ end }} +//! ``` +//! +//! **Loops `for .. in`** +//! +//! ```nomo +//! {{ for in }} +//! {{ else }} +//! {{ end }} +//! ``` use std::collections::HashMap; @@ -67,6 +136,9 @@ unstable_pub!( mod parser ); +/// Nomo Functions +pub mod errors; + /// Nomo Functions pub mod functions; /// Input for nomo @@ -81,13 +153,13 @@ pub enum NomoError { ParseError { #[from] #[expect(missing_docs)] - source: lexer::ParseFailure, + source: errors::ParseFailure, }, /// Invalid Template AstError { #[from] #[expect(missing_docs)] - source: parser::AstFailure, + source: errors::AstFailure, }, /// An error occurred while evaluating diff --git a/src/parser/mod.rs b/src/parser/mod.rs index a357725..73cc0b2 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1,4 +1,3 @@ -use thiserror::Error; use winnow::Parser; use winnow::RecoverableParser; use winnow::combinator::Infix::Left; @@ -28,6 +27,7 @@ use winnow::stream::TokenSlice; use winnow::token::any; use crate::SourceSpan; +use crate::errors::AstFailure; use crate::lexer::TemplateToken; use crate::lexer::TokenKind; use crate::lexer::TokenOperator; @@ -151,55 +151,6 @@ impl ParserError> for AstError { } } -#[derive(Debug, Error)] -pub struct AstFailure { - errors: Vec, -} - -impl std::fmt::Display for AstFailure { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("TODO") - } -} - -impl AstFailure { - fn from_errors(errors: Vec) -> AstFailure { - AstFailure { errors } - } - - pub fn to_report(&self, source: &str) -> String { - let reports = self - .errors - .iter() - .map(|error| { - annotate_snippets::Level::ERROR - .primary_title( - error - .message - .as_deref() - .unwrap_or("An error occurred while producing an Ast"), - ) - .element( - annotate_snippets::Snippet::source(source).annotation( - annotate_snippets::AnnotationKind::Primary - .span(error.span.clone().map(|s| s.range).unwrap_or_else(|| 0..0)), - ), - ) - .elements( - error - .help - .as_ref() - .map(|help| annotate_snippets::Level::HELP.message(help)), - ) - }) - .collect::>(); - - let renderer = annotate_snippets::Renderer::styled() - .decor_style(annotate_snippets::renderer::DecorStyle::Unicode); - renderer.render(&reports) - } -} - type Input<'input> = Recoverable, AstError>; impl<'input> Parser, TemplateToken, AstError> for TokenKind { diff --git a/src/value.rs b/src/value.rs index 903fc73..b652dfd 100644 --- a/src/value.rs +++ b/src/value.rs @@ -246,12 +246,12 @@ impl NomoValue { pub(crate) fn try_to_string(&self) -> Option { match self { NomoValue::String { value } => Some(value.to_string()), - NomoValue::Array { .. } => None, NomoValue::Bool { value } => Some(value.to_string()), - NomoValue::Object { .. } => None, NomoValue::Integer { value } => Some(value.to_string()), NomoValue::SignedInteger { value } => Some(value.to_string()), NomoValue::Float { value } => Some(value.to_string()), + NomoValue::Array { .. } => None, + NomoValue::Object { .. } => None, NomoValue::Iterator { .. } => None, NomoValue::Undefined => None, }