Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 88 additions & 18 deletions xee-xslt-compiler/src/ast_ir.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::collections::HashMap;

use ahash::HashSetExt;
use xee_name::{Name, Namespaces, FN_NAMESPACE};

Expand All @@ -12,6 +14,9 @@ use crate::{default_declarations::text_only_copy_declarations, priority::default
struct IrConverter<'a> {
variables: Variables,
static_context: &'a StaticContext,
// Named templates by name: the ordered xsl:param declarations, used to
// resolve an xsl:call-template's arguments regardless of declaration order.
template_signatures: HashMap<xot::xmlname::OwnedName, Vec<ast::Param>>,
}

pub fn compile(
Expand Down Expand Up @@ -56,6 +61,7 @@ impl<'a> IrConverter<'a> {
IrConverter {
variables: Variables::new(),
static_context,
template_signatures: HashMap::new(),
}
}

Expand Down Expand Up @@ -117,6 +123,7 @@ impl<'a> IrConverter<'a> {

fn transform(&mut self, transform: &ast::Transform) -> error::SpannedResult<ir::Declarations> {
let main_sequence_constructor = self.main_sequence_constructor();
self.collect_template_signatures(transform);
let main = self.sequence_constructor_function(&main_sequence_constructor)?;
let mut declarations = ir::Declarations::new(main);

Expand All @@ -126,6 +133,19 @@ impl<'a> IrConverter<'a> {
Ok(declarations)
}

fn collect_template_signatures(&mut self, transform: &ast::Transform) {
for declaration in &transform.declarations {
if let ast::Declaration::Template(template) = declaration {
if template.match_.is_none() {
if let Some(name) = &template.name {
self.template_signatures
.insert(name.clone(), template.params.clone());
}
}
}
}
}

fn declaration(
&mut self,
declarations: &mut ir::Declarations,
Expand Down Expand Up @@ -186,14 +206,8 @@ impl<'a> IrConverter<'a> {
"A template must have a match pattern or a name".to_string(),
)
})?;
if !template.params.is_empty() {
return Err(error::Error::Unsupported(
"Template parameters are not supported yet".to_string(),
)
.into());
}
let function_definition =
self.sequence_constructor_function(&template.sequence_constructor)?;
self.named_template_function(&template.params, &template.sequence_constructor)?;
declarations.named_templates.push(ir::NamedTemplate {
name,
function_definition,
Expand Down Expand Up @@ -360,6 +374,45 @@ impl<'a> IrConverter<'a> {
})
}

// A named template is a function taking the context (item, position, last)
// followed by its declared parameters, so xsl:call-template can supply them.
fn named_template_function(
&mut self,
params: &[ast::Param],
sequence_constructor: &ast::SequenceConstructor,
) -> error::SpannedResult<ir::FunctionDefinition> {
let context_names = self.variables.push_context();
let param_names: Vec<ir::Name> = params
.iter()
.map(|param| self.variables.new_var_name(&param.name))
.collect();
let bindings = self.sequence_constructor(sequence_constructor)?;
self.variables.pop_context();

let mut ir_params = vec![
ir::Param {
name: context_names.item,
type_: None,
},
ir::Param {
name: context_names.position,
type_: None,
},
ir::Param {
name: context_names.last,
type_: None,
},
];
for name in param_names {
ir_params.push(ir::Param { name, type_: None });
}
Ok(ir::FunctionDefinition {
params: ir_params,
return_type: None,
body: Box::new(bindings.expr()),
})
}

fn sequence_constructor(
&mut self,
sequence_constructor: &[ast::SequenceConstructorItem],
Expand Down Expand Up @@ -582,24 +635,41 @@ impl<'a> IrConverter<'a> {
&mut self,
call_template: &ast::CallTemplate,
) -> error::SpannedResult<Bindings> {
if !call_template.with_params.is_empty() {
return Err(error::Error::Unsupported(
"Passing parameters with xsl:with-param is not supported yet".to_string(),
)
.into());
}
// A named template is a function taking the context (item, position,
// last); xsl:call-template keeps the caller's context, so forward it.
// The template runs with the caller's context (item, position, last),
// which xsl:call-template preserves, followed by its parameters. Each
// parameter takes the matching xsl:with-param, or the parameter's own
// default when the call omits it.
let context = self.variables.current_context_names().ok_or_else(|| {
error::Error::Unsupported("xsl:call-template outside a template context".to_string())
})?;
let args = vec![
let signature = self
.template_signatures
.get(&call_template.name)
.cloned()
.unwrap_or_default();

let mut combined = Bindings::empty();
let mut args = vec![
Spanned::new(ir::Atom::Variable(context.item), (0..0).into()),
Spanned::new(ir::Atom::Variable(context.position), (0..0).into()),
Spanned::new(ir::Atom::Variable(context.last), (0..0).into()),
];
let bindings = Bindings::empty();
Ok(bindings.bind_expr_no_span(
for param in &signature {
let supplied = call_template
.with_params
.iter()
.find(|with_param| with_param.name == param.name);
let (atom, bindings) = match supplied {
Some(with_param) => self
.select_or_sequence_constructor(with_param)?
.atom_bindings(),
None => self.select_or_sequence_constructor(param)?.atom_bindings(),
};
combined = combined.concat(bindings);
args.push(atom);
}

Ok(combined.bind_expr_no_span(
&mut self.variables,
ir::Expr::CallTemplate(ir::CallTemplate {
name: call_template.name.clone(),
Expand Down
32 changes: 32 additions & 0 deletions xee-xslt-compiler/tests/test_xslt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1263,3 +1263,35 @@ fn test_call_template_keeps_caller_context() {
.unwrap();
assert_eq!(xml(&xot, output), "<r>doc</r>");
}

#[test]
fn test_call_template_with_param() {
let mut xot = Xot::new();
let output = evaluate(
&mut xot,
"<doc/>",
r#"
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3">
<xsl:template match="/"><a><xsl:call-template name="echo"><xsl:with-param name="x" select="'hi'"/></xsl:call-template></a></xsl:template>
<xsl:template name="echo"><xsl:param name="x"/><xsl:value-of select="$x"/></xsl:template>
</xsl:transform>"#,
)
.unwrap();
assert_eq!(xml(&xot, output), "<a>hi</a>");
}

#[test]
fn test_call_template_param_default() {
let mut xot = Xot::new();
let output = evaluate(
&mut xot,
"<doc/>",
r#"
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3">
<xsl:template match="/"><a><xsl:call-template name="echo"/></a></xsl:template>
<xsl:template name="echo"><xsl:param name="x" select="'default'"/><xsl:value-of select="$x"/></xsl:template>
</xsl:transform>"#,
)
.unwrap();
assert_eq!(xml(&xot, output), "<a>default</a>");
}