Skip to content

Commit

Permalink
Move Syntax configuration and check into parser
Browse files Browse the repository at this point in the history
  • Loading branch information
Kijewski committed Aug 19, 2024
1 parent cd3ff13 commit 5ee4728
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 134 deletions.
2 changes: 1 addition & 1 deletion rinja_derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ rust-version = "1.71"
proc-macro = true

[features]
config = ["dep:serde", "dep:basic-toml"]
config = ["dep:serde", "dep:basic-toml", "parser/config"]
humansize = []
urlencode = []
serde_json = []
Expand Down
127 changes: 5 additions & 122 deletions rinja_derive/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::{env, fs};

use once_map::sync::OnceMap;
use parser::node::Whitespace;
use parser::{ParseError, Parsed, Syntax};
use parser::{ParseError, Parsed, Syntax, SyntaxBuilder};
use proc_macro2::Span;
use rustc_hash::FxBuildHasher;
#[cfg(feature = "config")]
Expand Down Expand Up @@ -157,9 +157,9 @@ impl Config {
let name = raw_s.name;
match syntaxes.entry(name.to_string()) {
Entry::Vacant(entry) => {
entry.insert(SyntaxAndCache::new(
raw_s.to_syntax(config_span, file_info.as_ref())?,
));
entry.insert(raw_s.to_syntax().map(SyntaxAndCache::new).map_err(
|err| CompileError::new_with_span(err, file_info, config_span),
)?);
}
Entry::Occupied(_) => {
return Err(CompileError::new(
Expand Down Expand Up @@ -307,118 +307,12 @@ impl<'a> SyntaxAndCache<'a> {
}
}

impl<'a> RawSyntax<'a> {
fn to_syntax(
&self,
config_span: Option<Span>,
file_info: Option<&FileInfo<'_>>,
) -> Result<Syntax<'a>, CompileError> {
let default = Syntax::default();
let syntax = Syntax {
block_start: self.block_start.unwrap_or(default.block_start),
block_end: self.block_end.unwrap_or(default.block_end),
expr_start: self.expr_start.unwrap_or(default.expr_start),
expr_end: self.expr_end.unwrap_or(default.expr_end),
comment_start: self.comment_start.unwrap_or(default.comment_start),
comment_end: self.comment_end.unwrap_or(default.comment_end),
};

for (s, k) in [
(syntax.block_start, "opening block"),
(syntax.block_end, "closing block"),
(syntax.expr_start, "opening expression"),
(syntax.expr_end, "closing expression"),
(syntax.comment_start, "opening comment"),
(syntax.comment_end, "closing comment"),
] {
if s.len() < 2 {
return Err(CompileError::new_with_span(
format!(
"delimiters must be at least two characters long. \
The {k} delimiter ({s:?}) is too short",
),
file_info.copied(),
config_span,
));
} else if s.chars().any(|c| c.is_whitespace()) {
return Err(CompileError::new_with_span(
format!(
"delimiters may not contain white spaces. \
The {k} delimiter ({s:?}) contains white spaces",
),
file_info.copied(),
config_span,
));
}
}

for ((s1, k1), (s2, k2)) in [
(
(syntax.block_start, "block"),
(syntax.expr_start, "expression"),
),
(
(syntax.block_start, "block"),
(syntax.comment_start, "comment"),
),
(
(syntax.expr_start, "expression"),
(syntax.comment_start, "comment"),
),
] {
if s1.starts_with(s2) || s2.starts_with(s1) {
let (s1, k1, s2, k2) = match s1.len() < s2.len() {
true => (s1, k1, s2, k2),
false => (s2, k2, s1, k1),
};
return Err(CompileError::new_with_span(
format!(
"an opening delimiter may not be the prefix of another delimiter. \
The {k1} delimiter ({s1:?}) clashes with the {k2} delimiter ({s2:?})",
),
file_info.copied(),
config_span,
));
}
}

for (end, kind) in [
(syntax.block_end, "block"),
(syntax.expr_end, "expression"),
(syntax.comment_end, "comment"),
] {
for prefix in ["<<", ">>", "&&", "..", "||"] {
if end.starts_with(prefix) {
let msg = if end == prefix {
format!(
"a closing delimiter must not start with an operator. \
The {kind} delimiter ({end:?}) is also an operator",
)
} else {
format!(
"a closing delimiter must not start an with operator. \
The {kind} delimiter ({end:?}) starts with the {prefix:?} operator",
)
};
return Err(CompileError::new_with_span(
msg,
file_info.copied(),
config_span,
));
}
}
}

Ok(syntax)
}
}

#[cfg_attr(feature = "config", derive(Deserialize))]
#[derive(Default)]
struct RawConfig<'a> {
#[cfg_attr(feature = "config", serde(borrow))]
general: Option<General<'a>>,
syntax: Option<Vec<RawSyntax<'a>>>,
syntax: Option<Vec<SyntaxBuilder<'a>>>,
escaper: Option<Vec<RawEscaper<'a>>>,
}

Expand Down Expand Up @@ -473,17 +367,6 @@ struct General<'a> {
whitespace: WhitespaceHandling,
}

#[cfg_attr(feature = "config", derive(Deserialize))]
struct RawSyntax<'a> {
name: &'a str,
block_start: Option<&'a str>,
block_end: Option<&'a str>,
expr_start: Option<&'a str>,
expr_end: Option<&'a str>,
comment_start: Option<&'a str>,
comment_end: Option<&'a str>,
}

