Compare commits

..

3 commits

Author SHA1 Message Date
79a037b749 Flesh out syntax part of documentation
Signed-off-by: Marcel Müller <neikos@neikos.email>
2026-03-16 10:04:33 +01:00
560d37f633 Move ParseFailure to errors
Signed-off-by: Marcel Müller <neikos@neikos.email>
2026-03-16 09:46:25 +01:00
058e6be516 Move AstFailure to error module
Signed-off-by: Marcel Müller <neikos@neikos.email>
2026-03-16 09:44:38 +01:00
5 changed files with 187 additions and 103 deletions

108
src/errors.rs Normal file
View file

@ -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<AstError>,
}
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<AstError>) -> 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::<Vec<_>>();
let renderer = annotate_snippets::Renderer::styled()
.decor_style(annotate_snippets::renderer::DecorStyle::Unicode);
renderer.render(&reports)
}
}
#[derive(Debug, Error)]
pub struct ParseFailure {
input: Arc<str>,
errors: Vec<ParseError>,
}
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<ParseError>, 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::<Vec<_>>();
let renderer =
Renderer::styled().decor_style(annotate_snippets::renderer::DecorStyle::Unicode);
renderer.render(&reports)
}
}

View file

@ -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<LocatingSlice<NomoInput>, ParseError>;
type PResult<'input, T> = Result<T, ParseError>;
#[derive(Debug, Error)]
pub struct ParseFailure {
input: Arc<str>,
errors: Vec<ParseError>,
}
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<ParseError>, 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::<Vec<_>>();
let renderer =
Renderer::styled().decor_style(annotate_snippets::renderer::DecorStyle::Unicode);
renderer.render(&reports)
}
}
#[derive(Debug, Clone)]
pub struct ParseError {
pub(crate) message: Option<String>,

View file

@ -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:
//!
//! - `{{= <expr> }}` Interpolations
//! - `{{ <control> }}` 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 <expr> }}
//! {{ else if <expr> }}
//! {{ else }}
//! {{ end }}
//! ```
//!
//! **Loops `for .. in`**
//!
//! ```nomo
//! {{ for <identifier> in <expr> }}
//! {{ 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

View file

@ -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<Input<'_>> for AstError {
}
}
#[derive(Debug, Error)]
pub struct AstFailure {
errors: Vec<AstError>,
}
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<AstError>) -> 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::<Vec<_>>();
let renderer = annotate_snippets::Renderer::styled()
.decor_style(annotate_snippets::renderer::DecorStyle::Unicode);
renderer.render(&reports)
}
}
type Input<'input> = Recoverable<TokenSlice<'input, TemplateToken>, AstError>;
impl<'input> Parser<Input<'input>, TemplateToken, AstError> for TokenKind {

View file

@ -246,12 +246,12 @@ impl NomoValue {
pub(crate) fn try_to_string(&self) -> Option<String> {
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,
}