Skip to content

Commit

Permalink
Merge pull request #264 from GuillaumeGomez/allow-variables-inheritan…
Browse files Browse the repository at this point in the history
…ce-in-block

Allow variables inheritance in block
  • Loading branch information
Kijewski authored Nov 23, 2024
2 parents 9173cb3 + 476a50d commit 8709364
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 107 deletions.
18 changes: 12 additions & 6 deletions book/update-theme.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from json import load
from os import PathLike
from pathlib import Path
from time import sleep


INDEX_HBS_DOMAIN = "api.github.com"
Expand Down Expand Up @@ -53,12 +54,17 @@


def update_theme(target: PathLike) -> None:
with closing(HTTPSConnection(INDEX_HBS_DOMAIN, INDEX_HBS_PORT)) as conn:
conn.request(INDEX_HBS_PROTO, INDEX_HBS_PATH, INDEX_HBS_DATA, INDEX_HBS_HEADERS)
res = conn.getresponse()
if res.status != 200:
raise Exception(f"Status={res.status!r}")
data = load(res)
for i in reversed(range(3)):
with closing(HTTPSConnection(INDEX_HBS_DOMAIN, INDEX_HBS_PORT)) as conn:
conn.request(INDEX_HBS_PROTO, INDEX_HBS_PATH, INDEX_HBS_DATA, INDEX_HBS_HEADERS)
res = conn.getresponse()
if res.status == 200:
data = load(res)
break
if i != 0:
sleep(1.0)
else:
raise Exception(f"Status={res.status!r}")

if data["encoding"] != "base64":
raise Exception(f'Encoding={data["encoding"]!r}')
Expand Down
170 changes: 85 additions & 85 deletions rinja_derive/src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::collections::hash_map::{Entry, HashMap};
use std::ops::Deref;
use std::path::Path;
use std::sync::Arc;
use std::{cmp, hash, mem, str};
use std::{mem, str};

