Merge pull request #142 from Kijewski/pr-more-const-lit

derive: escape `int`, `float`, `bool` at compile time
This commit is contained in:
Guillaume Gomez 2024-08-19 14:43:09 +02:00 committed by GitHub
commit 88047ac92b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 383 additions and 207 deletions

View File

@ -9,7 +9,10 @@ use std::{cmp, hash, mem, str};
use parser::node::{
Call, Comment, Cond, CondTest, FilterBlock, If, Include, Let, Lit, Loop, Match, Whitespace, Ws,
};
use parser::{CharLit, CharPrefix, Expr, Filter, Node, StrLit, StrPrefix, Target, WithSpan};
use parser::{
CharLit, CharPrefix, Expr, Filter, FloatKind, IntKind, Node, Num, StrLit, StrPrefix, Target,
WithSpan,
};
use quote::quote;
use rustc_hash::FxBuildHasher;
@ -399,7 +402,7 @@ impl<'a> Generator<'a> {
let (expr, span) = expr.deconstruct();
match expr {
Expr::NumLit(_)
Expr::NumLit(_, _)
| Expr::StrLit(_)
| Expr::CharLit(_)
| Expr::Var(_)
@ -1256,88 +1259,9 @@ impl<'a> Generator<'a> {
}
fn write_expr(&mut self, ws: Ws, s: &'a WithSpan<'a, Expr<'a>>) {
// In here, we inspect in the expression if it is a literal, and if it is, whether it
// can be escaped at compile time. We use an IIFE to make the code more readable
// (immediate returns, try expressions).
let writable = (|| -> Option<Writable<'a>> {
enum InputKind<'a> {
StrLit(&'a str),
CharLit(&'a str),
}
enum OutputKind {
Html,
Text,
}
// for now, we only escape strings and chars at compile time
let (lit, escape_prefix) = match &**s {
Expr::StrLit(StrLit { prefix, content }) => {
(InputKind::StrLit(content), prefix.map(|p| p.to_char()))
}
Expr::CharLit(CharLit { prefix, content }) => (
InputKind::CharLit(content),
if *prefix == Some(CharPrefix::Binary) {
Some('b')
} else {
None
},
),
_ => return None,
};
// we only optimize for known escapers
let output = match self.input.escaper.strip_prefix(CRATE)? {
"::filters::Html" => OutputKind::Html,
"::filters::Text" => OutputKind::Text,
_ => return None,
};
// the input could be string escaped if it contains any backslashes
let escaped = match lit {
InputKind::StrLit(s) => s,
InputKind::CharLit(s) => s,
};
let unescaped = if escaped.find('\\').is_none() {
// if the literal does not contain any backslashes, then it does not need unescaping
Cow::Borrowed(escaped)
} else {
// convert the input into a TokenStream and extract the first token
Cow::Owned(match lit {
InputKind::StrLit(escaped) => {
let input = format!(r#""{escaped}""#);
let input = input.parse().ok()?;
let input = syn::parse2::<syn::LitStr>(input).ok()?;
input.value()
}
InputKind::CharLit(escaped) => {
let input = format!(r#"'{escaped}'"#);
let input = input.parse().ok()?;
let input = syn::parse2::<syn::LitChar>(input).ok()?;
input.value().to_string()
}
})
};
// escape the un-string-escaped input using the selected escaper
Some(Writable::Lit(match output {
OutputKind::Text => unescaped,
OutputKind::Html => {
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()?;
match escaped == unescaped {
true => unescaped,
false => Cow::Owned(escaped),
}
}
}))
})()
.unwrap_or(Writable::Expr(s));
self.handle_ws(ws);
self.buf_writable.push(writable);
self.buf_writable
.push(compile_time_escape(s, self.input.escaper).unwrap_or(Writable::Expr(s)));
}
// Write expression buffer and empty
@ -1489,7 +1413,7 @@ impl<'a> Generator<'a> {
) -> Result<DisplayWrap, CompileError> {
Ok(match **expr {
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(ref s) => self.visit_str_lit(buf, s),
Expr::CharLit(ref s) => self.visit_char_lit(buf, s),
Expr::Var(s) => self.visit_var(buf, s),
@ -2206,7 +2130,7 @@ impl<'a> Generator<'a> {
}
self.visit_str_lit(buf, s);
}
Target::NumLit(s) => {
Target::NumLit(s, _) => {
if first_level {
buf.write('&');
}
@ -2286,6 +2210,144 @@ impl<'a> Generator<'a> {
}
}
/// In here, we inspect in the expression if it is a literal, and if it is, whether it
/// can be escaped at compile time.
fn compile_time_escape<'a>(expr: &Expr<'a>, escaper: &str) -> Option<Writable<'a>> {
// we only optimize for known escapers
enum OutputKind {
Html,
Text,
}
// we only optimize for known escapers
let output = match escaper.strip_prefix(CRATE)? {
"::filters::Html" => OutputKind::Html,
"::filters::Text" => OutputKind::Text,
_ => return None,
};
// for now, we only escape strings, chars, numbers, and bools at compile time
let value = match *expr {
Expr::StrLit(StrLit {
prefix: None,
content,
}) => {
if content.find('\\').is_none() {
// if the literal does not contain any backslashes, then it does not need unescaping
Cow::Borrowed(content)
} else {
// the input could be string escaped if it contains any backslashes
let input = format!(r#""{content}""#);
let input = input.parse().ok()?;
let input = syn::parse2::<syn::LitStr>(input).ok()?;
Cow::Owned(input.value())
}
}
Expr::CharLit(CharLit {
prefix: None,
content,
}) => {
if content.find('\\').is_none() {
// if the literal does not contain any backslashes, then it does not need unescaping
Cow::Borrowed(content)
} else {
// the input could be string escaped if it contains any backslashes
let input = format!(r#"'{content}'"#);
let input = input.parse().ok()?;
let input = syn::parse2::<syn::LitChar>(input).ok()?;
Cow::Owned(input.value().to_string())
}
}
Expr::NumLit(_, value) => {
enum NumKind {
Int(Option<IntKind>),
Float(Option<FloatKind>),
}
let (orig_value, kind) = match value {
Num::Int(value, kind) => (value, NumKind::Int(kind)),
Num::Float(value, kind) => (value, NumKind::Float(kind)),
};
let value = match orig_value.chars().any(|c| c == '_') {
true => Cow::Owned(orig_value.chars().filter(|&c| c != '_').collect()),
false => Cow::Borrowed(orig_value),
};
fn int<T: ToString, E>(
from_str_radix: impl Fn(&str, u32) -> Result<T, E>,
value: &str,
) -> Option<String> {
Some(from_str_radix(value, 10).ok()?.to_string())
}
let value = match kind {
NumKind::Int(Some(IntKind::I8)) => int(i8::from_str_radix, &value)?,
NumKind::Int(Some(IntKind::I16)) => int(i16::from_str_radix, &value)?,
NumKind::Int(Some(IntKind::I32)) => int(i32::from_str_radix, &value)?,
NumKind::Int(Some(IntKind::I64)) => int(i64::from_str_radix, &value)?,
NumKind::Int(Some(IntKind::I128)) => int(i128::from_str_radix, &value)?,
NumKind::Int(Some(IntKind::Isize)) => {
if cfg!(target_pointer_width = "16") {
int(i16::from_str_radix, &value)?
} else if cfg!(target_pointer_width = "32") {
int(i32::from_str_radix, &value)?
} else if cfg!(target_pointer_width = "64") {
int(i64::from_str_radix, &value)?
} else {
unreachable!("unexpected `cfg!(target_pointer_width)`")
}
}
NumKind::Int(Some(IntKind::U8)) => int(u8::from_str_radix, &value)?,
NumKind::Int(Some(IntKind::U16)) => int(u16::from_str_radix, &value)?,
NumKind::Int(Some(IntKind::U32)) => int(u32::from_str_radix, &value)?,
NumKind::Int(Some(IntKind::U64)) => int(u64::from_str_radix, &value)?,
NumKind::Int(Some(IntKind::U128)) => int(u128::from_str_radix, &value)?,
NumKind::Int(Some(IntKind::Usize)) => {
if cfg!(target_pointer_width = "16") {
int(u16::from_str_radix, &value)?
} else if cfg!(target_pointer_width = "32") {
int(u32::from_str_radix, &value)?
} else if cfg!(target_pointer_width = "64") {
int(u64::from_str_radix, &value)?
} else {
unreachable!("unexpected `cfg!(target_pointer_width)`")
}
}
NumKind::Int(None) => match value.starts_with('-') {
true => int(i128::from_str_radix, &value)?,
false => int(u128::from_str_radix, &value)?,
},
NumKind::Float(Some(FloatKind::F32)) => value.parse::<f32>().ok()?.to_string(),
NumKind::Float(Some(FloatKind::F64) | None) => {
value.parse::<f64>().ok()?.to_string()
}
// FIXME: implement once `f16` and `f128` are available
NumKind::Float(Some(FloatKind::F16 | FloatKind::F128)) => return None,
};
match value == orig_value {
true => Cow::Borrowed(orig_value),
false => Cow::Owned(value),
}
}
Expr::BoolLit(true) => Cow::Borrowed("true"),
Expr::BoolLit(false) => Cow::Borrowed("false"),
_ => return None,
};
// escape the un-string-escaped input using the selected escaper
Some(Writable::Lit(match output {
OutputKind::Text => value,
OutputKind::Html => {
let mut escaped = String::with_capacity(value.len() + 20);
write_escaped_str(&mut escaped, &value).ok()?;
match escaped == value {
true => value,
false => Cow::Owned(escaped),
}
}
}))
}
#[derive(Debug)]
struct Buffer {
// The buffer to generate the code into
@ -2615,7 +2677,7 @@ fn is_copyable(expr: &Expr<'_>) -> bool {
fn is_copyable_within_op(expr: &Expr<'_>, within_op: bool) -> bool {
match expr {
Expr::BoolLit(_) | Expr::NumLit(_) | Expr::StrLit(_) | Expr::CharLit(_) => true,
Expr::BoolLit(_) | Expr::NumLit(_, _) | Expr::StrLit(_) | Expr::CharLit(_) => true,
Expr::Unary(.., expr) => is_copyable_within_op(expr, true),
Expr::BinOp(_, lhs, rhs) => {
is_copyable_within_op(lhs, true) && is_copyable_within_op(rhs, true)
@ -2651,7 +2713,7 @@ pub(crate) fn is_cacheable(expr: &WithSpan<'_, Expr<'_>>) -> bool {
match &**expr {
// Literals are the definition of pure:
Expr::BoolLit(_) => true,
Expr::NumLit(_) => true,
Expr::NumLit(_, _) => true,
Expr::StrLit(_) => true,
Expr::CharLit(_) => true,
// fmt::Display should have no effects:

View File

@ -580,4 +580,11 @@ A
&[],
23,
);
compare(
r#"{{ 1_2_3_4 }} {{ 4e3 }} {{ false }}"#,
r#"writer.write_str("1234 4000 false")?;"#,
&[],
15,
);
}

View File

@ -3,8 +3,8 @@ use std::str;
use nom::branch::alt;
use nom::bytes::complete::{tag, take_till};
use nom::character::complete::char;
use nom::combinator::{cut, fail, map, not, opt, peek, recognize, value};
use nom::character::complete::{char, digit1};
use nom::combinator::{consumed, cut, fail, map, not, opt, peek, recognize, value};
use nom::error::ErrorKind;
use nom::error_position;
use nom::multi::{fold_many0, many0, separated_list0};
@ -12,7 +12,7 @@ use nom::sequence::{pair, preceded, terminated, tuple};
use crate::{
char_lit, filter, identifier, keyword, not_ws, num_lit, path_or_identifier, str_lit, ws,
CharLit, ErrorContext, Level, ParseResult, PathOrIdentifier, StrLit, WithSpan,
CharLit, ErrorContext, Level, Num, ParseResult, PathOrIdentifier, StrLit, WithSpan,
};
macro_rules! expr_prec_layer {
@ -35,7 +35,7 @@ macro_rules! expr_prec_layer {
#[derive(Clone, Debug, PartialEq)]
pub enum Expr<'a> {
BoolLit(bool),
NumLit(&'a str),
NumLit(&'a str, Num<'a>),
StrLit(StrLit<'a>),
CharLit(CharLit<'a>),
Var(&'a str),
@ -359,7 +359,8 @@ impl<'a> Expr<'a> {
fn num(i: &'a str) -> ParseResult<'a, WithSpan<'a, Self>> {
let start = i;
map(num_lit, |i| WithSpan::new(Self::NumLit(i), start))(i)
let (i, (full, num)) = consumed(num_lit)(i)?;
Ok((i, WithSpan::new(Expr::NumLit(full, num), start)))
}
fn char(i: &'a str) -> ParseResult<'a, WithSpan<'a, Self>> {
@ -374,7 +375,7 @@ impl<'a> Expr<'a> {
Self::BinOp("&&" | "||", left, right) => {
left.contains_bool_lit_or_is_defined() || right.contains_bool_lit_or_is_defined()
}
Self::NumLit(_)
Self::NumLit(_, _)
| Self::StrLit(_)
| Self::CharLit(_)
| Self::Var(_)
@ -530,7 +531,7 @@ impl<'a> Suffix<'a> {
map(
preceded(
ws(pair(char('.'), not(char('.')))),
cut(alt((num_lit, identifier))),
cut(alt((digit1, identifier))),
),
Self::Attr,
)(i)

View File

@ -12,7 +12,7 @@ use std::{fmt, str};
use nom::branch::alt;
use nom::bytes::complete::{escaped, is_not, tag, take_till, take_while_m_n};
use nom::character::complete::{anychar, char, one_of, satisfy};
use nom::combinator::{complete, cut, eof, fail, map, not, opt, recognize};
use nom::combinator::{complete, consumed, cut, eof, fail, map, not, opt, recognize, value};
use nom::error::{ErrorKind, FromExternalError};
use nom::multi::{many0_count, many1};
use nom::sequence::{delimited, pair, preceded, terminated, tuple};
@ -340,65 +340,94 @@ fn bool_lit(i: &str) -> ParseResult<'_> {
alt((keyword("false"), keyword("true")))(i)
}
fn num_lit(i: &str) -> ParseResult<'_> {
fn suffix<'a>(
start: &'a str,
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Num<'a> {
Int(&'a str, Option<IntKind>),
Float(&'a str, Option<FloatKind>),
}
fn num_lit<'a>(start: &'a str) -> ParseResult<'a, Num<'a>> {
fn num_lit_suffix<'a, T: Copy>(
kind: &'a str,
list: &'a [&str],
ignore: &'a [&str],
) -> impl Fn(&'a str) -> ParseResult<'a> + Copy + 'a {
move |i| {
let (i, suffix) = identifier(i)?;
if list.contains(&suffix) {
Ok((i, suffix))
} else if ignore.contains(&suffix) {
// no need for a message, this case only occures in an `opt(…)`
fail(i)
} else {
Err(nom::Err::Failure(ErrorContext::new(
format!("unknown {kind} suffix `{suffix}`"),
start,
)))
}
list: &[(&str, T)],
start: &'a str,
i: &'a str,
) -> ParseResult<'a, T> {
let (i, suffix) = identifier(i)?;
if let Some(value) = list
.iter()
.copied()
.find_map(|(name, value)| (name == suffix).then_some(value))
{
Ok((i, value))
} else {
Err(nom::Err::Failure(ErrorContext::new(
format!("unknown {kind} suffix `{suffix}`"),
start,
)))
}
}
let integer_suffix = suffix(i, "integer", INTEGER_TYPES, &[]);
let float_suffix = suffix(i, "float", FLOAT_TYPES, &["e"]);
let either_suffix = suffix(i, "number", NUM_TYPES, &["e"]);
// Equivalent to <https://github.com/rust-lang/rust/blob/e3f909b2bbd0b10db6f164d466db237c582d3045/compiler/rustc_lexer/src/lib.rs#L587-L620>.
let int_with_base = pair(opt(char('-')), |i| {
let (i, (kind, base)) = consumed(preceded(
char('0'),
alt((
value(2, char('b')),
value(8, char('o')),
value(16, char('x')),
)),
))(i)?;
match opt(separated_digits(base, false))(i)? {
(i, Some(_)) => Ok((i, ())),
(_, None) => Err(nom::Err::Failure(ErrorContext::new(
format!("expected digits after `{kind}`"),
start,
))),
}
});
recognize(tuple((
opt(char('-')),
alt((
recognize(tuple((
char('0'),
alt((
recognize(tuple((char('b'), separated_digits(2, false)))),
recognize(tuple((char('o'), separated_digits(8, false)))),
recognize(tuple((char('x'), separated_digits(16, false)))),
)),
opt(integer_suffix),
))),
recognize(tuple((
separated_digits(10, true),
opt(alt((
either_suffix,
recognize(tuple((
opt(tuple((char('.'), separated_digits(10, true)))),
one_of("eE"),
opt(one_of("+-")),
separated_digits(10, false),
opt(float_suffix),
))),
recognize(tuple((
char('.'),
separated_digits(10, true),
opt(float_suffix),
))),
// Equivalent to <https://github.com/rust-lang/rust/blob/e3f909b2bbd0b10db6f164d466db237c582d3045/compiler/rustc_lexer/src/lib.rs#L626-L653>:
// no `_` directly after the decimal point `.`, or between `e` and `+/-`.
let float = |i: &'a str| -> ParseResult<'a, ()> {
let (i, has_dot) = opt(pair(char('.'), separated_digits(10, true)))(i)?;
let (i, has_exp) = opt(|i| {
let (i, (kind, op)) = pair(one_of("eE"), opt(one_of("+-")))(i)?;
match opt(separated_digits(10, op.is_none()))(i)? {
(i, Some(_)) => Ok((i, ())),
(_, None) => Err(nom::Err::Failure(ErrorContext::new(
format!("expected decimal digits, `+` or `-` after exponent `{kind}`"),
start,
))),
))),
)),
)))(i)
}
})(i)?;
match (has_dot, has_exp) {
(Some(_), _) | (_, Some(_)) => Ok((i, ())),
_ => fail(start),
}
};
let (i, num) = if let Ok((i, Some(num))) = opt(recognize(int_with_base))(start) {
let (i, suffix) = opt(|i| num_lit_suffix("integer", INTEGER_TYPES, start, i))(i)?;
(i, Num::Int(num, suffix))
} else {
let (i, (num, float)) = consumed(preceded(
pair(opt(char('-')), separated_digits(10, true)),
opt(float),
))(start)?;
if float.is_some() {
let (i, suffix) = opt(|i| num_lit_suffix("float", FLOAT_TYPES, start, i))(i)?;
(i, Num::Float(num, suffix))
} else {
let (i, suffix) = opt(|i| num_lit_suffix("number", NUM_TYPES, start, i))(i)?;
match suffix {
Some(NumKind::Int(kind)) => (i, Num::Int(num, Some(kind))),
Some(NumKind::Float(kind)) => (i, Num::Float(num, Some(kind))),
None => (i, Num::Int(num, None)),
}
}
};
Ok((i, num))
}
/// Underscore separated digits of the given base, unless `start` is true this may start
@ -933,27 +962,75 @@ pub fn strip_common(base: &Path, path: &Path) -> String {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IntKind {
I8,
I16,
I32,
I64,
I128,
Isize,
U8,
U16,
U32,
U64,
U128,
Usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FloatKind {
F16,
F32,
F64,
F128,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum NumKind {
Int(IntKind),
Float(FloatKind),
}
/// Primitive integer types. Also used as number suffixes.
const INTEGER_TYPES: &[&str] = &[
"i8", "i16", "i32", "i64", "i128", "isize", "u8", "u16", "u32", "u64", "u128", "usize",
const INTEGER_TYPES: &[(&str, IntKind)] = &[
("i8", IntKind::I8),
("i16", IntKind::I16),
("i32", IntKind::I32),
("i64", IntKind::I64),
("i128", IntKind::I128),
("isize", IntKind::Isize),
("u8", IntKind::U8),
("u16", IntKind::U16),
("u32", IntKind::U32),
("u64", IntKind::U64),
("u128", IntKind::U128),
("usize", IntKind::Usize),
];
/// Primitive floating point types. Also used as number suffixes.
const FLOAT_TYPES: &[&str] = &["f16", "f32", "f64", "f128"];
const FLOAT_TYPES: &[(&str, FloatKind)] = &[
("f16", FloatKind::F16),
("f32", FloatKind::F32),
("f64", FloatKind::F64),
("f128", FloatKind::F128),
];
/// Primitive numeric types. Also used as number suffixes.
const NUM_TYPES: &[&str] = &{
let mut list = [""; INTEGER_TYPES.len() + FLOAT_TYPES.len()];
const NUM_TYPES: &[(&str, NumKind)] = &{
let mut list = [("", NumKind::Int(IntKind::I8)); INTEGER_TYPES.len() + FLOAT_TYPES.len()];
let mut i = 0;
let mut o = 0;
while i < INTEGER_TYPES.len() {
list[o] = INTEGER_TYPES[i];
let (name, value) = INTEGER_TYPES[i];
list[o] = (name, NumKind::Int(value));
i += 1;
o += 1;
}
let mut i = 0;
while i < FLOAT_TYPES.len() {
list[o] = FLOAT_TYPES[i];
let (name, value) = FLOAT_TYPES[i];
list[o] = (name, NumKind::Float(value));
i += 1;
o += 1;
}
@ -966,7 +1043,7 @@ const PRIMITIVE_TYPES: &[&str] = &{
let mut i = 0;
let mut o = 0;
while i < NUM_TYPES.len() {
list[o] = NUM_TYPES[i];
list[o] = NUM_TYPES[i].0;
i += 1;
o += 1;
}
@ -979,7 +1056,7 @@ const PRIMITIVE_TYPES: &[&str] = &{
mod test {
use std::path::Path;
use super::{char_lit, num_lit, str_lit, strip_common, StrLit, StrPrefix};
use super::*;
#[test]
fn test_strip_common() {
@ -1012,14 +1089,42 @@ mod test {
// Should fail.
assert!(num_lit(".").is_err());
// Should succeed.
assert_eq!(num_lit("1.2E-02").unwrap(), ("", "1.2E-02"));
assert_eq!(
num_lit("1.2E-02").unwrap(),
("", Num::Float("1.2E-02", None))
);
assert_eq!(num_lit("4e3").unwrap(), ("", Num::Float("4e3", None)),);
assert_eq!(num_lit("4e+_3").unwrap(), ("", Num::Float("4e+_3", None)),);
// Not supported because Rust wants a number before the `.`.
assert!(num_lit(".1").is_err());
assert!(num_lit(".1E-02").is_err());
// A `_` directly after the `.` denotes a field.
assert_eq!(num_lit("1._0").unwrap(), ("._0", Num::Int("1", None)));
assert_eq!(num_lit("1_.0").unwrap(), ("", Num::Float("1_.0", None)));
// Not supported (voluntarily because of `1..` syntax).
assert_eq!(num_lit("1.").unwrap(), (".", "1"));
assert_eq!(num_lit("1_.").unwrap(), (".", "1_"));
assert_eq!(num_lit("1_2.").unwrap(), (".", "1_2"));
assert_eq!(num_lit("1.").unwrap(), (".", Num::Int("1", None)));
assert_eq!(num_lit("1_.").unwrap(), (".", Num::Int("1_", None)));
assert_eq!(num_lit("1_2.").unwrap(), (".", Num::Int("1_2", None)));
// Numbers with suffixes
assert_eq!(
num_lit("-1usize").unwrap(),
("", Num::Int("-1", Some(IntKind::Usize)))
);
assert_eq!(
num_lit("123_f32").unwrap(),
("", Num::Float("123_", Some(FloatKind::F32)))
);
assert_eq!(
num_lit("1_.2_e+_3_f64|into_isize").unwrap(),
(
"|into_isize",
Num::Float("1_.2_e+_3_", Some(FloatKind::F64))
)
);
assert_eq!(
num_lit("4e3f128").unwrap(),
("", Num::Float("4e3", Some(FloatKind::F128))),
);
}
#[test]

View File

@ -7,7 +7,7 @@ use nom::sequence::{pair, preceded, tuple};
use crate::{
bool_lit, char_lit, identifier, keyword, num_lit, path_or_identifier, str_lit, ws, CharLit,
ErrorContext, ParseErr, ParseResult, PathOrIdentifier, State, StrLit, WithSpan,
ErrorContext, Num, ParseErr, ParseResult, PathOrIdentifier, State, StrLit, WithSpan,
};
#[derive(Clone, Debug, PartialEq)]
@ -16,7 +16,7 @@ pub enum Target<'a> {
Tuple(Vec<&'a str>, Vec<Target<'a>>),
Array(Vec<&'a str>, Vec<Target<'a>>),
Struct(Vec<&'a str>, Vec<(&'a str, Target<'a>)>),
NumLit(&'a str),
NumLit(&'a str, Num<'a>),
StrLit(StrLit<'a>),
CharLit(CharLit<'a>),
BoolLit(&'a str),
@ -118,7 +118,7 @@ impl<'a> Target<'a> {
alt((
map(str_lit, Self::StrLit),
map(char_lit, Self::CharLit),
map(num_lit, Self::NumLit),
map(consumed(num_lit), |(full, num)| Target::NumLit(full, num)),
map(bool_lit, Self::BoolLit),
))(i)
}

View File

@ -1,5 +1,5 @@
use crate::node::{Lit, Whitespace, Ws};
use crate::{Ast, Expr, Filter, InnerSyntax, Node, StrLit, Syntax, WithSpan};
use crate::{Ast, Expr, Filter, InnerSyntax, Node, Num, StrLit, Syntax, WithSpan};
impl<T> WithSpan<'static, T> {
fn no_span(inner: T) -> Self {
@ -29,6 +29,10 @@ fn test_invalid_block() {
Ast::from_str("{% extend \"blah\" %}", None, &Syntax::default()).unwrap();
}
fn int_lit(i: &str) -> Expr<'_> {
Expr::NumLit(i, Num::Int(i, None))
}
#[test]
fn test_parse_filter() {
let syntax = Syntax::default();
@ -50,7 +54,7 @@ fn test_parse_filter() {
Ws(None, None),
WithSpan::no_span(Expr::Filter(Filter {
name: "abs",
arguments: vec![WithSpan::no_span(Expr::NumLit("2"))]
arguments: vec![WithSpan::no_span(int_lit("2"))]
})),
)],
);
@ -62,7 +66,7 @@ fn test_parse_filter() {
name: "abs",
arguments: vec![WithSpan::no_span(Expr::Unary(
"-",
WithSpan::no_span(Expr::NumLit("2")).into()
WithSpan::no_span(int_lit("2")).into()
))]
})),
)],
@ -78,8 +82,8 @@ fn test_parse_filter() {
arguments: vec![WithSpan::no_span(Expr::Group(
WithSpan::no_span(Expr::BinOp(
"-",
WithSpan::no_span(Expr::NumLit("1")).into(),
WithSpan::no_span(Expr::NumLit("2")).into()
WithSpan::no_span(int_lit("1")).into(),
WithSpan::no_span(int_lit("2")).into()
))
.into()
))],
@ -93,16 +97,13 @@ fn test_parse_numbers() {
let syntax = Syntax::default();
assert_eq!(
Ast::from_str("{{ 2 }}", None, &syntax).unwrap().nodes,
vec![Node::Expr(
Ws(None, None),
WithSpan::no_span(Expr::NumLit("2"))
)],
vec![Node::Expr(Ws(None, None), WithSpan::no_span(int_lit("2")))],
);
assert_eq!(
Ast::from_str("{{ 2.5 }}", None, &syntax).unwrap().nodes,
vec![Node::Expr(
Ws(None, None),
WithSpan::no_span(Expr::NumLit("2.5"))
WithSpan::no_span(Expr::NumLit("2.5", Num::Float("2.5", None)))
)],
);
}
@ -180,7 +181,7 @@ fn test_parse_path() {
Ws(None, None),
WithSpan::no_span(Expr::Call(
Box::new(WithSpan::no_span(Expr::Path(vec!["Some"]))),
vec![WithSpan::no_span(Expr::NumLit("123"))]
vec![WithSpan::no_span(int_lit("123"))]
)),
)],
);
@ -191,7 +192,7 @@ fn test_parse_path() {
Ws(None, None),
WithSpan::no_span(Expr::Call(
Box::new(WithSpan::no_span(Expr::Path(vec!["Ok"]))),
vec![WithSpan::no_span(Expr::NumLit("123"))]
vec![WithSpan::no_span(int_lit("123"))]
)),
)],
);
@ -201,7 +202,7 @@ fn test_parse_path() {
Ws(None, None),
WithSpan::no_span(Expr::Call(
Box::new(WithSpan::no_span(Expr::Path(vec!["Err"]))),
vec![WithSpan::no_span(Expr::NumLit("123"))]
vec![WithSpan::no_span(int_lit("123"))]
)),
)],
);
@ -222,7 +223,7 @@ fn test_parse_var_call() {
content: "123",
prefix: None,
})),
WithSpan::no_span(Expr::NumLit("3"))
WithSpan::no_span(int_lit("3"))
]
)),
)],
@ -248,7 +249,7 @@ fn test_parse_path_call() {
Ws(None, None),
WithSpan::no_span(Expr::Call(
Box::new(WithSpan::no_span(Expr::Path(vec!["Option", "Some"]))),
vec![WithSpan::no_span(Expr::NumLit("123"))],
vec![WithSpan::no_span(int_lit("123"))],
),)
)],
);
@ -266,7 +267,7 @@ fn test_parse_path_call() {
content: "123",
prefix: None,
})),
WithSpan::no_span(Expr::NumLit("3"))
WithSpan::no_span(int_lit("3"))
],
),)
)],
@ -791,35 +792,35 @@ fn test_parse_tuple() {
Ast::from_str("{{ (1) }}", None, &syntax).unwrap().nodes,
vec![Node::Expr(
Ws(None, None),
WithSpan::no_span(Expr::Group(Box::new(WithSpan::no_span(Expr::NumLit("1"))),))
WithSpan::no_span(Expr::Group(Box::new(WithSpan::no_span(int_lit("1"))),))
)],
);
assert_eq!(
Ast::from_str("{{ (1,) }}", None, &syntax).unwrap().nodes,
vec![Node::Expr(
Ws(None, None),
WithSpan::no_span(Expr::Tuple(vec![WithSpan::no_span(Expr::NumLit("1"))])),
WithSpan::no_span(Expr::Tuple(vec![WithSpan::no_span(int_lit("1"))])),
)],
);
assert_eq!(
Ast::from_str("{{ (1, ) }}", None, &syntax).unwrap().nodes,
vec![Node::Expr(
Ws(None, None),
WithSpan::no_span(Expr::Tuple(vec![WithSpan::no_span(Expr::NumLit("1"))])),
WithSpan::no_span(Expr::Tuple(vec![WithSpan::no_span(int_lit("1"))])),
)],
);
assert_eq!(
Ast::from_str("{{ (1 ,) }}", None, &syntax).unwrap().nodes,
vec![Node::Expr(
Ws(None, None),
WithSpan::no_span(Expr::Tuple(vec![WithSpan::no_span(Expr::NumLit("1"))])),
WithSpan::no_span(Expr::Tuple(vec![WithSpan::no_span(int_lit("1"))])),
)],
);
assert_eq!(
Ast::from_str("{{ (1 , ) }}", None, &syntax).unwrap().nodes,
vec![Node::Expr(
Ws(None, None),
WithSpan::no_span(Expr::Tuple(vec![WithSpan::no_span(Expr::NumLit("1"))])),
WithSpan::no_span(Expr::Tuple(vec![WithSpan::no_span(int_lit("1"))])),
)],
);
assert_eq!(
@ -827,8 +828,8 @@ fn test_parse_tuple() {
vec![Node::Expr(
Ws(None, None),
WithSpan::no_span(Expr::Tuple(vec![
WithSpan::no_span(Expr::NumLit("1")),
WithSpan::no_span(Expr::NumLit("2"))
WithSpan::no_span(int_lit("1")),
WithSpan::no_span(int_lit("2"))
])),
)],
);
@ -837,8 +838,8 @@ fn test_parse_tuple() {
vec![Node::Expr(
Ws(None, None),
WithSpan::no_span(Expr::Tuple(vec![
WithSpan::no_span(Expr::NumLit("1")),
WithSpan::no_span(Expr::NumLit("2"))
WithSpan::no_span(int_lit("1")),
WithSpan::no_span(int_lit("2"))
])),
)],
);
@ -849,9 +850,9 @@ fn test_parse_tuple() {
vec![Node::Expr(
Ws(None, None),
WithSpan::no_span(Expr::Tuple(vec![
WithSpan::no_span(Expr::NumLit("1")),
WithSpan::no_span(Expr::NumLit("2")),
WithSpan::no_span(Expr::NumLit("3"))
WithSpan::no_span(int_lit("1")),
WithSpan::no_span(int_lit("2")),
WithSpan::no_span(int_lit("3"))
])),
)],
);
@ -872,7 +873,7 @@ fn test_parse_tuple() {
WithSpan::no_span(Expr::Filter(Filter {
name: "abs",
arguments: vec![WithSpan::no_span(Expr::Group(Box::new(WithSpan::no_span(
Expr::NumLit("1")
int_lit("1")
))))]
})),
)],
@ -886,7 +887,7 @@ fn test_parse_tuple() {
WithSpan::no_span(Expr::Filter(Filter {
name: "abs",
arguments: vec![WithSpan::no_span(Expr::Tuple(vec![WithSpan::no_span(
Expr::NumLit("1")
int_lit("1")
)]))]
})),
)],
@ -900,8 +901,8 @@ fn test_parse_tuple() {
WithSpan::no_span(Expr::Filter(Filter {
name: "abs",
arguments: vec![WithSpan::no_span(Expr::Tuple(vec![
WithSpan::no_span(Expr::NumLit("1")),
WithSpan::no_span(Expr::NumLit("2"))
WithSpan::no_span(int_lit("1")),
WithSpan::no_span(int_lit("2"))
]))]
})),
)],
@ -932,21 +933,21 @@ fn test_parse_array() {
Ast::from_str("{{ [1] }}", None, &syntax).unwrap().nodes,
vec![Node::Expr(
Ws(None, None),
WithSpan::no_span(Expr::Array(vec![WithSpan::no_span(Expr::NumLit("1"))]))
WithSpan::no_span(Expr::Array(vec![WithSpan::no_span(int_lit("1"))]))
)],
);
assert_eq!(
Ast::from_str("{{ [ 1] }}", None, &syntax).unwrap().nodes,
vec![Node::Expr(
Ws(None, None),
WithSpan::no_span(Expr::Array(vec![WithSpan::no_span(Expr::NumLit("1"))]))
WithSpan::no_span(Expr::Array(vec![WithSpan::no_span(int_lit("1"))]))
)],
);
assert_eq!(
Ast::from_str("{{ [1 ] }}", None, &syntax).unwrap().nodes,
vec![Node::Expr(
Ws(None, None),
WithSpan::no_span(Expr::Array(vec![WithSpan::no_span(Expr::NumLit("1"))]))
WithSpan::no_span(Expr::Array(vec![WithSpan::no_span(int_lit("1"))]))
)],
);
assert_eq!(
@ -954,8 +955,8 @@ fn test_parse_array() {
vec![Node::Expr(
Ws(None, None),
WithSpan::no_span(Expr::Array(vec![
WithSpan::no_span(Expr::NumLit("1")),
WithSpan::no_span(Expr::NumLit("2"))
WithSpan::no_span(int_lit("1")),
WithSpan::no_span(int_lit("2"))
]))
)],
);
@ -964,8 +965,8 @@ fn test_parse_array() {
vec![Node::Expr(
Ws(None, None),
WithSpan::no_span(Expr::Array(vec![
WithSpan::no_span(Expr::NumLit("1")),
WithSpan::no_span(Expr::NumLit("2"))
WithSpan::no_span(int_lit("1")),
WithSpan::no_span(int_lit("2"))
]))
)],
);
@ -974,8 +975,8 @@ fn test_parse_array() {
vec![Node::Expr(
Ws(None, None),
WithSpan::no_span(Expr::Array(vec![
WithSpan::no_span(Expr::NumLit("1")),
WithSpan::no_span(Expr::NumLit("2"))
WithSpan::no_span(int_lit("1")),
WithSpan::no_span(int_lit("2"))
]))
)],
);
@ -984,8 +985,8 @@ fn test_parse_array() {
vec![Node::Expr(
Ws(None, None),
WithSpan::no_span(Expr::Array(vec![
WithSpan::no_span(Expr::NumLit("1")),
WithSpan::no_span(Expr::NumLit("2"))
WithSpan::no_span(int_lit("1")),
WithSpan::no_span(int_lit("2"))
]))
)],
);