xee_interpreter/interpreter/
runnable.rs

1use std::rc::Rc;
2
3use ibig::ibig;
4use iri_string::types::IriReferenceStr;
5use xot::Xot;
6
7use crate::context::DocumentsRef;
8use crate::context::DynamicContext;
9use crate::context::StaticContext;
10use crate::error::SpannedError;
11use crate::function::Function;
12use crate::interpreter::interpret::ContextInfo;
13use crate::sequence;
14use crate::stack;
15use crate::{error, string};
16
17use super::program::FunctionInfo;
18use super::Interpreter;
19use super::Program;
20
21#[derive(Debug)]
22pub struct Runnable<'a> {
23    program: &'a Program,
24    // TODO: this should be private, but is needed right now
25    // to implement call_static without lifetime issues.
26    // We could possibly obtain context from the interpreter directly,
27    // but this leads to lifetime issues right now.
28    pub(crate) dynamic_context: &'a DynamicContext<'a>,
29}
30
31impl<'a> Runnable<'a> {
32    pub(crate) fn new(program: &'a Program, dynamic_context: &'a DynamicContext) -> Self {
33        Self {
34            program,
35            dynamic_context,
36        }
37    }
38
39    fn run_value(&self, xot: &'a mut Xot) -> error::SpannedResult<stack::Value> {
40        let arguments = self.dynamic_context.arguments().unwrap();
41        let mut interpreter = Interpreter::new(self, xot);
42
43        let context_info = if let Some(context_item) = self.dynamic_context.context_item() {
44            ContextInfo {
45                item: context_item.clone().into(),
46                position: ibig!(1).into(),
47                size: ibig!(1).into(),
48            }
49        } else {
50            ContextInfo {
51                item: stack::Value::Absent,
52                position: stack::Value::Absent,
53                size: stack::Value::Absent,
54            }
55        };
56
57        interpreter.start(context_info, arguments);
58        interpreter.run(0)?;
59
60        let state = interpreter.state();
61        // the stack has to be 1 values and return the result of the expression
62        // why 1 value if the context item is on the top of the stack? This is because
63        // the outer main function will pop the context item; this code is there to
64        // remove the function id from the stack but the main function has no function id
65        assert_eq!(
66            state.stack().len(),
67            1,
68            "stack must only have 1 value but found {:?}",
69            state.stack()
70        );
71        let value = state.stack().last().unwrap().clone();
72        match value {
73            stack::Value::Absent => Err(SpannedError {
74                error: error::Error::XPDY0002,
75                span: Some(self.program.span().into()),
76            }),
77            _ => Ok(value),
78        }
79    }
80
81    /// Run the program against a sequence item.
82    pub fn many(&self, xot: &'a mut Xot) -> error::SpannedResult<sequence::Sequence> {
83        Ok(self.run_value(xot)?.try_into()?)
84    }
85
86    /// Run the program, expect a single item as the result.
87    pub fn one(&self, xot: &'a mut Xot) -> error::SpannedResult<sequence::Item> {
88        let sequence = self.many(xot)?;
89        sequence.one().map_err(|error| SpannedError {
90            error,
91            span: Some(self.program.span().into()),
92        })
93    }
94
95    /// Run the program, expect an optional single item as the result.
96    pub fn option(&self, xot: &'a mut Xot) -> error::SpannedResult<Option<sequence::Item>> {
97        let sequence = self.many(xot)?;
98        let items = sequence.iter();
99        sequence::option(items).map_err(|error| SpannedError {
100            error,
101            span: Some(self.program.span().into()),
102        })
103    }
104
105    pub(crate) fn program(&self) -> &'a Program {
106        self.program
107    }
108
109    pub fn dynamic_context(&self) -> &'a DynamicContext {
110        self.dynamic_context
111    }
112
113    pub fn documents(&self) -> DocumentsRef {
114        self.dynamic_context.documents()
115    }
116
117    pub fn static_context(&self) -> &StaticContext {
118        self.program.static_context()
119    }
120
121    pub fn default_collation_uri(&self) -> &IriReferenceStr {
122        self.dynamic_context
123            .static_context()
124            .default_collation_uri()
125    }
126
127    pub fn default_collation(&self) -> error::Result<Rc<string::Collation>> {
128        self.dynamic_context.static_context().default_collation()
129    }
130
131    pub fn implicit_timezone(&self) -> chrono::FixedOffset {
132        self.dynamic_context.implicit_timezone()
133    }
134
135    pub fn function_info<'b>(&self, function: &'b Function) -> FunctionInfo<'a, 'b> {
136        self.program.function_info(function)
137    }
138}