Acord/core/src/interp/hooks.rs

111 lines
4.1 KiB
Rust

use std::collections::HashMap;
use std::rc::Rc;
use super::ast::{Expr, Op, Stmt};
use super::parse::Parser;
use super::token::Token;
use super::value::{ExternHandle, Value};
use super::Interpreter;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DisplayFormat {
Inline,
Table,
Tree,
}
pub trait InterpreterHook {
fn name(&self) -> &str;
fn priority(&self) -> i32 { 0 }
fn before(&self) -> &'static [&'static str] { &[] }
fn after(&self) -> &'static [&'static str] { &[] }
fn rewrite_tokens(&self, _toks: &mut Vec<Token>) {}
fn try_parse_stmt(&self, _p: &mut Parser) -> Option<Result<Stmt, String>> { None }
fn try_call(&self, _i: &mut Interpreter, _name: &str, _args: &[Expr], _depth: u32)
-> Option<Result<Value, String>> { None }
fn after_exec(&self, _i: &Interpreter, _stmt: &Stmt, _result: &Value) {}
fn display_extern(&self, _h: &ExternHandle, _fmt: DisplayFormat) -> Option<Value> { None }
fn clone_extern(&self, _h: &ExternHandle) -> Option<Value> { None }
fn extern_field_get(&self, _h: &ExternHandle, _field: &str) -> Option<Value> { None }
fn extern_field_set(&self, _h: &ExternHandle, _field: &str, _v: Value)
-> Option<Result<(), String>> { None }
fn extern_method_call(&self, _i: &mut Interpreter, _h: &ExternHandle,
_method: &str, _args: &[Value], _depth: u32) -> Option<Result<Value, String>> { None }
fn extern_eq(&self, _a: &ExternHandle, _b: &ExternHandle) -> Option<bool> { None }
fn extern_binop(&self, _i: &mut Interpreter, _op: Op, _left: &Value, _right: &Value, _depth: u32)
-> Option<Result<Value, String>> { None }
fn extern_unop(&self, _i: &mut Interpreter, _op: Op, _operand: &Value, _depth: u32)
-> Option<Result<Value, String>> { None }
}
pub(crate) struct HookList {
pub(crate) hooks: Vec<Rc<dyn InterpreterHook>>,
}
impl HookList {
pub fn new() -> Self { Self { hooks: Vec::new() } }
pub fn register(&mut self, hook: Rc<dyn InterpreterHook>) -> Result<(), String> {
let name = hook.name().to_string();
if self.hooks.iter().any(|h| h.name() == name) {
return Err(format!("hook '{}' already registered", name));
}
self.hooks.push(hook);
self.resolve_order()
}
/// Kahn-style topo sort with priority tie-breaking on the resolved free set.
fn resolve_order(&mut self) -> Result<(), String> {
let n = self.hooks.len();
if n <= 1 { return Ok(()); }
let names: Vec<String> = self.hooks.iter().map(|h| h.name().to_string()).collect();
let name_idx: HashMap<String, usize> = names.iter().enumerate()
.map(|(i, s)| (s.clone(), i)).collect();
let mut edges: Vec<(usize, usize)> = Vec::new();
for (i, h) in self.hooks.iter().enumerate() {
for after_name in h.after() {
if let Some(&j) = name_idx.get(*after_name) {
edges.push((j, i));
}
}
for before_name in h.before() {
if let Some(&j) = name_idx.get(*before_name) {
edges.push((i, j));
}
}
}
let mut indeg = vec![0usize; n];
for &(_, v) in &edges { indeg[v] += 1; }
let mut placed = vec![false; n];
let mut order = Vec::with_capacity(n);
for _ in 0..n {
let mut best: Option<usize> = None;
for i in 0..n {
if placed[i] || indeg[i] > 0 { continue; }
best = Some(match best {
None => i,
Some(b) if self.hooks[i].priority() > self.hooks[b].priority() => i,
Some(b) => b,
});
}
let chosen = best.ok_or_else(|| "hook ordering has a cycle".to_string())?;
placed[chosen] = true;
order.push(chosen);
for &(u, v) in &edges {
if u == chosen { indeg[v] = indeg[v].saturating_sub(1); }
}
}
let new_hooks: Vec<Rc<dyn InterpreterHook>> = order.iter()
.map(|&i| self.hooks[i].clone()).collect();
self.hooks = new_hooks;
Ok(())
}
}