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
2 changes: 1 addition & 1 deletion xee-interpreter/src/function/inline_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub struct CastType {
pub empty_sequence_allowed: bool,
}

#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Name(pub(crate) String);

impl Name {
Expand Down
21 changes: 21 additions & 0 deletions xee-interpreter/src/interpreter/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ pub enum Instruction {
CopyShallow,
CopyDeep,
ApplyTemplates(u16),
LoadGlobal(u16),
SetGlobal(u16),
PrintTop,
PrintStack,
}
Expand Down Expand Up @@ -157,6 +159,8 @@ pub(crate) enum EncodedInstruction {
ApplyTemplates,
CopyShallow,
CopyDeep,
LoadGlobal,
SetGlobal,
PrintTop,
PrintStack,
}
Expand Down Expand Up @@ -198,6 +202,14 @@ pub(crate) fn decode_instruction(bytes: &[u8]) -> (Instruction, usize) {
let variable = u16::from_le_bytes([bytes[1], bytes[2]]);
(Instruction::ClosureVar(variable), 3)
}
EncodedInstruction::LoadGlobal => {
let index = u16::from_le_bytes([bytes[1], bytes[2]]);
(Instruction::LoadGlobal(index), 3)
}
EncodedInstruction::SetGlobal => {
let index = u16::from_le_bytes([bytes[1], bytes[2]]);
(Instruction::SetGlobal(index), 3)
}
EncodedInstruction::Comma => (Instruction::Comma, 1),
EncodedInstruction::CurlyArray => (Instruction::CurlyArray, 1),
EncodedInstruction::SquareArray => (Instruction::SquareArray, 1),
Expand Down Expand Up @@ -340,6 +352,14 @@ pub fn encode_instruction(instruction: Instruction, bytes: &mut Vec<u8>) {
bytes.push(EncodedInstruction::ClosureVar.to_u8().unwrap());
bytes.extend_from_slice(&variable.to_le_bytes());
}
Instruction::LoadGlobal(index) => {
bytes.push(EncodedInstruction::LoadGlobal.to_u8().unwrap());
bytes.extend_from_slice(&index.to_le_bytes());
}
Instruction::SetGlobal(index) => {
bytes.push(EncodedInstruction::SetGlobal.to_u8().unwrap());
bytes.extend_from_slice(&index.to_le_bytes());
}
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()),
Expand Down Expand Up @@ -530,6 +550,7 @@ pub fn instruction_size(instruction: &Instruction) -> usize {
| Instruction::ReturnConvert(_)
| Instruction::JumpIfFalse(_) => 3,
Instruction::ApplyTemplates(_) => 3,
Instruction::LoadGlobal(_) | Instruction::SetGlobal(_) => 3,
}
}

Expand Down
10 changes: 10 additions & 0 deletions xee-interpreter/src/interpreter/interpret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,16 @@ impl<'a> Interpreter<'a> {
let index = self.read_u16();
self.state.push_closure_var(index as usize)?;
}
EncodedInstruction::LoadGlobal => {
let index = self.read_u16();
let value = self.state.get_global(index as usize);
self.state.push_value(value);
}
EncodedInstruction::SetGlobal => {
let index = self.read_u16();
let value = self.state.pop_value();
self.state.set_global(index as usize, value);
}
EncodedInstruction::Comma => {
let b = self.state.pop()?;
let a = self.state.pop()?;
Expand Down
19 changes: 19 additions & 0 deletions xee-interpreter/src/interpreter/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ pub struct State<'a> {
stack: Vec<stack::Value>,
build_stack: Vec<BuildStackEntry>,
frames: ArrayVec<Frame, FRAMES_MAX>,
// Global variable values, indexed by their declaration order. They live for
// the whole run so every frame (main, rules, named templates) reads the same
// values through LoadGlobal.
globals: Vec<stack::Value>,
regex_cache: RefCell<HashMap<RegexKey, Rc<regexml::Regex>>>,
pub(crate) xot: &'a mut Xot,
}
Expand Down Expand Up @@ -87,11 +91,26 @@ impl<'a> State<'a> {
stack: vec![],
build_stack: vec![],
frames: ArrayVec::new(),
globals: vec![],
regex_cache: RefCell::new(HashMap::new()),
xot,
}
}

pub(crate) fn set_global(&mut self, index: usize, value: stack::Value) {
if index >= self.globals.len() {
self.globals.resize(index + 1, stack::Value::Absent);
}
self.globals[index] = value;
}

pub(crate) fn get_global(&self, index: usize) -> stack::Value {
self.globals
.get(index)
.cloned()
.unwrap_or(stack::Value::Absent)
}

pub(crate) fn push<T>(&mut self, sequence: T)
where
T: Into<sequence::Sequence>,
Expand Down
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, GlobalIds, ModeIds},
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_global_ids = GlobalIds::new();
let mut compiler =
FunctionCompiler::new(builder, &mut scopes, &empty_mode_ids, &empty_global_ids);
compiler.compile_expr(&expr)?;
Ok(program)
}
Expand Down
16 changes: 15 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 GlobalIds = HashMap<ir::Name, usize>;

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,
global_ids: GlobalIds,
}

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(),
global_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.global_ids,
)
}

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

// register a slot id per global variable so a reference anywhere, in main
// or in a rule, compiles to LoadGlobal.
for (id, name) in declarations.global_variables.iter().enumerate() {
self.global_ids.insert(name.clone(), id);
}

