Introduce TemplateFunction as wrapper of Vec<Step>

This commit is contained in:
Olivier 'reivilibre' 2025-05-18 11:19:18 +01:00
parent 16f3488d20
commit 659f3a88e4
10 changed files with 483 additions and 435 deletions

View File

@ -6,7 +6,7 @@ use bevy_reflect::{FromReflect, Reflect, ReflectRef, VariantType};
use fluent_templates::lazy_static::lazy_static;
use hornbeam_grammar::ast::{Binding, MatchBinding};
use hornbeam_grammar::Locator;
use hornbeam_ir::ir::{Expression, Step, StepDef, StringPiece};
use hornbeam_ir::ir::{Expression, Step, StepDef, StringPiece, TemplateFunction};
use itertools::Itertools;
use std::any::TypeId;
use std::borrow::Cow;
@ -28,7 +28,7 @@ pub(crate) struct Scope<'a> {
pub(crate) struct Interpreter<'a, O, LS> {
pub(crate) entrypoint: String,
pub(crate) program: &'a BTreeMap<String, Arc<Vec<Step>>>,
pub(crate) program: &'a BTreeMap<String, Arc<TemplateFunction>>,
pub(crate) output: O,
pub(crate) localisation: Arc<LS>,
pub(crate) locale: String,
@ -445,6 +445,14 @@ impl<'a, O: OutputSystem + Send, LS: LocalisationSystem + Sync + Send> Interpret
}
}
StepDef::Call { name, args, slots } => {
let Some(module) = self.program.get(name as &str) else {
return Err(InterpreterError::TypeError {
context: "Call".to_string(),
conflict: format!("no entrypoint for {name:?}."),
location: step.locator.clone(),
});
};
let mut evaled_args = BTreeMap::new();
for (key, expr) in args {
evaled_args.insert(
@ -464,23 +472,18 @@ impl<'a, O: OutputSystem + Send, LS: LocalisationSystem + Sync + Send> Interpret
);
}
// TODO check slots
// ...
// check params
self.scopes.push(Scope {
variables: evaled_args,
slots: filled_in_slots,
});
let next_scope_idx = self.scopes.len() - 1;
let steps = if let Some(steps) = self.program.get(name as &str) {
steps
} else {
return Err(InterpreterError::TypeError {
context: "Call".to_string(),
conflict: format!("no entrypoint for {name:?}."),
location: step.locator.clone(),
});
};
self.run_steps(next_scope_idx, steps).await?;
self.run_steps(next_scope_idx, &module.steps).await?;
self.scopes.pop();
assert_eq!(self.scopes.len(), next_scope_idx);
@ -892,16 +895,15 @@ impl<'a, O: OutputSystem + Send, LS: LocalisationSystem + Sync + Send> Interpret
}
pub async fn run(mut self) -> Result<(), InterpreterError<LS::Error, O::Error>> {
let main = if let Some(main) = self.program.get(&self.entrypoint) {
main
} else {
let Some(main) = self.program.get(&self.entrypoint) else {
return Err(InterpreterError::TypeError {
context: format!("No entrypoint called {:?}", self.entrypoint),
conflict: "".to_string(),
location: Locator::empty(),
});
};
self.run_steps(0, main).await?;
// TODO slot + params check
self.run_steps(0, &main.steps).await?;
Ok(())
}
}

View File

