Rewrote value storage and implemented class instantiation

* Probably not the last time.
  * Object-like things (functions, classes, etc...) are stored as
    ref-counted cells.
  * Separate data structure for native functions.
  * with_callable() method on value to run specific code on objects
    that are callable
  * Instance type added.
  * Instance construction implemented
This commit is contained in:
Emmanuel BENOîT 2023-01-08 10:40:36 +01:00
parent 85e9c7db38
commit d8dba3ac5f
8 changed files with 274 additions and 100 deletions

View file

@ -1,18 +1,15 @@
use std::{cell::RefCell, fmt::Debug, rc::Rc}; use std::fmt::Debug;
use crate::errors::SloxResult; use crate::errors::SloxResult;
use super::{InterpreterState, Value}; use super::{InterpreterState, Value};
/// A callable is some object that supports being called. /// A callable is some object that supports being called.
pub trait Callable: Debug + ToString { pub trait Callable: Debug {
/// Return the amount of arguments supported by the callable. /// Return the amount of arguments supported by the callable.
fn arity(&self) -> usize; fn arity(&self) -> usize;
/// Run the callable in the execution environment with the specified /// Run the callable in the execution environment with the specified
/// arguments. /// arguments.
fn call(&self, callee: &Value, environment: &mut InterpreterState, arguments: Vec<Value>) -> SloxResult<Value>; fn call(&self, itpr_state: &mut InterpreterState, arguments: Vec<Value>) -> SloxResult<Value>;
} }
/// A reference to a callable.
pub type CallableRef = Rc<RefCell<dyn Callable>>;

View file

@ -1,9 +1,28 @@
use std::{cell::RefCell, fmt::Display, rc::Rc};
use crate::errors::SloxResult;
use super::{Callable, InterpreterState, Value};
/// A Lox class. /// A Lox class.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Class { pub struct Class {
name: String, name: String,
} }
/// Classes are mostly used through references.
pub type ClassRef = Rc<RefCell<Class>>;
/// An instance of a Lox class
#[derive(Debug, Clone)]
pub struct Instance {
class: Rc<RefCell<Class>>,
}
/* -------------------- *
* Class implementation *
* -------------------- */
impl Class { impl Class {
/// Create a new class, specifying its name. /// Create a new class, specifying its name.
pub fn new(name: String) -> Self { pub fn new(name: String) -> Self {
@ -11,8 +30,40 @@ impl Class {
} }
} }
impl ToString for Class { impl Display for Class {
fn to_string(&self) -> String { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.name.clone() f.write_str("<class ")?;
f.write_str(&self.name)?;
f.write_str(">")
}
}
impl Callable for ClassRef {
fn arity(&self) -> usize {
return 0;
}
fn call(&self, _itpr_state: &mut InterpreterState, _arguments: Vec<Value>) -> SloxResult<Value> {
let instance = Instance::new(self.clone());
Ok(Value::from(instance))
}
}
/* ----------------------- *
* Instance implementation *
* ----------------------- */
impl Instance {
fn new(class: ClassRef) -> Self {
Self { class }
}
}
impl Display for Instance {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"<Instance of {}>",
self.class.borrow().to_string()
))
} }
} }

View file

@ -5,7 +5,7 @@ use crate::{
tokens::Token, tokens::Token,
}; };
use super::{native_fn, CallableRef, Value}; use super::{Value, native_fn::{self, NativeFunction}};
/// A mutable reference to an environment. /// A mutable reference to an environment.
pub(super) type EnvironmentRef = Rc<RefCell<Environment>>; pub(super) type EnvironmentRef = Rc<RefCell<Environment>>;
@ -27,7 +27,7 @@ impl Default for Environment {
enclosing: None, enclosing: None,
values: HashMap::new(), values: HashMap::new(),
}; };
env.add_default_fun("clock", native_fn::clock()); env.add_default_fun(native_fn::clock());
env env
} }
} }
@ -42,9 +42,10 @@ impl Environment {
} }
/// Add a default function to the environment. /// Add a default function to the environment.
fn add_default_fun(&mut self, name: &str, fun: CallableRef) { fn add_default_fun(&mut self, fun: NativeFunction) {
let value = Some(Value::Callable(fun)); let name = fun.name().to_owned();
self.values.insert(name.to_owned(), value); let value = Some(Value::from(fun));
self.values.insert(name, value);
} }
/// Define a new variable. /// Define a new variable.

View file

@ -1,4 +1,4 @@
use std::{cell::RefCell, rc::Rc}; use std::fmt::Display;
use itertools::izip; use itertools::izip;
@ -9,8 +9,8 @@ use super::{
use crate::{ast, errors::SloxResult, tokens::Token}; use crate::{ast, errors::SloxResult, tokens::Token};
/// A function implemented in the Lox-ish language. /// A function implemented in the Lox-ish language.
#[derive(Debug)] #[derive(Debug, Clone)]
pub(super) struct Function { pub struct Function {
name: Option<Token>, name: Option<Token>,
params: Vec<Token>, params: Vec<Token>,
body: Vec<ast::StmtNode>, body: Vec<ast::StmtNode>,
@ -23,14 +23,13 @@ impl Function {
params: &[Token], params: &[Token],
body: &[ast::StmtNode], body: &[ast::StmtNode],
environment: EnvironmentRef, environment: EnvironmentRef,
) -> Rc<RefCell<Self>> { ) -> Self {
let fun = Self { Self {
name: name.cloned(), name: name.cloned(),
params: params.to_owned(), params: params.to_owned(),
body: body.to_owned(), body: body.to_owned(),
env: environment, env: environment,
}; }
Rc::new(RefCell::new(fun))
} }
} }
@ -39,17 +38,12 @@ impl Callable for Function {
self.params.len() self.params.len()
} }
fn call( fn call(&self, itpr_state: &mut InterpreterState, arguments: Vec<Value>) -> SloxResult<Value> {
&self,
_callee: &Value,
es: &mut InterpreterState,
arguments: Vec<Value>,
) -> SloxResult<Value> {
assert_eq!(arguments.len(), self.arity()); assert_eq!(arguments.len(), self.arity());
let param_env = InterpreterState { let param_env = InterpreterState {
environment: Environment::create_child(&self.env), environment: Environment::create_child(&self.env),
globals: es.globals.clone(), globals: itpr_state.globals.clone(),
locals: es.locals, locals: itpr_state.locals,
}; };
for (arg, value) in izip!(self.params.iter(), arguments.into_iter()) { for (arg, value) in izip!(self.params.iter(), arguments.into_iter()) {
param_env param_env
@ -72,11 +66,15 @@ impl Callable for Function {
} }
} }
impl ToString for Function { impl Display for Function {
fn to_string(&self) -> String { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.name { match &self.name {
None => "<lambda>".to_owned(), None => f.write_str("<lambda>"),
Some(token) => format!("<fun {}>", token.lexeme), Some(token) => {
f.write_str("<fun ")?;
f.write_str(&token.lexeme)?;
f.write_str(">")
}
} }
} }
} }

View file

