Add support for b prefix for char literals

This commit is contained in:
Guillaume Gomez 2024-08-18 14:22:58 +02:00
parent f1806f7219
commit 1a24ace17b
5 changed files with 124 additions and 38 deletions

View File

@ -9,7 +9,7 @@ use std::{cmp, hash, mem, str};
use parser::node::{ use parser::node::{
Call, Comment, Cond, CondTest, FilterBlock, If, Include, Let, Lit, Loop, Match, Whitespace, Ws, Call, Comment, Cond, CondTest, FilterBlock, If, Include, Let, Lit, Loop, Match, Whitespace, Ws,
}; };
use parser::{Expr, Filter, Node, Target, WithSpan}; use parser::{CharLit, CharPrefix, Expr, Filter, Node, Target, WithSpan};
use quote::quote; use quote::quote;
use rustc_hash::FxBuildHasher; use rustc_hash::FxBuildHasher;
@ -1270,9 +1270,16 @@ impl<'a> Generator<'a> {
} }
// for now, we only escape strings and chars at compile time // for now, we only escape strings and chars at compile time
let lit = match &**s { let (lit, escape_prefix) = match &**s {
Expr::StrLit(input) => InputKind::StrLit(input), Expr::StrLit(input) => (InputKind::StrLit(input), None),
Expr::CharLit(input) => InputKind::CharLit(input), Expr::CharLit(CharLit { prefix, content }) => (
InputKind::CharLit(content),
if *prefix == Some(CharPrefix::Binary) {
Some('b')
} else {
None
},
),
_ => return None, _ => return None,
}; };
@ -1314,6 +1321,9 @@ impl<'a> Generator<'a> {
OutputKind::Text => unescaped, OutputKind::Text => unescaped,
OutputKind::Html => { OutputKind::Html => {
let mut escaped = String::with_capacity(unescaped.len() + 20); let mut escaped = String::with_capacity(unescaped.len() + 20);
if let Some(escape_prefix) = escape_prefix {
escaped.push(escape_prefix);
}
write_escaped_str(&mut escaped, &unescaped).ok()?; write_escaped_str(&mut escaped, &unescaped).ok()?;
match escaped == unescaped { match escaped == unescaped {
true => unescaped, true => unescaped,
@ -1479,7 +1489,7 @@ impl<'a> Generator<'a> {
Expr::BoolLit(s) => self.visit_bool_lit(buf, s), Expr::BoolLit(s) => self.visit_bool_lit(buf, s),
Expr::NumLit(s) => self.visit_num_lit(buf, s), Expr::NumLit(s) => self.visit_num_lit(buf, s),
Expr::StrLit(s) => self.visit_str_lit(buf, s), Expr::StrLit(s) => self.visit_str_lit(buf, s),
Expr::CharLit(s) => self.visit_char_lit(buf, s), Expr::CharLit(ref s) => self.visit_char_lit(buf, s),
Expr::Var(s) => self.visit_var(buf, s), Expr::Var(s) => self.visit_var(buf, s),
Expr::Path(ref path) => self.visit_path(buf, path), Expr::Path(ref path) => self.visit_path(buf, path),
Expr::Array(ref elements) => self.visit_array(ctx, buf, elements)?, Expr::Array(ref elements) => self.visit_array(ctx, buf, elements)?,
@ -2082,8 +2092,11 @@ impl<'a> Generator<'a> {
DisplayWrap::Unwrapped DisplayWrap::Unwrapped
} }
fn visit_char_lit(&mut self, buf: &mut Buffer, s: &str) -> DisplayWrap { fn visit_char_lit(&mut self, buf: &mut Buffer, c: &CharLit<'_>) -> DisplayWrap {
buf.write(format_args!("'{s}'")); if c.prefix == Some(CharPrefix::Binary) {
buf.write('b');
}
buf.write(format_args!("'{}'", c.content));
DisplayWrap::Unwrapped DisplayWrap::Unwrapped
} }

View File

@ -10,11 +10,10 @@ use nom::error_position;
use nom::multi::{fold_many0, many0, separated_list0}; use nom::multi::{fold_many0, many0, separated_list0};
use nom::sequence::{pair, preceded, terminated, tuple}; use nom::sequence::{pair, preceded, terminated, tuple};
use super::{ use crate::{
char_lit, filter, identifier, keyword, not_ws, num_lit, path_or_identifier, str_lit, ws, Level, char_lit, filter, identifier, keyword, not_ws, num_lit, path_or_identifier, str_lit, ws,
PathOrIdentifier, CharLit, ErrorContext, Level, ParseResult, PathOrIdentifier, WithSpan,
}; };
use crate::{ErrorContext, ParseResult, WithSpan};
macro_rules! expr_prec_layer { macro_rules! expr_prec_layer {
( $name:ident, $inner:ident, $op:expr ) => { ( $name:ident, $inner:ident, $op:expr ) => {
@ -38,7 +37,7 @@ pub enum Expr<'a> {
BoolLit(bool), BoolLit(bool),
NumLit(&'a str), NumLit(&'a str),
StrLit(&'a str), StrLit(&'a str),
CharLit(&'a str), CharLit(CharLit<'a>),
Var(&'a str), Var(&'a str),
Path(Vec<&'a str>), Path(Vec<&'a str>),
Array(Vec<WithSpan<'a, Expr<'a>>>), Array(Vec<WithSpan<'a, Expr<'a>>>),

View File

@ -425,15 +425,29 @@ fn str_lit(i: &str) -> ParseResult<'_> {
Ok((i, s.unwrap_or_default())) Ok((i, s.unwrap_or_default()))
} }
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum CharPrefix {
Binary,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CharLit<'a> {
pub prefix: Option<CharPrefix>,
pub content: &'a str,
}
// Information about allowed character escapes is available at: // Information about allowed character escapes is available at:
// <https://doc.rust-lang.org/reference/tokens.html#character-literals>. // <https://doc.rust-lang.org/reference/tokens.html#character-literals>.
fn char_lit(i: &str) -> ParseResult<'_> { fn char_lit(i: &str) -> Result<(&str, CharLit<'_>), ParseErr<'_>> {
let start = i; let start = i;
let (i, s) = delimited( let (i, (b_prefix, s)) = tuple((
char('\''), opt(char('b')),
opt(escaped(is_not("\\\'"), '\\', anychar)), delimited(
char('\''), char('\''),
)(i)?; opt(escaped(is_not("\\\'"), '\\', anychar)),
char('\''),
),
))(i)?;
let Some(s) = s else { let Some(s) = s else {
return Err(nom::Err::Failure(ErrorContext::new( return Err(nom::Err::Failure(ErrorContext::new(
@ -449,7 +463,15 @@ fn char_lit(i: &str) -> ParseResult<'_> {
}; };
let (nb, max_value, err1, err2) = match c { let (nb, max_value, err1, err2) = match c {
Char::Literal | Char::Escaped => return Ok((i, s)), Char::Literal | Char::Escaped => {
return Ok((
i,
CharLit {
prefix: b_prefix.map(|_| CharPrefix::Binary),
content: s,
},
));
}
Char::AsciiEscape(nb) => ( Char::AsciiEscape(nb) => (
nb, nb,
// `0x7F` is the maximum value for a `\x` escaped character. // `0x7F` is the maximum value for a `\x` escaped character.
@ -473,7 +495,13 @@ fn char_lit(i: &str) -> ParseResult<'_> {
return Err(nom::Err::Failure(ErrorContext::new(err2, start))); return Err(nom::Err::Failure(ErrorContext::new(err2, start)));
} }
Ok((i, s)) Ok((
i,
CharLit {
prefix: b_prefix.map(|_| CharPrefix::Binary),
content: s,
},
))
} }
/// Represents the different kinds of char declarations: /// Represents the different kinds of char declarations:
@ -820,26 +848,43 @@ mod test {
#[test] #[test]
fn test_char_lit() { fn test_char_lit() {
assert_eq!(char_lit("'a'").unwrap(), ("", "a")); let lit = |s: &'static str| crate::CharLit {
assert_eq!(char_lit("'字'").unwrap(), ("", "")); prefix: None,
content: s,
};
assert_eq!(char_lit("'a'").unwrap(), ("", lit("a")));
assert_eq!(char_lit("'字'").unwrap(), ("", lit("")));
// Escaped single characters. // Escaped single characters.
assert_eq!(char_lit("'\\\"'").unwrap(), ("", "\\\"")); assert_eq!(char_lit("'\\\"'").unwrap(), ("", lit("\\\"")));
assert_eq!(char_lit("'\\''").unwrap(), ("", "\\'")); assert_eq!(char_lit("'\\''").unwrap(), ("", lit("\\'")));
assert_eq!(char_lit("'\\t'").unwrap(), ("", "\\t")); assert_eq!(char_lit("'\\t'").unwrap(), ("", lit("\\t")));
assert_eq!(char_lit("'\\n'").unwrap(), ("", "\\n")); assert_eq!(char_lit("'\\n'").unwrap(), ("", lit("\\n")));
assert_eq!(char_lit("'\\r'").unwrap(), ("", "\\r")); assert_eq!(char_lit("'\\r'").unwrap(), ("", lit("\\r")));
assert_eq!(char_lit("'\\0'").unwrap(), ("", "\\0")); assert_eq!(char_lit("'\\0'").unwrap(), ("", lit("\\0")));
// Escaped ascii characters (up to `0x7F`). // Escaped ascii characters (up to `0x7F`).
assert_eq!(char_lit("'\\x12'").unwrap(), ("", "\\x12")); assert_eq!(char_lit("'\\x12'").unwrap(), ("", lit("\\x12")));
assert_eq!(char_lit("'\\x02'").unwrap(), ("", "\\x02")); assert_eq!(char_lit("'\\x02'").unwrap(), ("", lit("\\x02")));
assert_eq!(char_lit("'\\x6a'").unwrap(), ("", "\\x6a")); assert_eq!(char_lit("'\\x6a'").unwrap(), ("", lit("\\x6a")));
assert_eq!(char_lit("'\\x7F'").unwrap(), ("", "\\x7F")); assert_eq!(char_lit("'\\x7F'").unwrap(), ("", lit("\\x7F")));
// Escaped unicode characters (up to `0x10FFFF`). // Escaped unicode characters (up to `0x10FFFF`).
assert_eq!(char_lit("'\\u{A}'").unwrap(), ("", "\\u{A}")); assert_eq!(char_lit("'\\u{A}'").unwrap(), ("", lit("\\u{A}")));
assert_eq!(char_lit("'\\u{10}'").unwrap(), ("", "\\u{10}")); assert_eq!(char_lit("'\\u{10}'").unwrap(), ("", lit("\\u{10}")));
assert_eq!(char_lit("'\\u{aa}'").unwrap(), ("", "\\u{aa}")); assert_eq!(char_lit("'\\u{aa}'").unwrap(), ("", lit("\\u{aa}")));
assert_eq!(char_lit("'\\u{10FFFF}'").unwrap(), ("", "\\u{10FFFF}")); assert_eq!(char_lit("'\\u{10FFFF}'").unwrap(), ("", lit("\\u{10FFFF}")));
// Check with `b` prefix.
assert_eq!(
char_lit("b'a'").unwrap(),
(
"",
crate::CharLit {
prefix: Some(crate::CharPrefix::Binary),
content: "a"
}
)
);
// Should fail. // Should fail.
assert!(char_lit("''").is_err()); assert!(char_lit("''").is_err());

View File

@ -6,7 +6,7 @@ use nom::multi::separated_list1;
use nom::sequence::{pair, preceded, tuple}; use nom::sequence::{pair, preceded, tuple};
use crate::{ use crate::{
bool_lit, char_lit, identifier, keyword, num_lit, path_or_identifier, str_lit, ws, bool_lit, char_lit, identifier, keyword, num_lit, path_or_identifier, str_lit, ws, CharLit,
ErrorContext, ParseErr, ParseResult, PathOrIdentifier, State, WithSpan, ErrorContext, ParseErr, ParseResult, PathOrIdentifier, State, WithSpan,
}; };
@ -18,7 +18,7 @@ pub enum Target<'a> {
Struct(Vec<&'a str>, Vec<(&'a str, Target<'a>)>), Struct(Vec<&'a str>, Vec<(&'a str, Target<'a>)>),
NumLit(&'a str), NumLit(&'a str),
StrLit(&'a str), StrLit(&'a str),
CharLit(&'a str), CharLit(CharLit<'a>),
BoolLit(&'a str), BoolLit(&'a str),
Path(Vec<&'a str>), Path(Vec<&'a str>),
OrChain(Vec<Target<'a>>), OrChain(Vec<Target<'a>>),

29
testing/tests/literal.rs Normal file
View File

@ -0,0 +1,29 @@
use rinja::Template;
#[derive(Template)]
#[template(source = "{% if x == b'a' %}bc{% endif %}", ext = "txt")]
struct Expr {
x: u8,
}
#[test]
fn test_prefix_char_literal_in_expr() {
let t = Expr { x: b'a' };
assert_eq!(t.render().unwrap(), "bc");
}
#[derive(Template)]
#[template(
source = "{% if let Some(b'a') = Some(b'a') %}bc{% endif %}
{%- if data == [b'h', b'i'] %} hoy{% endif %}",
ext = "txt"
)]
struct Target {
data: &'static [u8],
}
#[test]
fn test_prefix_char_literal_in_target() {
let t = Target { data: b"hi" };
assert_eq!(t.render().unwrap(), "bc hoy");
}