implement chapter 13 in rust!
This commit is contained in:
parent
621c97102a
commit
32e5e8a41a
23 changed files with 318 additions and 19 deletions
6
lox/class_fields.lox
Normal file
6
lox/class_fields.lox
Normal file
|
@ -0,0 +1,6 @@
|
|||
class Dog{}
|
||||
|
||||
var dog = Dog();
|
||||
dog.name = "Leika";
|
||||
|
||||
print dog.name;
|
3
lox/class_instance.lox
Normal file
3
lox/class_instance.lox
Normal file
|
@ -0,0 +1,3 @@
|
|||
class Bagel {}
|
||||
var bagel = Bagel();
|
||||
print bagel;
|
7
lox/class_methods.lox
Normal file
7
lox/class_methods.lox
Normal file
|
@ -0,0 +1,7 @@
|
|||
class Bacon {
|
||||
eat() {
|
||||
print "Crunch crunch crunch!";
|
||||
}
|
||||
}
|
||||
|
||||
Bacon().eat();
|
7
lox/class_name.lox
Normal file
7
lox/class_name.lox
Normal file
|
@ -0,0 +1,7 @@
|
|||
class DevonshireCream {
|
||||
serveOn() {
|
||||
return "Scones";
|
||||
}
|
||||
}
|
||||
|
||||
print DevonshireCream;
|
10
lox/class_this.lox
Normal file
10
lox/class_this.lox
Normal file
|
@ -0,0 +1,10 @@
|
|||
class Cake {
|
||||
taste() {
|
||||
var adjective = "delicious";
|
||||
print "The " + this.flavor + " cake is " + adjective + "!";
|
||||
}
|
||||
}
|
||||
|
||||
var cake = Cake();
|
||||
cake.flavor = "German chocolate";
|
||||
cake.taste();
|
9
lox/fibonacci_timed.lox
Normal file
9
lox/fibonacci_timed.lox
Normal file
|
@ -0,0 +1,9 @@
|
|||
fun fib(n) {
|
||||
if (n < 2) return n;
|
||||
return fib(n - 1) + fib(n - 2);
|
||||
}
|
||||
|
||||
var before = clock();
|
||||
print fib(40);
|
||||
var after = clock();
|
||||
print after - before;
|
9
lox/init.lox
Normal file
9
lox/init.lox
Normal file
|
@ -0,0 +1,9 @@
|
|||
class Foo {
|
||||
init() {
|
||||
this.name = "test";
|
||||
print this;
|
||||
}
|
||||
}
|
||||
|
||||
var foo = Foo();
|
||||
print foo.name;
|
8
lox/init_early_return.lox
Normal file
8
lox/init_early_return.lox
Normal file
|
@ -0,0 +1,8 @@
|
|||
class Foo {
|
||||
init() {
|
||||
return;
|
||||
print this;
|
||||
}
|
||||
}
|
||||
|
||||
var foo = Foo();
|
5
lox/init_return.lox
Normal file
5
lox/init_return.lox
Normal file
|
@ -0,0 +1,5 @@
|
|||
class Foo {
|
||||
init() {
|
||||
return "something else";
|
||||
}
|
||||
}
|
1
lox/self_inheritance.lox
Normal file
1
lox/self_inheritance.lox
Normal file
|
@ -0,0 +1 @@
|
|||
class Oops < Oops {}
|
14
lox/super_call.lox
Normal file
14
lox/super_call.lox
Normal file
|
@ -0,0 +1,14 @@
|
|||
class Doughnut {
|
||||
cook() {
|
||||
print "Fry until golden brown.";
|
||||
}
|
||||
}
|
||||
|
||||
class BostonCream < Doughnut {
|
||||
cook() {
|
||||
super.cook();
|
||||
print "Pipe full of custard and coat with chocolate.";
|
||||
}
|
||||
}
|
||||
|
||||
BostonCream().cook();
|
6
lox/super_without_superclass.lox
Normal file
6
lox/super_without_superclass.lox
Normal file
|
@ -0,0 +1,6 @@
|
|||
class Eclair {
|
||||
cook() {
|
||||
super.cook();
|
||||
print "Pipe full of crème pâtissière.";
|
||||
}
|
||||
}
|
9
lox/superclass_method.lox
Normal file
9
lox/superclass_method.lox
Normal file
|
@ -0,0 +1,9 @@
|
|||
class Doughnut {
|
||||
cook() {
|
||||
print "Fry until golden brown.";
|
||||
}
|
||||
}
|
||||
|
||||
class BostonCream < Doughnut {}
|
||||
|
||||
BostonCream().cook();
|
3
lox/superclass_not_class.lox
Normal file
3
lox/superclass_not_class.lox
Normal file
|
@ -0,0 +1,3 @@
|
|||
var NotAClass = "I am totally not a class";
|
||||
|
||||
class Subclass < NotAClass {}
|
|
@ -10,6 +10,7 @@ use crate::{
|
|||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Class {
|
||||
pub name: String,
|
||||
pub superclass: Option<Box<Class>>,
|
||||
pub methods: HashMap<String, Function>,
|
||||
}
|
||||
|
||||
|
@ -20,12 +21,28 @@ impl Display for Class {
|
|||
}
|
||||
|
||||
impl Class {
|
||||
pub fn new(name: String, methods: HashMap<String, Function>) -> Self {
|
||||
Self { name, methods }
|
||||
pub fn new(
|
||||
name: String,
|
||||
superclass: Option<Class>,
|
||||
methods: HashMap<String, Function>,
|
||||
) -> Self {
|
||||
let superclass = superclass.map(|x| Box::new(x));
|
||||
|
||||
Self {
|
||||
name,
|
||||
superclass,
|
||||
methods,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_method(&self, name: &str) -> Option<&Function> {
|
||||
self.methods.get(name)
|
||||
self.methods.get(name).or_else(|| {
|
||||
if let Some(superclass) = &self.superclass {
|
||||
superclass.find_method(name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -47,4 +47,8 @@ pub enum Expression {
|
|||
Variable {
|
||||
name: Token,
|
||||
},
|
||||
Super {
|
||||
keyword: Token,
|
||||
method: Token,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -2,7 +2,11 @@ use std::{cell::RefCell, collections::HashMap, fmt::Display, rc::Rc};
|
|||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{class::Class, token::Token, value::Value};
|
||||
use crate::{
|
||||
class::Class,
|
||||
token::Token,
|
||||
value::{CallableType, Value},
|
||||
};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum InstanceError {
|
||||
|
@ -34,7 +38,10 @@ impl Instance {
|
|||
if let Some(field) = self.fields.get(name.lexeme.as_str()) {
|
||||
Ok(field.clone())
|
||||
} else if let Some(method) = self.class.find_method(name.lexeme.as_str()) {
|
||||
Ok(Value::Callable(Rc::new(method.bind(instance))))
|
||||
Ok(Value::Callable((
|
||||
Rc::new(method.bind(instance)),
|
||||
CallableType::Function,
|
||||
)))
|
||||
} else {
|
||||
Err(InstanceError::NoSuchProperty(
|
||||
name.line,
|
||||
|
|
|
@ -13,7 +13,7 @@ use crate::{
|
|||
native_functions,
|
||||
statement::Statement,
|
||||
token::{Literal, Token, TokenType},
|
||||
value::Value,
|
||||
value::{CallableType, Value},
|
||||
};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
|
@ -36,8 +36,14 @@ pub enum InterpreterError {
|
|||
FailedToCall(usize, CallingError),
|
||||
#[error("line {0}: only instances have fields.")]
|
||||
OnlyInstancesHaveFields(usize),
|
||||
#[error("line {0}: superclass must be a class.")]
|
||||
SuperclassNotClass(usize),
|
||||
#[error("line {0}: Undefined property '{1}'.")]
|
||||
UndefinedProperty(usize, String),
|
||||
#[error("{0}")]
|
||||
InstanceError(#[from] crate::instance::InstanceError),
|
||||
#[error("{0}")]
|
||||
EnvironmentError(#[from] crate::environment::EnvironmentError),
|
||||
}
|
||||
|
||||
/// Interpreter for the Lox language.
|
||||
|
@ -111,7 +117,11 @@ impl Interpreter {
|
|||
self.function_statement(name, params, body)
|
||||
}
|
||||
Statement::Return { keyword: _, value } => self.return_statement(value),
|
||||
Statement::Class { name, methods } => self.class(name, methods),
|
||||
Statement::Class {
|
||||
name,
|
||||
superclass,
|
||||
methods,
|
||||
} => self.class(name, superclass, methods),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -147,12 +157,38 @@ impl Interpreter {
|
|||
fn class(
|
||||
&mut self,
|
||||
name: &Token,
|
||||
superclass: &Option<Expression>,
|
||||
methods: &[Statement],
|
||||
) -> Result<Option<Value>, InterpreterError> {
|
||||
let superclass = if let Some(superclass) = superclass {
|
||||
let superclass = self.evaluate(superclass)?;
|
||||
if let Value::Callable((_, CallableType::Class(class))) = superclass {
|
||||
Some(class)
|
||||
} else {
|
||||
return Err(InterpreterError::SuperclassNotClass(name.line));
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.environment
|
||||
.borrow_mut()
|
||||
.define(name.lexeme.clone(), Value::Nil);
|
||||
|
||||
let old_env = self.environment.clone();
|
||||
if let Some(ref superclass) = superclass {
|
||||
self.environment = Rc::new(RefCell::new(Environment::with_enclosing(
|
||||
self.environment.clone(),
|
||||
)));
|
||||
self.environment.borrow_mut().define(
|
||||
"super".to_string(),
|
||||
Value::Callable((
|
||||
Rc::new(superclass.clone()),
|
||||
CallableType::Class(superclass.clone()),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
let mut class_methods: HashMap<String, Function> = HashMap::new();
|
||||
|
||||
for method in methods {
|
||||
|
@ -168,10 +204,17 @@ impl Interpreter {
|
|||
}
|
||||
}
|
||||
|
||||
let class = Class::new(name.lexeme.clone(), class_methods);
|
||||
let class = Class::new(name.lexeme.clone(), superclass, class_methods);
|
||||
|
||||
if class.superclass.is_some() {
|
||||
self.environment = old_env;
|
||||
}
|
||||
self.environment
|
||||
.borrow_mut()
|
||||
.assign(name, Value::Callable(Rc::new(class)))
|
||||
.assign(
|
||||
name,
|
||||
Value::Callable((Rc::new(class.clone()), CallableType::Class(class))),
|
||||
)
|
||||
.map_err(InterpreterError::UndefinedVariable)?;
|
||||
|
||||
Ok(None)
|
||||
|
@ -210,6 +253,7 @@ impl Interpreter {
|
|||
value,
|
||||
} => self.set(object, name, value),
|
||||
Expression::This { keyword } => self.lookup_var(keyword, expression),
|
||||
Expression::Super { keyword, method } => self.super_expr(expression, keyword, method),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -228,7 +272,7 @@ impl Interpreter {
|
|||
args.push(self.evaluate(arg)?);
|
||||
}
|
||||
|
||||
if let Value::Callable(f) = callee {
|
||||
if let Value::Callable((f, _)) = callee {
|
||||
f.call(self, args)
|
||||
.map_err(|e| InterpreterError::FailedToCall(paren.line, e))
|
||||
} else {
|
||||
|
@ -242,6 +286,7 @@ impl Interpreter {
|
|||
_ => Err(InterpreterError::OnlyInstancesHaveFields(name.line)),
|
||||
}
|
||||
}
|
||||
|
||||
fn set(
|
||||
&mut self,
|
||||
object: &Expression,
|
||||
|
@ -259,6 +304,44 @@ impl Interpreter {
|
|||
}
|
||||
}
|
||||
|
||||
fn super_expr(
|
||||
&mut self,
|
||||
expression: &Expression,
|
||||
keyword: &Token,
|
||||
method: &Token,
|
||||
) -> Result<Value, InterpreterError> {
|
||||
if let Some(distance) = self.locals.get(expression) {
|
||||
let superclass = self.environment.borrow().get_at(*distance, keyword)?;
|
||||
let object = self.environment.borrow().get_at(
|
||||
*distance - 1,
|
||||
&Token {
|
||||
token_type: TokenType::This,
|
||||
lexeme: "this".to_string(),
|
||||
literal: Some(Literal::String("this".to_string())),
|
||||
line: 0,
|
||||
},
|
||||
)?;
|
||||
let Value::Instance(instance) = object else {
|
||||
return Err(InterpreterError::SuperclassNotClass(keyword.line));
|
||||
};
|
||||
|
||||
let Value::Callable((_, CallableType::Class(class))) = superclass else {
|
||||
return Err(InterpreterError::SuperclassNotClass(keyword.line));
|
||||
};
|
||||
if let Some(method) = class.find_method(&method.lexeme) {
|
||||
let method = method.bind(instance);
|
||||
Ok(Value::Callable((Rc::new(method), CallableType::Function)))
|
||||
} else {
|
||||
Err(InterpreterError::UndefinedProperty(
|
||||
keyword.line,
|
||||
method.lexeme.clone(),
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Err(InterpreterError::SuperclassNotClass(keyword.line))
|
||||
}
|
||||
}
|
||||
|
||||
/// Define a new function.
|
||||
fn function_statement(
|
||||
&mut self,
|
||||
|
@ -275,9 +358,10 @@ impl Interpreter {
|
|||
closure: self.environment.clone(),
|
||||
};
|
||||
|
||||
self.environment
|
||||
.borrow_mut()
|
||||
.define(fn_name, Value::Callable(Rc::new(fun)));
|
||||
self.environment.borrow_mut().define(
|
||||
fn_name,
|
||||
Value::Callable((Rc::new(fun), CallableType::Function)),
|
||||
);
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use ordered_float::OrderedFloat;
|
|||
use crate::{
|
||||
callable::{Callable, CallingError},
|
||||
interpreter::Interpreter,
|
||||
value::Value,
|
||||
value::{CallableType, Value},
|
||||
};
|
||||
|
||||
struct Clock;
|
||||
|
@ -37,5 +37,8 @@ impl Callable for Clock {
|
|||
}
|
||||
|
||||
pub fn all() -> Vec<(String, Value)> {
|
||||
vec![("clock".into(), Value::Callable(Rc::new(Clock {})))]
|
||||
vec![(
|
||||
"clock".into(),
|
||||
Value::Callable((Rc::new(Clock {}), CallableType::Function)),
|
||||
)]
|
||||
}
|
||||
|
|
|
@ -65,6 +65,10 @@ pub enum ParserError {
|
|||
RightBraceAfterClassExpected(usize),
|
||||
#[error("line {0}: expected property name after '.'.")]
|
||||
PropertyNameAfterDotExpected(usize),
|
||||
#[error("line {0}: expected '.' after 'super'.")]
|
||||
DotAfterSuper(usize),
|
||||
#[error("line {0}: expected superclass method name.")]
|
||||
SuperclassMethodNameExpected(usize),
|
||||
}
|
||||
|
||||
/// Parse the Lox language.
|
||||
|
@ -314,6 +318,16 @@ impl Parser {
|
|||
.ok_or(ParserError::ClassNameExpected(line))?
|
||||
.clone();
|
||||
|
||||
let superclass = if self.matches(&[Less]) {
|
||||
self.consume(&Identifier)
|
||||
.ok_or(ParserError::ClassNameExpected(line))?;
|
||||
let name = self.previous()?.clone();
|
||||
|
||||
Some(Expression::Variable { name })
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.consume(&LeftBrace)
|
||||
.ok_or(ParserError::LeftBraceBeforeClassExpected(line))?;
|
||||
|
||||
|
@ -326,7 +340,11 @@ impl Parser {
|
|||
self.consume(&RightBrace)
|
||||
.ok_or(ParserError::RightBraceAfterClassExpected(line))?;
|
||||
|
||||
Ok(Statement::Class { name, methods })
|
||||
Ok(Statement::Class {
|
||||
name,
|
||||
superclass,
|
||||
methods,
|
||||
})
|
||||
}
|
||||
|
||||
fn var_declaration(&mut self) -> Result<Statement, ParserError> {
|
||||
|
@ -585,6 +603,16 @@ impl Parser {
|
|||
Ok(Expression::Literal {
|
||||
value: token::Literal::Nil,
|
||||
})
|
||||
} else if self.matches(&[Super]) {
|
||||
let line = self.current_token.line;
|
||||
let keyword = self.previous()?.clone();
|
||||
self.consume(&Dot).ok_or(ParserError::DotAfterSuper(line))?;
|
||||
let method = self
|
||||
.consume(&Identifier)
|
||||
.ok_or(ParserError::SuperclassMethodNameExpected(line))?
|
||||
.clone();
|
||||
|
||||
Ok(Expression::Super { keyword, method })
|
||||
} else if self.matches(&[This]) {
|
||||
Ok(Expression::This {
|
||||
keyword: self.previous()?.clone(),
|
||||
|
|
|
@ -14,6 +14,12 @@ pub enum ResolverError {
|
|||
ThisOutsideClass(usize),
|
||||
#[error("line {0}: can not return a value from an initializer.")]
|
||||
ReturnFromInitializer(usize),
|
||||
#[error("line {0}: a class can not inherit from itself.")]
|
||||
SelfInheritance(usize),
|
||||
#[error("line {0}: can not use 'super' outside of a class.")]
|
||||
SuperOutsideClass(usize),
|
||||
#[error("line {0}: can not use 'super' in a class with no superclass.")]
|
||||
SuperWithoutSuperclass(usize),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
@ -28,6 +34,7 @@ enum FunctionType {
|
|||
enum ClassType {
|
||||
None,
|
||||
Class,
|
||||
Subclass,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -75,7 +82,11 @@ impl<'a> Resolver<'a> {
|
|||
self.resolve_function_stmt(name, params, body)
|
||||
}
|
||||
Statement::Return { keyword, value } => self.resolve_return_stmt(keyword, value),
|
||||
Statement::Class { name, methods } => self.resolve_class_stmt(name, methods),
|
||||
Statement::Class {
|
||||
name,
|
||||
superclass,
|
||||
methods,
|
||||
} => self.resolve_class_stmt(name, superclass, methods),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,6 +125,9 @@ impl<'a> Resolver<'a> {
|
|||
|
||||
self.resolve_local(expr, keyword)
|
||||
}
|
||||
Expression::Super { keyword, method: _ } => {
|
||||
self.resolve_super_expression(keyword, expr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -154,6 +168,7 @@ impl<'a> Resolver<'a> {
|
|||
fn resolve_class_stmt(
|
||||
&mut self,
|
||||
name: &Token,
|
||||
superclass: &Option<Expression>,
|
||||
methods: &[Statement],
|
||||
) -> Result<(), ResolverError> {
|
||||
let enclosing_class = self.current_class;
|
||||
|
@ -162,6 +177,25 @@ impl<'a> Resolver<'a> {
|
|||
self.declare(name)?;
|
||||
self.define(name);
|
||||
|
||||
if let Some(
|
||||
superclass @ Expression::Variable {
|
||||
name: superclass_name,
|
||||
},
|
||||
) = superclass
|
||||
{
|
||||
if superclass_name.lexeme == name.lexeme {
|
||||
return Err(ResolverError::SelfInheritance(superclass_name.line));
|
||||
}
|
||||
|
||||
self.current_class = ClassType::Subclass;
|
||||
self.resolve_expression(superclass)?;
|
||||
|
||||
self.begin_scope();
|
||||
if let Some(scope) = self.scopes.last_mut() {
|
||||
scope.insert("super".to_string(), true);
|
||||
}
|
||||
}
|
||||
|
||||
self.begin_scope();
|
||||
|
||||
if let Some(scope) = self.scopes.last_mut() {
|
||||
|
@ -181,6 +215,10 @@ impl<'a> Resolver<'a> {
|
|||
|
||||
self.end_scope();
|
||||
|
||||
if superclass.is_some() {
|
||||
self.end_scope();
|
||||
}
|
||||
|
||||
self.current_class = enclosing_class;
|
||||
|
||||
Ok(())
|
||||
|
@ -288,6 +326,20 @@ impl<'a> Resolver<'a> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_super_expression(
|
||||
&mut self,
|
||||
keyword: &Token,
|
||||
expression: &Expression,
|
||||
) -> Result<(), ResolverError> {
|
||||
if self.current_class == ClassType::None {
|
||||
return Err(ResolverError::SuperOutsideClass(keyword.line));
|
||||
} else if self.current_class != ClassType::Subclass {
|
||||
return Err(ResolverError::SuperWithoutSuperclass(keyword.line));
|
||||
}
|
||||
|
||||
self.resolve_local(expression, keyword)
|
||||
}
|
||||
|
||||
fn resolve_if_stmt(
|
||||
&mut self,
|
||||
condition: &Expression,
|
||||
|
|
|
@ -30,6 +30,7 @@ pub enum Statement {
|
|||
},
|
||||
Class {
|
||||
name: Token,
|
||||
superclass: Option<Expression>,
|
||||
methods: Vec<Statement>,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -4,11 +4,17 @@ use ordered_float::OrderedFloat;
|
|||
|
||||
use crate::{callable::Callable, instance, token::Literal};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum CallableType {
|
||||
Function,
|
||||
Class(crate::class::Class),
|
||||
}
|
||||
|
||||
/// Concrete value in the interpreter.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Value {
|
||||
Return(Box<Value>),
|
||||
Callable(Rc<dyn Callable>),
|
||||
Callable((Rc<dyn Callable>, CallableType)),
|
||||
String(String),
|
||||
Number(OrderedFloat<f64>),
|
||||
Boolean(bool),
|
||||
|
@ -20,7 +26,7 @@ impl Display for Value {
|
|||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Value::Return(x) => write!(f, "{x}"),
|
||||
Value::Callable(x) => write!(f, "{x}"),
|
||||
Value::Callable(x) => write!(f, "{}", x.0),
|
||||
Value::String(x) => write!(f, "{x}"),
|
||||
Value::Number(x) => write!(f, "{x}"),
|
||||
Value::Boolean(x) => write!(f, "{x}"),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue