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
6 changes: 4 additions & 2 deletions xee-ir/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use ahash::HashMapExt;
use xee_interpreter::{context::StaticContext, error::SpannedResult, interpreter::Program};

use crate::{
declaration_compiler::{DeclarationCompiler, ModeIds},
declaration_compiler::{DeclarationCompiler, ModeIds, TemplateIds},
ir, FunctionBuilder, FunctionCompiler, Scopes,
};

Expand All @@ -11,7 +11,9 @@ pub fn compile_xpath(expr: ir::ExprS, static_context: StaticContext) -> SpannedR
let mut scopes = Scopes::new();
let builder = FunctionBuilder::new(&mut program);
let empty_mode_ids = ModeIds::new();
let mut compiler = FunctionCompiler::new(builder, &mut scopes, &empty_mode_ids);
let empty_template_ids = TemplateIds::new();
let mut compiler =
FunctionCompiler::new(builder, &mut scopes, &empty_mode_ids, &empty_template_ids);
compiler.compile_expr(&expr)?;
Ok(program)
}
Expand Down
23 changes: 22 additions & 1 deletion xee-ir/src/declaration_compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ impl RuleBuilder {
}

pub type ModeIds = HashMap<ir::ApplyTemplatesModeValue, ModeId>;
pub type TemplateIds = HashMap<xot::xmlname::OwnedName, function::InlineFunctionId>;

pub struct DeclarationCompiler<'a> {
program: &'a mut interpreter::Program,
scopes: Scopes,
rule_declaration_order: i64,
rule_builders: HashMap<ir::ModeValue, Vec<RuleBuilder>>,
mode_ids: ModeIds,
template_ids: TemplateIds,
}

impl<'a> DeclarationCompiler<'a> {
Expand All @@ -46,12 +48,18 @@ impl<'a> DeclarationCompiler<'a> {
rule_declaration_order: 0,
rule_builders: HashMap::new(),
mode_ids: HashMap::new(),
template_ids: HashMap::new(),
}
}

fn function_compiler(&mut self) -> FunctionCompiler<'_> {
let function_builder = FunctionBuilder::new(self.program);
FunctionCompiler::new(function_builder, &mut self.scopes, &self.mode_ids)
FunctionCompiler::new(
function_builder,
&mut self.scopes,
&self.mode_ids,
&self.template_ids,
)
}