#[cfg_attr(feature = "config", derive(Deserialize))]
struct RawEscaper<'a> {
path: &'a str,
Expand Down
2 changes: 1 addition & 1 deletion rinja_derive_standalone/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ publish = false
[features]
default = ["__standalone"]
__standalone = []
config = ["dep:serde", "dep:basic-toml"]
config = ["dep:serde", "dep:basic-toml", "parser/config"]
humansize = []
urlencode = []
serde_json = []
Expand Down
4 changes: 4 additions & 0 deletions rinja_parser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@ readme = "README.md"
edition = "2021"
rust-version = "1.71"

[features]
config = ["dep:serde"]

[dependencies]
memchr = "2"
nom = { version = "7", default-features = false, features = ["alloc"] }
serde = { version = "1.0", optional = true, features = ["derive"] }

[dev-dependencies]
criterion = "0.5"
Expand Down
142 changes: 139 additions & 3 deletions rinja_parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -696,8 +696,12 @@ impl<'a> State<'a> {
}
}

#[derive(Debug, Hash, PartialEq)]
pub struct Syntax<'a> {
#[derive(Default, Hash, PartialEq, Clone, Copy)]
pub struct Syntax<'a>(InnerSyntax<'a>);

// This abstraction ensures that the fields are readable, but not writable.
#[derive(Hash, PartialEq, Clone, Copy)]
pub struct InnerSyntax<'a> {
pub block_start: &'a str,
pub block_end: &'a str,
pub expr_start: &'a str,
Expand All @@ -706,7 +710,16 @@ pub struct Syntax<'a> {
pub comment_end: &'a str,
}

impl Default for Syntax<'static> {
impl<'a> Deref for Syntax<'a> {
type Target = InnerSyntax<'a>;

#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}

impl Default for InnerSyntax<'static> {
fn default() -> Self {
Self {
block_start: "{%",
Expand All @@ -719,6 +732,129 @@ impl Default for Syntax<'static> {
}
}

impl<'a> fmt::Debug for InnerSyntax<'a> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt_syntax("Syntax", self, f)
}
}

impl<'a> fmt::Debug for Syntax<'a> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt_syntax("InnerSyntax", self, f)
}
}

fn fmt_syntax(name: &str, inner: &InnerSyntax<'_>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct(name)
.field("block_start", &inner.block_start)
.field("block_end", &inner.block_end)
.field("expr_start", &inner.expr_start)
.field("expr_end", &inner.expr_end)
.field("comment_start", &inner.comment_start)
.field("comment_end", &inner.comment_end)
.finish()
}

#[derive(Debug, Default, Clone, Copy, Hash, PartialEq)]
#[cfg_attr(feature = "config", derive(serde::Deserialize))]
pub struct SyntaxBuilder<'a> {
pub name: &'a str,
pub block_start: Option<&'a str>,
pub block_end: Option<&'a str>,
pub expr_start: Option<&'a str>,
pub expr_end: Option<&'a str>,
pub comment_start: Option<&'a str>,
pub comment_end: Option<&'a str>,
}

