Add set
statements, to set variables (in a way that isn't block-scoped)
This commit is contained in:
parent
c92ab5aaff
commit
d08a088257
@ -11,6 +11,7 @@ pub struct Template {
|
||||
pub enum Block {
|
||||
HtmlElement(HtmlElement),
|
||||
ComponentElement(ComponentElement),
|
||||
SetStatement(SetStatement),
|
||||
IfBlock(IfBlock),
|
||||
ForBlock(ForBlock),
|
||||
MatchBlock(MatchBlock),
|
||||
@ -43,6 +44,13 @@ pub struct ComponentElement {
|
||||
pub loc: Locator,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
|
||||
pub struct SetStatement {
|
||||
pub binding: Binding,
|
||||
pub expression: Expression,
|
||||
pub loc: Locator,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
|
||||
pub struct IfBlock {
|
||||
pub condition: Expression,
|
||||
|
@ -12,6 +12,7 @@ BlockContent = _{
|
||||
Text |
|
||||
RawUnescapedHtml |
|
||||
DefineExpandSlot |
|
||||
SetStatement |
|
||||
ForBlock |
|
||||
MatchBlock |
|
||||
DefineFragment
|
||||
@ -39,6 +40,10 @@ RawUnescapedHtml = {
|
||||
"raw" ~ ws+ ~ String ~ lineEnd
|
||||
}
|
||||
|
||||
SetStatement = {
|
||||
"set" ~ ws+ ~ Binding ~ ws+ ~ "=" ~ ws+ ~ Expr ~ lineEnd
|
||||
}
|
||||
|
||||
IfBlock = {
|
||||
"if" ~ ws+ ~ IfCondition ~ lineEnd ~ NewBlock ~
|
||||
ElseBlock?
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
use crate::ast::{
|
||||
Binding, Block, ComponentElement, DefineExpandSlot, DefineFragment, ElementAttributeFlags,
|
||||
Expression, ForBlock, HtmlElement, IfBlock, MatchBinding, MatchBlock, StringExpr, StringPiece,
|
||||
Template,
|
||||
Expression, ForBlock, HtmlElement, IfBlock, MatchBinding, MatchBlock, SetStatement, StringExpr,
|
||||
StringPiece, Template,
|
||||
};
|
||||
use crate::{intern, IStr, Locator};
|
||||
use lazy_static::lazy_static;
|
||||
@ -415,6 +415,22 @@ impl HornbeamParser {
|
||||
))
|
||||
}
|
||||
|
||||
fn SetStatement(input: Node) -> PCResult<Block> {
|
||||
let loc = nodeloc(&input);
|
||||
|
||||
let (binding, expression) = match_nodes!(input.into_children();
|
||||
[Binding(binding), Expr(expression)] => {
|
||||
(binding, expression)
|
||||
},
|
||||
);
|
||||
|
||||
Ok(Block::SetStatement(SetStatement {
|
||||
binding,
|
||||
expression,
|
||||
loc,
|
||||
}))
|
||||
}
|
||||
|
||||
fn IfCondition(input: Node) -> PCResult<Expression> {
|
||||
Self::Expr(input.into_children().single()?)
|
||||
}
|
||||
@ -559,6 +575,7 @@ impl HornbeamParser {
|
||||
Rule::Element => Some(HornbeamParser::Element(input)?),
|
||||
Rule::Text => Some(HornbeamParser::Text(input)?),
|
||||
Rule::RawUnescapedHtml => Some(HornbeamParser::RawUnescapedHtml(input)?),
|
||||
Rule::SetStatement => Some(HornbeamParser::SetStatement(input)?),
|
||||
Rule::IfBlock => Some(HornbeamParser::IfBlock(input)?),
|
||||
Rule::ForBlock => Some(HornbeamParser::ForBlock(input)?),
|
||||
Rule::MatchBlock => Some(HornbeamParser::MatchBlock(input)?),
|
||||
|
@ -243,6 +243,20 @@ impl<'a, O: OutputSystem + Send, LS: LocalisationSystem + Sync + Send> Interpret
|
||||
.await
|
||||
.map_err(|underlying| InterpreterError::OutputError { underlying })?;
|
||||
}
|
||||
StepDef::Set {
|
||||
expression,
|
||||
binding,
|
||||
} => {
|
||||
let expr_evaled = self.evaluate_expression(scope_idx, expression, &step.locator)?;
|
||||
|
||||
// Bind the expression
|
||||
// Unlike the other places where we bind variables, we never unbind this one,
|
||||
// since `set` is not block-scoped.
|
||||
// (Ideally we would have slightly more careful scoping rules, but `set` is intentionally being
|
||||
// added to allow variables to escape their respective blocks...)
|
||||
let mut binder = Binder::new();
|
||||
binder.bind(&mut self.scopes[scope_idx].variables, binding, expr_evaled);
|
||||
}
|
||||
StepDef::If {
|
||||
condition,
|
||||
true_steps,
|
||||
|
@ -85,3 +85,15 @@ for $part in $sts.carrot.split("A")
|
||||
"#
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snapshot_004() {
|
||||
assert_snapshot!(simple_render(
|
||||
r#"
|
||||
for $part in $sts.carrot.split("A")
|
||||
set $final_part = $part
|
||||
|
||||
"the final part was: $final_part"
|
||||
"#
|
||||
))
|
||||
}
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
source: hornbeam_interpreter/tests/snapshots.rs
|
||||
expression: "simple_render(r#\"\nfor $part in $sts.carrot.split(\"A\")\n set $final_part = $part\n\n\"the final part was: $final_part\"\n \"#)"
|
||||
---
|
||||
the final part was: RROT!
|
@ -133,7 +133,10 @@ fn pull_out_entrypoints_from_block<'a>(
|
||||
}
|
||||
}
|
||||
}
|
||||
Block::Text(_) | Block::RawUnescapedHtml(_) | Block::DefineExpandSlot(_) => { /* nop */ }
|
||||
Block::Text(_)
|
||||
| Block::RawUnescapedHtml(_)
|
||||
| Block::DefineExpandSlot(_)
|
||||
| Block::SetStatement(_) => { /* nop */ }
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -304,6 +307,13 @@ fn compile_ast_block_to_steps(
|
||||
locator: ce.loc.clone(),
|
||||
})
|
||||
}
|
||||
Block::SetStatement(ss) => instructions.push(Step {
|
||||
def: StepDef::Set {
|
||||
binding: ss.binding.clone(),
|
||||
expression: ss.expression.clone(),
|
||||
},
|
||||
locator: ss.loc.clone(),
|
||||
}),
|
||||
Block::IfBlock(ifb) => {
|
||||
let mut true_instrs = Vec::new();
|
||||
let mut false_instrs = Vec::new();
|
||||
|
@ -28,6 +28,10 @@ pub enum StepDef {
|
||||
escape: bool,
|
||||
expr: Expression,
|
||||
},
|
||||
Set {
|
||||
expression: Expression,
|
||||
binding: Binding,
|
||||
},
|
||||
If {
|
||||
condition: Expression,
|
||||
true_steps: Vec<Step>,
|
||||
|
@ -54,6 +54,7 @@ fn apply_peephole_pass<F: Fn(&mut Vec<Step>)>(steps: &mut Vec<Step>, pass: &F) {
|
||||
match &mut step.def {
|
||||
StepDef::WriteLiteral { .. } => {}
|
||||
StepDef::WriteEval { .. } => {}
|
||||
StepDef::Set { .. } => {}
|
||||
StepDef::If {
|
||||
true_steps,
|
||||
false_steps,
|
||||
|
Loading…
x
Reference in New Issue
Block a user