@ -200,7 +200,7 @@ impl StmtNode {
let class = Class::new(decl.name.lexeme.clone()); let class = Class::new(decl.name.lexeme.clone());
es.environment es.environment
.borrow_mut() .borrow_mut()
.assign(&decl.name, Value::Class(class))?; .assign(&decl.name, class.into())?;
Ok(InterpreterFlowControl::default()) Ok(InterpreterFlowControl::default())
} }
@ -214,7 +214,7 @@ impl StmtNode {
); );
es.environment es.environment
.borrow_mut() .borrow_mut()
.define(&decl.name, Some(Value::Callable(fun)))?; .define(&decl.name, Some(fun.into()))?;
Ok(InterpreterFlowControl::default()) Ok(InterpreterFlowControl::default())
} }
@ -338,10 +338,8 @@ impl Interpretable for ExprNode {
arguments, arguments,
} => self.on_call(es, callee, right_paren, arguments), } => self.on_call(es, callee, right_paren, arguments),
ExprNode::Lambda { params, body } => { ExprNode::Lambda { params, body } => {
Ok( let lambda = Function::new(None, params, body, es.environment.clone());
Value::Callable(Function::new(None, params, body, es.environment.clone())) Ok(Value::from(lambda).into())
.into(),
)
} }
} }
} }
@ -492,23 +490,23 @@ impl ExprNode {
} }
v v
}; };
if let Value::Callable(callable_ref) = &callee { callee.with_callable(
let callable = callable_ref.borrow(); |callable| {
if callable.arity() != arg_values.len() { if callable.arity() != arg_values.len() {
Err(SloxError::with_token( Err(SloxError::with_token(
ErrorKind::Runtime, ErrorKind::Runtime,
right_paren, right_paren,
format!( format!(
"expected {} arguments, found {}", "expected {} arguments, found {}",
arg_values.len(), arg_values.len(),
callable.arity() callable.arity()
), ),
)) ))
} else { } else {
Ok(callable.call(&callee, es, arg_values)?.into()) Ok(callable.call(es, arg_values)?.into())
} }
} else { },
error(right_paren, "can only call functions and classes") || error(right_paren, "expression result is not callable")
} )
} }
} }

View file

@ -6,7 +6,7 @@ mod interpretable;
mod native_fn; mod native_fn;
mod value; mod value;
pub(self) use callable::{Callable, CallableRef}; pub(self) use callable::Callable;
pub(self) use environment::*; pub(self) use environment::*;
pub use interpretable::*; pub use interpretable::*;
pub use value::*; pub use value::*;

View file

@ -1,45 +1,94 @@
use std::{ use std::{
cell::RefCell, fmt::{Debug, Display},
rc::Rc,
time::{SystemTime, UNIX_EPOCH}, time::{SystemTime, UNIX_EPOCH},
}; };
use crate::errors::SloxResult; use crate::errors::SloxResult;
use super::{Callable, CallableRef, InterpreterState, Value}; use super::{Callable, InterpreterState, Value};
/* ----------------------- *
* Native function support *
* ----------------------- */
/// A function pointer to the implementation of some native function.
type NativeFunctionHandler = fn(&mut InterpreterState, Vec<Value>) -> SloxResult<Value>;
/// A native function.
#[derive(Clone)]
pub struct NativeFunction {
/// Name of the function.
name: String,
/// Arity of the function.
arity: usize,
/// Rust function that actually implements the function.
handler: NativeFunctionHandler,
}
impl NativeFunction {
/// Initialize a native function's record.
fn new(name: &str, arity: usize, handler: NativeFunctionHandler) -> Self {
Self {
name: name.to_owned(),
arity,
handler,
}
}
/// Access the native function's name
pub fn name(&self) -> &str {
&self.name
}
}
impl PartialEq for NativeFunction {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}
impl Display for NativeFunction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("<native function ")?;
f.write_str(&self.name)?;
f.write_str(">")
}
}
impl Debug for NativeFunction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"NativeFunction {{ name: {:?}, arity: {} }}",
self.name, self.arity
))
}
}
impl Callable for NativeFunction {
fn arity(&self) -> usize {
self.arity
}
fn call(&self, itpr_state: &mut InterpreterState, arguments: Vec<Value>) -> SloxResult<Value> {
(self.handler)(itpr_state, arguments)
}
}
/* ----------- * /* ----------- *
* clock() * * clock() *
* ----------- */ * ----------- */
#[derive(Debug)] fn _clock_implementation(
struct Clock; _environment: &mut InterpreterState,
_arguments: Vec<Value>,
impl Callable for Clock { ) -> SloxResult<Value> {
fn arity(&self) -> usize { let now = SystemTime::now();
0 let since_epoch = now
} .duration_since(UNIX_EPOCH)
.expect("looks like it's 2038 already");
fn call( Ok(Value::Number((since_epoch.as_millis() as f64) / 1000.0))
&self,
_callee: &Value,
_environment: &mut InterpreterState,
_arguments: Vec<Value>,
) -> SloxResult<Value> {
let now = SystemTime::now();
let since_epoch = now
.duration_since(UNIX_EPOCH)
.expect("looks like it's 2038 already");
Ok(Value::Number((since_epoch.as_millis() as f64) / 1000.0))
}
} }
impl ToString for Clock { pub(super) fn clock() -> NativeFunction {
fn to_string(&self) -> String { NativeFunction::new("clock", 0, _clock_implementation)
"<native function clock()>".to_owned()
}
}
pub(super) fn clock() -> CallableRef {
Rc::new(RefCell::new(Clock {}))
} }

