Skip to main content

online_dsl_forge/
runtime.rs

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}