interpreter: use an explicit stack, rather than a cons list

Also gets rid of some dead code
This commit is contained in:
John 2024-01-05 22:47:16 -06:00
parent 54bae6a9c5
commit 53f9ec2356

View File

@ -8,7 +8,7 @@ use temp_type_impl::ConValue;
/// Callable types can be called from within a Conlang program
pub trait Callable: std::fmt::Debug {
/// Calls this [Callable] in the provided [Interpreter], with [ConValue] args \
/// Calls this [Callable] in the provided [Environment], with [ConValue] args \
/// The Callable is responsible for checking the argument count and validating types
fn call(&self, interpreter: &mut Environment, args: &[ConValue]) -> IResult<ConValue>;
/// Returns the common name of this identifier.
@ -285,7 +285,7 @@ pub mod temp_type_impl {
/// A work-in-progress tree walk interpreter for Conlang
pub trait Interpret {
/// Interprets this thing using the given [`Interpreter`].
/// Interprets this thing in the given [`Environment`].
///
/// Everything returns a value!™
fn interpret(&self, env: &mut Environment) -> IResult<ConValue>;
@ -353,25 +353,20 @@ impl Interpret for Assign {
use operator::Assign;
let math::Assign { target, operator, init } = self;
let init = init.interpret(env)?;
let resolved = env.get_mut(&target.name)?;
let target = env.get_mut(&target.name)?;
if let Assign::Assign = operator {
use std::mem::discriminant as variant;
// runtime typecheck
match resolved.as_mut() {
Some(value) if variant(value) == variant(&init) => {
match target {
value if variant(value) == variant(&init) => {
*value = init;
}
None => *resolved = Some(init),
_ => Err(Error::TypeError)?,
}
return Ok(ConValue::Empty);
}
let Some(target) = resolved.as_mut() else {
Err(Error::NotInitialized(target.name.to_owned()))?
};
match operator {
Assign::AddAssign => target.add_assign(init)?,
Assign::SubAssign => target.sub_assign(init)?,
@ -494,7 +489,7 @@ impl Interpret for Primary {
}
impl Interpret for Identifier {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
env.resolve(&self.name)
env.get(&self.name).cloned()
}
}
impl Interpret for Literal {
@ -622,7 +617,7 @@ impl Interpret for Return {
}
impl Interpret for Break {
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
Err(Error::Return(self.expr.interpret(env)?))
Err(Error::Break(self.expr.interpret(env)?))
}
}
@ -634,42 +629,35 @@ pub mod function {
#[derive(Clone, Debug)]
pub struct Function {
/// Stores the contents of the function declaration
declaration: Box<FnDecl>,
// /// Stores the enclosing scope of the function
// TODO: Capture variables
//environment: Box<Environment>,
decl: Box<FnDecl>,
/// Stores the enclosing scope of the function
env: Box<Environment>,
}
impl Function {
pub fn new(declaration: &FnDecl) -> Self {
Self {
declaration: declaration.clone().into(),
//environment: Box::new(Environment::new()),
}
pub fn new(decl: &FnDecl, env: Environment) -> Self {
Self { decl: decl.clone().into(), env: Box::new(env) }
}
}
impl Callable for Function {
fn name(&self) -> &str {
&self.declaration.name.symbol.name
&self.decl.name.symbol.name
}
fn call(&self, env: &mut Environment, args: &[ConValue]) -> IResult<ConValue> {
fn call(&self, _env: &mut Environment, args: &[ConValue]) -> IResult<ConValue> {
// Check arg mapping
if args.len() != self.declaration.args.len() {
return Err(Error::ArgNumber {
want: self.declaration.args.len(),
got: args.len(),
});
if args.len() != self.decl.args.len() {
return Err(Error::ArgNumber { want: self.decl.args.len(), got: args.len() });
}
// TODO: Isolate cross-function scopes!
let mut frame = env.frame("function args");
let mut env = self.env.clone();
let mut frame = env.frame("fn args");
for (Name { symbol: Identifier { name, .. }, .. }, value) in
self.declaration.args.iter().zip(args)
self.decl.args.iter().zip(args)
{
frame.insert(name, Some(value.clone()));
}
match self.declaration.body.interpret(&mut frame) {
match self.decl.body.interpret(&mut frame) {
Err(Error::Return(value)) => Ok(value),
Err(Error::Break(value)) => Err(Error::BadBreak(value)),
result => result,
@ -679,6 +667,7 @@ pub mod function {
}
pub mod builtin {
//! Implementations of built-in functions
mod builtin_imports {
pub use crate::interpreter::{
env::Environment, error::IResult, temp_type_impl::ConValue, BuiltIn, Callable,
@ -759,34 +748,32 @@ pub mod env {
/// Implements a nested lexical scope
#[derive(Clone, Debug)]
pub struct Environment {
outer: Option<Box<Self>>,
vars: HashMap<String, Option<ConValue>>,
name: &'static str,
frames: Vec<(HashMap<String, Option<ConValue>>, &'static str)>,
}
impl Display for Environment {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "--- '{}' ---", self.name)?;
for (var, val) in &self.vars {
write!(f, "{var}: ")?;
match val {
Some(value) => writeln!(f, "{value}"),
None => writeln!(f, "<undefined>"),
}?
}
if let Some(outer) = &self.outer {
outer.fmt(f)?;
for (frame, name) in self.frames.iter().rev() {
writeln!(f, "--- {name} ---")?;
for (var, val) in frame {
write!(f, "- {var}: ")?;
match val {
Some(value) => writeln!(f, "{value}"),
None => writeln!(f, "<undefined>"),
}?
}
}
Ok(())
}
}
impl Default for Environment {
fn default() -> Self {
let mut outer = Self::no_builtins("builtins");
let mut builtins = HashMap::new();
for &builtin in DEFAULT_BUILTINS {
outer.insert(builtin.name(), Some(ConValue::BuiltIn(builtin)));
builtins.insert(builtin.name().into(), Some(ConValue::BuiltIn(builtin)));
}
Self { outer: Some(Box::new(outer)), vars: Default::default(), name: "base" }
// FIXME: Temporary until modules are implemented
Self { frames: vec![(builtins, "builtins"), (HashMap::new(), "globals")] }
}
}
@ -794,13 +781,9 @@ pub mod env {
pub fn new() -> Self {
Self::default()
}
fn no_builtins(name: &'static str) -> Self {
Self { outer: None, vars: Default::default(), name }
}
/// Temporary function
#[deprecated]
pub fn resolve(&mut self, name: &str) -> IResult<ConValue> {
self.get(name).cloned()
/// Creates an [Environment] with no [builtins](super::builtin)
pub fn no_builtins(name: &'static str) -> Self {
Self { frames: vec![(Default::default(), name)] }
}
pub fn eval(&mut self, node: &impl Interpret) -> IResult<ConValue> {
@ -810,7 +793,8 @@ pub mod env {
/// Calls a function inside the interpreter's scope,
/// and returns the result
pub fn call(&mut self, name: &str, args: &[ConValue]) -> IResult<ConValue> {
let function = self.resolve(name)?;
// FIXME: Clone to satisfy the borrow checker
let function = self.get(name)?.clone();
function.call(self, args)
}
/// Enters a nested scope, returning a [`Frame`] stack-guard.
@ -822,36 +806,42 @@ pub mod env {
/// Resolves a variable mutably
///
/// Returns a mutable reference to the variable's record, if it exists
pub fn get_mut(&mut self, id: &str) -> IResult<&mut Option<ConValue>> {
match self.vars.get_mut(id) {
Some(var) => Ok(var),
None => self
.outer
.as_mut()
.ok_or_else(|| Error::NotDefined(id.into()))?
.get_mut(id),
pub fn get_mut(&mut self, id: &str) -> IResult<&mut ConValue> {
for (frame, _) in self.frames.iter_mut() {
match frame.get_mut(id) {
Some(Some(var)) => return Ok(var),
Some(None) => return Err(Error::NotInitialized(id.into())),
_ => (),
}
}
Err(Error::NotDefined(id.into()))
}
/// Resolves a variable immutably
pub fn get(&self, id: &str) -> IResult<&ConValue> {
match self.vars.get(id) {
Some(var) => var.as_ref().ok_or_else(|| Error::NotInitialized(id.into())),
None => self
.outer
.as_ref()
.ok_or_else(|| Error::NotDefined(id.into()))?
.get(id),
for (frame, _) in self.frames.iter() {
match frame.get(id) {
Some(Some(var)) => return Ok(var),
Some(None) => return Err(Error::NotInitialized(id.into())),
_ => (),
}
}
Err(Error::NotDefined(id.into()))
}
/// Inserts a new [ConValue] into this [Environment]
pub fn insert(&mut self, id: &str, value: Option<ConValue>) {
self.vars.insert(id.to_string(), value);
if let Some((frame, _)) = self.frames.last_mut() {
frame.insert(id.into(), value);
}
}
/// A convenience function for registering a [FnDecl] as a [Function]
pub fn insert_fn(&mut self, decl: &FnDecl) {
self.vars.insert(
let (name, function) = (
decl.name.symbol.name.clone(),
Some(Function::new(decl).into()),
Some(Function::new(decl, self.clone()).into()),
);
if let Some((frame, _)) = self.frames.last_mut() {
frame.insert(name, function);
}
}
}
@ -859,16 +849,15 @@ pub mod env {
impl Environment {
/// Enters a scope, creating a new namespace for variables
fn enter(&mut self, name: &'static str) -> &mut Self {
let outer = std::mem::replace(self, Environment::no_builtins(name));
self.outer = Some(outer.into());
self.frames.push((Default::default(), name));
self
}
/// Exits the scope, destroying all local variables and
/// returning the outer scope, if there is one
fn exit(&mut self) -> &mut Self {
if let Some(outer) = std::mem::take(&mut self.outer) {
*self = *outer;
if self.frames.len() > 2 {
self.frames.pop();
}
self
}
@ -902,34 +891,14 @@ pub mod env {
}
}
pub mod module {
//! Implements non-lexical "global" scoping rules
}
pub mod error {
//! The [Error] type represents any error thrown by the [Interpreter](super::Interpreter)
//! The [Error] type represents any error thrown by the [Environment](super::Environment)
use super::temp_type_impl::ConValue;
pub type IResult<T> = Result<T, Error>;
/// Represents any error thrown by the [Interpreter](super::Interpreter)
impl Error {
/// Creates a [Return](Error::Return) error, with the given [value](ConValue)
pub fn ret(value: ConValue) -> Self {
Error::Return(value)
}
/// Creates a [Break](Error::Break) error, with the given [value](ConValue)
pub fn brk(value: ConValue) -> Self {
Error::Break(value)
}
/// Creates a [Continue](Error::Continue) error
pub fn cnt() -> Self {
Error::Continue
}
}
/// The reason for the [Error]
/// Represents any error thrown by the [Environment](super::Environment)
#[derive(Clone, Debug)]
pub enum Error {
/// Propagate a Return value