Skip to content

Commit

Permalink
Chapter 10: Functions (#7)
Browse files Browse the repository at this point in the history
This patch implements the [chapter on functions](http://craftinginterpreters.com/functions.html).

Instead of using an interface `LoxCallable` and and anonymous instance for native functions
I opted for an enum with the variants `Native` and `User`. Native functions are defined with a
function pointer and user functions are implemented the same way `LoxFunction`s are
implemented in the book.

The biggest difference is that we use `Err` and `Result` to jump back out of a call stack as
we don't have `Exception`s.
  • Loading branch information
jeschkies authored Dec 6, 2019
1 parent d1f8d67 commit 0e10d13
Show file tree
Hide file tree
Showing 11 changed files with 342 additions and 12 deletions.
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ A [Lox](http://craftinginterpreters.com/the-lox-language.html) Interpreter in Ru

Each commit corresponds to one chapter in the book:

* [Chapter 4: Scanning](https://github.com/jeschkies/lox-rs/commit/9fef15e73fdf57a3e428bb074059c7e144e257f7)
* [Chapter 5: Representing Code](https://github.com/jeschkies/lox-rs/commit/0156a95b4bf448dbff9cb4341a2339b741a163ca)
* [Chapter 6: Parsing Expressions](https://github.com/jeschkies/lox-rs/commit/9508c9d887a88540597d314520ae6aa045256e7d)
* [Chapter 7: Evaluating Expressions](https://github.com/jeschkies/lox-rs/commit/fd90ef985c88832c9af6f193e0614e41dd13ef28)
* [Chapter 8: Statements and State](https://github.com/jeschkies/lox-rs/commit/941cbba900acb5876dbe6031b24ef31ff81ca99e)
## A Tree-Walk Interpreter
* [Chapter 4: Scanning](https://github.com/jeschkies/lox-rs/commit/9fef15e73fdf57a3e428bb074059c7e144e257f7)
* [Chapter 5: Representing Code](https://github.com/jeschkies/lox-rs/commit/0156a95b4bf448dbff9cb4341a2339b741a163ca)
* [Chapter 6: Parsing Expressions](https://github.com/jeschkies/lox-rs/commit/9508c9d887a88540597d314520ae6aa045256e7d)
* [Chapter 7: Evaluating Expressions](https://github.com/jeschkies/lox-rs/commit/fd90ef985c88832c9af6f193e0614e41dd13ef28)
* [Chapter 8: Statements and State](https://github.com/jeschkies/lox-rs/commit/941cbba900acb5876dbe6031b24ef31ff81ca99e)
* [Chapter 9: Control Flow](https://github.com/jeschkies/lox-rs/commit/d1f8d67f65fa4d66e24e654fec7bd8d1529b124d)
13 changes: 13 additions & 0 deletions examples/counter.lox
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
fun makeCounter() {
var i = 0;
fun count() {
i = i + 1;
print i;
}

return count;
}

var counter = makeCounter();
counter(); // "1".
counter(); // "2".
8 changes: 8 additions & 0 deletions examples/fibonacci.lox
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
fun fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 2) + fibonacci(n - 1);
}

for (var i = 0; i < 20; i = i + 1) {
print fibonacci(i);
}
5 changes: 5 additions & 0 deletions examples/hello.lox
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
fun sayHi(first, last) {
print "Hi, " + first + " " + last + "!";
}

sayHi("Dear", "Reader");
3 changes: 3 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::convert;
use std::fmt;
use std::io;

use crate::object::Object;
use crate::token::{Token, TokenType};

pub fn error(line: i32, message: &str) {
Expand All @@ -25,6 +26,7 @@ pub fn parser_error(token: &Token, message: &str) {
pub enum Error {
Io(io::Error),
Parse,
Return { value: Object },
Runtime { token: Token, message: String },
}

Expand All @@ -33,6 +35,7 @@ impl fmt::Display for Error {
match self {
Error::Io(underlying) => write!(f, "IoError {}", underlying),
Error::Parse => write!(f, "ParseError"),
Error::Return { value } => write!(f, "Return {:?}", value),
Error::Runtime { message, .. } => write!(f, "RuntimeError {}", message),
}
}
Expand Down
79 changes: 79 additions & 0 deletions src/function.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use crate::env::Environment;
use crate::error::Error;
use crate::interpreter::Interpreter;
use crate::object::Object;
use crate::syntax::Stmt;
use crate::token::Token;

use std::cell::RefCell;
use std::fmt;
use std::rc::Rc;

#[derive(Clone)]
pub enum Function {
// An anonymous implementation of LoxCallable in the book.
Native {
arity: usize,
body: Box<fn(&Vec<Object>) -> Object>,
},

// A LoxFunction in the book.
User {
name: Token,
params: Vec<Token>,
body: Vec<Stmt>,
closure: Rc<RefCell<Environment>>,
},
}

impl Function {
pub fn call(
&self,
interpreter: &mut Interpreter,
arguments: &Vec<Object>,
) -> Result<Object, Error> {
match self {
Function::Native { body, .. } => Ok(body(arguments)),
Function::User { params, body, closure, .. } => {
let mut environment =
Rc::new(RefCell::new(Environment::from(closure)));
for (param, argument) in params.iter().zip(arguments.iter()) {
environment
.borrow_mut()
.define(param.lexeme.clone(), argument.clone());
}
match interpreter.execute_block(body, environment) {
Err(Error::Return { value }) => Ok(value),
Err(other) => Err(other),
Ok(..) => Ok(Object::Null), // We don't have a return statement.
}
}
}
}

pub fn arity(&self) -> usize {
match self {
Function::Native { arity, .. } => *arity,
Function::User { params, .. } => params.len(),
}
}
}

impl fmt::Debug for Function {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Function::Native { .. } => write!(f, "<native func>"),
Function::User { name, .. } => write!(f, "<fn {}>", name.lexeme),
}
}
}

/// This implements the `to_string` aka `toString` from the book.
impl fmt::Display for Function {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Function::Native { .. } => write!(f, "<native func>"),
Function::User { name, .. } => write!(f, "<fn {}>", name.lexeme),
}
}
}
90 changes: 87 additions & 3 deletions src/interpreter.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,38 @@
use crate::env::Environment;
use crate::error::Error;
use crate::function::Function;
use crate::object::Object;
use crate::syntax::{expr, stmt};
use crate::syntax::{Expr, LiteralValue, Stmt};
use crate::token::{Token, TokenType};

use std::cell::RefCell;
use std::rc::Rc;
use std::time::{SystemTime, UNIX_EPOCH};

pub struct Interpreter {
pub globals: Rc<RefCell<Environment>>,
environment: Rc<RefCell<Environment>>,
}

impl Interpreter {
pub fn new() -> Self {
let globals = Rc::new(RefCell::new(Environment::new()));
let clock: Object = Object::Callable(Function::Native {
arity: 0,
body: Box::new(|args: &Vec<Object>| {
Object::Number(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Could not retrieve time.")
.as_millis() as f64,
)
}),
});
globals.borrow_mut().define("clock".to_string(), clock);
Interpreter {
environment: Rc::new(RefCell::new(Environment::new())),
globals: Rc::clone(&globals),
environment: Rc::clone(&globals),
}
}

Expand All @@ -34,7 +51,7 @@ impl Interpreter {
statement.accept(self)
}

fn execute_block(
pub fn execute_block(
&mut self,
statements: &Vec<Stmt>,
environment: Rc<RefCell<Environment>>,
Expand Down Expand Up @@ -66,9 +83,10 @@ impl Interpreter {

fn stringify(&self, object: Object) -> String {
match object {
Object::Boolean(b) => b.to_string(),
Object::Callable(f) => f.to_string(),
Object::Null => "nil".to_string(),
Object::Number(n) => n.to_string(),
Object::Boolean(b) => b.to_string(),
Object::String(s) => s,
}
}
Expand Down Expand Up @@ -153,6 +171,42 @@ impl expr::Visitor<Object> for Interpreter {
}
}

fn visit_call_expr(
&mut self,
callee: &Expr,
paren: &Token,
arguments: &Vec<Expr>,
) -> Result<Object, Error> {
let callee_value = self.evaluate(callee)?;

let argument_values: Result<Vec<Object>, Error> = arguments
.into_iter()
.map(|expr| self.evaluate(expr))
.collect();
let args = argument_values?;

if let Object::Callable(function) = callee_value {
let args_size = args.len();
if args_size != function.arity() {
Err(Error::Runtime {
token: paren.clone(),
message: format!(
"Expected {} arguments but got {}.",
function.arity(),
args_size
),
})
} else {
function.call(self, &args)
}
} else {
Err(Error::Runtime {
token: paren.clone(),
message: "Can only call functions and classes.".to_string(),
})
}
}

fn visit_grouping_expr(&mut self, expr: &Expr) -> Result<Object, Error> {
self.evaluate(expr)
}
Expand Down Expand Up @@ -224,6 +278,24 @@ impl stmt::Visitor<()> for Interpreter {
Ok(())
}

fn visit_function_stmt(
&mut self,
name: &Token,
params: &Vec<Token>,
body: &Vec<Stmt>,
) -> Result<(), Error> {
let function = Function::User {
name: name.clone(),
params: params.clone(),
body: body.clone(),
closure: Rc::clone(&self.environment),
};
self.environment
.borrow_mut()
.define(name.lexeme.clone(), Object::Callable(function));
Ok(())
}

fn visit_if_stmt(
&mut self,
condition: &Expr,
Expand All @@ -246,6 +318,18 @@ impl stmt::Visitor<()> for Interpreter {
Ok(())
}

fn visit_return_stmt(&mut self, keyword: &Token, value: &Option<Expr>) -> Result<(), Error> {
let return_value: Object = value
.as_ref()
.map(|v| self.evaluate(v))
.unwrap_or(Ok(Object::Null))?;

// We use Err to jump back to the top.
Err(Error::Return {
value: return_value,
})
}

fn visit_var_stmt(&mut self, name: &Token, initializer: &Option<Expr>) -> Result<(), Error> {
let value: Object = initializer
.as_ref()
Expand Down
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod env;
mod error;
mod function;
mod interpreter;
mod object;
mod parser;
Expand Down Expand Up @@ -59,6 +60,7 @@ fn main() -> Result<(), Box<dyn std::error::Error + 'static>> {
match args.as_slice() {
[_, file] => match lox.run_file(file) {
Ok(_) => (),
Err(Error::Return { .. }) => unreachable!(),
Err(Error::Runtime { .. }) => exit(70),
Err(Error::Parse) => exit(65),
Err(Error::Io(_)) => unimplemented!(),
Expand Down
3 changes: 3 additions & 0 deletions src/object.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use crate::function::Function;

/// A simple representation of an Lox object akin to a Java `Object`.
#[derive(Debug, Clone)]
pub enum Object {
Boolean(bool),
Callable(Function),
Null,
Number(f64),
String(String),
Expand Down
Loading

0 comments on commit 0e10d13

Please sign in to comment.