mirror of
https://github.com/askama-rs/askama.git
synced 2025-09-28 13:30:59 +00:00
Merge pull request #142 from Kijewski/pr-more-const-lit
derive: escape `int`, `float`, `bool` at compile time
This commit is contained in:
commit
88047ac92b
@ -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:
|
||||
|
@ -580,4 +580,11 @@ A
|
||||
&[],
|
||||
23,
|
||||
);
|
||||
|
||||
compare(
|
||||
r#"{{ 1_2_3_4 }} {{ 4e3 }} {{ false }}"#,
|
||||
r#"writer.write_str("1234 4000 false")?;"#,
|
||||
&[],
|
||||
15,
|
||||
);
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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]
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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"))
|
||||
]))
|
||||
)],
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user