use parser::node::{
Call, Comment, Cond, CondTest, FilterBlock, If, Include, Let, Lit, Loop, Macro, Match,
Expand All @@ -25,7 +25,7 @@ use crate::{BUILT_IN_FILTERS, CRATE, CompileError, FileInfo, MsgValidEscapers};
pub(crate) fn template_to_string(
input: &TemplateInput<'_>,
contexts: &HashMap<&Arc<Path>, Context<'_>, FxBuildHasher>,
heritage: Option<&Heritage<'_>>,
heritage: Option<&Heritage<'_, '_>>,
) -> Result<String, CompileError> {
let mut buf = Buffer::new();
template_into_buffer(input, contexts, heritage, &mut buf, false)?;
Expand All @@ -35,7 +35,7 @@ pub(crate) fn template_to_string(
pub(crate) fn template_into_buffer(
input: &TemplateInput<'_>,
contexts: &HashMap<&Arc<Path>, Context<'_>, FxBuildHasher>,
heritage: Option<&Heritage<'_>>,
heritage: Option<&Heritage<'_, '_>>,
buf: &mut Buffer,
only_template: bool,
) -> Result<(), CompileError> {
Expand All @@ -57,15 +57,15 @@ pub(crate) fn template_into_buffer(
result
}

struct Generator<'a> {
struct Generator<'a, 'h> {
// The template input state: original struct AST and attributes
input: &'a TemplateInput<'a>,
// All contexts, keyed by the package-relative template path
contexts: &'a HashMap<&'a Arc<Path>, Context<'a>, FxBuildHasher>,
// The heritage contains references to blocks and their ancestry
heritage: Option<&'a Heritage<'a>>,
heritage: Option<&'h Heritage<'a, 'h>>,
// Variables accessible directly from the current scope (not redirected to context)
locals: MapChain<'a, Cow<'a, str>, LocalMeta>,
locals: MapChain<'a>,
// Suffix whitespace from the previous literal. Will be flushed to the
// output buffer unless suppressed by whitespace suppression on the next
// non-literal.
Expand All @@ -81,16 +81,16 @@ struct Generator<'a> {
is_in_filter_block: usize,
}

impl<'a> Generator<'a> {
fn new<'n>(
input: &'n TemplateInput<'_>,
contexts: &'n HashMap<&'n Arc<Path>, Context<'n>, FxBuildHasher>,
heritage: Option<&'n Heritage<'_>>,
locals: MapChain<'n, Cow<'n, str>, LocalMeta>,
impl<'a, 'h> Generator<'a, 'h> {
fn new(
input: &'a TemplateInput<'a>,
contexts: &'a HashMap<&'a Arc<Path>, Context<'a>, FxBuildHasher>,
heritage: Option<&'h Heritage<'a, 'h>>,
locals: MapChain<'a>,
buf_writable_discard: bool,
is_in_filter_block: usize,
) -> Generator<'n> {
Generator {
) -> Self {
Self {
input,
contexts,
heritage,
Expand Down Expand Up @@ -131,12 +131,45 @@ impl<'a> Generator<'a> {
Ok(())
}

fn push_locals<T, F: FnOnce(&mut Self) -> Result<T, CompileError>>(
fn push_locals<T, F>(&mut self, callback: F) -> Result<T, CompileError>
where
F: FnOnce(&mut Self) -> Result<T, CompileError>,
{
self.locals.scopes.push(HashMap::default());
let res = callback(self);
self.locals.scopes.pop().unwrap();
res
}

fn with_child<'b, T, F>(
&mut self,
heritage: Option<&'b Heritage<'a, 'b>>,
callback: F,
) -> Result<T, CompileError> {
) -> Result<T, CompileError>
where
F: FnOnce(&mut Generator<'a, 'b>) -> Result<T, CompileError>,
{
self.locals.scopes.push(HashMap::default());
let res = callback(self);

let buf_writable = mem::take(&mut self.buf_writable);
let locals = mem::replace(&mut self.locals, MapChain::new_empty());

let mut child = Generator::new(
self.input,
self.contexts,
heritage,
locals,
self.buf_writable.discard,
self.is_in_filter_block,
);
child.buf_writable = buf_writable;
let res = callback(&mut child);
Generator {
locals: self.locals,
buf_writable: self.buf_writable,
..
} = child;

self.locals.scopes.pop().unwrap();
res
}
Expand Down Expand Up @@ -304,8 +337,7 @@ impl<'a> Generator<'a> {
}

fn is_var_defined(&self, var_name: &str) -> bool {
self.locals.get(&var_name.into()).is_some()
|| self.input.fields.iter().any(|f| f == var_name)
self.locals.get(var_name).is_some() || self.input.fields.iter().any(|f| f == var_name)
}

fn evaluate_condition(
Expand Down Expand Up @@ -966,17 +998,14 @@ impl<'a> Generator<'a> {
Some(heritage) => heritage.root,
None => child_ctx,
};
let locals = MapChain::with_parent(&self.locals);
let mut child = Self::new(
self.input,
self.contexts,
heritage.as_ref(),
locals,
self.buf_writable.discard,
self.is_in_filter_block,
);
let mut size_hint = child.handle(handle_ctx, handle_ctx.nodes, buf, AstLevel::Top)?;
size_hint += child.write_buf_writable(handle_ctx, buf)?;

let size_hint = self.with_child(heritage.as_ref(), |child| {
let mut size_hint = 0;
size_hint += child.handle(handle_ctx, handle_ctx.nodes, buf, AstLevel::Top)?;
size_hint += child.write_buf_writable(handle_ctx, buf)?;
Ok(size_hint)
})?;

self.prepare_ws(i.ws);

Ok(size_hint)
Expand All @@ -991,7 +1020,7 @@ impl<'a> Generator<'a> {
match var {
Target::Name(name) => {
let name = normalize_identifier(name);
match self.locals.get(&Cow::Borrowed(name)) {
match self.locals.get(name) {
// declares a new variable
None => Ok(false),
// an initialized variable gets shadowed
Expand Down Expand Up @@ -1062,7 +1091,7 @@ impl<'a> Generator<'a> {
}
if shadowed
|| !matches!(l.var, Target::Name(_))
|| matches!(&l.var, Target::Name(name) if self.locals.get(&Cow::Borrowed(name)).is_none())
|| matches!(&l.var, Target::Name(name) if self.locals.get(name).is_none())
{
buf.write("let ");
}
Expand Down Expand Up @@ -1152,30 +1181,21 @@ impl<'a> Generator<'a> {
.or_insert_with(|| import.clone());
}

let mut child = Self::new(
self.input,
self.contexts,
Some(heritage),
// Variables are NOT inherited from the parent scope.
MapChain::default(),
self.buf_writable.discard,
self.is_in_filter_block,
);
child.buf_writable = mem::take(&mut self.buf_writable);

// Handle inner whitespace suppression spec and process block nodes
child.prepare_ws(def.ws1);
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)?;
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)?;
}
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);
self.buf_writable = child.buf_writable;
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
Expand Down Expand Up @@ -2631,7 +2651,7 @@ enum EvaluatedResult {
}

impl<'a> Conds<'a> {
fn compute_branches(generator: &Generator<'a>, i: &'a If<'a>) -> Self {
fn compute_branches(generator: &Generator<'a, '_>, i: &'a If<'a>) -> Self {
let mut conds = Vec::with_capacity(i.branches.len());
let mut ws_before = None;
let mut ws_after = None;
Expand Down Expand Up @@ -2754,40 +2774,26 @@ impl LocalMeta {
}
}

#[derive(Debug, Clone)]
struct MapChain<'a, K, V>
where
K: cmp::Eq + hash::Hash,
{
parent: Option<&'a MapChain<'a, K, V>>,
scopes: Vec<HashMap<K, V, FxBuildHasher>>,
struct MapChain<'a> {
scopes: Vec<HashMap<Cow<'a, str>, LocalMeta, FxBuildHasher>>,
}

impl<'a, K: 'a, V: 'a> MapChain<'a, K, V>
where
K: cmp::Eq + hash::Hash,
{
fn with_parent<'p>(parent: &'p MapChain<'_, K, V>) -> MapChain<'p, K, V> {
MapChain {
parent: Some(parent),
scopes: vec![HashMap::default()],
}
impl<'a> MapChain<'a> {
fn new_empty() -> Self {
Self { scopes: vec![] }
}

/// Iterates the scopes in reverse and returns `Some(LocalMeta)`
/// from the first scope where `key` exists.
fn get(&self, key: &K) -> Option<&V> {
let mut scopes = self.scopes.iter().rev();
scopes
.find_map(|set| set.get(key))
.or_else(|| self.parent.and_then(|set| set.get(key)))
fn get<'b>(&'b self, key: &str) -> Option<&'b LocalMeta> {
self.scopes.iter().rev().find_map(|set| set.get(key))
}

fn is_current_empty(&self) -> bool {
self.scopes.last().unwrap().is_empty()
}

fn insert(&mut self, key: K, val: V) {
fn insert(&mut self, key: Cow<'a, str>, val: LocalMeta) {
self.scopes.last_mut().unwrap().insert(key, val);

// Note that if `insert` returns `Some` then it implies
Expand All @@ -2797,15 +2803,10 @@ where
// compile error "identifier `a` used more than once".
}

fn insert_with_default(&mut self, key: K)
where
V: Default,
{
self.insert(key, V::default());
fn insert_with_default(&mut self, key: Cow<'a, str>) {
self.insert(key, LocalMeta::default());
}
}

impl MapChain<'_, Cow<'_, str>, LocalMeta> {
fn resolve(&self, name: &str) -> Option<String> {
let name = normalize_identifier(name);
self.get(&Cow::Borrowed(name)).map(|meta| match &meta.refs {
Expand All @@ -2820,10 +2821,9 @@ impl MapChain<'_, Cow<'_, str>, LocalMeta> {
}
}

impl<'a, K: Eq + hash::Hash, V> Default for MapChain<'a, K, V> {
impl Default for MapChain<'_> {
fn default() -> Self {
Self {
parent: None,
scopes: vec![HashMap::default()],
}
}
Expand Down
33 changes: 17 additions & 16 deletions rinja_derive/src/heritage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,35 @@ use rustc_hash::FxBuildHasher;
use crate::config::Config;
use crate::{CompileError, FileInfo};

pub(crate) struct Heritage<'a> {
pub(crate) root: &'a Context<'a>,
pub(crate) blocks: BlockAncestry<'a>,
pub(crate) struct Heritage<'a, 'h> {
pub(crate) root: &'h Context<'a>,
pub(crate) blocks: BlockAncestry<'a, 'h>,
}

impl Heritage<'_> {
pub(crate) fn new<'n>(
mut ctx: &'n Context<'n>,
contexts: &'n HashMap<&'n Arc<Path>, Context<'n>, FxBuildHasher>,
) -> Heritage<'n> {
let mut blocks: BlockAncestry<'n> = ctx
impl<'a, 'h> Heritage<'a, 'h> {
pub(crate) fn new(
mut root: &'h Context<'a>,
contexts: &'a HashMap<&'a Arc<Path>, Context<'a>, FxBuildHasher>,
) -> Self {
let mut blocks: BlockAncestry<'a, 'h> = root
.blocks
.iter()
.map(|(name, def)| (*name, vec![(ctx, *def)]))
.map(|(name, def)| (*name, vec![(root, *def)]))
.collect();

while let Some(path) = &ctx.extends {
ctx = &contexts[path];
for (name, def) in &ctx.blocks {
blocks.entry(name).or_default().push((ctx, def));
while let Some(path) = &root.extends {
root = &contexts[path];
for (name, def) in &root.blocks {
blocks.entry(name).or_default().push((root, def));
}
}

Heritage { root: ctx, blocks }
Self { root, blocks }
}
}

type BlockAncestry<'a> = HashMap<&'a str, Vec<(&'a Context<'a>, &'a BlockDef<'a>)>, FxBuildHasher>;
type BlockAncestry<'a, 'h> =
HashMap<&'a str, Vec<(&'h Context<'a>, &'a BlockDef<'a>)>, FxBuildHasher>;

#[derive(Clone)]
pub(crate) struct Context<'a> {
Expand Down
2 changes: 2 additions & 0 deletions testing/templates/base-decl.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{%- let variable = 42 -%}
{%- block extended %}{% endblock -%}
1 change: 1 addition & 0 deletions testing/templates/use-var.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{ variable }}
Loading

0 comments on commit 8709364

Please sign in to comment.