Skip to content

Commit

Permalink
feat: add inheritance
Browse files Browse the repository at this point in the history
"There's always a bigger fish" - Qui-Gon Jinn

Signed-off-by: Prajwal S N <[email protected]>
  • Loading branch information
snprajwal committed Dec 27, 2023
1 parent 6fc5bf1 commit 176d44e
Show file tree
Hide file tree
Showing 11 changed files with 262 additions and 104 deletions.
33 changes: 24 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ A general-purpose interpreted programming language written in Rust. Guaranteed t

# Why the name?

This was inspired by the Lox programming language from [Crafting Interpreters](https://craftinginterpreters.com) by Robert Nystrom.
This is loosely based on the Lox programming language from [Crafting Interpreters](https://craftinginterpreters.com) by Robert Nystrom.

For obvious reasons, I cannot name it Lust, hence Lox + Rust = Lost :)

Expand All @@ -21,25 +21,28 @@ The syntax is fairly straightforward, and is mostly borrowed from existing langu
```rust
fn foo(n) {
if (n == 0) {
print("Zero");
print("zero");
while (n <= 1) {
print(n);
n = n + 1;
}
return true;
} else {
print("Not zero");
print("not zero");
// Post-increment (i++) tends to be ambiguous, both in
// expected behaviour and actual outcome. We're better
// off without using it - the only type of increment
// available is pre-increment (++i).
for (let i = 0; i < n; ++i) {
print(i);
}
return false;
}
}

let n = 5; // All numbers are handled as floating point values
let isZero = foo(n);
print(isZero); // false
print(foo(0)); // true
let n = 5; // All numbers are floating point values
print(foo(n)); // false
print(foo(0)); // true, prints "Zero"

let a; // Uninitialised variables are null by default
print(a); // null
Expand All @@ -48,12 +51,24 @@ print(a); // null
// There are no fields in the class declaration, only methods.
// Self-referencing works with the `this` keyword.
class Vehicle {
// The constructor is just a function
// with the same name as the class
fn Vehicle() {
this.wheels = 0;
}

fn countWheels() {
print(this.wheels);
}
}

let car = Vehicle();
car.wheels = 4;
// Classes can inherit from a parent class with the `<-` operator
class Car <- Vehicle {
fn Car() {
this.wheels = 4;
}
}

let car = Car();
car.countWheels(); // 4
```
2 changes: 1 addition & 1 deletion lost_compile/src/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ impl Env {

pub fn set(&mut self, name: String, value: Type) {
debug!("Set {name} -> {value:?}");
self.values.insert(name, value.clone());
self.values.insert(name, value);
}

pub fn get(&self, name: String) -> Result<Type, Exception> {
Expand Down
20 changes: 15 additions & 5 deletions lost_compile/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ pub enum ErrorMsg {
ExpectedNumber,
ExpectedNumOrStr,
ExpectedIdent,
ExpectedObject,
ExpectedClass,
InvalidCall,
InvalidStrOp,
InvalidCallExpr,
InvalidObject,
TooManyArgs,
TooFewArgs,
GetConstructor,
Expand All @@ -34,9 +35,13 @@ pub enum ErrorMsg {
UndefinedVar,
MisresolvedVar,
UndefinedMember,
UndefinedMethod,
SelfIntialiser,
SelfInherit,
ReturnOutsideFunction,
ThisOutsideMethod,
SuperOutsideMethod,
SuperWithoutParent,
}

impl Display for ErrorMsg {
Expand All @@ -45,19 +50,24 @@ impl Display for ErrorMsg {
Self::ExpectedNumber => "expected numeric operand, found",
Self::ExpectedNumOrStr => "expected both operands to be numeric or string, found",
Self::ExpectedIdent => "expected identifier, found",
Self::ExpectedObject => "expected object, found",
Self::ExpectedClass => "expected class, found",
Self::InvalidCall => "cannot call non-callable value",
Self::InvalidStrOp => "invalid string operation",
Self::InvalidCallExpr => "cannot call non-function value",
Self::InvalidObject => "invalid object, found",
Self::TooManyArgs => "too many arguments in function call",
Self::TooFewArgs => "too few arguments in function call",
Self::GetConstructor => "illegal to get constructor of class",
Self::NoScope => "no scope present to resolve",
Self::UndefinedVar => "undefined variable",
Self::UndefinedMethod => "undefined class method",
Self::MisresolvedVar => "misresolved variable",
Self::UndefinedMember => "undefined object member",
Self::SelfIntialiser => "cannot use self to initialise",
Self::SelfInherit => "cannot inherit self in class",
Self::ReturnOutsideFunction => "cannot return from outside a function",
Self::ThisOutsideMethod => "cannot use `this` variable outside class methods",
Self::ThisOutsideMethod => "cannot use `this` outside class methods",
Self::SuperOutsideMethod => "cannot use `super` outside class methods",
Self::SuperWithoutParent => "cannot use `super` without parent class",
})
}
}
Expand Down
99 changes: 77 additions & 22 deletions lost_compile/src/interpret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ impl Interpreter {
depths: HashMap<Ident, usize>,
) -> Result<(), Exception> {
self.depths.extend(depths);
self.interpret_all(source.items)
self.interpret_all(&source.items)
}

pub fn interpret_all(&mut self, items: Vec<Item>) -> Result<(), Exception> {
pub fn interpret_all(&mut self, items: &[Item]) -> Result<(), Exception> {
items
.into_iter()
.try_for_each(|item| self.interpret_item(item))
.iter()
.try_for_each(|item| self.interpret_item(item.clone()))
}

fn interpret_item(&mut self, item: Item) -> Result<(), Exception> {
Expand All @@ -48,10 +48,14 @@ impl Interpreter {
Item::WhileStmt { condition, body } => self.interpret_while_stmt(condition, *body),
Item::ReturnStmt(expr) => self.interpret_return_stmt(expr),
Item::Block(items) => self
.interpret_block(items, Env::with_parent(Rc::clone(&self.env)))
.interpret_block(&items, Env::with_parent(Rc::clone(&self.env)))
.map(|_| ()),
Item::Function { ident, args, body } => self.interpret_function(ident, args, body),
Item::Class { ident, methods } => self.interpret_class(ident, methods),
Item::Class {
ident,
parent,
methods,
} => self.interpret_class(ident, parent, methods),
}
}

Expand Down Expand Up @@ -92,11 +96,7 @@ impl Interpreter {
Ok(())
}

fn interpret_block(
&mut self,
items: Vec<Item>,
env: Rc<RefCell<Env>>,
) -> Result<(), Exception> {
fn interpret_block(&mut self, items: &[Item], env: Rc<RefCell<Env>>) -> Result<(), Exception> {
let old_env = Rc::clone(&self.env);
self.env = env;
let ret = self.interpret_all(items);
Expand Down Expand Up @@ -125,7 +125,24 @@ impl Interpreter {
}
}

fn interpret_class(&mut self, ident: Ident, methods: Vec<Item>) -> Result<(), Exception> {
fn interpret_class(
&mut self,
ident: Ident,
parent: Option<Ident>,
methods: Vec<Item>,
) -> Result<(), Exception> {
let (old_env, parent_class) = if let Some(p) = parent {
let t @ Type::Class(c) = &self.interpret_ident(p.clone())? else {
return Err(runtime_error(ErrorMsg::ExpectedClass, p.name));
};
// Create a new parent env with the parent class
let old_env = Rc::clone(&self.env);
self.env = Env::with_parent(Rc::clone(&self.env));
self.env.borrow_mut().set("super".to_string(), t.clone());
(Some(old_env), Some(Box::new(c.clone())))
} else {
(None, None)
};
let mut method_map: HashMap<String, Func> = HashMap::default();
for method in methods {
if let Item::Function { ident, args, body } = method {
Expand All @@ -135,10 +152,16 @@ impl Interpreter {
unreachable!("non-functions cannot be passed as methods");
}
}
if let Some(env) = old_env {
// Go back to the old env without `super`
self.env = env;
}

self.env.borrow_mut().set(
ident.name.clone(),
Type::Class(Class {
name: ident.name,
parent: parent_class,
methods: method_map,
}),
);
Expand All @@ -165,6 +188,7 @@ impl Interpreter {
field,
value,
} => self.interpret_field_set(*object, field, *value),
Expr::Super(super_, method) => self.interpret_super(super_, method),
}
}

Expand Down Expand Up @@ -316,7 +340,7 @@ impl Interpreter {
Type::Func(f) => Box::new(f),
Type::NativeFunc(f) => Box::new(f),
Type::Class(c) => Box::new(c),
_ => return Err(runtime_error(ErrorMsg::InvalidCallExpr, ty.to_string())),
_ => return Err(runtime_error(ErrorMsg::InvalidCall, ty.to_string())),
};
// Ensure the number of arguments matches the function definition
match func.arity().cmp(&arg_exprs.len()) {
Expand All @@ -342,14 +366,14 @@ impl Interpreter {
func.call(self, args)
}

pub(crate) fn call(&mut self, func: Func, args: Vec<Type>) -> Result<Type, Exception> {
let env = Env::with_parent(func.env);
pub(crate) fn call(&mut self, func: &Func, args: Vec<Type>) -> Result<Type, Exception> {
let env = Env::with_parent(Rc::clone(&func.env));
// Add the arguments to the function env
for (ident, value) in func.args.into_iter().zip(args) {
env.borrow_mut().set(ident, value);
for (ident, value) in func.args.iter().zip(args) {
env.borrow_mut().set(ident.clone(), value);
}
// If there is no error or return value, return null
let Err(exception) = self.interpret_block(func.body, env) else {
let Err(exception) = self.interpret_block(&func.body, env) else {
return Ok(Type::Null);
};
// If the exception is a return value, propagate
Expand All @@ -364,7 +388,7 @@ impl Interpreter {
fn interpret_field_get(&mut self, object: Expr, field: Ident) -> Result<Type, Exception> {
let ty = self.interpret_expr(object)?;
let Type::Instance(instance) = ty else {
return Err(runtime_error(ErrorMsg::InvalidObject, ty.to_string()));
return Err(runtime_error(ErrorMsg::ExpectedObject, ty.to_string()));
};
instance.get(field.name)
}
Expand All @@ -380,18 +404,49 @@ impl Interpreter {
};
let ty = self.interpret_expr(object.clone())?;
let Type::Instance(mut instance) = ty else {
return Err(runtime_error(ErrorMsg::InvalidObject, ty.to_string()));
return Err(runtime_error(ErrorMsg::ExpectedObject, ty.to_string()));
};
let value = self.interpret_expr(expr)?;
instance.set(field.name, value)?;
self.env.borrow_mut().assign_at_depth(
instance.to_string(),
ident.name.clone(),
Type::Instance(instance),
*self.depths.get(ident).expect("unresolved object"),
)?;
Ok(Type::Null)
}

fn interpret_super(&self, super_: Ident, method: Ident) -> Result<Type, Exception> {
let depth = self
.depths
.get(&super_)
.expect("no `super` variable present");
let Type::Class(parent) = self
.env
.borrow()
.get_at_depth("super".to_string(), *depth)?
else {
unreachable!("`super` cannot be a non-class type")
};
let Type::Instance(this) = self
.env
.borrow()
.get_at_depth("this".to_string(), *depth - 1)?
else {
unreachable!("`this` cannot be a non-instance type")
};
let Some(mut method) = parent.get_method(&method.name) else {
return Err(runtime_error(ErrorMsg::UndefinedMethod, method.name));
};
// Add `this` into a parent env for the function to access
method.env = Env::with_parent(method.env);
method
.env
.borrow_mut()
.set("this".to_string(), Type::Instance(this));
return Ok(Type::Func(method));
}

fn is_eq(&self, left: &Type, right: &Type) -> bool {
match (left, right) {
(Type::Number(m), Type::Number(n)) => m == n,
Expand Down Expand Up @@ -468,7 +523,7 @@ mod tests {
1,
);
assert!(interpreter
.interpret_block(items, Env::with_parent(Rc::clone(&interpreter.env)))
.interpret_block(&items, Env::with_parent(Rc::clone(&interpreter.env)))
.is_ok());
}

Expand Down
Loading

0 comments on commit 176d44e

Please sign in to comment.