1mod capability_check;
2mod context;
3mod defaults;
4mod operators;
5mod pattern_sets;
6
7use std::collections::{BTreeMap, HashMap};
8use std::error::Error;
9use std::fmt;
10use std::sync::Arc;
11
12use crate::parser::{BinaryOp, SourceSpan, UnaryOp};
13use crate::sema::{VerifiedExprKindRef, VerifiedExpression, VerifiedProgram};
14
15use crate::compile::{
16 CapabilityKind, CapabilityMeta, CapabilityTicket, CompiledExpression, RuntimeSchema,
17};
18use crate::value::Value;
19use capability_check::verify_runtime_capabilities;
20pub use context::RuntimeCallContext;
21pub use defaults::default_registry;
22use operators::{
23 add_values, binary_op_from_name, compare_values, expect_bool, numeric_arithmetic,
24 unary_op_from_name,
25};
26pub use pattern_sets::{
27 RuntimePatternSetConfig, RuntimePatternSetError, RuntimePatternSetKind, RuntimePatternSetLimits,
28 RuntimePatternSets, oxirule_pattern_set_registry, register_oxirule_pattern_set_methods,
29};
30
31type FunctionHandler =
32 Arc<dyn for<'a> Fn(RuntimeCallContext<'a>, &[Value]) -> Result<Value, EvalError> + Send + Sync>;
33type MethodHandler = Arc<
34 dyn for<'a> Fn(RuntimeCallContext<'a>, &Value, &[Value]) -> Result<Value, EvalError>
35 + Send
36 + Sync,
37>;
38type UnaryHandler = Arc<dyn Fn(Value) -> Result<Value, EvalError> + Send + Sync>;
39type BinaryHandler = Arc<dyn Fn(Value, Value) -> Result<Value, EvalError> + Send + Sync>;
40
41#[derive(Debug, Clone, Eq, PartialEq)]
42pub struct EvalError {
43 pub message: String,
44 pub span: SourceSpan,
45}
46
47impl EvalError {
48 pub fn new(message: impl Into<String>, span: SourceSpan) -> Self {
49 Self {
50 message: message.into(),
51 span,
52 }
53 }
54}
55
56impl fmt::Display for EvalError {
57 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
58 write!(
59 formatter,
60 "{} at {}..{}",
61 self.message, self.span.start, self.span.end
62 )
63 }
64}
65
66impl Error for EvalError {}
67
68#[derive(Debug, Clone, Copy, Eq, PartialEq)]
69pub struct EvalLimits {
70 pub max_steps: usize,
71 pub max_depth: usize,
72 pub max_string_bytes: usize,
73 pub max_array_items: usize,
74}
75
76impl Default for EvalLimits {
77 fn default() -> Self {
78 Self {
79 max_steps: 10_000,
80 max_depth: 128,
81 max_string_bytes: 64 * 1024,
82 max_array_items: 4096,
83 }
84 }
85}
86
87#[derive(Clone, Default)]
88pub struct DynamicRegistry {
89 functions: BTreeMap<String, Vec<FunctionEntry>>,
90 methods: BTreeMap<String, Vec<MethodEntry>>,
91 unary_ops: HashMap<UnaryOp, UnaryEntry>,
92 binary_ops: HashMap<BinaryOp, BinaryEntry>,
93}
94
95#[derive(Clone)]
96struct FunctionEntry {
97 arity: usize,
98 capability: CapabilityMeta,
99 handler: FunctionHandler,
100}
101
102#[derive(Clone)]
103struct MethodEntry {
104 arity: usize,
105 capability: CapabilityMeta,
106 handler: MethodHandler,
107}
108
109#[derive(Clone)]
110struct UnaryEntry {
111 capability: CapabilityMeta,
112 handler: UnaryHandler,
113}
114
115#[derive(Clone)]
116struct BinaryEntry {
117 capability: CapabilityMeta,
118 handler: BinaryHandler,
119}
120
121impl DynamicRegistry {
122 pub fn new() -> Self {
123 Self::default()
124 }
125
126 pub fn register_function(
127 &mut self,
128 name: impl Into<String>,
129 arity: usize,
130 handler: impl Fn(&[Value]) -> Result<Value, EvalError> + Send + Sync + 'static,
131 ) -> &mut Self {
132 self.register_function_capability(CapabilityMeta::function(name, arity), handler)
133 }
134
135 pub fn register_function_with_context(
136 &mut self,
137 name: impl Into<String>,
138 arity: usize,
139 handler: impl for<'a> Fn(RuntimeCallContext<'a>, &[Value]) -> Result<Value, EvalError>
140 + Send
141 + Sync
142 + 'static,
143 ) -> &mut Self {
144 self.register_function_capability_with_context(CapabilityMeta::function(name, arity), handler)
145 }
146
147 pub fn register_function_capability(
148 &mut self,
149 capability: CapabilityMeta,
150 handler: impl Fn(&[Value]) -> Result<Value, EvalError> + Send + Sync + 'static,
151 ) -> &mut Self {
152 self.register_function_capability_with_context(capability, move |_, args| handler(args))
153 }
154
155 pub fn register_function_capability_with_context(
156 &mut self,
157 capability: CapabilityMeta,
158 handler: impl for<'a> Fn(RuntimeCallContext<'a>, &[Value]) -> Result<Value, EvalError>
159 + Send
160 + Sync
161 + 'static,
162 ) -> &mut Self {
163 self
164 .functions
165 .entry(capability.name.clone())
166 .or_default()
167 .push(FunctionEntry {
168 arity: capability.arity,
169 capability,
170 handler: Arc::new(handler),
171 });
172 self
173 }
174
175 pub fn register_method(
176 &mut self,
177 name: impl Into<String>,
178 arity: usize,
179 handler: impl Fn(&Value, &[Value]) -> Result<Value, EvalError> + Send + Sync + 'static,
180 ) -> &mut Self {
181 self.register_method_capability(CapabilityMeta::method(name, arity), handler)
182 }
183
184 pub fn register_method_with_context(
185 &mut self,
186 name: impl Into<String>,
187 arity: usize,
188 handler: impl for<'a> Fn(RuntimeCallContext<'a>, &Value, &[Value]) -> Result<Value, EvalError>
189 + Send
190 + Sync
191 + 'static,
192 ) -> &mut Self {
193 self.register_method_capability_with_context(CapabilityMeta::method(name, arity), handler)
194 }
195
196 pub fn register_method_capability(
197 &mut self,
198 capability: CapabilityMeta,
199 handler: impl Fn(&Value, &[Value]) -> Result<Value, EvalError> + Send + Sync + 'static,
200 ) -> &mut Self {
201 self.register_method_capability_with_context(capability, move |_, receiver, args| {
202 handler(receiver, args)
203 })
204 }
205
206 pub fn register_method_capability_with_context(
207 &mut self,
208 capability: CapabilityMeta,
209 handler: impl for<'a> Fn(RuntimeCallContext<'a>, &Value, &[Value]) -> Result<Value, EvalError>
210 + Send
211 + Sync
212 + 'static,
213 ) -> &mut Self {
214 self
215 .methods
216 .entry(capability.name.clone())
217 .or_default()
218 .push(MethodEntry {
219 arity: capability.arity,
220 capability,
221 handler: Arc::new(handler),
222 });
223 self
224 }
225
226 pub fn register_unary_operator(
227 &mut self,
228 op: UnaryOp,
229 handler: impl Fn(Value) -> Result<Value, EvalError> + Send + Sync + 'static,
230 ) -> &mut Self {
231 self.register_unary_operator_capability(CapabilityMeta::unary_operator(op), handler)
232 }
233
234 pub fn register_unary_operator_capability(
235 &mut self,
236 capability: CapabilityMeta,
237 handler: impl Fn(Value) -> Result<Value, EvalError> + Send + Sync + 'static,
238 ) -> &mut Self {
239 if let Some(op) = unary_op_from_name(&capability.name) {
240 self.unary_ops.insert(
241 op,
242 UnaryEntry {
243 capability,
244 handler: Arc::new(handler),
245 },
246 );
247 }
248 self
249 }
250
251 pub fn register_binary_operator(
252 &mut self,
253 op: BinaryOp,
254 handler: impl Fn(Value, Value) -> Result<Value, EvalError> + Send + Sync + 'static,
255 ) -> &mut Self {
256 self.register_binary_operator_capability(CapabilityMeta::binary_operator(op), handler)
257 }
258
259 pub fn register_binary_operator_capability(
260 &mut self,
261 capability: CapabilityMeta,
262 handler: impl Fn(Value, Value) -> Result<Value, EvalError> + Send + Sync + 'static,
263 ) -> &mut Self {
264 if let Some(op) = binary_op_from_name(&capability.name) {
265 self.binary_ops.insert(
266 op,
267 BinaryEntry {
268 capability,
269 handler: Arc::new(handler),
270 },
271 );
272 }
273 self
274 }
275
276 pub fn schema(&self) -> RuntimeSchema {
277 let mut schema = RuntimeSchema::new();
278 for entries in self.functions.values() {
279 for entry in entries {
280 schema.add_function_capability(entry.capability.clone());
281 }
282 }
283 for entries in self.methods.values() {
284 for entry in entries {
285 schema.add_method_capability(entry.capability.clone());
286 }
287 }
288 for entry in self.unary_ops.values() {
289 schema.add_unary_operator_capability(entry.capability.clone());
290 }
291 for entry in self.binary_ops.values() {
292 schema.add_binary_operator_capability(entry.capability.clone());
293 }
294 schema
295 }
296
297 fn capability_for_ticket(&self, ticket: &CapabilityTicket) -> Option<CapabilityMeta> {
298 match ticket.kind {
299 CapabilityKind::Function => self
300 .functions
301 .get(&ticket.name)
302 .and_then(|entries| entries.iter().find(|entry| entry.arity == ticket.arity))
303 .map(|entry| entry.capability.clone()),
304 CapabilityKind::Method => self
305 .methods
306 .get(&ticket.name)
307 .and_then(|entries| entries.iter().find(|entry| entry.arity == ticket.arity))
308 .map(|entry| entry.capability.clone()),
309 CapabilityKind::UnaryOp => {
310 let op = unary_op_from_name(&ticket.name)?;
311 self
312 .unary_ops
313 .get(&op)
314 .map(|entry| entry.capability.clone())
315 .or_else(|| Some(CapabilityMeta::unary_operator(op)))
316 }
317 CapabilityKind::BinaryOp => {
318 let op = binary_op_from_name(&ticket.name)?;
319 self
320 .binary_ops
321 .get(&op)
322 .map(|entry| entry.capability.clone())
323 .or_else(|| Some(CapabilityMeta::binary_operator(op)))
324 }
325 }
326 }
327
328 fn call_function(
329 &self,
330 context: RuntimeCallContext<'_>,
331 name: &str,
332 args: &[Value],
333 span: SourceSpan,
334 ) -> Result<Value, EvalError> {
335 let Some(entries) = self.functions.get(name) else {
336 return Err(EvalError::new(format!("unknown function {name}"), span));
337 };
338 let Some(entry) = entries.iter().find(|entry| entry.arity == args.len()) else {
339 return Err(EvalError::new(
340 format!("function {name} does not accept {} arguments", args.len()),
341 span,
342 ));
343 };
344 (entry.handler)(context, args).map_err(|error| EvalError { span, ..error })
345 }
346
347 fn call_method(
348 &self,
349 context: RuntimeCallContext<'_>,
350 receiver: &Value,
351 name: &str,
352 args: &[Value],
353 span: SourceSpan,
354 ) -> Result<Value, EvalError> {
355 let Some(entries) = self.methods.get(name) else {
356 return Err(EvalError::new(format!("unknown method {name}"), span));
357 };
358 let Some(entry) = entries.iter().find(|entry| entry.arity == args.len()) else {
359 return Err(EvalError::new(
360 format!("method {name} does not accept {} arguments", args.len()),
361 span,
362 ));
363 };
364 (entry.handler)(context, receiver, args).map_err(|error| EvalError { span, ..error })
365 }
366}
367
368pub trait RuntimeContext {
369 fn get_variable(&self, name: &str) -> Option<Value>;
370 fn registry(&self) -> &DynamicRegistry;
371}
372
373#[derive(Clone)]
374pub struct MapRuntime {
375 variables: BTreeMap<String, Value>,
376 registry: DynamicRegistry,
377}
378
379impl MapRuntime {
380 pub fn new(variables: BTreeMap<String, Value>, registry: DynamicRegistry) -> Self {
381 Self {
382 variables,
383 registry,
384 }
385 }
386
387 pub fn from_json_bindings(bindings: serde_json::Value) -> Result<Self, EvalError> {
388 let Value::Object(variables) = Value::try_from(bindings)
389 .map_err(|error| EvalError::new(error.to_string(), SourceSpan::default()))?
390 else {
391 return Err(EvalError::new(
392 "bindings must be a JSON object",
393 SourceSpan::default(),
394 ));
395 };
396 Ok(Self::new(variables, default_registry()))
397 }
398
399 pub fn schema(&self) -> RuntimeSchema {
400 let mut schema = self.registry.schema();
401 for name in self.variables.keys() {
402 schema.add_variable(name.clone());
403 }
404 schema
405 }
406}
407
408impl RuntimeContext for MapRuntime {
409 fn get_variable(&self, name: &str) -> Option<Value> {
410 self.variables.get(name).cloned()
411 }
412
413 fn registry(&self) -> &DynamicRegistry {
414 &self.registry
415 }
416}
417
418pub fn evaluate(
419 expression: &CompiledExpression,
420 context: &dyn RuntimeContext,
421 limits: EvalLimits,
422) -> Result<Value, EvalError> {
423 evaluate_verified(expression.verified_program(), context, limits)
424}
425
426pub fn evaluate_verified(
427 program: &VerifiedProgram,
428 context: &dyn RuntimeContext,
429 limits: EvalLimits,
430) -> Result<Value, EvalError> {
431 verify_runtime_capabilities(program, context.registry())?;
432 let mut state = EvalState {
433 limits,
434 steps: 0,
435 program,
436 locals: Vec::new(),
437 };
438 state.eval(program.root(), context, 0)
439}
440
441struct EvalState<'a> {
442 limits: EvalLimits,
443 steps: usize,
444 program: &'a VerifiedProgram,
445 locals: Vec<BTreeMap<String, Value>>,
446}
447
448struct ExpressionFunctionFrame<'a> {
449 name: &'a str,
450 params: &'a [String],
451 args: &'a [VerifiedExpression],
452 body: &'a VerifiedExpression,
453 span: SourceSpan,
454}
455
456impl EvalState<'_> {
457 fn eval(
458 &mut self,
459 expression: &VerifiedExpression,
460 context: &dyn RuntimeContext,
461 depth: usize,
462 ) -> Result<Value, EvalError> {
463 let span = expression.span();
464 self.step(span)?;
465 if depth > self.limits.max_depth {
466 return Err(EvalError::new("evaluation depth limit exceeded", span));
467 }
468
469 match expression.kind() {
470 VerifiedExprKindRef::Null => Ok(Value::Null),
471 VerifiedExprKindRef::Bool(value) => Ok(Value::Bool(value)),
472 VerifiedExprKindRef::Int(value) => Ok(Value::Int(value)),
473 VerifiedExprKindRef::Float(value) => Ok(Value::Float(value)),
474 VerifiedExprKindRef::String(value) => self.checked_string(value.to_string(), span),
475 VerifiedExprKindRef::Array(items) => self.eval_array(items, context, depth, span),
476 VerifiedExprKindRef::Identifier(name) => self
477 .local_value(name)
478 .or_else(|| context.get_variable(name))
479 .ok_or_else(|| EvalError::new(format!("unknown variable {name}"), span)),
480 VerifiedExprKindRef::Member { receiver, name } => {
481 let value = self.eval(receiver, context, depth + 1)?;
482 self.eval_member(value, name, span)
483 }
484 VerifiedExprKindRef::FunctionCall { name, args } => {
485 let args = self.eval_args(args, context, depth)?;
486 context
487 .registry()
488 .call_function(self.call_context(span), name, &args, span)
489 }
490 VerifiedExprKindRef::ExpressionFunctionCall {
491 name,
492 params,
493 args,
494 body,
495 } => self.eval_expression_function(
496 ExpressionFunctionFrame {
497 name,
498 params,
499 args,
500 body,
501 span,
502 },
503 context,
504 depth,
505 ),
506 VerifiedExprKindRef::MethodCall {
507 receiver,
508 name,
509 args,
510 } => {
511 let receiver = self.eval(receiver, context, depth + 1)?;
512 let args = self.eval_args(args, context, depth)?;
513 context
514 .registry()
515 .call_method(self.call_context(span), &receiver, name, &args, span)
516 }
517 VerifiedExprKindRef::Unary { op, expr } => {
518 let value = self.eval(expr, context, depth + 1)?;
519 self.eval_unary(op, value, context.registry(), span)
520 }
521 VerifiedExprKindRef::Binary { left, op, right } => {
522 self.eval_binary(left, op, right, context, depth, span)
523 }
524 }
525 }
526
527 fn step(&mut self, span: SourceSpan) -> Result<(), EvalError> {
528 self.steps = self
529 .steps
530 .checked_add(1)
531 .ok_or_else(|| EvalError::new("evaluation step counter overflowed", span))?;
532 if self.steps > self.limits.max_steps {
533 Err(EvalError::new("evaluation step limit exceeded", span))
534 } else {
535 Ok(())
536 }
537 }
538
539 fn eval_array(
540 &mut self,
541 items: &[VerifiedExpression],
542 context: &dyn RuntimeContext,
543 depth: usize,
544 span: SourceSpan,
545 ) -> Result<Value, EvalError> {
546 if items.len() > self.limits.max_array_items {
547 return Err(EvalError::new("array item limit exceeded", span));
548 }
549 items
550 .iter()
551 .map(|item| self.eval(item, context, depth + 1))
552 .collect::<Result<Vec<_>, _>>()
553 .map(Value::Array)
554 }
555
556 fn eval_args(
557 &mut self,
558 args: &[VerifiedExpression],
559 context: &dyn RuntimeContext,
560 depth: usize,
561 ) -> Result<Vec<Value>, EvalError> {
562 args
563 .iter()
564 .map(|arg| self.eval(arg, context, depth + 1))
565 .collect()
566 }
567
568 fn eval_expression_function(
569 &mut self,
570 frame: ExpressionFunctionFrame<'_>,
571 context: &dyn RuntimeContext,
572 depth: usize,
573 ) -> Result<Value, EvalError> {
574 if frame.params.len() != frame.args.len() {
575 return Err(EvalError::new(
576 format!(
577 "verified expression function {} expected {} arguments but got {}",
578 frame.name,
579 frame.params.len(),
580 frame.args.len()
581 ),
582 frame.span,
583 ));
584 }
585
586 let values = self.eval_args(frame.args, context, depth)?;
587 let locals = frame
588 .params
589 .iter()
590 .cloned()
591 .zip(values)
592 .collect::<BTreeMap<_, _>>();
593 self.locals.push(locals);
594 let result = self.eval(frame.body, context, depth + 1);
595 self.locals.pop();
596 result
597 }
598
599 fn local_value(&self, name: &str) -> Option<Value> {
600 self
601 .locals
602 .iter()
603 .rev()
604 .find_map(|locals| locals.get(name).cloned())
605 }
606
607 fn eval_member(&self, value: Value, name: &str, span: SourceSpan) -> Result<Value, EvalError> {
608 match value {
609 Value::Object(values) => values
610 .get(name)
611 .cloned()
612 .ok_or_else(|| EvalError::new(format!("missing object member {name}"), span)),
613 other => Err(EvalError::new(
614 format!("cannot read member {name} from {}", other.type_name()),
615 span,
616 )),
617 }
618 }
619
620 fn eval_unary(
621 &self,
622 op: UnaryOp,
623 value: Value,
624 registry: &DynamicRegistry,
625 span: SourceSpan,
626 ) -> Result<Value, EvalError> {
627 if let Some(entry) = registry.unary_ops.get(&op) {
628 return (entry.handler)(value).map_err(|error| EvalError { span, ..error });
629 }
630 match (op, value) {
631 (UnaryOp::Not, Value::Bool(value)) => Ok(Value::Bool(!value)),
632 (UnaryOp::Neg, Value::Int(value)) => value
633 .checked_neg()
634 .map(Value::Int)
635 .ok_or_else(|| EvalError::new("integer negation overflowed", span)),
636 (UnaryOp::Neg, Value::Float(value)) => Ok(Value::Float(-value)),
637 (op, value) => Err(EvalError::new(
638 format!(
639 "operator {} does not accept {}",
640 op.as_str(),
641 value.type_name()
642 ),
643 span,
644 )),
645 }
646 }
647
648 fn eval_binary(
649 &mut self,
650 left: &VerifiedExpression,
651 op: BinaryOp,
652 right: &VerifiedExpression,
653 context: &dyn RuntimeContext,
654 depth: usize,
655 span: SourceSpan,
656 ) -> Result<Value, EvalError> {
657 let left_value = self.eval(left, context, depth + 1)?;
658 match op {
659 BinaryOp::And => {
660 let left_bool = expect_bool(left_value, span)?;
661 if !left_bool {
662 return Ok(Value::Bool(false));
663 }
664 let right_bool = expect_bool(self.eval(right, context, depth + 1)?, span)?;
665 Ok(Value::Bool(right_bool))
666 }
667 BinaryOp::Or => {
668 let left_bool = expect_bool(left_value, span)?;
669 if left_bool {
670 return Ok(Value::Bool(true));
671 }
672 let right_bool = expect_bool(self.eval(right, context, depth + 1)?, span)?;
673 Ok(Value::Bool(right_bool))
674 }
675 _ => {
676 let right_value = self.eval(right, context, depth + 1)?;
677 if let Some(entry) = context.registry().binary_ops.get(&op) {
678 return (entry.handler)(left_value, right_value)
679 .map_err(|error| EvalError { span, ..error });
680 }
681 self.eval_builtin_binary(left_value, op, right_value, span)
682 }
683 }
684 }
685
686 fn eval_builtin_binary(
687 &self,
688 left: Value,
689 op: BinaryOp,
690 right: Value,
691 span: SourceSpan,
692 ) -> Result<Value, EvalError> {
693 match op {
694 BinaryOp::Eq => Ok(Value::Bool(left == right)),
695 BinaryOp::Ne => Ok(Value::Bool(left != right)),
696 BinaryOp::Add => add_values(left, right, span, self.limits.max_string_bytes),
697 BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div | BinaryOp::Rem => {
698 numeric_arithmetic(left, op, right, span)
699 }
700 BinaryOp::Lt | BinaryOp::Le | BinaryOp::Gt | BinaryOp::Ge => {
701 compare_values(left, op, right, span)
702 }
703 BinaryOp::And | BinaryOp::Or => Err(EvalError::new("internal boolean dispatch error", span)),
704 }
705 }
706
707 fn checked_string(&self, value: String, span: SourceSpan) -> Result<Value, EvalError> {
708 if value.len() > self.limits.max_string_bytes {
709 Err(EvalError::new("string byte limit exceeded", span))
710 } else {
711 Ok(Value::String(value))
712 }
713 }
714
715 fn call_context(&self, span: SourceSpan) -> RuntimeCallContext<'_> {
716 RuntimeCallContext::new(self.program.profile(), self.program.regex_cache(), span)
717 }
718}