@ -4,7 +4,7 @@ use async_trait::async_trait;
use bevy_reflect::Reflect;
use hornbeam_grammar::parse_template;
use hornbeam_ir::ast_to_optimised_ir;
use hornbeam_ir::ir::Step;
use hornbeam_ir::ir::TemplateFunction;
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::convert::Infallible;
@ -46,7 +46,7 @@ use crate::{default_template_accessible_methods, InterpreterError};
pub struct LoadedTemplates<LS> {
// todo might be tempted to use e.g. ouroboros here, to keep the file source adjacent?
// or do we just staticify?
template_functions: BTreeMap<String, Arc<Vec<Step>>>,
template_functions: BTreeMap<String, Arc<TemplateFunction>>,
methods: Arc<BTreeMap<String, TemplateAccessibleMethod>>,
@ -170,6 +170,8 @@ impl<'a, LS> LoadedTemplates<LS> {
params: Params,
locale: String,
) -> PreparedTemplate<LS> {
// TODO add support for running an `init` or `pre` fragment before running any fragment
// This would allow boilerplate `set` statements to be done ahead of time for example...
PreparedTemplate {
all_instructions: Arc::new(self.template_functions.clone()),
entrypoint: if let Some(frag) = fragment_name {
@ -186,7 +188,8 @@ impl<'a, LS> LoadedTemplates<LS> {
}
pub struct PreparedTemplate<LS> {
pub(crate) all_instructions: Arc<BTreeMap<String, Arc<Vec<Step>>>>,
// TODO rename to `template_functions`
pub(crate) all_instructions: Arc<BTreeMap<String, Arc<TemplateFunction>>>,
pub(crate) methods: Arc<BTreeMap<String, TemplateAccessibleMethod>>,
pub(crate) entrypoint: String,
pub(crate) variables: Params,

View File

@ -1,4 +1,4 @@
use crate::ir::{Step, StepDef};
use crate::ir::{Step, StepDef, TemplateFunction};
use hornbeam_grammar::ast::{
Binding, Block, Expression, HtmlElement, MatchBinding, StringExpr, StringPiece, Template,
};
@ -144,14 +144,21 @@ fn pull_out_entrypoints_from_block(
/// Step 2. Compile the AST to IR steps.
pub(crate) fn compile_functions(
functions: &BTreeMap<String, Vec<Block>>,
) -> Result<BTreeMap<String, Vec<Step>>, AstToIrError> {
) -> Result<BTreeMap<String, TemplateFunction>, AstToIrError> {
let mut result = BTreeMap::new();
for (func_name, func_blocks) in functions {
let mut steps = Vec::new();
for block in func_blocks {
compile_ast_block_to_steps(block, &mut steps)?;
}
result.insert(func_name.clone(), steps);
// TODO save more information
result.insert(
func_name.clone(),
TemplateFunction {
param_defs: None,
steps,
},
);
}
Ok(result)
}

View File

@ -11,6 +11,20 @@ pub struct Function {
pub steps: Vec<Step>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct TemplateFunction {
/// `None` if we don't have static parameter information.
pub param_defs: Option<Vec<ParamDef>>,
pub steps: Vec<Step>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct ParamDef {
pub name: IStr,
// TODO type information
pub default: Option<Expression>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct Step {
#[serde(flatten)]

View File

@ -8,7 +8,6 @@
//! For using the IR, see `hornbeam_interpreter` (dynamic) or `hornbeam_macros` (code gen).
use crate::ast_to_ir::pull_out_entrypoints;
use crate::ir::Step;
use crate::peephole::apply_all_peephole_passes;
use hornbeam_grammar::ast::Template;
use std::collections::BTreeMap;
@ -19,14 +18,16 @@ mod peephole;
pub use ast_to_ir::AstToIrError;
use self::ir::TemplateFunction;
pub fn ast_to_optimised_ir(
template_name: &str,
template: Template,
) -> Result<BTreeMap<String, Vec<Step>>, AstToIrError> {
) -> Result<BTreeMap<String, TemplateFunction>, AstToIrError> {
let entrypoints = pull_out_entrypoints(template, template_name)?;
let mut compiled_funcs = ast_to_ir::compile_functions(&entrypoints)?;
for steps in compiled_funcs.values_mut() {
apply_all_peephole_passes(steps);
for func in compiled_funcs.values_mut() {
apply_all_peephole_passes(&mut func.steps);
}
Ok(compiled_funcs)

View File

@ -167,16 +167,21 @@ pub fn apply_all_peephole_passes(steps: &mut Vec<Step>) {
#[cfg(test)]
mod tests {
use super::*;
use crate::ast_to_ir::{compile_functions, pull_out_entrypoints};
use crate::{
ast_to_ir::{compile_functions, pull_out_entrypoints},
ir::TemplateFunction,
};
use hornbeam_grammar::parse_template;
use insta::assert_yaml_snapshot;
use std::collections::BTreeMap;
fn parse_ir_and_peephole(text: &str) -> BTreeMap<String, Vec<Step>> {
fn parse_ir_and_peephole(text: &str) -> BTreeMap<String, TemplateFunction> {
let template = parse_template(text, "inp").unwrap();
let entrypoints = pull_out_entrypoints(template, "TemplateName").unwrap();
let mut compiled = compile_functions(&entrypoints).unwrap();
compiled.values_mut().for_each(apply_all_peephole_passes);
compiled
.values_mut()
.for_each(|func| apply_all_peephole_passes(&mut func.steps));
compiled
}

View File

@ -3,115 +3,122 @@ source: hornbeam_ir/src/ast_to_ir.rs
expression: "compile_functions(&pull_out_entrypoints(template,\n \"TemplateName\").unwrap()).unwrap()"
---
TemplateName:
- WriteLiteral:
escape: false
text: "<div"
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: false
text: ">"
locator:
filename: inp
line: 2
column: 1
- Call:
name: TemplateName__Frag1
args: {}
slots: {}
locator:
filename: inp
line: 3
column: 5
- Call:
name: TemplateName__Footer
args: {}
slots: {}
locator:
filename: inp
line: 9
column: 5
- WriteLiteral:
escape: false
text: "</div>"
locator:
filename: inp
line: 2
column: 1
param_defs: ~
steps:
- WriteLiteral:
escape: false
text: "<div"
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: false
text: ">"
locator:
filename: inp
line: 2
column: 1
- Call:
name: TemplateName__Frag1
args: {}
slots: {}
locator:
filename: inp
line: 3
column: 5
- Call:
name: TemplateName__Footer
args: {}
slots: {}
locator:
filename: inp
line: 9
column: 5
- WriteLiteral:
escape: false
text: "</div>"
locator:
filename: inp
line: 2
column: 1
TemplateName__Footer:
- WriteLiteral:
escape: true
text: Or even adjacent ones
locator:
filename: ""
line: 0
column: 0
param_defs: ~
steps:
- WriteLiteral:
escape: true
text: Or even adjacent ones
locator:
filename: ""
line: 0
column: 0
TemplateName__Frag1:
- WriteLiteral:
escape: false
text: "<span"
locator:
filename: inp
line: 4
column: 9
- WriteLiteral:
escape: false
text: ">"
locator:
filename: inp
line: 4
column: 9
- WriteLiteral:
escape: true
text: This is a fragment!!
locator:
filename: ""
line: 0
column: 0
- WriteLiteral:
escape: false
text: "</span>"
locator:
filename: inp
line: 4
column: 9
- Call:
name: TemplateName__Frag2
args: {}
slots: {}
locator:
filename: inp
line: 6
column: 9
param_defs: ~
steps:
- WriteLiteral:
escape: false
text: "<span"
locator:
filename: inp
line: 4
column: 9
- WriteLiteral:
escape: false
text: ">"
locator:
filename: inp
line: 4
column: 9
- WriteLiteral:
escape: true
text: This is a fragment!!
locator:
filename: ""
line: 0
column: 0
- WriteLiteral:
escape: false
text: "</span>"
locator:
filename: inp
line: 4
column: 9
- Call:
name: TemplateName__Frag2
args: {}
slots: {}
locator:
filename: inp
line: 6
column: 9
TemplateName__Frag2:
- WriteLiteral:
escape: false
text: "<div"
locator:
filename: inp
line: 7
column: 13
- WriteLiteral:
escape: false
text: ">"
locator:
filename: inp
line: 7
column: 13
- WriteLiteral:
escape: true
text: "There's no problem having nested fragments!"
locator:
filename: ""
line: 0
column: 0
- WriteLiteral:
escape: false
text: "</div>"
locator:
filename: inp
line: 7
column: 13
param_defs: ~
steps:
- WriteLiteral:
escape: false
text: "<div"
locator:
filename: inp
line: 7
column: 13
- WriteLiteral:
escape: false
text: ">"
locator:
filename: inp
line: 7
column: 13
- WriteLiteral:
escape: true
text: "There's no problem having nested fragments!"
locator:
filename: ""
line: 0
column: 0
- WriteLiteral:
escape: false
text: "</div>"
locator:
filename: inp
line: 7
column: 13

View File

@ -3,171 +3,172 @@ source: hornbeam_ir/src/ast_to_ir.rs
expression: "compile_functions(&pull_out_entrypoints(template,\n \"TemplateName\").unwrap()).unwrap()"
---
TemplateName:
- WriteLiteral:
escape: false
text: "<div"
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: false
text: " id=\""
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: true
text: myid
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: false
text: "\""
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: false
text: " class=\""
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: true
text: stylish
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: false
text: "\""
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: false
text: " arb=\""
locator:
filename: inp
line: 2
column: 1
- WriteEval:
escape: true
expr:
Variable:
name: ritrary
loc:
filename: inp
line: 2
column: 47
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: false
text: "\""
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: false
text: " size=\""
locator:
filename: inp
line: 2
column: 1
- WriteEval:
escape: true
expr:
IntLiteral:
val: 42
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: false
text: "\""
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: false
text: " stringy=\""
locator:
filename: inp
line: 2
column: 1
- WriteEval:
escape: true
expr:
StringExpr:
pieces:
- Literal: yup
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: false
text: "\""
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: false
text: ">"
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: true
text: This is a div with a few extras
locator:
filename: ""
line: 0
column: 0
- Call:
name: OtherComponent
args:
param1:
IntLiteral:
val: 1
param2:
StringExpr:
pieces:
- Literal: two
param3:
param_defs: ~
steps:
- WriteLiteral:
escape: false
text: "<div"
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: false
text: " id=\""
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: true
text: myid
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: false
text: "\""
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: false
text: " class=\""
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: true
text: stylish
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: false
text: "\""
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: false
text: " arb=\""
locator:
filename: inp
line: 2
column: 1
- WriteEval:
escape: true
expr:
Variable:
name: three
name: ritrary
loc:
filename: inp
line: 4
column: 52
slots:
main: []
locator:
filename: inp
line: 4
column: 5
- WriteLiteral:
escape: false
text: "</div>"
locator:
filename: inp
line: 2
column: 1
line: 2
column: 47
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: false
text: "\""
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: false
text: " size=\""
locator:
filename: inp
line: 2
column: 1
- WriteEval:
escape: true
expr:
IntLiteral:
val: 42
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: false
text: "\""
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: false
text: " stringy=\""
locator:
filename: inp
line: 2
column: 1
- WriteEval:
escape: true
expr:
StringExpr:
pieces:
- Literal: yup
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: false
text: "\""
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: false
text: ">"
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: true
text: This is a div with a few extras
locator:
filename: ""
line: 0
column: 0
- Call:
name: OtherComponent
args:
param1:
IntLiteral:
val: 1
param2:
StringExpr:
pieces:
- Literal: two
param3:
Variable:
name: three
loc:
filename: inp
line: 4
column: 52
slots:
main: []
locator:
filename: inp
line: 4
column: 5
- WriteLiteral:
escape: false
text: "</div>"
locator:
filename: inp
line: 2
column: 1

View File

@ -3,66 +3,73 @@ source: hornbeam_ir/src/peephole.rs
expression: "parse_ir_and_peephole(r#\"\ndiv\n fragment Frag1\n span\n \"This is a fragment!!\"\n fragment Frag2\n div\n \"There's no problem having <<nested>> fragments!\"\n fragment Footer\n \"Or even adjacent ones\"\n \"#)"
---
TemplateName:
- WriteLiteral:
escape: false
text: "<div>"
locator:
filename: inp
line: 2
column: 1
- Call:
name: TemplateName__Frag1
args: {}
slots: {}
locator:
filename: inp
line: 3
column: 5
- Call:
name: TemplateName__Footer
args: {}
slots: {}
locator:
filename: inp
line: 9
column: 5
- WriteLiteral:
escape: false
text: "</div>"
locator:
filename: inp
line: 2
column: 1
param_defs: ~
steps:
- WriteLiteral:
escape: false
text: "<div>"
locator:
filename: inp
line: 2
column: 1
- Call:
name: TemplateName__Frag1
args: {}
slots: {}
locator:
filename: inp
line: 3
column: 5
- Call:
name: TemplateName__Footer
args: {}
slots: {}
locator:
filename: inp
line: 9
column: 5
- WriteLiteral:
escape: false
text: "</div>"
locator:
filename: inp
line: 2
column: 1
TemplateName__Footer:
- WriteLiteral:
escape: false
text: Or even adjacent ones
locator:
filename: ""
line: 0
column: 0
param_defs: ~
steps:
- WriteLiteral:
escape: false
text: Or even adjacent ones
locator:
filename: ""
line: 0
column: 0
TemplateName__Frag1:
- WriteLiteral:
escape: false
text: "<span>This is a fragment!!</span>"
locator:
filename: inp
line: 4
column: 9
- Call:
name: TemplateName__Frag2
args: {}
slots: {}
locator:
filename: inp
line: 6
column: 9
param_defs: ~
steps:
- WriteLiteral:
escape: false
text: "<span>This is a fragment!!</span>"
locator:
filename: inp
line: 4
column: 9
- Call:
name: TemplateName__Frag2
args: {}
slots: {}
locator:
filename: inp
line: 6
column: 9
TemplateName__Frag2:
- WriteLiteral:
escape: false
text: "<div>There&#x27;s no problem having &lt;&lt;nested&gt;&gt; fragments!</div>"
locator:
filename: inp
line: 7
column: 13
param_defs: ~
steps:
- WriteLiteral:
escape: false
text: "<div>There&#x27;s no problem having &lt;&lt;nested&gt;&gt; fragments!</div>"
locator:
filename: inp
line: 7
column: 13

View File

@ -3,77 +3,78 @@ source: hornbeam_ir/src/peephole.rs
expression: "parse_ir_and_peephole(r#\"\ndiv.stylish#myid {size=42, stringy=\"yup\", arb=$ritrary}\n \"This is a div with a few extras\"\n OtherComponent {param1=1, param2=\"two\", param3=$three}\n \"#)"
---
TemplateName:
- WriteLiteral:
escape: false
text: "<div id=\"myid\" class=\"stylish\" arb=\""
locator:
filename: inp
line: 2
column: 1
- WriteEval:
escape: true
expr:
Variable:
name: ritrary
loc:
filename: inp
line: 2
column: 47
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: false
text: "\" size=\""
locator:
filename: inp
line: 2
column: 1
- WriteEval:
escape: true
expr:
IntLiteral:
val: 42
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: false
text: "\" stringy=\"yup\">This is a div with a few extras"
locator:
filename: inp
line: 2
column: 1
- Call:
name: OtherComponent
args:
param1:
IntLiteral:
val: 1
param2:
StringExpr:
pieces:
- Literal: two
param3:
param_defs: ~
steps:
- WriteLiteral:
escape: false
text: "<div id=\"myid\" class=\"stylish\" arb=\""
locator:
filename: inp
line: 2
column: 1
- WriteEval:
escape: true
expr:
Variable:
name: three
name: ritrary
loc:
filename: inp
line: 4
column: 52
slots:
main: []
locator:
filename: inp
line: 4
column: 5
- WriteLiteral:
escape: false
text: "</div>"
locator:
filename: inp
line: 2
column: 1
line: 2
column: 47
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: false
text: "\" size=\""
locator:
filename: inp
line: 2
column: 1
- WriteEval:
escape: true
expr:
IntLiteral:
val: 42
locator:
filename: inp
line: 2
column: 1
- WriteLiteral:
escape: false
text: "\" stringy=\"yup\">This is a div with a few extras"
locator:
filename: inp
line: 2
column: 1
- Call:
name: OtherComponent
args:
param1:
IntLiteral:
val: 1
param2:
StringExpr:
pieces:
- Literal: two
param3:
Variable:
name: three
loc:
filename: inp
line: 4
column: 52
slots:
main: []
locator:
filename: inp
line: 4
column: 5
- WriteLiteral:
escape: false
text: "</div>"
locator:
filename: inp
line: 2
column: 1