if pos == 0 { if cond_info.generate_condition {
buf.write("if ");
} // Otherwise it means it will be the only condition generated, // so nothing to be added here.
} elseif cond_info.generate_condition {
buf.write("} else if ");
} else {
buf.write("} else {");
has_else = true;
}
iflet Some(target) = target { letmut expr_buf = Buffer::new();
buf.write("let "); // If this is a chain condition, then we need to declare the variable after the // left expression has been handled but before the right expression is handled // but this one should have access to the let-bound variable. match &**expr {
Expr::BinOp(op @ ("||" | "&&"), ref left, ref right) => { let display_wrap =
this.visit_expr_first(ctx, &mut expr_buf, left)?;
this.visit_target(buf, true, true, target);
this.visit_expr_not_first(ctx, &mut expr_buf, left, display_wrap)?;
buf.write(format_args!("= &{expr_buf}"));
buf.write(format_args!(" {op} "));
this.visit_condition(ctx, buf, right)?;
}
_ => { let display_wrap =
this.visit_expr_first(ctx, &mut expr_buf, expr)?;
this.visit_target(buf, true, true, target);
this.visit_expr_not_first(ctx, &mut expr_buf, expr, display_wrap)?;
buf.write(format_args!("= &{expr_buf}"));
}
}
buf.write("{");
} elseif cond_info.generate_condition {
this.visit_condition(ctx, buf, expr)?;
buf.write('{');
}
} elseif pos != 0 {
buf.write("} else {");
has_else = true;
}
let has_else_nodes = !loop_block.else_nodes.is_empty();
let flushed = this.write_buf_writable(ctx, buf)?;
buf.write('{'); if has_else_nodes {
buf.write("let mut _did_loop = false;");
} match &*loop_block.iter {
Expr::Range(_, _, _) => buf.write(format_args!("let _iter = {expr_code};")),
Expr::Array(..) => buf.write(format_args!("let _iter = {expr_code}.iter();")), // If `iter` is a call then we assume it's something that returns // an iterator. If not then the user can explicitly add the needed // call without issues.
Expr::Call { .. } | Expr::Index(..) => {
buf.write(format_args!("let _iter = ({expr_code}).into_iter();"));
} // If accessing `self` then it most likely needs to be // borrowed, to prevent an attempt of moving.
_ if expr_code.starts_with("self.") => {
buf.write(format_args!("let _iter = (&{expr_code}).into_iter();"));
} // If accessing a field then it most likely needs to be // borrowed, to prevent an attempt of moving.
Expr::Attr(..) => {
buf.write(format_args!("let _iter = (&{expr_code}).into_iter();"));
} // Otherwise, we borrow `iter` assuming that it implements `IntoIterator`.
_ => buf.write(format_args!("let _iter = ({expr_code}).into_iter();")),
} iflet Some(cond) = &loop_block.cond {
this.push_locals(|this| {
buf.write("let _iter = _iter.filter(|");
this.visit_target(buf, true, true, &loop_block.var);
buf.write("| -> bool {");
this.visit_expr(ctx, buf, cond)?;
buf.write("});");
Ok(0)
})?;
}
let size_hint1 = this.push_locals(|this| {
buf.write("for (");
this.visit_target(buf, true, true, &loop_block.var);
buf.write(", _loop_item) in askama::helpers::TemplateLoop::new(_iter) {");
self.flush_ws(ws); // Cannot handle_ws() here: whitespace from macro definition comes first let size_hint = self.push_locals(|this| {
macro_call_ensure_arg_count(call, def, ctx)?;
letmut named_arguments: HashMap<&str, _, FxBuildHasher> = HashMap::default(); // Since named arguments can only be passed last, we only need to check if the last argument // is a named one. iflet Some(Expr::NamedArgument(_, _)) = args.last().map(|expr| &**expr) { // First we check that all named arguments actually exist in the called item. for (index, arg) in args.iter().enumerate().rev() { let Expr::NamedArgument(arg_name, _) = &**arg else { break;
}; if !def.args.iter().any(|(arg, _)| arg == arg_name) { return Err(ctx.generate_error(
format_args!("no argument named `{arg_name}` in macro {name:?}"),
call.span(),
));
}
named_arguments.insert(arg_name, (index, arg));
}
}
letmut value = Buffer::new();
// Handling both named and unnamed arguments requires to be careful of the named arguments // order. To do so, we iterate through the macro defined arguments and then check if we have // a named argument with this name: // // * If there is one, we add it and move to the next argument. // * If there isn't one, then we pick the next argument (we can do it without checking // anything since named arguments are always last). letmut allow_positional = true; letmut used_named_args = vec![false; args.len()]; for (index, (arg, default_value)) in def.args.iter().enumerate() { let expr = iflet Some((index, expr)) = named_arguments.get(arg) {
used_named_args[*index] = true;
allow_positional = false;
expr
} else { match args.get(index) {
Some(arg_expr) if !matches!(**arg_expr, Expr::NamedArgument(_, _)) => { // If there is already at least one named argument, then it's not allowed // to use unnamed ones at this point anymore. if !allow_positional { return Err(ctx.generate_error(
format_args!( "cannot have unnamed argument (`{arg}`) after named argument \ in call to macro {name:?}"
),
call.span(),
));
}
arg_expr
}
Some(arg_expr) if used_named_args[index] => { let Expr::NamedArgument(name, _) = **arg_expr else { unreachable!() }; return Err(ctx.generate_error(
format_args!("`{name}` is passed more than once"),
call.span(),
));
}
_ => { iflet Some(default_value) = default_value {
default_value
} else { return Err(ctx.generate_error(format_args!("missing `{arg}` argument"), call.span()));
}
}
}
}; match &**expr { // If `expr` is already a form of variable then // don't reintroduce a new variable. This is // to avoid moving non-copyable values.
Expr::Var(name) if *name != "self" => { let var = this.locals.resolve_or_self(name);
this.locals
.insert(Cow::Borrowed(arg), LocalMeta::with_ref(var));
}
Expr::Attr(obj, attr) => { letmut attr_buf = Buffer::new();
this.visit_attr(ctx, &mut attr_buf, obj, attr)?;
let attr = attr_buf.into_string(); let var = this.locals.resolve(&attr).unwrap_or(attr);
this.locals
.insert(Cow::Borrowed(arg), LocalMeta::with_ref(var));
} // Everything else still needs to become variables, // to avoid having the same logic be executed // multiple times, e.g. in the case of macro // parameters being used multiple times.
_ => {
value.clear(); let (before, after) = if !is_copyable(expr) {
("&(", ")")
} else {
("", "")
};
value.write(this.visit_expr_root(ctx, expr)?); // We need to normalize the arg to write it, thus we need to add it to // locals in the normalized manner let normalized_arg = normalize_identifier(arg);
buf.write(format_args!("let {} = {before}{value}{after};", normalized_arg));
this.locals.insert_with_default(Cow::Borrowed(normalized_arg));
}
}
}
// We clone the context of the child in order to preserve their macros and imports. // But also add all the imports and macros from this template that don't override the // child's ones to preserve this template's context. let child_ctx = &mutself.contexts[&path].clone(); for (name, mac) in &ctx.macros {
child_ctx.macros.entry(name).or_insert(mac);
} for (name, import) in &ctx.imports {
child_ctx
.imports
.entry(name)
.or_insert_with(|| import.clone());
}
// Create a new generator for the child, and call it like in `impl_template` as if it were // a full template, while preserving the context. let heritage = if !child_ctx.blocks.is_empty() || child_ctx.extends.is_some() {
Some(Heritage::new(child_ctx, self.contexts))
} else {
None
};
let handle_ctx = match &heritage {
Some(heritage) => heritage.root,
None => child_ctx,
};
let shadowed = self.is_shadowing_variable(ctx, &l.var, l.span())?; if shadowed { // Need to flush the buffer if the variable is being shadowed, // to ensure the old variable is used. self.write_buf_writable(ctx, buf)?;
} if shadowed
|| !matches!(l.var, Target::Name(_))
|| matches!(&l.var, Target::Name(name) ifself.locals.get(name).is_none())
{
buf.write("let ");
}
self.visit_target(buf, true, true, &l.var); // If it's not taking the ownership of a local variable or copyable, then we need to add // a reference. let (before, after) = if !matches!(**val, Expr::Try(..))
&& !matches!(**val, Expr::Var(name) ifself.locals.get(name).is_some())
&& !is_copyable(val)
{
("&(", ")")
} else {
("", "")
};
buf.write(format_args!(" = {before}{expr_buf}{after};"));
Ok(())
}
// If `name` is `Some`, this is a call to a block definition, and we have to find // the first block for that name from the ancestry chain. If name is `None`, this // is from a `super()` call, and we can get the name from `self.super_block`. fn write_block(
&mutself,
ctx: &Context<'a>,
buf: &mut Buffer,
name: Option<&'a str>,
outer: Ws,
node: Span<'_>,
) -> Result<usize, CompileError> { ifself.is_in_filter_block > 0 { return Err(ctx.generate_error("cannot have a block inside a filter block", node));
} // Flush preceding whitespace according to the outer WS spec self.flush_ws(outer);
let cur = match (name, self.super_block) { // The top-level context contains a block definition
(Some(cur_name), None) => (cur_name, 0), // A block definition contains a block definition of the same name
(Some(cur_name), Some((prev_name, _))) if cur_name == prev_name => { return Err(ctx.generate_error(
format_args!("cannot define recursive blocks ({cur_name})"),
node,
));
} // A block definition contains a definition of another block
(Some(cur_name), Some((_, _))) => (cur_name, 0), // `super()` was called inside a block
(None, Some((prev_name, gen))) => (prev_name, gen + 1), // `super()` is called from outside a block
(None, None) => { return Err(ctx.generate_error("cannot call 'super()' outside block", node));
}
};
self.write_buf_writable(ctx, buf)?;
let block_fragment_write = self.input.block.map(|(block, _)| block) == name && self.buf_writable.discard; // Allow writing to the buffer if we're in the block fragment if block_fragment_write { self.buf_writable.discard = false;
} let prev_buf_discard = buf.is_discard();
buf.set_discard(self.buf_writable.discard);
// Get the block definition from the heritage chain let heritage = self
.heritage
.ok_or_else(|| ctx.generate_error("no block ancestors available", node))?; let (child_ctx, def) = *heritage.blocks[cur.0].get(cur.1).ok_or_else(|| {
ctx.generate_error( match name {
None => fmt_left!("no super() block found for block '{}'", cur.0),
Some(name) => fmt_right!(move"no block found for name '{name}'"),
},
node,
)
})?;
// We clone the context of the child in order to preserve their macros and imports. // But also add all the imports and macros from this template that don't override the // child's ones to preserve this template's context. letmut child_ctx = child_ctx.clone(); for (name, mac) in &ctx.macros {
child_ctx.macros.entry(name).or_insert(mac);
} for (name, import) in &ctx.imports {
child_ctx
.imports
.entry(name)
.or_insert_with(|| import.clone());
}
let size_hint = self.with_child(Some(heritage), |child| { // Handle inner whitespace suppression spec and process block nodes
child.prepare_ws(def.ws1);
child.super_block = Some(cur); let size_hint = child.handle(&child_ctx, &def.nodes, buf, AstLevel::Block)?;
if !child.locals.is_current_empty() { // Need to flush the buffer before popping the variable stack
child.write_buf_writable(ctx, buf)?;
}
child.flush_ws(def.ws2);
Ok(size_hint)
})?;
// Restore original block context and set whitespace suppression for // succeeding whitespace according to the outer WS spec self.prepare_ws(outer);
// If we are rendering a specific block and the discard changed, it means that we're done // with the block we want to render and that from this point, everything will be discarded. // // To get this block content rendered as well, we need to write to the buffer before then. if buf.is_discard() != prev_buf_discard { self.write_buf_writable(ctx, buf)?;
} // Restore the original buffer discarding state if block_fragment_write { self.buf_writable.discard = true;
}
buf.set_discard(prev_buf_discard);
Ok(size_hint)
}
fn write_expr(&mutself, ws: Ws, s: &'a WithSpan<'a, Expr<'a>>) { self.handle_ws(ws); let items = iflet Expr::Concat(exprs) = &**s {
exprs
} else {
std::slice::from_ref(s)
}; for s in items { self.buf_writable
.push(compile_time_escape(s, self.input.escaper).unwrap_or(Writable::Expr(s)));
}
}
if !val.is_empty() { self.skip_ws = Whitespace::Preserve; self.buf_writable.push(Writable::Lit(Cow::Borrowed(val)));
}
if !rws.is_empty() { self.next_ws = Some(rws);
}
}
// Helper methods for dealing with whitespace nodes
// Combines `flush_ws()` and `prepare_ws()` to handle both trailing whitespace from the // preceding literal and leading whitespace from the succeeding literal. fn handle_ws(&mutself, ws: Ws) { self.flush_ws(ws); self.prepare_ws(ws);
}
// If the previous literal left some trailing whitespace in `next_ws` and the // prefix whitespace suppressor from the given argument, flush that whitespace. // In either case, `next_ws` is reset to `None` (no trailing whitespace). fn flush_ws(&mutself, ws: Ws) { ifself.next_ws.is_none() { return;
}
// If `whitespace` is set to `suppress`, we keep the whitespace characters only if there is // a `+` character. matchself.should_trim_ws(ws.0) {
Whitespace::Preserve => { let val = self.next_ws.unwrap(); if !val.is_empty() { self.buf_writable.push(Writable::Lit(Cow::Borrowed(val)));
}
}
Whitespace::Minimize => { let val = self.next_ws.unwrap(); if !val.is_empty() { self.buf_writable.push(Writable::Lit(Cow::Borrowed( match val.contains('\n') { true => "\n", false => " ",
},
)));
}
}
Whitespace::Suppress => {}
} self.next_ws = None;
}
// Sets `skip_ws` to match the suffix whitespace suppressor from the given // argument, to determine whether to suppress leading whitespace from the // next literal. fn prepare_ws(&mutself, ws: Ws) { self.skip_ws = self.should_trim_ws(ws.1);
}
}
for cond in &i.branches { if stop_loop {
ws_after = Some(cond.ws); break;
} iflet Some(CondTest {
expr,
contains_bool_lit_or_is_defined,
..
}) = &cond.cond
{ letmut only_contains_is_defined = true;
let (evaluated_result, cond_expr) = if *contains_bool_lit_or_is_defined { let (evaluated_result, expr) =
generator.evaluate_condition(expr.clone(), &mut only_contains_is_defined);
(evaluated_result, Some(expr))
} else {
(EvaluatedResult::Unknown, None)
};
match evaluated_result { // We generate the condition in case some calls are changing a variable, but // no need to generate the condition body since it will never be called. // // However, if the condition only contains "is (not) defined" checks, then we // can completely skip it.
EvaluatedResult::AlwaysFalse => { if only_contains_is_defined { if conds.is_empty() && ws_before.is_none() { // If this is the first `if` and it's skipped, we definitely don't // want its whitespace control to be lost.
ws_before = Some(cond.ws);
} continue;
}
nb_conds += 1;
conds.push(CondInfo {
cond,
cond_expr,
generate_condition: true,
generate_content: false,
});
} // This case is more interesting: it means that we will always enter this // condition, meaning that any following should not be generated. Another // thing to take into account: if there are no if branches before this one, // no need to generate an `else`.
EvaluatedResult::AlwaysTrue => { let generate_condition = !only_contains_is_defined; if generate_condition {
nb_conds += 1;
}
conds.push(CondInfo {
cond,
cond_expr,
generate_condition,
generate_content: true,
}); // Since it's always true, we can stop here.
stop_loop = true;
}
EvaluatedResult::Unknown => {
nb_conds += 1;
conds.push(CondInfo {
cond,
cond_expr,
generate_condition: true,
generate_content: true,
});
}
}
} else { let generate_condition = !conds.is_empty(); if generate_condition {
nb_conds += 1;
}
conds.push(CondInfo {
cond,
cond_expr: None,
generate_condition,
generate_content: true,
});
}
} Self {
conds,
ws_before,
ws_after,
nb_conds,
}
}
}
// First we list of arguments position, then we remove every argument with a value. letmut args: Vec<_> = def.args.iter().map(|&(name, _)| Some(name)).collect(); for (pos, arg) in call.args.iter().enumerate() { let pos = match **arg {
Expr::NamedArgument(name, ..) => {
def.args.iter().position(|(arg_name, _)| *arg_name == name)
}
_ => Some(pos),
}; iflet Some(pos) = pos { if mem::take(&mut args[pos]).is_none() { // This argument was already passed, so error. return Err(ctx.generate_error(
format_args!( "argument `{}` was passed more than once when calling macro `{}`",
def.args[pos].0, def.name,
),
call.span(),
));
}
}
}
// Now we can check off arguments with a default value, too. for (pos, (_, dflt)) in def.args.iter().enumerate() { if dflt.is_some() {
args[pos] = None;
}
}
// Now that we have a needed information, we can print an error message (if needed). struct FmtMissing<'a, I> {
count: usize,
missing: I,
name: &'a str,
}
impl<'a, I: Iterator<Item = &'a str> + Clone> fmt::Display for FmtMissing<'a, I> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { ifself.count == 1 { let a = self.missing.clone().next().unwrap();
write!(
f, "missing argument when calling macro `{}`: `{a}`", self.name
)
} else {
write!(f, "missing arguments when calling macro `{}`: ", self.name)?; for (idx, a) inself.missing.clone().enumerate() { if idx == self.count - 1 {
write!(f, " and ")?;
} elseif idx > 0 {
write!(f, ", ")?;
}
write!(f, "`{a}`")?;
}
Ok(())
}
}
}
let missing = args.iter().filter_map(Option::as_deref); let fmt_missing = FmtMissing {
count: missing.clone().count(),
missing,
name: def.name,
}; if fmt_missing.count == 0 {
Ok(())
} else {
Err(ctx.generate_error(fmt_missing, call.span()))
}
}
/// Returns `true` if the outcome of this expression may be used multiple times in the same /// `write!()` call, without evaluating the expression again, i.e. the expression should be /// side-effect free. fn is_cacheable(expr: &WithSpan<'_, Expr<'_>>) -> bool { match &**expr { // Literals are the definition of pure:
Expr::BoolLit(_) => true,
Expr::NumLit(_, _) => true,
Expr::StrLit(_) => true,
Expr::CharLit(_) => true, // fmt::Display should have no effects:
Expr::Var(_) => true,
Expr::Path(_) => true, // Check recursively:
Expr::Array(args) => args.iter().all(is_cacheable),
Expr::Attr(lhs, _) => is_cacheable(lhs),
Expr::Index(lhs, rhs) => is_cacheable(lhs) && is_cacheable(rhs),
Expr::Filter(Filter { arguments, .. }) => arguments.iter().all(is_cacheable),
Expr::Unary(_, arg) => is_cacheable(arg),
Expr::BinOp(_, lhs, rhs) => is_cacheable(lhs) && is_cacheable(rhs),
Expr::IsDefined(_) | Expr::IsNotDefined(_) => true,
Expr::Range(_, lhs, rhs) => {
lhs.as_ref().is_none_or(|v| is_cacheable(v))
&& rhs.as_ref().is_none_or(|v| is_cacheable(v))
}
Expr::Group(arg) => is_cacheable(arg),
Expr::Tuple(args) => args.iter().all(is_cacheable),
Expr::NamedArgument(_, expr) => is_cacheable(expr),
Expr::As(expr, _) => is_cacheable(expr),
Expr::Try(expr) => is_cacheable(expr),
Expr::Concat(args) => args.iter().all(is_cacheable), // Doesn't make sense in this context.
Expr::LetCond(_) => false, // We have too little information to tell if the expression is pure:
Expr::Call { .. } => false,
Expr::RustMacro(_, _) => false, // Should never be encountered:
Expr::FilterSource => unreachable!("FilterSource in expression?"),
Expr::ArgumentPlaceholder => unreachable!("ExpressionPlaceholder in expression?"),
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.