for rule in &declarations.rules {
self.compile_rule(rule)?;
}
Expand Down
31 changes: 30 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::{GlobalIds, ModeIds};
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) global_ids: &'a GlobalIds,
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,
global_ids: &'a GlobalIds,
) -> Self {
Self {
builder,
scopes,
mode_ids,
global_ids,
}
}

Expand All @@ -38,6 +41,7 @@ impl<'a> FunctionCompiler<'a> {
match &expr.value {
ir::Expr::Atom(atom) => self.compile_atom(atom),
ir::Expr::Let(let_) => self.compile_let(let_, span),
ir::Expr::SetGlobal(set_global) => self.compile_set_global(set_global, span),
ir::Expr::Binary(binary) => self.compile_binary(binary, span),
ir::Expr::Unary(unary) => self.compile_unary(unary, span),
ir::Expr::FunctionDefinition(function_definition) => {
Expand Down Expand Up @@ -146,6 +150,15 @@ impl<'a> FunctionCompiler<'a> {
self.builder
.emit(Instruction::ClosureVar(index as u16), span);
Ok(())
} else if let Some(id) = self.global_ids.get(name) {
// Not a local or a closed-over name, so it must be a top-level
// xsl:variable; locals shadow globals as they are checked first.
let id = *id;
if id > u16::MAX as usize {
return Err(Error::XPDY0130.with_span(span));
}
self.builder.emit(Instruction::LoadGlobal(id as u16), span);
Ok(())
} else {
// TODO: this should be unreachable but
// the XSLT test suite for some reason triggers
Expand Down Expand Up @@ -182,6 +195,21 @@ impl<'a> FunctionCompiler<'a> {
Ok(())
}

fn compile_set_global(
&mut self,
set_global: &ir::SetGlobal,
span: SourceSpan,
) -> error::SpannedResult<()> {
if set_global.id > u16::MAX as usize {
return Err(Error::XPDY0130.with_span(span));
}
self.compile_expr(&set_global.value)?;
self.builder
.emit(Instruction::SetGlobal(set_global.id as u16), span);
self.compile_expr(&set_global.return_expr)?;
Ok(())
}

fn compile_if(&mut self, if_: &ir::If, span: SourceSpan) -> error::SpannedResult<()> {
self.compile_atom(&if_.condition)?;
let jump_else = self.builder.emit_jump_forward(JumpCondition::False, span);
Expand Down Expand Up @@ -342,6 +370,7 @@ impl<'a> FunctionCompiler<'a> {
builder: nested_builder,
scopes: self.scopes,
mode_ids: self.mode_ids,
global_ids: self.global_ids,
};

for param in &function_definition.params {
Expand Down
15 changes: 15 additions & 0 deletions xee-ir/src/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub type ExprS = Spanned<Expr>;
pub enum Expr {
Atom(AtomS),
Let(Let),
SetGlobal(SetGlobal),
If(If),
Binary(Binary),
Unary(Unary),
Expand Down Expand Up @@ -92,6 +93,16 @@ pub struct Let {
pub return_expr: Box<ExprS>,
}

// A top-level xsl:variable: evaluate `value` once and store it in the global
// slot `id`, then continue with `return_expr`. The main function evaluates these
// before applying templates, so every rule and named template can read them.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SetGlobal {
pub id: usize,
pub value: Box<ExprS>,
pub return_expr: Box<ExprS>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct If {
pub condition: AtomS,
Expand Down Expand Up @@ -375,6 +386,9 @@ pub struct Declarations {
pub rules: Vec<Rule>,
pub modes: HashMap<Option<xmlname::OwnedName>, Mode>,
pub functions: Vec<FunctionBinding>,
// Global variable names in declaration order; the index is the global slot id
// used by SetGlobal and LoadGlobal.
pub global_variables: Vec<Name>,
pub main: FunctionDefinition,
pub serialization_params: SerializationParameters,
}
Expand All @@ -385,6 +399,7 @@ impl Declarations {
rules: Vec::new(),
modes: HashMap::new(),
functions: Vec::new(),
global_variables: Vec::new(),
main,
serialization_params: SerializationParameters::new(),
}
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::{GlobalIds, ModeIds};
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, GlobalIds, ModeIds, Scopes};
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_global_ids = GlobalIds::new();
let mut compiler = FunctionCompiler::new(
function_builder,
&mut scopes,
&empty_mode_ids,
&empty_global_ids,
);

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

Expand Down
6 changes: 6 additions & 0 deletions xee-xslt-ast/src/ast_core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1705,6 +1705,12 @@ impl From<Variable> for OverrideContent {
}
}

impl From<Variable> for Declaration {
fn from(v: Variable) -> Self {
Declaration::Variable(Box::new(v))
}
}

#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct When {
Expand Down
1 change: 1 addition & 0 deletions xee-xslt-ast/src/names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ impl DeclarationName {
DeclarationName::Accumulator => ast::Accumulator::parse_declaration(attributes),
DeclarationName::Template => ast::Template::parse_declaration(attributes),
DeclarationName::Output => ast::Output::parse_declaration(attributes),
DeclarationName::Variable => ast::Variable::parse_declaration(attributes),
_ => Err(ElementError::Unsupported(format!(
"Unsupported declaration: {:?}",
&self
Expand Down
Loading