From 0e43b05fe272cb354c40ad4fef38a0d6461766ec Mon Sep 17 00:00:00 2001 From: Bojidar Marinov Date: Tue, 28 Oct 2025 01:09:09 +0200 Subject: [PATCH] Reimplement ir::ApplyTemplates entirely in the interpreter VM Now, instruction::ApplyTemplates has been replaced with the leaner instruction::MatchPattern, which in turn allows for a much simpler implementation of xsl:param root-level declarations. Also, implement `#current` mode with a ClosureVar because it's easy. :tada: --- vendor/xslt-tests/filters | 4 - .../src/function/inline_function.rs | 3 + .../src/interpreter/instruction.rs | 25 +- xee-interpreter/src/interpreter/interpret.rs | 76 +---- xee-interpreter/src/interpreter/program.rs | 3 - xee-interpreter/src/interpreter/state.rs | 7 + xee-ir/src/builder.rs | 21 +- xee-ir/src/declaration_compiler.rs | 168 ++-------- xee-ir/src/function_compiler.rs | 303 ++++++++++++++++-- xee-ir/src/ir.rs | 13 +- 10 files changed, 360 insertions(+), 263 deletions(-) diff --git a/vendor/xslt-tests/filters b/vendor/xslt-tests/filters index f5218eac4..acd3f462c 100644 --- a/vendor/xslt-tests/filters +++ b/vendor/xslt-tests/filters @@ -1070,7 +1070,6 @@ bug-6201 bug-6301 bug-6401 = built-in-templates -built-in-templates-0101 built-in-templates-0201 built-in-templates-0202 built-in-templates-0301 @@ -1303,9 +1302,7 @@ copy-0103 copy-0104 copy-0105 copy-0501 -copy-0602 copy-0603 -copy-0604 copy-0606 copy-0609 copy-0610 @@ -10364,7 +10361,6 @@ string-120 string-121 string-125 string-131 -string-133 string-134 string-135 string-136 diff --git a/xee-interpreter/src/function/inline_function.rs b/xee-interpreter/src/function/inline_function.rs index 16d5d3592..8f370b9a9 100644 --- a/xee-interpreter/src/function/inline_function.rs +++ b/xee-interpreter/src/function/inline_function.rs @@ -1,4 +1,5 @@ use xee_schema_type::Xs; +use xee_xpath_ast::Pattern; use xee_xpath_type::ast::SequenceType; use crate::sequence; @@ -6,6 +7,7 @@ use crate::span::SourceSpan; use crate::xml; use super::signature::Signature; +use super::InlineFunctionId; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct CastType { @@ -32,6 +34,7 @@ pub struct InlineFunction { pub cast_types: Vec, pub sequence_types: Vec, pub closure_names: Vec, + pub patterns: Vec>, // the compiled code, and the spans of each instruction pub chunk: Vec, pub spans: Vec, diff --git a/xee-interpreter/src/interpreter/instruction.rs b/xee-interpreter/src/interpreter/instruction.rs index f7b4ff14e..f291f0e79 100644 --- a/xee-interpreter/src/interpreter/instruction.rs +++ b/xee-interpreter/src/interpreter/instruction.rs @@ -20,6 +20,7 @@ pub enum Instruction { Var(u16), Set(u16), ClosureVar(u16), + ClosureRecursiveSelf, Comma, CurlyArray, SquareArray, @@ -77,7 +78,7 @@ pub enum Instruction { XmlAppend, CopyShallow, CopyDeep, - ApplyTemplates(u16), + MatchPattern(u16), PrintTop, PrintStack, } @@ -99,6 +100,7 @@ pub(crate) enum EncodedInstruction { Var, Set, ClosureVar, + ClosureRecursiveSelf, Comma, CurlyArray, SquareArray, @@ -154,7 +156,7 @@ pub(crate) enum EncodedInstruction { XmlComment, XmlProcessingInstruction, XmlAppend, - ApplyTemplates, + MatchPattern, CopyShallow, CopyDeep, PrintTop, @@ -198,6 +200,7 @@ pub(crate) fn decode_instruction(bytes: &[u8]) -> (Instruction, usize) { let variable = u16::from_le_bytes([bytes[1], bytes[2]]); (Instruction::ClosureVar(variable), 3) } + EncodedInstruction::ClosureRecursiveSelf => (Instruction::ClosureRecursiveSelf, 1), EncodedInstruction::Comma => (Instruction::Comma, 1), EncodedInstruction::CurlyArray => (Instruction::CurlyArray, 1), EncodedInstruction::SquareArray => (Instruction::SquareArray, 1), @@ -285,9 +288,9 @@ pub(crate) fn decode_instruction(bytes: &[u8]) -> (Instruction, usize) { EncodedInstruction::XmlAppend => (Instruction::XmlAppend, 1), EncodedInstruction::CopyShallow => (Instruction::CopyShallow, 1), EncodedInstruction::CopyDeep => (Instruction::CopyDeep, 1), - EncodedInstruction::ApplyTemplates => { - let mode_id = u16::from_le_bytes([bytes[1], bytes[2]]); - (Instruction::ApplyTemplates(mode_id), 3) + EncodedInstruction::MatchPattern => { + let pattern_id = u16::from_le_bytes([bytes[1], bytes[2]]); + (Instruction::MatchPattern(pattern_id), 3) } EncodedInstruction::PrintTop => (Instruction::PrintTop, 1), EncodedInstruction::PrintStack => (Instruction::PrintStack, 1), @@ -340,6 +343,9 @@ pub fn encode_instruction(instruction: Instruction, bytes: &mut Vec) { bytes.push(EncodedInstruction::ClosureVar.to_u8().unwrap()); bytes.extend_from_slice(&variable.to_le_bytes()); } + Instruction::ClosureRecursiveSelf => { + bytes.push(EncodedInstruction::ClosureRecursiveSelf.to_u8().unwrap()) + } Instruction::Comma => bytes.push(EncodedInstruction::Comma.to_u8().unwrap()), Instruction::CurlyArray => bytes.push(EncodedInstruction::CurlyArray.to_u8().unwrap()), Instruction::SquareArray => bytes.push(EncodedInstruction::SquareArray.to_u8().unwrap()), @@ -437,9 +443,9 @@ pub fn encode_instruction(instruction: Instruction, bytes: &mut Vec) { Instruction::XmlAppend => bytes.push(EncodedInstruction::XmlAppend.to_u8().unwrap()), Instruction::CopyShallow => bytes.push(EncodedInstruction::CopyShallow.to_u8().unwrap()), Instruction::CopyDeep => bytes.push(EncodedInstruction::CopyDeep.to_u8().unwrap()), - Instruction::ApplyTemplates(mode_id) => { - bytes.push(EncodedInstruction::ApplyTemplates.to_u8().unwrap()); - bytes.extend_from_slice(&mode_id.to_le_bytes()); + Instruction::MatchPattern(pattern_id) => { + bytes.push(EncodedInstruction::MatchPattern.to_u8().unwrap()); + bytes.extend_from_slice(&pattern_id.to_le_bytes()); } Instruction::PrintTop => bytes.push(EncodedInstruction::PrintTop.to_u8().unwrap()), Instruction::PrintStack => bytes.push(EncodedInstruction::PrintStack.to_u8().unwrap()), @@ -468,6 +474,7 @@ pub fn instruction_size(instruction: &Instruction) -> usize { | Instruction::CurlyArray | Instruction::SquareArray | Instruction::CurlyMap + | Instruction::ClosureRecursiveSelf | Instruction::Eq | Instruction::Ne | Instruction::Lt @@ -529,7 +536,7 @@ pub fn instruction_size(instruction: &Instruction) -> usize { | Instruction::Treat(_) | Instruction::ReturnConvert(_) | Instruction::JumpIfFalse(_) => 3, - Instruction::ApplyTemplates(_) => 3, + Instruction::MatchPattern(_) => 3, } } diff --git a/xee-interpreter/src/interpreter/interpret.rs b/xee-interpreter/src/interpreter/interpret.rs index 7e2a34a5a..27d1e3993 100644 --- a/xee-interpreter/src/interpreter/interpret.rs +++ b/xee-interpreter/src/interpreter/interpret.rs @@ -14,13 +14,13 @@ use crate::atomic::{ op_add, op_div, op_idiv, op_mod, op_multiply, op_subtract, OpEq, OpGe, OpGt, OpLe, OpLt, OpNe, }; use crate::context::DynamicContext; +use crate::error; use crate::function; use crate::pattern::PredicateMatcher; use crate::sequence; use crate::span::SourceSpan; use crate::stack; use crate::xml; -use crate::{error, pattern}; use super::instruction::{read_i16, read_instruction, read_u16, read_u8, EncodedInstruction}; use super::runnable::Runnable; @@ -176,6 +176,9 @@ impl<'a> Interpreter<'a> { let index = self.read_u16(); self.state.push_closure_var(index as usize)?; } + EncodedInstruction::ClosureRecursiveSelf => { + self.state.push_closure_recursive()?; + } EncodedInstruction::Comma => { let b = self.state.pop()?; let a = self.state.pop()?; @@ -630,12 +633,13 @@ impl<'a> Interpreter<'a> { } self.state.push(new_sequence); } - EncodedInstruction::ApplyTemplates => { - let value = self.state.pop()?; - let mode_id = self.read_u16(); - let mode = pattern::ModeId::new(mode_id as usize); - let value = self.apply_templates_sequence(mode, value)?; - self.state.push(value); + EncodedInstruction::MatchPattern => { + let pattern_id = self.read_u16(); + let items = self.state.top()?; + let item = items.one()?; + let pattern = &(self.current_inline_function().patterns[pattern_id as usize]); + let result = self.matches(pattern, &item); + self.state.push(sequence::Item::Atomic(result.into())); } EncodedInstruction::PrintTop => { let top = self.state.top()?; @@ -694,7 +698,7 @@ impl<'a> Interpreter<'a> { Ok(function::StaticFunctionData::new(static_function_id, closure_vars).into()) } - pub(crate) fn current_inline_function(&self) -> &function::InlineFunction { + pub(crate) fn current_inline_function(&self) -> &'a function::InlineFunction { self.runnable .program() .inline_function(self.state.frame().function()) @@ -1183,62 +1187,6 @@ impl<'a> Interpreter<'a> { } } - fn apply_templates_sequence( - &mut self, - mode: pattern::ModeId, - sequence: sequence::Sequence, - ) -> error::Result { - let mut r: Vec = Vec::new(); - let size: IBig = sequence.len().into(); - - for (i, item) in sequence.iter().enumerate() { - let sequence = self.apply_templates_item(mode, item, i, size.clone())?; - if let Some(sequence) = sequence { - for item in sequence.iter() { - r.push(item.clone()); - } - } - } - Ok(r.into()) - } - - fn apply_templates_item( - &mut self, - mode: pattern::ModeId, - item: sequence::Item, - position: usize, - size: IBig, - ) -> error::Result> { - let function_id = self.lookup_pattern(mode, &item); - - if let Some(function_id) = function_id { - let position: IBig = (position + 1).into(); - let arguments: Vec = vec![ - item.into(), - atomic::Atomic::from(position).into(), - atomic::Atomic::from(size.clone()).into(), - ]; - let function = function::InlineFunctionData::new(function_id, Vec::new()).into(); - self.call_function_with_arguments(&function, &arguments) - .map(Some) - } else { - Ok(None) - } - } - - pub(crate) fn lookup_pattern( - &mut self, - mode: pattern::ModeId, - item: &sequence::Item, - ) -> Option { - self.runnable - .program() - .declarations - .mode_lookup - .lookup(mode, |pattern| self.matches(pattern, item)) - .copied() - } - // The interpreter can return an error for any byte code, in any level of // nesting in the function. When this happens the interpreter stops with // the error code. We here wrap it in a SpannedError using the current diff --git a/xee-interpreter/src/interpreter/program.rs b/xee-interpreter/src/interpreter/program.rs index 685a91c46..3e323dad7 100644 --- a/xee-interpreter/src/interpreter/program.rs +++ b/xee-interpreter/src/interpreter/program.rs @@ -1,5 +1,4 @@ use crate::context; -use crate::declaration::Declarations; use crate::function; use xee_name::Name; use xee_xpath_ast::ast::Span; @@ -10,7 +9,6 @@ use super::Runnable; pub struct Program { span: Span, pub functions: Vec, - pub declarations: Declarations, static_context: context::StaticContext, map_signature: function::Signature, array_signature: function::Signature, @@ -21,7 +19,6 @@ impl Program { Program { span, functions: Vec::new(), - declarations: Declarations::new(), static_context, map_signature: function::Signature::map_signature(), array_signature: function::Signature::array_signature(), diff --git a/xee-interpreter/src/interpreter/state.rs b/xee-interpreter/src/interpreter/state.rs index 22f250532..cb3079367 100644 --- a/xee-interpreter/src/interpreter/state.rs +++ b/xee-interpreter/src/interpreter/state.rs @@ -140,6 +140,13 @@ impl<'a> State<'a> { Ok(()) } + pub(crate) fn push_closure_recursive(&mut self) -> error::Result<()> { + let function = self.function()?; + let item: sequence::Item = function.into(); + self.stack.push(stack::Value::Sequence(item.into())); + Ok(()) + } + pub(crate) fn set_var(&mut self, index: usize) { let base = self.frame().base; self.stack[base + index] = self.stack.pop().unwrap(); diff --git a/xee-ir/src/builder.rs b/xee-ir/src/builder.rs index f93e04956..cb94aa32c 100644 --- a/xee-ir/src/builder.rs +++ b/xee-ir/src/builder.rs @@ -1,4 +1,5 @@ -use xee_xpath_ast::ast; +use xee_interpreter::function::InlineFunctionId; +use xee_xpath_ast::{ast, Pattern}; use xee_interpreter::interpreter::instruction::{ encode_instruction, instruction_size, Instruction, @@ -31,6 +32,7 @@ pub struct FunctionBuilder<'a> { cast_types: Vec, sequence_types: Vec, closure_names: Vec, + patterns: Vec>, } impl<'a> FunctionBuilder<'a> { @@ -44,6 +46,7 @@ impl<'a> FunctionBuilder<'a> { cast_types: Vec::new(), sequence_types: Vec::new(), closure_names: Vec::new(), + patterns: Vec::new(), } } @@ -80,6 +83,15 @@ impl<'a> FunctionBuilder<'a> { index } + pub(crate) fn add_pattern(&mut self, pattern: Pattern) -> usize { + let index = self.patterns.len(); + self.patterns.push(pattern.clone()); + if index > (u16::MAX as usize) { + panic!("too many pattern names"); + } + index + } + pub(crate) fn add_step(&mut self, step: xml::Step) -> usize { let step_id = self.steps.len(); self.steps.push(step); @@ -164,10 +176,10 @@ impl<'a> FunctionBuilder<'a> { pub(crate) fn finish( mut self, name: String, - function_definition: &ir::FunctionDefinition, + signature: function::Signature, span: span::SourceSpan, ) -> function::InlineFunction { - if let Some(return_type) = &function_definition.return_type { + if let Some(return_type) = signature.return_type() { let sequence_type_id = self.add_sequence_type(return_type.clone()); if sequence_type_id > (u16::MAX as usize) { panic!("too many sequence types"); @@ -177,7 +189,7 @@ impl<'a> FunctionBuilder<'a> { self.emit(Instruction::Return, span); function::InlineFunction { name, - signature: function_definition.signature(), + signature, chunk: self.compiled, spans: self.spans, closure_names: self.closure_names, @@ -185,6 +197,7 @@ impl<'a> FunctionBuilder<'a> { steps: self.steps, cast_types: self.cast_types, sequence_types: self.sequence_types, + patterns: self.patterns, } } diff --git a/xee-ir/src/declaration_compiler.rs b/xee-ir/src/declaration_compiler.rs index ca0147c33..be3bbc8d8 100644 --- a/xee-ir/src/declaration_compiler.rs +++ b/xee-ir/src/declaration_compiler.rs @@ -1,40 +1,13 @@ -use ahash::{HashMap, HashMapExt, HashSet, HashSetExt}; -use rust_decimal::Decimal; -use xee_interpreter::pattern::ModeId; -use xee_xpath_ast::Pattern; +use crate::{function_compiler::Scopes, ir, FunctionBuilder, FunctionCompiler}; -use crate::function_compiler::Scopes; -use crate::{ir, FunctionBuilder, FunctionCompiler}; - -use xee_interpreter::{error, function, interpreter}; -use xee_xpath_ast::pattern::transform_pattern; - -#[derive(Debug, Clone)] -pub(crate) struct RuleBuilder { - priority: Decimal, - declaration_order: i64, - pattern: Pattern, - function_id: function::InlineFunctionId, -} - -impl RuleBuilder { - fn rule( - self, - ) -> ( - Pattern, - function::InlineFunctionId, - ) { - (self.pattern, self.function_id) - } -} +use ahash::{HashMap, HashMapExt}; +use xee_interpreter::{error, interpreter, pattern::ModeId}; pub type ModeIds = HashMap; pub struct DeclarationCompiler<'a> { program: &'a mut interpreter::Program, scopes: Scopes, - rule_declaration_order: i64, - rule_builders: HashMap>, mode_ids: ModeIds, } @@ -43,8 +16,6 @@ impl<'a> DeclarationCompiler<'a> { Self { program, scopes: Scopes::new(), - rule_declaration_order: 0, - rule_builders: HashMap::new(), mode_ids: HashMap::new(), } } @@ -58,123 +29,22 @@ impl<'a> DeclarationCompiler<'a> { &mut self, declarations: &ir::Declarations, ) -> error::SpannedResult<()> { - // first keep track of what modes exist, to create a ModeId for them. We do - // this early so any mode reference within apply-templates will resolve. - self.compile_modes(declarations); - - for rule in &declarations.rules { - self.compile_rule(rule)?; - } - // now add compiled rules from builder to the program - self.add_rules(); - let mut function_compiler = self.function_compiler(); - function_compiler.compile_function_definition(&declarations.main, (0..0).into()) - } - - fn compile_modes(&mut self, declarations: &ir::Declarations) { - for rule in &declarations.rules { - for mode_value in &rule.modes { - // we don't register All modes - if matches!(mode_value, ir::ModeValue::All) { - continue; - } - let apply_templates_mode_value = match mode_value { - ir::ModeValue::All => continue, - ir::ModeValue::Named(name) => ir::ApplyTemplatesModeValue::Named(name.clone()), - ir::ModeValue::Unnamed => ir::ApplyTemplatesModeValue::Unnamed, - }; - // we want the mode id to be unique and not overwritten - if self.mode_ids.contains_key(&apply_templates_mode_value) { - continue; - } - let mode_id = ModeId::new(self.mode_ids.len()); - self.mode_ids.insert(apply_templates_mode_value, mode_id); - } - } - } - - fn compile_rule(&mut self, rule: &ir::Rule) -> error::SpannedResult<()> { + let declarations = declarations.clone(); let mut function_compiler = self.function_compiler(); - let function_id = - function_compiler.compile_function_id(&rule.function_definition, (0..0).into())?; - - let pattern = transform_pattern(&rule.pattern, |function_definition| { - function_compiler.compile_function_id(function_definition, (0..0).into()) - })?; - - self.add_rule(&rule.modes, rule.priority, &pattern, function_id); - Ok(()) - } - - fn add_rule( - &mut self, - modes: &[ir::ModeValue], - priority: Decimal, - pattern: &Pattern, - function_id: function::InlineFunctionId, - ) { - // ensure there are no duplicate modes - let mut mode_seen = HashSet::new(); - - let declaration_order = self.rule_declaration_order; - self.rule_declaration_order += 1; - for mode in modes { - if mode_seen.contains(mode) { - continue; - } - mode_seen.insert(mode); - self.rule_builders - .entry(mode.clone()) - .or_default() - .push(RuleBuilder { - priority, - declaration_order, - pattern: pattern.clone(), - function_id, - }); - } - } - - fn add_rules(&mut self) { - // we don't want to register #all normally - let all_rule_builders = self.rule_builders.remove(&ir::ModeValue::All); - - // we add the all rule builders to each rule builders, as they apply to - // all modes. We do this before the final registration so we benefit - // from priority sorting later - if let Some(all_rule_builders) = all_rule_builders { - for rule_builders in self.rule_builders.values_mut() { - for all_rule_builder in &all_rule_builders { - rule_builders.push(all_rule_builder.clone()); - } - } - } - - for (mode, mut rule_builders) in self.rule_builders.drain() { - // higher priorities first, same priorities last declaration order wins - rule_builders.sort_by_key(|rule_builder| { - (-rule_builder.priority, -rule_builder.declaration_order) - }); - let rules = rule_builders - .drain(..) - .map(|rule_builder| rule_builder.rule()) - .collect(); - let apply_templates_mode_value = match mode { - ir::ModeValue::Named(name) => ir::ApplyTemplatesModeValue::Named(name), - ir::ModeValue::Unnamed => ir::ApplyTemplatesModeValue::Unnamed, - ir::ModeValue::All => { - unreachable!() - } - }; - let mode_id = self - .mode_ids - .get(&apply_templates_mode_value) - .cloned() - .expect("Mode should have been registered"); - self.program - .declarations - .mode_lookup - .add_rules(mode_id, rules) - } + function_compiler.compile_function_definition( + &ir::FunctionDefinition { + params: declarations.main.params, + return_type: declarations.main.return_type, + body: Box::new(ir::ExprS::new( + ir::Expr::DefineTemplates(ir::DefineTemplates { + rules: declarations.rules, + modes: declarations.modes, + body: declarations.main.body, + }), + (0..0).into(), + )), + }, + (0..0).into(), + ) } } diff --git a/xee-ir/src/function_compiler.rs b/xee-ir/src/function_compiler.rs index 70f4a30d2..7c2117711 100644 --- a/xee-ir/src/function_compiler.rs +++ b/xee-ir/src/function_compiler.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use ibig::{ibig, IBig}; use xee_interpreter::error::Error; @@ -5,6 +7,8 @@ use xee_interpreter::function::FunctionRule; use xee_interpreter::interpreter::instruction::Instruction; use xee_interpreter::span::SourceSpan; use xee_interpreter::{error, function, sequence}; +use xee_xpath_ast::pattern::transform_pattern; +use xee_xpath_ast::Pattern; use crate::declaration_compiler::ModeIds; use crate::ir; @@ -92,6 +96,9 @@ impl<'a> FunctionCompiler<'a> { } ir::Expr::CopyShallow(copy_shallow) => self.compile_copy_shallow(copy_shallow, span), ir::Expr::CopyDeep(copy_deep) => self.compile_copy_deep(copy_deep, span), + ir::Expr::DefineTemplates(define_templates) => { + self.compile_define_templates(define_templates, span) + } } } @@ -112,6 +119,9 @@ impl<'a> FunctionCompiler<'a> { ir::Const::Decimal(d) => { self.builder.emit_constant((*d).into(), span); } + ir::Const::Name(n) => { + self.builder.emit_constant((n.clone()).into(), span); + } ir::Const::EmptySequence => self .builder .emit_constant(sequence::Sequence::default(), span), @@ -330,9 +340,10 @@ impl<'a> FunctionCompiler<'a> { Ok(()) } - pub fn compile_function_id( + pub fn compile_function( &mut self, - function_definition: &ir::FunctionDefinition, + function_body: impl FnOnce(&mut FunctionCompiler) -> error::SpannedResult<()>, + signature: function::Signature, span: SourceSpan, ) -> error::SpannedResult { let nested_builder = self.builder.builder(); @@ -344,19 +355,13 @@ impl<'a> FunctionCompiler<'a> { mode_ids: self.mode_ids, }; - for param in &function_definition.params { - compiler.scopes.push_name(¶m.name); - } - compiler.compile_expr(&function_definition.body)?; - for _ in &function_definition.params { - compiler.scopes.pop_name(); - } + function_body(&mut compiler)?; compiler.scopes.pop_scope(); let function = compiler .builder - .finish("inline".to_string(), function_definition, span); + .finish("inline".to_string(), signature, span); // now place all captured names on stack, to ensure we have the // closure // in reverse order so we can pop them off in the right order @@ -366,6 +371,27 @@ impl<'a> FunctionCompiler<'a> { Ok(self.builder.add_function(function)) } + pub fn compile_function_id( + &mut self, + function_definition: &ir::FunctionDefinition, + span: SourceSpan, + ) -> error::SpannedResult { + self.compile_function( + |compiler| { + for param in &function_definition.params { + compiler.scopes.push_name(¶m.name); + } + compiler.compile_expr(&function_definition.body)?; + for _ in &function_definition.params { + compiler.scopes.pop_name(); + } + Ok(()) + }, + function_definition.signature(), + span, + ) + } + pub(crate) fn compile_function_definition( &mut self, function_definition: &ir::FunctionDefinition, @@ -998,30 +1024,253 @@ impl<'a> FunctionCompiler<'a> { Ok(()) } + fn compile_define_templates( + &mut self, + define_templates: &ir::DefineTemplates, + span: SourceSpan, + ) -> error::SpannedResult<()> { + // HACK: Define types for mode_patterns instead of using tuples + // Also maybe move into mutliple functions like the old declaration_compiler + let mut mode_patterns = HashMap::<_, Vec<_>>::new(); + for (declaration_order, rule) in define_templates.rules.iter().enumerate() { + let pattern = transform_pattern(&rule.pattern, |function_definition| { + self.compile_function_id(function_definition, span) + })?; + for mode in rule.modes.iter() { + let patterns = mode_patterns.entry(mode.clone()).or_default(); + patterns.push(( + (-rule.priority, -(declaration_order as isize)), + (pattern.clone(), &rule.function_definition, span), + )); + } + } + + let all_mode_patterns = mode_patterns.remove(&ir::ModeValue::All); + if let Some(all_mode_patterns) = all_mode_patterns { + for patterns in mode_patterns.values_mut() { + patterns.extend(all_mode_patterns.iter().cloned()); + } + } + + let mut modes_num = 0; + for (mode, mut patterns) in mode_patterns.into_iter() { + patterns.sort_by_key(|(key, _)| *key); + let mode_key = match &mode { + ir::ModeValue::Named(x) => Some(x.clone()), + ir::ModeValue::Unnamed => None, + ir::ModeValue::All => unreachable!(), + }; + let mode_config = define_templates.modes.get(&mode_key); + let mode_constant = match mode { + ir::ModeValue::Named(name) => { + let name: sequence::Item = name.into(); + name.into() + } + ir::ModeValue::Unnamed => { + let name: sequence::Item = 0.into(); + name.into() + } + ir::ModeValue::All => unreachable!(), + }; + modes_num += 1; + self.builder.emit_constant(mode_constant, span); + self.compile_mode(mode_config, patterns.into_iter().map(|(_, val)| val), span)?; + } + self.builder.emit_constant(modes_num.into(), span); + self.builder.emit(Instruction::CurlyMap, span); + let modes_map = function::Name::new("modes_map".to_string()); + self.scopes.push_name(&modes_map); + + let mode_param = function::Name::new("mode".to_string()); + //ir::ContextNames + let items_param = function::Name::new("items".to_string()); + let context_names = ir::ContextNames { + item: function::Name::new("item".to_string()), + position: function::Name::new("pos".to_string()), + last: function::Name::new("len".to_string()), + }; + let mode_var = function::Name::new("mode_var".to_string()); + let apply_templates = function::Name::new("apply_templates".to_string()); + let signature = function::Signature::new(vec![None, None], None); + let function_id = self.compile_function( + |compiler| { + compiler.scopes.push_name(&mode_param); + compiler.scopes.push_name(&items_param); + let items_atom = &ir::AtomS::new(ir::Atom::Variable(items_param), (0..0).into()); + compiler + .builder + .emit(Instruction::ClosureRecursiveSelf, span); + compiler.scopes.push_name(&apply_templates); + + compiler.builder.emit(Instruction::BuildNew, span); + + // Fetch mode from map + compiler.compile_variable(&modes_map, span)?; + compiler.compile_variable(&mode_param, span)?; + compiler.builder.emit(Instruction::Call(1), span); + compiler.scopes.push_name(&mode_var); + + // Iterate over items_param + let (loop_start, loop_end) = + compiler.compile_sequence_loop_init(items_atom, &context_names, span)?; + + // Get the item + compiler.compile_sequence_get_item(items_atom, &context_names, span)?; + compiler.scopes.push_name(&context_names.item); + + // Call mode for each item + compiler.compile_variable(&mode_var, span)?; + compiler.compile_variable(&context_names.item, span)?; + compiler.compile_variable(&context_names.position, span)?; + compiler.compile_variable(&context_names.last, span)?; + compiler.compile_variable(&apply_templates, span)?; + compiler.compile_variable(&mode_param, span)?; + compiler.builder.emit(Instruction::Call(5), span); + compiler.builder.emit(Instruction::BuildPush, span); + + // Remove the item - can be optimized by not using a variable + compiler.builder.emit(Instruction::Pop, span); + compiler.scopes.pop_name(); // context_names.item + + // Finish loop + compiler.compile_sequence_loop_iterate(loop_start, &context_names, span)?; + + compiler.builder.patch_jump(loop_end); + compiler.compile_sequence_loop_end(span); + compiler.scopes.pop_name(); // context_names.position + compiler.scopes.pop_name(); // context_names.last + + // Finish building the result sequence + compiler.builder.emit(Instruction::BuildComplete, span); + + compiler.scopes.pop_name(); // apply_templates + compiler.scopes.pop_name(); // item_param + compiler.scopes.pop_name(); // mode_param + Ok(()) + }, + signature, + span, + )?; + self.builder + .emit(Instruction::Closure(function_id.as_u16()), span); + self.scopes.push_name(&apply_templates); + + self.compile_expr(&define_templates.body)?; + + self.builder.emit(Instruction::LetDone, span); + self.scopes.pop_name(); // apply_templates + self.builder.emit(Instruction::LetDone, span); + self.scopes.pop_name(); // modes_map + Ok(()) + } + + fn compile_mode<'b>( + &mut self, + _mode: Option<&ir::Mode>, + rules: impl Iterator< + Item = ( + Pattern, + &'b ir::FunctionDefinition, + SourceSpan, + ), + >, + span: SourceSpan, + ) -> error::SpannedResult<()> { + let item_param = function::Name::new("item".to_string()); + let pos_param = function::Name::new("pos".to_string()); + let len_param = function::Name::new("len".to_string()); + let apply_templates_param = function::Name::new("apply_templates".to_string()); + let current_mode_param = function::Name::new("current_mode".to_string()); + let signature = function::Signature::new(vec![None, None, None, None, None], None); + let function_id = self.compile_function( + |compiler| { + compiler.scopes.push_name(&item_param); + compiler.scopes.push_name(&pos_param); + compiler.scopes.push_name(&len_param); + compiler.scopes.push_name(&apply_templates_param); + compiler.scopes.push_name(¤t_mode_param); + + let mut jumps_to_end = vec![]; + + compiler.compile_variable(&item_param, span)?; // Push item_param to the stack + + for (pattern, definition, inner_span) in rules { + let pattern_id = compiler.builder.add_pattern(pattern.clone()); + compiler + .builder + .emit(Instruction::MatchPattern(pattern_id as u16), span); // item_param remains on stack + + let failed_match = compiler + .builder + .emit_jump_forward(JumpCondition::False, span); + + compiler.builder.emit(Instruction::Pop, span); // Pop extra item_param + + compiler.compile_function_definition(definition, inner_span)?; + compiler.compile_variable(&item_param, span)?; + compiler.compile_variable(&pos_param, inner_span)?; + compiler.compile_variable(&len_param, inner_span)?; + compiler.builder.emit(Instruction::Call(3), inner_span); + jumps_to_end.push( + compiler + .builder + .emit_jump_forward(JumpCondition::Always, span), + ); + + compiler.builder.patch_jump(failed_match); + } + + compiler.builder.emit(Instruction::Pop, span); // Pop extra item_param + // All matches failed: empty sequence result + compiler + .builder + .emit_constant(sequence::Sequence::default(), span); + + for jump in jumps_to_end { + compiler.builder.patch_jump(jump); + } + + compiler.scopes.pop_name(); // current_mode_param + compiler.scopes.pop_name(); // apply_templates_param + compiler.scopes.pop_name(); // len_param + compiler.scopes.pop_name(); // pos_param + compiler.scopes.pop_name(); // item_param + Ok(()) + }, + signature, + span, + )?; + self.builder + .emit(Instruction::Closure(function_id.as_u16()), span); + Ok(()) + } + fn compile_apply_templates( &mut self, apply_templates: &ir::ApplyTemplates, span: SourceSpan, ) -> error::SpannedResult<()> { - self.compile_atom(&apply_templates.select)?; + // TODO: Must match exactly the param names in compile_mode! + let apply_templates_closure_var = function::Name::new("apply_templates".to_string()); + let current_mode_closure_var = function::Name::new("current_mode".to_string()); - let mode_id = if matches!( - apply_templates.mode, - ir::ApplyTemplatesModeValue::Named(_) | ir::ApplyTemplatesModeValue::Unnamed - ) { - self.mode_ids.get(&apply_templates.mode) - } else { - todo!("#current mode not handled yet") + let apply_templates_atom = ir::AtomS::new( + ir::Atom::Variable(apply_templates_closure_var), + (0..0).into(), + ); + + let mode_key = match &apply_templates.mode { + ir::ApplyTemplatesModeValue::Named(name) => { + ir::Atom::Const(ir::Const::Name(name.clone())) + } + ir::ApplyTemplatesModeValue::Unnamed => ir::Atom::Const(ir::Const::Integer(0.into())), + ir::ApplyTemplatesModeValue::Current => ir::Atom::Variable(current_mode_closure_var), }; - if let Some(mode_id) = mode_id { - self.builder - .emit(Instruction::ApplyTemplates(mode_id.get() as u16), span); - } else { - // the mode was never used by any templates, so compile the empty - // sequence - self.builder - .emit_constant(sequence::Sequence::default(), span); - } + self.compile_atom(&apply_templates_atom)?; + self.compile_atom(&ir::AtomS::new(mode_key, (0..0).into()))?; + self.compile_atom(&apply_templates.select)?; + self.builder.emit(Instruction::Call(2), span); + Ok(()) } diff --git a/xee-ir/src/ir.rs b/xee-ir/src/ir.rs index d00a2e2ee..229169c20 100644 --- a/xee-ir/src/ir.rs +++ b/xee-ir/src/ir.rs @@ -56,6 +56,7 @@ pub enum Expr { XmlProcessingInstruction(XmlProcessingInstruction), XmlAppend(XmlAppend), ApplyTemplates(ApplyTemplates), + DefineTemplates(DefineTemplates), CopyShallow(CopyShallow), CopyDeep(CopyDeep), } @@ -74,6 +75,7 @@ pub enum Const { Double(OrderedFloat), Decimal(Decimal), StaticFunctionReference(StaticFunctionId, Option), + Name(xmlname::OwnedName), // XXX replace this with a sequence constant? useful once we have constant folding EmptySequence, } @@ -342,6 +344,13 @@ pub enum ApplyTemplatesModeValue { Current, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DefineTemplates { + pub rules: Vec, + pub modes: HashMap, Mode>, + pub body: Box, +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct CopyShallow { pub select: AtomS, @@ -367,14 +376,13 @@ pub enum ModeValue { All, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct Mode {} #[derive(Debug, Clone, PartialEq, Eq)] pub struct Declarations { pub rules: Vec, pub modes: HashMap, Mode>, - pub functions: Vec, pub main: FunctionDefinition, pub serialization_params: SerializationParameters, } @@ -384,7 +392,6 @@ impl Declarations { Self { rules: Vec::new(), modes: HashMap::new(), - functions: Vec::new(), main, serialization_params: SerializationParameters::new(), }