pub fn compile_declarations(
Expand All @@ -62,6 +70,19 @@ impl<'a> DeclarationCompiler<'a> {
// this early so any mode reference within apply-templates will resolve.
self.compile_modes(declarations);

// Named templates compile to functions invoked by name through
// xsl:call-template. Register their ids before the rules and main, so a
// call resolves regardless of the caller's position.
for named_template in &declarations.named_templates {
let function_id = {
let mut function_compiler = self.function_compiler();
function_compiler
.compile_function_id(&named_template.function_definition, (0..0).into())?
};
self.template_ids
.insert(named_template.name.clone(), function_id);
}

for rule in &declarations.rules {
self.compile_rule(rule)?;
}
Expand Down
32 changes: 31 additions & 1 deletion xee-ir/src/function_compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use xee_interpreter::interpreter::instruction::Instruction;
use xee_interpreter::span::SourceSpan;
use xee_interpreter::{error, function, sequence};

use crate::declaration_compiler::ModeIds;
use crate::declaration_compiler::{ModeIds, TemplateIds};
use crate::ir;

use super::builder::{BackwardJumpRef, ForwardJumpRef, FunctionBuilder, JumpCondition};
Expand All @@ -17,6 +17,7 @@ pub(crate) type Scopes = scope::Scopes<ir::Name>;
pub struct FunctionCompiler<'a> {
pub(crate) scopes: &'a mut Scopes,
pub(crate) mode_ids: &'a ModeIds,
pub(crate) template_ids: &'a TemplateIds,
pub(crate) builder: FunctionBuilder<'a>,
}

Expand All @@ -25,11 +26,13 @@ impl<'a> FunctionCompiler<'a> {
builder: FunctionBuilder<'a>,
scopes: &'a mut Scopes,
mode_ids: &'a ModeIds,
template_ids: &'a TemplateIds,
) -> Self {
Self {
builder,
scopes,
mode_ids,
template_ids,
}
}

Expand Down Expand Up @@ -90,6 +93,9 @@ impl<'a> FunctionCompiler<'a> {
ir::Expr::ApplyTemplates(apply_templates) => {
self.compile_apply_templates(apply_templates, span)
}
ir::Expr::CallTemplate(call_template) => {
self.compile_call_template(call_template, span)
}
ir::Expr::CopyShallow(copy_shallow) => self.compile_copy_shallow(copy_shallow, span),
ir::Expr::CopyDeep(copy_deep) => self.compile_copy_deep(copy_deep, span),
}
Expand Down Expand Up @@ -342,6 +348,7 @@ impl<'a> FunctionCompiler<'a> {
builder: nested_builder,
scopes: self.scopes,
mode_ids: self.mode_ids,
template_ids: self.template_ids,
};

for param in &function_definition.params {
Expand Down Expand Up @@ -438,6 +445,29 @@ impl<'a> FunctionCompiler<'a> {
Ok(())
}

fn compile_call_template(
&mut self,
call_template: &ir::CallTemplate,
span: SourceSpan,
) -> error::SpannedResult<()> {
let function_id = self
.template_ids
.get(&call_template.name)
.copied()
.ok_or_else(|| {
Error::Unsupported(format!("No template named {:?}", call_template.name))
.with_span(span)
})?;
self.builder
.emit(Instruction::Closure(function_id.as_u16()), span);
for arg in &call_template.args {
self.compile_atom(arg)?;
}
self.builder
.emit(Instruction::Call(call_template.args.len() as u8), span);
Ok(())
}

fn compile_lookup(
&mut self,
lookup: &ir::Lookup,
Expand Down
16 changes: 16 additions & 0 deletions xee-ir/src/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ pub enum Expr {
XmlProcessingInstruction(XmlProcessingInstruction),
XmlAppend(XmlAppend),
ApplyTemplates(ApplyTemplates),
CallTemplate(CallTemplate),
CopyShallow(CopyShallow),
CopyDeep(CopyDeep),
}
Expand Down Expand Up @@ -360,6 +361,19 @@ pub struct Rule {
pub function_definition: FunctionDefinition,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CallTemplate {
pub name: xmlname::OwnedName,
// The caller's context (item, position, last), forwarded to the template.
pub args: Vec<AtomS>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NamedTemplate {
pub name: xmlname::OwnedName,
pub function_definition: FunctionDefinition,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ModeValue {
Named(xmlname::OwnedName),
Expand All @@ -373,6 +387,7 @@ pub struct Mode {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Declarations {
pub rules: Vec<Rule>,
pub named_templates: Vec<NamedTemplate>,
pub modes: HashMap<Option<xmlname::OwnedName>, Mode>,
pub functions: Vec<FunctionBinding>,
pub main: FunctionDefinition,
Expand All @@ -383,6 +398,7 @@ impl Declarations {
pub fn new(main: FunctionDefinition) -> Self {
Self {
rules: Vec::new(),
named_templates: Vec::new(),
modes: HashMap::new(),
functions: Vec::new(),
main,
Expand Down
2 changes: 1 addition & 1 deletion xee-ir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ mod variables;
pub use binding::{Binding, Bindings};
pub use builder::FunctionBuilder;
pub use compile::{compile_xpath, compile_xslt};
pub use declaration_compiler::ModeIds;
pub use declaration_compiler::{ModeIds, TemplateIds};
pub use function_compiler::FunctionCompiler;

pub use scope::Scopes;
Expand Down
10 changes: 8 additions & 2 deletions xee-ir/tests/test_xml_ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use ahash::HashMapExt;
use insta::assert_debug_snapshot;

use xee_interpreter::interpreter::{instruction::decode_instructions, Program};
use xee_ir::{ir, FunctionBuilder, FunctionCompiler, ModeIds, Scopes};
use xee_ir::{ir, FunctionBuilder, FunctionCompiler, ModeIds, Scopes, TemplateIds};
use xee_xpath_ast::span::Spanned;

fn spanned<T>(t: T) -> Spanned<T> {
Expand Down Expand Up @@ -85,7 +85,13 @@ fn test_generate_element() {
let function_builder = FunctionBuilder::new(&mut program);
let mut scopes = Scopes::new();
let empty_mode_ids = ModeIds::new();
let mut compiler = FunctionCompiler::new(function_builder, &mut scopes, &empty_mode_ids);
let empty_template_ids = TemplateIds::new();
let mut compiler = FunctionCompiler::new(
function_builder,
&mut scopes,
&empty_mode_ids,
&empty_template_ids,
);

compiler.compile_expr(&outer_expr).unwrap();

Expand Down
50 changes: 49 additions & 1 deletion xee-xslt-compiler/src/ast_ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,24 @@ impl<'a> IrConverter<'a> {
});
Ok(())
} else {
Err(error::Error::Unsupported("Named templates not supported".to_string()).into())
let name = template.name.clone().ok_or_else(|| {
error::Error::Unsupported(
"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)?;
declarations.named_templates.push(ir::NamedTemplate {
name,
function_definition,
});
Ok(())
}
}

Expand Down Expand Up @@ -426,6 +443,7 @@ impl<'a> IrConverter<'a> {
Namespace(namespace) => self.namespace(namespace),
Comment(comment) => self.comment(comment),
ProcessingInstruction(pi) => self.processing_instruction(pi),
CallTemplate(call_template) => self.call_template(call_template),
// TODO: xsl:variable does not produce content and is handled
// earlier already should be unreachable!() but at this point this
// can be reached so return unsupported
Expand Down Expand Up @@ -560,6 +578,36 @@ impl<'a> IrConverter<'a> {
))
}

fn call_template(
&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.
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![
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(
&mut self.variables,
ir::Expr::CallTemplate(ir::CallTemplate {
name: call_template.name.clone(),
args,
}),
))
}

fn select_or_sequence_constructor(
&mut self,
instruction: &impl ast::SelectOrSequenceConstructor,
Expand Down
33 changes: 33 additions & 0 deletions xee-xslt-compiler/tests/test_xslt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1230,3 +1230,36 @@ fn test_basic_iterate_params() {
"<o><baz>1</baz><baz>2</baz><baz>4</baz></o>"
);
}

#[test]
fn test_call_named_template() {
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="greet"/></a></xsl:template>
<xsl:template name="greet">hello</xsl:template>
</xsl:transform>"#,
)
.unwrap();
assert_eq!(xml(&xot, output), "<a>hello</a>");
}

#[test]
fn test_call_template_keeps_caller_context() {
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="/"><xsl:apply-templates/></xsl:template>
<xsl:template match="doc"><r><xsl:call-template name="show"/></r></xsl:template>
<xsl:template name="show"><xsl:value-of select="local-name()"/></xsl:template>
</xsl:transform>"#,
)
.unwrap();
assert_eq!(xml(&xot, output), "<r>doc</r>");
}