impl<'a> SyntaxBuilder<'a> {
pub fn to_syntax(&self) -> Result<Syntax<'a>, String> {
let default = InnerSyntax::default();
let syntax = Syntax(InnerSyntax {
block_start: self.block_start.unwrap_or(default.block_start),
block_end: self.block_end.unwrap_or(default.block_end),
expr_start: self.expr_start.unwrap_or(default.expr_start),
expr_end: self.expr_end.unwrap_or(default.expr_end),
comment_start: self.comment_start.unwrap_or(default.comment_start),
comment_end: self.comment_end.unwrap_or(default.comment_end),
});

for (s, k) in [
(syntax.block_start, "opening block"),
(syntax.block_end, "closing block"),
(syntax.expr_start, "opening expression"),
(syntax.expr_end, "closing expression"),
(syntax.comment_start, "opening comment"),
(syntax.comment_end, "closing comment"),
] {
if s.len() < 2 {
return Err(format!(
"delimiters must be at least two characters long. \
The {k} delimiter ({s:?}) is too short",
));
} else if s.chars().any(|c| c.is_whitespace()) {
return Err(format!(
"delimiters may not contain white spaces. \
The {k} delimiter ({s:?}) contains white spaces",
));
}
}

for ((s1, k1), (s2, k2)) in [
(
(syntax.block_start, "block"),
(syntax.expr_start, "expression"),
),
(
(syntax.block_start, "block"),
(syntax.comment_start, "comment"),
),
(
(syntax.expr_start, "expression"),
(syntax.comment_start, "comment"),
),
] {
if s1.starts_with(s2) || s2.starts_with(s1) {
let (s1, k1, s2, k2) = match s1.len() < s2.len() {
true => (s1, k1, s2, k2),
false => (s2, k2, s1, k1),
};
return Err(format!(
"an opening delimiter may not be the prefix of another delimiter. \
The {k1} delimiter ({s1:?}) clashes with the {k2} delimiter ({s2:?})",
));
}
}

for (end, kind) in [
(syntax.block_end, "block"),
(syntax.expr_end, "expression"),
(syntax.comment_end, "comment"),
] {
for prefix in ["<<", ">>", "&&", "..", "||"] {
if end.starts_with(prefix) {
let msg = if end == prefix {
format!(
"a closing delimiter must not start with an operator. \
The {kind} delimiter ({end:?}) is also an operator",
)
} else {
format!(
"a closing delimiter must not start an with operator. \
The {kind} delimiter ({end:?}) starts with the {prefix:?} operator",
)
};
return Err(msg);
}
}
}

Ok(syntax)
}
}

#[derive(Clone, Copy, Default)]
pub(crate) struct Level(u8);

Expand Down
14 changes: 7 additions & 7 deletions rinja_parser/src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::node::{Lit, Whitespace, Ws};
use crate::{Ast, Expr, Filter, Node, StrLit, Syntax, WithSpan};
use crate::{Ast, Expr, Filter, InnerSyntax, Node, StrLit, Syntax, WithSpan};

impl<T> WithSpan<'static, T> {
fn no_span(inner: T) -> Self {
Expand Down Expand Up @@ -372,21 +372,21 @@ fn test_rust_macro() {

#[test]
fn change_delimiters_parse_filter() {
let syntax = Syntax {
let syntax = Syntax(InnerSyntax {
expr_start: "{=",
expr_end: "=}",
..Syntax::default()
};
..InnerSyntax::default()
});
Ast::from_str("{= strvar|e =}", None, &syntax).unwrap();
}

#[test]
fn unicode_delimiters_in_syntax() {
let syntax = Syntax {
let syntax = Syntax(InnerSyntax {
expr_start: "🖎", // U+1F58E == b"\xf0\x9f\x96\x8e"
expr_end: "✍", // U+270D = b'\xe2\x9c\x8d'
..Syntax::default()
};
..InnerSyntax::default()
});
assert_eq!(
Ast::from_str("Here comes the expression: 🖎 e ✍.", None, &syntax)
.unwrap()
Expand Down

0 comments on commit 5ee4728

Please sign in to comment.