View file

@ -1,6 +1,11 @@
use std::{cell::RefCell, fmt::Display, rc::Rc}; use std::{cell::RefCell, fmt::Display, rc::Rc};
use super::{Callable, class::Class}; use super::{
class::{Class, ClassRef, Instance},
functions::Function,
native_fn::NativeFunction,
Callable,
};
/// A value being handled by the interpreter. /// A value being handled by the interpreter.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -9,8 +14,50 @@ pub enum Value {
Boolean(bool), Boolean(bool),
String(String), String(String),
Number(f64), Number(f64),
Callable(Rc<RefCell<dyn Callable>>), Object(Rc<RefCell<Object>>),
Class(Class), }
#[derive(Debug, Clone)]
pub enum Object {
NativeFunction(NativeFunction),
LoxFunction(Function),
Class(ClassRef),
Instance(Instance),
}
/* -------------------- *
* Value implementation *
* -------------------- */
impl Value {
/// Check whether a value is truthy or not.
pub fn is_truthy(&self) -> bool {
match self {
Self::Nil => false,
Self::Boolean(b) => *b,
_ => true,
}
}
/// Run some code using the callable object wrapped inside a value.
/// If the value is not callable, an error function will be called
/// instead.
pub fn with_callable<Fok, Ferr, Rt>(&self, fok: Fok, ferr: Ferr) -> Rt
where
Fok: FnOnce(&dyn Callable) -> Rt,
Ferr: FnOnce() -> Rt,
{
let obj = match self {
Value::Object(obj_ref) => obj_ref.borrow(),
_ => return ferr(),
};
match &*obj {
Object::NativeFunction(func) => fok(func),
Object::LoxFunction(func) => fok(func),
Object::Class(class) => fok(class),
Object::Instance(_) => ferr(),
}
}
} }
impl PartialEq for Value { impl PartialEq for Value {
@ -32,19 +79,52 @@ impl Display for Value {
Value::Boolean(b) => b.fmt(f), Value::Boolean(b) => b.fmt(f),
Value::String(s) => s.fmt(f), Value::String(s) => s.fmt(f),
Value::Number(n) => n.fmt(f), Value::Number(n) => n.fmt(f),
Value::Callable(c) => f.write_str(&c.borrow().to_string()), Value::Object(obj) => obj.borrow().fmt(f),
Value::Class(c) => f.write_str(&c.to_string()),
} }
} }
} }
impl Value { impl From<NativeFunction> for Value {
/// Check whether a value is truthy or not. fn from(value: NativeFunction) -> Self {
pub fn is_truthy(&self) -> bool { Value::Object(Rc::new(RefCell::new(Object::NativeFunction(value))))
}
}
impl From<Function> for Value {
fn from(value: Function) -> Self {
Value::Object(Rc::new(RefCell::new(Object::LoxFunction(value))))
}
}
impl From<Class> for Value {
fn from(value: Class) -> Self {
Value::from(Rc::new(RefCell::new(value)))
}
}
impl From<ClassRef> for Value {
fn from(value: ClassRef) -> Self {
Value::Object(Rc::new(RefCell::new(Object::Class(value))))
}
}
impl From<Instance> for Value {
fn from(value: Instance) -> Self {
Value::Object(Rc::new(RefCell::new(Object::Instance(value))))
}
}
/* --------------------- *
* Object implementation *
* --------------------- */
impl Display for Object {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::Nil => false, Object::NativeFunction(func) => func.fmt(f),
Self::Boolean(b) => *b, Object::LoxFunction(func) => func.fmt(f),
_ => true, Object::Class(cls) => cls.borrow().fmt(f),
Object::Instance(inst) => inst.fmt(f),
} }
} }
} }