Almost Alpha
Massive improvements across the board. One day I'll adopt incremental commits.
This commit is contained in:
@@ -1,19 +1,67 @@
|
||||
//! Addiitional information passed to the interpreter
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::fmt;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
|
||||
use super::nort::Expr;
|
||||
use super::handler::HandlerTable;
|
||||
use super::nort::{Clause, Expr};
|
||||
use crate::foreign::error::{RTError, RTErrorObj, RTResult};
|
||||
use crate::location::CodeLocation;
|
||||
use crate::name::Sym;
|
||||
|
||||
/// All the data associated with an interpreter run
|
||||
#[derive(Clone)]
|
||||
pub struct RunContext<'a> {
|
||||
/// Table used to resolve constants
|
||||
pub symbols: &'a HashMap<Sym, Expr>,
|
||||
/// The number of reduction steps the interpreter can take before returning
|
||||
pub gas: Option<usize>,
|
||||
/// The limit of recursion
|
||||
pub stack_size: usize,
|
||||
/// Data that must not change except in well-defined ways while any data
|
||||
/// associated with this process persists
|
||||
pub struct RunEnv<'a> {
|
||||
/// Mutable callbacks the code can invoke with continuation passing
|
||||
pub handlers: HandlerTable<'a>,
|
||||
/// Constants referenced in the code in [super::nort::Clause::Constant] nodes
|
||||
pub symbols: RefCell<HashMap<Sym, RTResult<Expr>>>,
|
||||
/// Callback to invoke when a symbol is not found
|
||||
pub symbol_cb: Box<dyn Fn(Sym, CodeLocation) -> RTResult<Expr> + 'a>,
|
||||
}
|
||||
impl<'a> RunContext<'a> {
|
||||
|
||||
impl<'a> RunEnv<'a> {
|
||||
/// Create a new context. The return values of the symbol callback are cached
|
||||
pub fn new(
|
||||
handlers: HandlerTable<'a>,
|
||||
symbol_cb: impl Fn(Sym, CodeLocation) -> RTResult<Expr> + 'a,
|
||||
) -> Self {
|
||||
Self { handlers, symbols: RefCell::new(HashMap::new()), symbol_cb: Box::new(symbol_cb) }
|
||||
}
|
||||
|
||||
/// Produce an error indicating that a symbol was missing
|
||||
pub fn sym_not_found(sym: Sym, location: CodeLocation) -> RTErrorObj {
|
||||
MissingSymbol { location, sym }.pack()
|
||||
}
|
||||
|
||||
/// Load a symbol from cache or invoke the callback
|
||||
pub fn load(&self, sym: Sym, location: CodeLocation) -> RTResult<Expr> {
|
||||
let mut guard = self.symbols.borrow_mut();
|
||||
let (_, r) = (guard.raw_entry_mut().from_key(&sym))
|
||||
.or_insert_with(|| (sym.clone(), (self.symbol_cb)(sym, location)));
|
||||
r.clone()
|
||||
}
|
||||
|
||||
/// Attempt to resolve the command with the command handler table
|
||||
pub fn dispatch(&self, expr: &Clause, location: CodeLocation) -> Option<Expr> {
|
||||
match expr {
|
||||
Clause::Atom(at) => self.handlers.dispatch(&*at.0, location),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Limits and other context that is subject to change
|
||||
pub struct RunParams {
|
||||
/// Number of reduction steps permitted before the program is preempted
|
||||
pub gas: Option<usize>,
|
||||
/// Maximum recursion depth. Orchid uses a soft stack so this can be very
|
||||
/// large, but it must not be
|
||||
pub stack: usize,
|
||||
}
|
||||
impl RunParams {
|
||||
/// Consume some gas if it is being counted
|
||||
pub fn use_gas(&mut self, amount: usize) {
|
||||
if let Some(g) = self.gas.as_mut() {
|
||||
@@ -22,39 +70,26 @@ impl<'a> RunContext<'a> {
|
||||
}
|
||||
/// Gas is being counted and there is none left
|
||||
pub fn no_gas(&self) -> bool { self.gas == Some(0) }
|
||||
}
|
||||
|
||||
/// All the data produced by an interpreter run
|
||||
#[derive(Clone)]
|
||||
pub struct Halt {
|
||||
/// The new expression tree
|
||||
pub state: Expr,
|
||||
/// Leftover [Context::gas] if counted
|
||||
pub gas: Option<usize>,
|
||||
/// If true, the next run would not modify the expression
|
||||
pub inert: bool,
|
||||
}
|
||||
impl Halt {
|
||||
/// Check if gas has run out. Returns false if gas is not being used
|
||||
pub fn preempted(&self) -> bool { self.gas.map_or(false, |g| g == 0) }
|
||||
/// Returns a general report of the return
|
||||
pub fn status(&self) -> ReturnStatus {
|
||||
if self.preempted() {
|
||||
ReturnStatus::Preempted
|
||||
} else if self.inert {
|
||||
ReturnStatus::Inert
|
||||
} else {
|
||||
ReturnStatus::Active
|
||||
/// Add gas to make execution longer, or to resume execution in a preempted
|
||||
/// expression
|
||||
pub fn add_gas(&mut self, amount: usize) {
|
||||
if let Some(g) = self.gas.as_mut() {
|
||||
*g = g.saturating_add(amount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Possible states of a [Return]
|
||||
pub enum ReturnStatus {
|
||||
/// The data is not normalizable any further
|
||||
Inert,
|
||||
/// Gas is being used and it ran out
|
||||
Preempted,
|
||||
/// Normalization stopped for a different reason and should continue.
|
||||
Active,
|
||||
/// The interpreter's sole output excluding error conditions is an expression
|
||||
pub type Halt = Expr;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct MissingSymbol {
|
||||
pub sym: Sym,
|
||||
pub location: CodeLocation,
|
||||
}
|
||||
impl RTError for MissingSymbol {}
|
||||
impl fmt::Display for MissingSymbol {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}, called at {} is not loaded", self.sym, self.location)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user