From 058e6be516091f6432973fb553d3067764290aaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Mon, 16 Mar 2026 09:44:38 +0100 Subject: [PATCH 1/3] Move AstFailure to error module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- src/errors.rs | 52 +++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 5 ++++- src/parser/mod.rs | 51 +--------------------------------------------- 3 files changed, 57 insertions(+), 51 deletions(-) create mode 100644 src/errors.rs diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..5ca0c36 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,52 @@ +use thiserror::Error; + +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) + } +} diff --git a/src/lib.rs b/src/lib.rs index 9311ac0..685fbb8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,6 +67,9 @@ unstable_pub!( mod parser ); +/// Nomo Functions +pub mod errors; + /// Nomo Functions pub mod functions; /// Input for nomo @@ -87,7 +90,7 @@ pub enum NomoError { 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 { From 560d37f633d6d571da397f279ec4e54531e40940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Mon, 16 Mar 2026 09:46:25 +0100 Subject: [PATCH 2/3] Move ParseFailure to errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- src/errors.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++++++ src/lexer/mod.rs | 49 +----------------------------------------- src/lib.rs | 4 ++-- 3 files changed, 59 insertions(+), 50 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 5ca0c36..427a343 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,5 +1,13 @@ +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)] @@ -50,3 +58,51 @@ impl AstFailure { 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 685fbb8..8d970d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,7 @@ feature = "document-features", cfg_attr(doc, doc = ::document_features::document_features!()) )] -//! +//! use std::collections::HashMap; @@ -84,7 +84,7 @@ pub enum NomoError { ParseError { #[from] #[expect(missing_docs)] - source: lexer::ParseFailure, + source: errors::ParseFailure, }, /// Invalid Template AstError { From 79a037b7493c877d48fe18da2cb8804bbfa8d0ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Mon, 16 Mar 2026 10:04:33 +0100 Subject: [PATCH 3/3] Flesh out syntax part of documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- src/lib.rs | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/value.rs | 4 +-- 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8d970d7..b16d474 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,75 @@ 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; 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, }