remove java implementation, was merely a book copy
This commit is contained in:
parent
175f672dd6
commit
c869360aaa
63 changed files with 4 additions and 2501 deletions
39
src/callable.rs
Normal file
39
src/callable.rs
Normal file
|
@ -0,0 +1,39 @@
|
|||
use crate::{interpreter::Interpreter, value::Value};
|
||||
use core::fmt::Debug;
|
||||
use std::fmt::Display;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum CallingError {
|
||||
#[error("Failed calling: {0}.")]
|
||||
CallFailed(String),
|
||||
#[error("Expected {0} arguments but got {1}.")]
|
||||
ArgumentMismatch(usize, usize),
|
||||
#[error("Failed calling: {0}.")]
|
||||
CallInterpretationFailed(String),
|
||||
}
|
||||
|
||||
/// Implementations can be called.
|
||||
pub trait Callable {
|
||||
fn name(&self) -> String;
|
||||
fn arity(&self) -> usize;
|
||||
fn call(&self, interpreter: &mut Interpreter, args: Vec<Value>) -> Result<Value, CallingError>;
|
||||
}
|
||||
|
||||
impl Debug for dyn Callable {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for dyn Callable {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.name())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for dyn Callable {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.name() == other.name() && self.arity() == other.arity()
|
||||
}
|
||||
}
|
75
src/class.rs
Normal file
75
src/class.rs
Normal file
|
@ -0,0 +1,75 @@
|
|||
use std::{collections::HashMap, fmt::Display};
|
||||
|
||||
use crate::{
|
||||
callable::{Callable, CallingError},
|
||||
function::Function,
|
||||
instance::Instance,
|
||||
value::Value,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Class {
|
||||
pub name: String,
|
||||
pub superclass: Option<Box<Class>>,
|
||||
pub methods: HashMap<String, Function>,
|
||||
}
|
||||
|
||||
impl Display for Class {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.name)
|
||||
}
|
||||
}
|
||||
|
||||
impl Class {
|
||||
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).or_else(|| {
|
||||
if let Some(superclass) = &self.superclass {
|
||||
superclass.find_method(name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Callable for Class {
|
||||
fn name(&self) -> String {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
fn arity(&self) -> usize {
|
||||
if let Some(initializer) = self.find_method("init") {
|
||||
initializer.arity()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn call(
|
||||
&self,
|
||||
interpreter: &mut crate::interpreter::Interpreter,
|
||||
args: Vec<crate::value::Value>,
|
||||
) -> Result<Value, CallingError> {
|
||||
let class = self.clone();
|
||||
let instance = Instance::new(class);
|
||||
if let Some(initializer) = self.find_method("init") {
|
||||
initializer.bind(instance.clone()).call(interpreter, args)?;
|
||||
};
|
||||
|
||||
Ok(Value::Instance(instance))
|
||||
}
|
||||
}
|
29
src/cli.rs
Normal file
29
src/cli.rs
Normal file
|
@ -0,0 +1,29 @@
|
|||
//! Cli interface.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
|
||||
/// Interpreter for the Lox language.
|
||||
#[derive(Parser)]
|
||||
#[command(version, about, long_about = None)]
|
||||
#[command(propagate_version = true)]
|
||||
pub struct Cli {
|
||||
#[command(subcommand)]
|
||||
pub command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
pub struct RunConfig {
|
||||
/// Path to Lox source file.
|
||||
#[arg(short, long)]
|
||||
pub source: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum Commands {
|
||||
/// Run a Lox source file.
|
||||
Run(RunConfig),
|
||||
/// Run a Lox REPL.
|
||||
Repl,
|
||||
}
|
106
src/environment.rs
Normal file
106
src/environment.rs
Normal file
|
@ -0,0 +1,106 @@
|
|||
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{token::Token, value::Value};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum EnvironmentError {
|
||||
#[error("line {0}: undefined variable: {1}")]
|
||||
UndefinedVariable(usize, String),
|
||||
#[error("invalid environment distance")]
|
||||
InvalidDistance,
|
||||
}
|
||||
|
||||
/// Environment mapping variable names to their respective values. Can have an optional enclosing
|
||||
/// environment. The outermost (global) environment has no enclosing one.
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct Environment {
|
||||
values: HashMap<String, Value>,
|
||||
enclosing: Option<Rc<RefCell<Environment>>>,
|
||||
}
|
||||
|
||||
impl Environment {
|
||||
/// Initialize a new environment with an enclosing one.
|
||||
pub fn with_enclosing(enclosing: Rc<RefCell<Environment>>) -> Self {
|
||||
Self {
|
||||
values: HashMap::default(),
|
||||
enclosing: Some(enclosing),
|
||||
}
|
||||
}
|
||||
|
||||
/// Define a new variable binding with a value.
|
||||
///
|
||||
/// Overwrite any binding of the same name.
|
||||
pub fn define(&mut self, name: String, value: Value) {
|
||||
self.values.insert(name, value);
|
||||
}
|
||||
|
||||
/// Assign a new value to an existing (defined) variable. Error if there is no such variable in
|
||||
/// this environment or any of the enclosing ones.
|
||||
pub fn assign(&mut self, token: &Token, value: Value) -> Result<(), EnvironmentError> {
|
||||
if self.values.contains_key(token.lexeme.as_str()) {
|
||||
self.values.insert(token.lexeme.clone(), value);
|
||||
Ok(())
|
||||
} else if let Some(enclosing) = &mut self.enclosing {
|
||||
enclosing.borrow_mut().assign(token, value)
|
||||
} else {
|
||||
Err(EnvironmentError::UndefinedVariable(
|
||||
token.line,
|
||||
token.lexeme.clone(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assign_at(
|
||||
&mut self,
|
||||
distance: usize,
|
||||
token: &Token,
|
||||
value: Value,
|
||||
) -> Result<(), EnvironmentError> {
|
||||
self.ancestor(distance)?
|
||||
.values
|
||||
.insert(token.lexeme.clone(), value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the value of an existing (defined) variable. Error if there is no such variable in
|
||||
/// this environment or any of the enclosing ones.
|
||||
pub fn get(&self, token: &Token) -> Result<Value, EnvironmentError> {
|
||||
if let Some(v) = self.values.get(token.lexeme.as_str()) {
|
||||
Ok(v.clone())
|
||||
} else if let Some(enclosing) = &self.enclosing {
|
||||
enclosing.borrow().get(token)
|
||||
} else {
|
||||
Err(EnvironmentError::UndefinedVariable(
|
||||
token.line,
|
||||
token.lexeme.clone(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_at(&self, distance: usize, token: &Token) -> Result<Value, EnvironmentError> {
|
||||
if let Some(v) = self.ancestor(distance)?.values.get(token.lexeme.as_str()) {
|
||||
Ok(v.clone())
|
||||
} else {
|
||||
Err(EnvironmentError::UndefinedVariable(
|
||||
token.line,
|
||||
token.lexeme.clone(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn ancestor(&self, distance: usize) -> Result<Environment, EnvironmentError> {
|
||||
let mut environment = self.clone();
|
||||
|
||||
for _ in 0..distance {
|
||||
let enclosing = match &environment.enclosing {
|
||||
Some(enc) => enc.borrow().clone(),
|
||||
None => return Err(EnvironmentError::InvalidDistance),
|
||||
};
|
||||
environment = enclosing;
|
||||
}
|
||||
|
||||
Ok(environment)
|
||||
}
|
||||
}
|
54
src/expression.rs
Normal file
54
src/expression.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
use crate::token::{self, Token};
|
||||
|
||||
/// Enumeration of all types of expressions.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Expression {
|
||||
Assign {
|
||||
name: Token,
|
||||
value: Box<Expression>,
|
||||
},
|
||||
Binary {
|
||||
left: Box<Expression>,
|
||||
operator: Token,
|
||||
right: Box<Expression>,
|
||||
},
|
||||
Call {
|
||||
callee: Box<Expression>,
|
||||
paren: Token,
|
||||
args: Vec<Expression>,
|
||||
},
|
||||
Get {
|
||||
object: Box<Expression>,
|
||||
name: Token,
|
||||
},
|
||||
Set {
|
||||
object: Box<Expression>,
|
||||
name: Token,
|
||||
value: Box<Expression>,
|
||||
},
|
||||
This {
|
||||
keyword: Token,
|
||||
},
|
||||
Grouping {
|
||||
expression: Box<Expression>,
|
||||
},
|
||||
Literal {
|
||||
value: token::Literal,
|
||||
},
|
||||
Logical {
|
||||
left: Box<Expression>,
|
||||
operator: Token,
|
||||
right: Box<Expression>,
|
||||
},
|
||||
Unary {
|
||||
operator: Token,
|
||||
right: Box<Expression>,
|
||||
},
|
||||
Variable {
|
||||
name: Token,
|
||||
},
|
||||
Super {
|
||||
keyword: Token,
|
||||
method: Token,
|
||||
},
|
||||
}
|
77
src/function.rs
Normal file
77
src/function.rs
Normal file
|
@ -0,0 +1,77 @@
|
|||
use crate::{
|
||||
callable::{Callable, CallingError},
|
||||
environment::Environment,
|
||||
instance::Instance,
|
||||
interpreter::Interpreter,
|
||||
statement::Statement,
|
||||
token::Token,
|
||||
value::Value,
|
||||
};
|
||||
use core::fmt::Debug;
|
||||
use std::{cell::RefCell, fmt::Display, rc::Rc};
|
||||
|
||||
/// A lox function.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Function {
|
||||
pub name: Token,
|
||||
pub is_initializer: bool,
|
||||
pub params: Vec<Token>,
|
||||
pub body: Vec<Statement>,
|
||||
pub closure: Rc<RefCell<Environment>>,
|
||||
}
|
||||
|
||||
impl Display for Function {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.name.lexeme)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Function {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.name == other.name && self.params == other.params && self.body == other.body
|
||||
}
|
||||
}
|
||||
|
||||
impl Callable for Function {
|
||||
fn name(&self) -> String {
|
||||
self.name.lexeme.clone()
|
||||
}
|
||||
|
||||
fn arity(&self) -> usize {
|
||||
self.params.len()
|
||||
}
|
||||
|
||||
fn call(&self, interpreter: &mut Interpreter, args: Vec<Value>) -> Result<Value, CallingError> {
|
||||
let mut env = Environment::with_enclosing(self.closure.clone());
|
||||
for (i, param) in self.params.iter().enumerate() {
|
||||
let arg = args
|
||||
.get(i)
|
||||
.ok_or(CallingError::ArgumentMismatch(
|
||||
self.params.len(),
|
||||
args.len(),
|
||||
))?
|
||||
.clone();
|
||||
env.define(param.lexeme.clone(), arg);
|
||||
}
|
||||
|
||||
Ok(interpreter
|
||||
.block(&self.body, env)
|
||||
.map_err(|e| CallingError::CallInterpretationFailed(e.to_string()))?
|
||||
.unwrap_or(Value::Nil))
|
||||
}
|
||||
}
|
||||
|
||||
impl Function {
|
||||
pub fn bind(&self, instance: Rc<RefCell<Instance>>) -> Self {
|
||||
let mut env = Environment::with_enclosing(self.closure.clone());
|
||||
env.define("this".to_string(), Value::Instance(instance));
|
||||
|
||||
Self {
|
||||
name: self.name.clone(),
|
||||
is_initializer: self.is_initializer,
|
||||
params: self.params.clone(),
|
||||
body: self.body.clone(),
|
||||
closure: Rc::new(RefCell::new(env)),
|
||||
}
|
||||
}
|
||||
}
|
56
src/instance.rs
Normal file
56
src/instance.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
use std::{cell::RefCell, collections::HashMap, fmt::Display, rc::Rc};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{
|
||||
class::Class,
|
||||
token::Token,
|
||||
value::{CallableType, Value},
|
||||
};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum InstanceError {
|
||||
#[error("line {0}: no property named {1}.")]
|
||||
NoSuchProperty(usize, String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Instance {
|
||||
class: Class,
|
||||
fields: HashMap<String, Value>,
|
||||
}
|
||||
|
||||
impl Display for Instance {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{} instance", self.class.name)
|
||||
}
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
pub fn new(class: Class) -> Rc<RefCell<Self>> {
|
||||
Rc::new(RefCell::new(Self {
|
||||
class,
|
||||
fields: HashMap::default(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn get(&self, name: &Token, instance: Rc<RefCell<Self>>) -> Result<Value, InstanceError> {
|
||||
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)),
|
||||
CallableType::Function,
|
||||
)))
|
||||
} else {
|
||||
Err(InstanceError::NoSuchProperty(
|
||||
name.line,
|
||||
name.lexeme.clone(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set(&mut self, name: &Token, value: Value) {
|
||||
self.fields.insert(name.lexeme.clone(), value);
|
||||
}
|
||||
}
|
614
src/interpreter.rs
Normal file
614
src/interpreter.rs
Normal file
|
@ -0,0 +1,614 @@
|
|||
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||
|
||||
use ordered_float::OrderedFloat;
|
||||
use thiserror::Error;
|
||||
use tracing::error;
|
||||
|
||||
use crate::{
|
||||
callable::CallingError,
|
||||
class::Class,
|
||||
environment::{Environment, EnvironmentError},
|
||||
expression::Expression,
|
||||
function::{self, Function},
|
||||
native_functions,
|
||||
statement::Statement,
|
||||
token::{Literal, Token, TokenType},
|
||||
value::{CallableType, Value},
|
||||
};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum InterpreterError {
|
||||
#[error("line {0}: MINUS unary expression expects a number on the right")]
|
||||
UnaryExpressionNotANumber(usize),
|
||||
#[error("line {0}: unknown unary operator: {1}")]
|
||||
UnaryOperatorUnknown(usize, String),
|
||||
#[error("line {0}: unknown binary operator: {1}")]
|
||||
BinaryOperatorUnknown(usize, String),
|
||||
#[error("line {0}: left or right is not a number.")]
|
||||
BinaryExpressionNeedsNumber(usize),
|
||||
#[error("line {0}: left or right is neither a number nor string.")]
|
||||
BinaryExpressionNeedsNumberOrString(usize),
|
||||
#[error("{0}")]
|
||||
UndefinedVariable(EnvironmentError),
|
||||
#[error("line {0}: {1} is not callable.")]
|
||||
NotACallable(usize, Value),
|
||||
#[error("line {0}: {1}.")]
|
||||
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.
|
||||
#[derive(Debug)]
|
||||
pub struct Interpreter {
|
||||
pub globals: Rc<RefCell<Environment>>,
|
||||
environment: Rc<RefCell<Environment>>,
|
||||
locals: HashMap<Expression, usize>,
|
||||
}
|
||||
|
||||
/// Default configuration for the interpreter, with builtin native functions.
|
||||
impl Default for Interpreter {
|
||||
fn default() -> Self {
|
||||
let env: Rc<RefCell<Environment>> = Default::default();
|
||||
|
||||
{
|
||||
// add all native functions to global environment
|
||||
let mut env = env.borrow_mut();
|
||||
for (name, fun) in native_functions::all() {
|
||||
env.define(name, fun);
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
globals: env.clone(),
|
||||
environment: env.clone(),
|
||||
locals: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Interpreter {
|
||||
/// Try to evaluate an expression and return its result.
|
||||
pub fn run(&mut self, statements: Vec<Statement>) -> Result<(), InterpreterError> {
|
||||
for stmt in statements {
|
||||
match self.execute(&stmt) {
|
||||
Ok(_) => {}
|
||||
Err(e) => error!("{e}"),
|
||||
};
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn resolve(&mut self, expression: Expression, depth: usize) {
|
||||
self.locals.insert(expression, depth);
|
||||
}
|
||||
|
||||
///Execute a statement.
|
||||
fn execute(&mut self, statement: &Statement) -> Result<Option<Value>, InterpreterError> {
|
||||
match statement {
|
||||
Statement::Block(statements) => {
|
||||
let sub_env = Environment::with_enclosing(self.environment.clone());
|
||||
self.block(statements, sub_env)
|
||||
}
|
||||
Statement::Print(expression) => self.print_statement(expression),
|
||||
Statement::Expression(expression) => Ok(Some(self.evaluate(expression)?)),
|
||||
Statement::Var { name, initializer } => {
|
||||
self.var_statement(name, initializer.as_ref().as_ref())
|
||||
}
|
||||
Statement::If {
|
||||
condition,
|
||||
then_branch,
|
||||
else_branch,
|
||||
} => {
|
||||
let else_branch = else_branch.as_ref().map(|x| *x.clone());
|
||||
self.if_statement(condition, then_branch, else_branch.as_ref())
|
||||
}
|
||||
Statement::While { condition, body } => self.while_statement(condition, body),
|
||||
Statement::Function { name, params, body } => {
|
||||
self.function_statement(name, params, body)
|
||||
}
|
||||
Statement::Return { keyword: _, value } => self.return_statement(value),
|
||||
Statement::Class {
|
||||
name,
|
||||
superclass,
|
||||
methods,
|
||||
} => self.class(name, superclass, methods),
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute all statements within a block, using a new environment (with the old one as the
|
||||
/// enclosing one). Immediately return the value if a Return Value is encountered after
|
||||
/// execution of a statement.
|
||||
pub fn block(
|
||||
&mut self,
|
||||
statements: &[Statement],
|
||||
environment: Environment,
|
||||
) -> Result<Option<Value>, InterpreterError> {
|
||||
let prev_env = self.environment.clone();
|
||||
self.environment = Rc::new(RefCell::new(environment));
|
||||
|
||||
for stmt in statements {
|
||||
let execution = self.execute(stmt);
|
||||
match execution {
|
||||
Ok(x) => {
|
||||
if let Some(Value::Return(x)) = x {
|
||||
self.environment = prev_env.clone();
|
||||
return Ok(Some(*x));
|
||||
}
|
||||
}
|
||||
Err(e) => error!("{e}"),
|
||||
}
|
||||
}
|
||||
|
||||
self.environment = prev_env.clone();
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
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 {
|
||||
if let Statement::Function { name, params, body } = method {
|
||||
let function = function::Function {
|
||||
name: name.clone(),
|
||||
is_initializer: name.lexeme == "init",
|
||||
params: params.clone(),
|
||||
body: body.clone(),
|
||||
closure: self.environment.clone(),
|
||||
};
|
||||
class_methods.insert(name.lexeme.clone(), function);
|
||||
}
|
||||
}
|
||||
|
||||
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.clone()), CallableType::Class(class))),
|
||||
)
|
||||
.map_err(InterpreterError::UndefinedVariable)?;
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Evaluate an expression and return its value.
|
||||
fn evaluate(&mut self, expression: &Expression) -> Result<Value, InterpreterError> {
|
||||
match expression {
|
||||
Expression::Literal { value } => self.literal(value.clone()),
|
||||
Expression::Grouping { expression } => self.grouping(expression),
|
||||
Expression::Unary {
|
||||
operator: op,
|
||||
right,
|
||||
} => self.unary(op, right),
|
||||
Expression::Binary {
|
||||
left,
|
||||
operator,
|
||||
right,
|
||||
} => self.binary(left, operator, right),
|
||||
Expression::Variable { name } => self.var_expression(name, expression),
|
||||
Expression::Assign { name, value } => self.assign(name, value),
|
||||
Expression::Logical {
|
||||
left,
|
||||
operator,
|
||||
right,
|
||||
} => self.logical_expression(left, operator, right),
|
||||
Expression::Call {
|
||||
callee,
|
||||
paren,
|
||||
args,
|
||||
} => self.call(callee, paren, args),
|
||||
Expression::Get { object, name } => self.get(object, name),
|
||||
Expression::Set {
|
||||
object,
|
||||
name,
|
||||
value,
|
||||
} => self.set(object, name, value),
|
||||
Expression::This { keyword } => self.lookup_var(keyword, expression),
|
||||
Expression::Super { keyword, method } => self.super_expr(expression, keyword, method),
|
||||
}
|
||||
}
|
||||
|
||||
/// Call a callable if it is one (meaning it starts with a LeftParen after an identifier),
|
||||
/// otherwise evaluate the expression.
|
||||
fn call(
|
||||
&mut self,
|
||||
callee: &Expression,
|
||||
paren: &Token,
|
||||
arg_expressions: &[Expression],
|
||||
) -> Result<Value, InterpreterError> {
|
||||
let callee = self.evaluate(callee)?;
|
||||
let mut args = Vec::new();
|
||||
|
||||
for arg in arg_expressions {
|
||||
args.push(self.evaluate(arg)?);
|
||||
}
|
||||
|
||||
if let Value::Callable((f, _)) = callee {
|
||||
f.call(self, args)
|
||||
.map_err(|e| InterpreterError::FailedToCall(paren.line, e))
|
||||
} else {
|
||||
Err(InterpreterError::NotACallable(paren.line, callee))
|
||||
}
|
||||
}
|
||||
|
||||
fn get(&mut self, object: &Expression, name: &Token) -> Result<Value, InterpreterError> {
|
||||
match self.evaluate(object)? {
|
||||
Value::Instance(instance) => Ok(instance.borrow().get(name, instance.clone())?),
|
||||
_ => Err(InterpreterError::OnlyInstancesHaveFields(name.line)),
|
||||
}
|
||||
}
|
||||
|
||||
fn set(
|
||||
&mut self,
|
||||
object: &Expression,
|
||||
name: &Token,
|
||||
value: &Expression,
|
||||
) -> Result<Value, InterpreterError> {
|
||||
match self.evaluate(object)? {
|
||||
Value::Instance(instance) => {
|
||||
let value = self.evaluate(value)?;
|
||||
instance.borrow_mut().set(name, value.clone());
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
_ => Err(InterpreterError::OnlyInstancesHaveFields(name.line)),
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
name: &Token,
|
||||
params: &[Token],
|
||||
body: &[Statement],
|
||||
) -> Result<Option<Value>, InterpreterError> {
|
||||
let fn_name = name.lexeme.clone();
|
||||
let fun = Function {
|
||||
name: name.clone(),
|
||||
is_initializer: false,
|
||||
params: params.to_vec(),
|
||||
body: body.to_vec(),
|
||||
closure: self.environment.clone(),
|
||||
};
|
||||
|
||||
self.environment.borrow_mut().define(
|
||||
fn_name,
|
||||
Value::Callable((Rc::new(fun), CallableType::Function)),
|
||||
);
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// If the condition evaluates to truthy, execute the then branch, otherwise the else branch.
|
||||
fn if_statement(
|
||||
&mut self,
|
||||
condition: &Expression,
|
||||
then_branch: &Statement,
|
||||
else_branch: Option<&Statement>,
|
||||
) -> Result<Option<Value>, InterpreterError> {
|
||||
if self.evaluate(condition)?.is_truthy() {
|
||||
self.execute(then_branch)
|
||||
} else if let Some(else_branch) = else_branch {
|
||||
self.execute(else_branch)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate an expression and print its value to stdout.
|
||||
fn print_statement(
|
||||
&mut self,
|
||||
expression: &Expression,
|
||||
) -> Result<Option<Value>, InterpreterError> {
|
||||
let value = self.evaluate(expression)?;
|
||||
println!("{value}");
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Return a value from a statement.
|
||||
fn return_statement(
|
||||
&mut self,
|
||||
value: &Option<Expression>,
|
||||
) -> Result<Option<Value>, InterpreterError> {
|
||||
Ok(match value {
|
||||
Some(x) => Some(Value::Return(Box::new(self.evaluate(x)?))),
|
||||
None => Some(Value::Return(Box::new(Value::Nil))),
|
||||
})
|
||||
}
|
||||
|
||||
/// Initialize a variable with an initializer expression or nil.
|
||||
fn var_statement(
|
||||
&mut self,
|
||||
name: &Token,
|
||||
initializer: Option<&Expression>,
|
||||
) -> Result<Option<Value>, InterpreterError> {
|
||||
let value = if let Some(initializer) = initializer {
|
||||
self.evaluate(initializer)
|
||||
} else {
|
||||
Ok(Value::Nil)
|
||||
}?;
|
||||
|
||||
self.environment
|
||||
.borrow_mut()
|
||||
.define(name.lexeme.clone(), value);
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Execute the body as long as the condition evaluates to true.
|
||||
fn while_statement(
|
||||
&mut self,
|
||||
condition: &Expression,
|
||||
body: &Statement,
|
||||
) -> Result<Option<Value>, InterpreterError> {
|
||||
while self.evaluate(condition)?.is_truthy() {
|
||||
self.execute(body)?;
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Assign the value of an expression to a variable.
|
||||
fn assign(&mut self, name: &Token, expression: &Expression) -> Result<Value, InterpreterError> {
|
||||
let value = self.evaluate(expression)?;
|
||||
|
||||
self.environment
|
||||
.borrow_mut()
|
||||
.assign(name, value.clone())
|
||||
.map_err(InterpreterError::UndefinedVariable)?;
|
||||
|
||||
if let Some(distance) = self.locals.get(expression) {
|
||||
self.environment
|
||||
.borrow_mut()
|
||||
.assign_at(*distance, name, value.clone())
|
||||
.map_err(InterpreterError::UndefinedVariable)?;
|
||||
} else {
|
||||
self.globals
|
||||
.borrow_mut()
|
||||
.assign(name, value.clone())
|
||||
.map_err(InterpreterError::UndefinedVariable)?;
|
||||
}
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// Convert the literal value into a Value.
|
||||
fn literal(&self, literal: Literal) -> Result<Value, InterpreterError> {
|
||||
Ok(literal.into())
|
||||
}
|
||||
|
||||
/// Evaluate left and if the operator is Or and left is truthy or the operator is not Or and
|
||||
/// not leftis truthy short circuit and return left. Otherwise evaluate and return right.
|
||||
fn logical_expression(
|
||||
&mut self,
|
||||
left: &Expression,
|
||||
operator: &Token,
|
||||
right: &Expression,
|
||||
) -> Result<Value, InterpreterError> {
|
||||
let left = self.evaluate(left)?;
|
||||
|
||||
let truthy = if operator.token_type == TokenType::Or {
|
||||
left.is_truthy()
|
||||
} else {
|
||||
!left.is_truthy()
|
||||
};
|
||||
|
||||
if truthy {
|
||||
Ok(left)
|
||||
} else {
|
||||
self.evaluate(right)
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate the inner expression.
|
||||
fn grouping(&mut self, inner: &Expression) -> Result<Value, InterpreterError> {
|
||||
self.evaluate(inner)
|
||||
}
|
||||
|
||||
/// Evaluate the expression on the right and use its result when evaluating the unary operator.
|
||||
fn unary(&mut self, op: &Token, right: &Expression) -> Result<Value, InterpreterError> {
|
||||
let right = self.evaluate(right)?;
|
||||
|
||||
match op.token_type {
|
||||
TokenType::Minus => {
|
||||
if let Value::Number(val) = right {
|
||||
Ok(Value::Number(-val))
|
||||
} else {
|
||||
Err(InterpreterError::UnaryExpressionNotANumber(op.line))
|
||||
}
|
||||
}
|
||||
TokenType::Bang => Ok(Value::Boolean(!right.is_truthy())),
|
||||
_ => Err(InterpreterError::UnaryOperatorUnknown(
|
||||
op.line,
|
||||
op.lexeme.clone(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the value of a variable.
|
||||
fn var_expression(
|
||||
&mut self,
|
||||
name: &Token,
|
||||
expression: &Expression,
|
||||
) -> Result<Value, InterpreterError> {
|
||||
self.lookup_var(name, expression)
|
||||
}
|
||||
|
||||
fn lookup_var(
|
||||
&mut self,
|
||||
name: &Token,
|
||||
expression: &Expression,
|
||||
) -> Result<Value, InterpreterError> {
|
||||
if let Some(distance) = self.locals.get(expression) {
|
||||
self.environment
|
||||
.borrow()
|
||||
.get_at(*distance, name)
|
||||
.map_err(InterpreterError::UndefinedVariable)
|
||||
} else {
|
||||
self.globals
|
||||
.borrow()
|
||||
.get(name)
|
||||
.map_err(InterpreterError::UndefinedVariable)
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate number operations.
|
||||
fn number_op(&self, left: f64, op: TokenType, right: f64) -> f64 {
|
||||
match op {
|
||||
TokenType::Minus => left - right,
|
||||
TokenType::Plus => left + right,
|
||||
TokenType::Slash => left / right,
|
||||
TokenType::Star => left * right,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate boolean operations.
|
||||
fn boolean_op(&self, left: f64, op: TokenType, right: f64) -> bool {
|
||||
match op {
|
||||
TokenType::Greater => left > right,
|
||||
TokenType::GreaterEqual => left >= right,
|
||||
TokenType::Less => left < right,
|
||||
TokenType::LessEqual => left <= right,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate the left and right expressions (in that order) and then combine them with the
|
||||
/// specified operator.
|
||||
fn binary(
|
||||
&mut self,
|
||||
left: &Expression,
|
||||
op: &Token,
|
||||
right: &Expression,
|
||||
) -> Result<Value, InterpreterError> {
|
||||
let left = self.evaluate(left)?;
|
||||
let right = self.evaluate(right)?;
|
||||
|
||||
match op.token_type {
|
||||
TokenType::Minus | TokenType::Slash | TokenType::Star | TokenType::Plus => {
|
||||
if let (Value::Number(left), Value::Number(right)) = (left.clone(), right.clone()) {
|
||||
Ok(Value::Number(OrderedFloat(self.number_op(
|
||||
*left,
|
||||
op.token_type,
|
||||
*right,
|
||||
))))
|
||||
} else if let (Value::String(left), Value::String(right)) = (left, right) {
|
||||
Ok(Value::String(format!("{}{}", left.clone(), right.clone())))
|
||||
} else {
|
||||
Err(InterpreterError::BinaryExpressionNeedsNumberOrString(
|
||||
op.line,
|
||||
))
|
||||
}
|
||||
}
|
||||
TokenType::Greater
|
||||
| TokenType::GreaterEqual
|
||||
| TokenType::Less
|
||||
| TokenType::LessEqual => {
|
||||
if let (Value::Number(left), Value::Number(right)) = (left, right) {
|
||||
Ok(Value::Boolean(self.boolean_op(
|
||||
*left,
|
||||
op.token_type,
|
||||
*right,
|
||||
)))
|
||||
} else {
|
||||
Err(InterpreterError::BinaryExpressionNeedsNumber(op.line))
|
||||
}
|
||||
}
|
||||
TokenType::BangEqual => Ok(Value::Boolean(left != right)),
|
||||
TokenType::EqualEqual => Ok(Value::Boolean(left == right)),
|
||||
_ => Err(InterpreterError::BinaryOperatorUnknown(
|
||||
op.line,
|
||||
op.lexeme.clone(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
26
src/keywords.rs
Normal file
26
src/keywords.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
use crate::token::TokenType::{self, *};
|
||||
use lazy_static::lazy_static;
|
||||
use std::collections::HashMap;
|
||||
|
||||
lazy_static! {
|
||||
/// Mapping of reserved keywords to their respective TokenType.
|
||||
pub static ref KEYWORDS: HashMap<std::string::String, TokenType> = {
|
||||
let mut m = HashMap::new();
|
||||
m.insert("and".into(), And);
|
||||
m.insert("class".into(), Class);
|
||||
m.insert("else".into(), Else);
|
||||
m.insert("false".into(), False);
|
||||
m.insert("for".into(), For);
|
||||
m.insert("fun".into(), Fun);
|
||||
m.insert("if".into(), If);
|
||||
m.insert("nil".into(), Nil);
|
||||
m.insert("or".into(), Or);
|
||||
m.insert("print".into(), Print);
|
||||
m.insert("return".into(), Return);
|
||||
m.insert("super".into(), Super);
|
||||
m.insert("true".into(), True);
|
||||
m.insert("var".into(), Var);
|
||||
m.insert("while".into(), While);
|
||||
m
|
||||
};
|
||||
}
|
97
src/lib.rs
Normal file
97
src/lib.rs
Normal file
|
@ -0,0 +1,97 @@
|
|||
//! Interpret the Lox language. Either compile (interpret for now though) some source code or run a
|
||||
//! REPL.
|
||||
|
||||
use std::{
|
||||
fs::{self},
|
||||
io::{self, Write},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use interpreter::{Interpreter, InterpreterError};
|
||||
use parser::ParserError;
|
||||
use resolver::{Resolver, ResolverError};
|
||||
use thiserror::Error;
|
||||
use tracing::error;
|
||||
|
||||
pub mod callable;
|
||||
pub mod class;
|
||||
pub mod cli;
|
||||
pub mod environment;
|
||||
pub mod expression;
|
||||
pub mod function;
|
||||
pub mod instance;
|
||||
pub mod interpreter;
|
||||
pub mod keywords;
|
||||
pub mod native_functions;
|
||||
pub mod parser;
|
||||
pub mod resolver;
|
||||
pub mod scanner;
|
||||
pub mod statement;
|
||||
pub mod token;
|
||||
pub mod tokenizer {
|
||||
pub mod comment;
|
||||
pub mod identifier;
|
||||
pub mod interface;
|
||||
pub mod lookahead;
|
||||
pub mod newline;
|
||||
pub mod number;
|
||||
pub mod single_char;
|
||||
pub mod string;
|
||||
pub mod whitespace;
|
||||
}
|
||||
pub mod value;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum RoxError {
|
||||
#[error("parser failed: {0}")]
|
||||
ParseError(#[from] ParserError),
|
||||
#[error("resolver failed: {0}")]
|
||||
ResolverError(#[from] ResolverError),
|
||||
#[error("interpreter failed: {0}")]
|
||||
InterpreterError(#[from] InterpreterError),
|
||||
}
|
||||
|
||||
/// Read the source code in a file and scan it to tokens.
|
||||
pub fn compile(source: &Path) -> Result<(), io::Error> {
|
||||
let input = fs::read_to_string(source)?;
|
||||
let mut interpreter = Interpreter::default();
|
||||
|
||||
run(&input, &mut interpreter);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run a Lox REPL until SIGINT.
|
||||
pub fn repl() {
|
||||
let mut interpreter = Interpreter::default();
|
||||
loop {
|
||||
print!("> ");
|
||||
let _ = io::stdout().flush();
|
||||
|
||||
let mut input = String::new();
|
||||
match io::stdin().read_line(&mut input) {
|
||||
Ok(_) => {}
|
||||
Err(e) => error!("{}", e),
|
||||
}
|
||||
let input = input.trim().to_string();
|
||||
run(&input, &mut interpreter);
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate a Lox input string and print errors or output.
|
||||
fn run(input: &str, interpreter: &mut Interpreter) {
|
||||
if let Err(e) = run_rox(input, interpreter) {
|
||||
error!("{e}");
|
||||
}
|
||||
}
|
||||
|
||||
fn run_rox(input: &str, interpreter: &mut Interpreter) -> Result<(), RoxError> {
|
||||
let tokens = scanner::tokenize(input);
|
||||
let mut resolver = Resolver::new(interpreter);
|
||||
|
||||
let ast = parser::ast(tokens)?;
|
||||
resolver.resolve(&ast)?;
|
||||
interpreter.run(ast)?;
|
||||
|
||||
Ok(())
|
||||
}
|
38
src/main.rs
Normal file
38
src/main.rs
Normal file
|
@ -0,0 +1,38 @@
|
|||
use std::process::exit;
|
||||
|
||||
use clap::Parser;
|
||||
use rox::cli::{Cli, Commands};
|
||||
use tracing::error;
|
||||
|
||||
/// Cli entrypoint.
|
||||
fn main() {
|
||||
if std::env::var_os("RUST_LOG").is_none() {
|
||||
std::env::set_var("RUST_LOG", "info");
|
||||
}
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let cli = Cli::parse();
|
||||
|
||||
match &cli.command {
|
||||
Commands::Run(compile_config) => {
|
||||
if !&compile_config.source.exists() {
|
||||
error!(
|
||||
"{} does not exist",
|
||||
&compile_config.source.to_string_lossy()
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if let Err(e) = rox::compile(&compile_config.source) {
|
||||
error!(
|
||||
"failed to compile {}: {}",
|
||||
&compile_config.source.to_string_lossy(),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
Commands::Repl => {
|
||||
rox::repl();
|
||||
}
|
||||
}
|
||||
}
|
44
src/native_functions.rs
Normal file
44
src/native_functions.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
use std::{
|
||||
rc::Rc,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use ordered_float::OrderedFloat;
|
||||
|
||||
use crate::{
|
||||
callable::{Callable, CallingError},
|
||||
interpreter::Interpreter,
|
||||
value::{CallableType, Value},
|
||||
};
|
||||
|
||||
struct Clock;
|
||||
|
||||
impl Callable for Clock {
|
||||
fn name(&self) -> String {
|
||||
"clock".into()
|
||||
}
|
||||
|
||||
fn arity(&self) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
fn call(
|
||||
&self,
|
||||
_interpreter: &mut Interpreter,
|
||||
_args: Vec<crate::value::Value>,
|
||||
) -> Result<Value, CallingError> {
|
||||
let start = SystemTime::now();
|
||||
let since_the_epoch = start
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map_err(|e| CallingError::CallFailed(e.to_string()))?;
|
||||
|
||||
Ok(Value::Number(OrderedFloat(since_the_epoch.as_secs_f64())))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all() -> Vec<(String, Value)> {
|
||||
vec![(
|
||||
"clock".into(),
|
||||
Value::Callable((Rc::new(Clock {}), CallableType::Function)),
|
||||
)]
|
||||
}
|
662
src/parser.rs
Normal file
662
src/parser.rs
Normal file
|
@ -0,0 +1,662 @@
|
|||
use crate::{
|
||||
expression::Expression,
|
||||
statement::Statement,
|
||||
token::{
|
||||
self, Literal, Token,
|
||||
TokenType::{self, *},
|
||||
},
|
||||
};
|
||||
use thiserror::Error;
|
||||
use tracing::error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ParserError {
|
||||
#[error("empty token stream")]
|
||||
NoTokens,
|
||||
#[error("line {0}: expected expression")]
|
||||
ExpressionExpected(usize),
|
||||
#[error("line {0}: expected ')' after expression.")]
|
||||
ParenAfterExpression(usize),
|
||||
#[error("Out of bounds access at index {0}.")]
|
||||
OutOfBoundsAccess(usize),
|
||||
#[error("line {0}: literal expected.")]
|
||||
LiteralExpected(usize),
|
||||
#[error("line {0}: expected ';' after value.")]
|
||||
SemicolonAfterValueExpected(usize),
|
||||
#[error("line {0}: expected ';' after expression.")]
|
||||
SemicolonAfterExpressionExpected(usize),
|
||||
#[error("line {0}: expected class name.")]
|
||||
ClassNameExpected(usize),
|
||||
#[error("line {0}: expected variable name.")]
|
||||
VariableNameExpected(usize),
|
||||
#[error("line {0}: invalid assignment target.")]
|
||||
InvalidAssignmentTarget(usize),
|
||||
#[error("line {0}: expected '}}' after block.")]
|
||||
RightBraceAfterBlockExpected(usize),
|
||||
#[error("line {0}: expected '(' after if.")]
|
||||
LeftParenAfterIfExpected(usize),
|
||||
#[error("line {0}: expected ')' after condition.")]
|
||||
RightParenAfterConditionExpected(usize),
|
||||
#[error("line {0}: expected '(' after while.")]
|
||||
LeftParenAfterWhileExpected(usize),
|
||||
#[error("line {0}: expected '(' after for.")]
|
||||
LeftParenAfterForExpected(usize),
|
||||
#[error("line {0}: expected ';' after loop condition.")]
|
||||
SemicolonAfterLoopConditionExpected(usize),
|
||||
#[error("line {0}: expected ')' after for clauses.")]
|
||||
RightParenAfterForClausesExpected(usize),
|
||||
#[error("line {0}: expected ')' after arguments.")]
|
||||
RightParenAfterArgumentsExpected(usize),
|
||||
#[error("line {0}: expected function name.")]
|
||||
FunctionNameExpected(usize),
|
||||
#[error("line {0}: expected '(' after function name.")]
|
||||
LeftParenAfterFunctionNameExpected(usize),
|
||||
#[error("line {0}: expected ')' after parameters.")]
|
||||
RightParenAfterParamsExpected(usize),
|
||||
#[error("line {0}: expected parameter name.")]
|
||||
ParamNameExpected(usize),
|
||||
#[error("line {0}: expected '{{' before function body.")]
|
||||
LeftBraceBeforeFunctionBodyExpected(usize),
|
||||
#[error("line {0}: expected ';' after return value.")]
|
||||
SemicolonAfterReturnExpected(usize),
|
||||
#[error("line {0}: expected '{{' before class body.")]
|
||||
LeftBraceBeforeClassExpected(usize),
|
||||
#[error("line {0}: expected '}}' after class body.")]
|
||||
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.
|
||||
#[derive(Debug, Clone)]
|
||||
struct Parser {
|
||||
current: usize,
|
||||
current_token: Token,
|
||||
tokens: Vec<Token>,
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
/// Create a new parser instance, fail if the tokens vector is empty.
|
||||
fn new(tokens: Vec<Token>) -> Result<Self, ParserError> {
|
||||
let current_token = tokens.first().ok_or(ParserError::NoTokens)?.clone();
|
||||
|
||||
Ok(Self {
|
||||
current: 0,
|
||||
current_token,
|
||||
tokens,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse all tokens to a list of statements for execution.
|
||||
fn run(&mut self) -> Result<Vec<Statement>, ParserError> {
|
||||
let mut statements = Vec::new();
|
||||
|
||||
while !self.is_at_end() {
|
||||
match self.declaration() {
|
||||
Ok(x) => statements.push(x),
|
||||
Err(e) => {
|
||||
error!("{e}");
|
||||
self.synchronize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(statements)
|
||||
}
|
||||
|
||||
/// Check if any of the provided types match the type of the current token.
|
||||
///
|
||||
/// If so, advance the current token.
|
||||
fn matches(&mut self, types: &[TokenType]) -> bool {
|
||||
let matches = types.iter().any(|x| self.check(x));
|
||||
matches.then(|| self.advance());
|
||||
matches
|
||||
}
|
||||
|
||||
/// Return true if the current token type matches the match_type, false otherwise.
|
||||
fn check(&self, match_type: &TokenType) -> bool {
|
||||
self.current_token.token_type == *match_type
|
||||
}
|
||||
|
||||
/// Advance the current token if we have not hit Eof yet.
|
||||
///
|
||||
/// Return the token before the advancement.
|
||||
fn advance(&mut self) -> Result<&Token, ParserError> {
|
||||
if !self.is_at_end() {
|
||||
self.current += 1;
|
||||
self.current_token = self
|
||||
.tokens
|
||||
.get(self.current)
|
||||
.ok_or(ParserError::OutOfBoundsAccess(self.current))?
|
||||
.clone();
|
||||
}
|
||||
|
||||
self.previous()
|
||||
}
|
||||
|
||||
/// Return true if the current token is Eof, false otherwise.
|
||||
fn is_at_end(&self) -> bool {
|
||||
self.current_token.token_type == Eof
|
||||
}
|
||||
|
||||
/// Return the token before the current one or an error if there is none.
|
||||
fn previous(&self) -> Result<&Token, ParserError> {
|
||||
self.tokens
|
||||
.get(self.current - 1)
|
||||
.ok_or_else(|| ParserError::OutOfBoundsAccess(self.current - 1))
|
||||
}
|
||||
|
||||
/// Consume the current token if its token type matches the provided token_type and advance the
|
||||
/// current token. Otherwise return None..
|
||||
fn consume(&mut self, token_type: &TokenType) -> Option<&Token> {
|
||||
if self.check(token_type) {
|
||||
self.advance().ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a binary expression using the next_precedence function and operators to match.
|
||||
fn binary_expr(
|
||||
&mut self,
|
||||
next_precedence: impl Fn(&mut Self) -> Result<Expression, ParserError>,
|
||||
operators: &[TokenType],
|
||||
) -> Result<Expression, ParserError> {
|
||||
let mut expr = next_precedence(self)?;
|
||||
|
||||
while self.matches(operators) {
|
||||
let operator = self.previous()?.clone();
|
||||
let right = next_precedence(self)?;
|
||||
expr = Expression::Binary {
|
||||
left: Box::new(expr.clone()),
|
||||
operator,
|
||||
right: Box::new(right),
|
||||
};
|
||||
}
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
/// expression -> equality ;
|
||||
fn expression(&mut self) -> Result<Expression, ParserError> {
|
||||
self.assignment()
|
||||
}
|
||||
|
||||
fn declaration(&mut self) -> Result<Statement, ParserError> {
|
||||
if self.matches(&[Class]) {
|
||||
self.class_declaration()
|
||||
} else if self.matches(&[Fun]) {
|
||||
self.function()
|
||||
} else if self.matches(&[Var]) {
|
||||
self.var_declaration()
|
||||
} else {
|
||||
self.statement()
|
||||
}
|
||||
}
|
||||
|
||||
fn statement(&mut self) -> Result<Statement, ParserError> {
|
||||
if self.matches(&[For]) {
|
||||
self.for_statement()
|
||||
} else if self.matches(&[If]) {
|
||||
self.if_statement()
|
||||
} else if self.matches(&[Print]) {
|
||||
self.print_statement()
|
||||
} else if self.matches(&[Return]) {
|
||||
self.return_statement()
|
||||
} else if self.matches(&[While]) {
|
||||
self.while_statement()
|
||||
} else if self.matches(&[LeftBrace]) {
|
||||
Ok(Statement::Block(self.block()?))
|
||||
} else {
|
||||
self.expression_statement()
|
||||
}
|
||||
}
|
||||
|
||||
/// Build up a while statement from a for statement.
|
||||
fn for_statement(&mut self) -> Result<Statement, ParserError> {
|
||||
let line = self.current_token.line;
|
||||
self.consume(&LeftParen)
|
||||
.ok_or(ParserError::LeftParenAfterForExpected(line))?;
|
||||
|
||||
let initializer = if self.matches(&[Semicolon]) {
|
||||
None
|
||||
} else if self.matches(&[Var]) {
|
||||
Some(self.var_declaration()?)
|
||||
} else {
|
||||
Some(self.expression_statement()?)
|
||||
};
|
||||
|
||||
let condition = if !self.matches(&[Semicolon]) {
|
||||
self.expression()?
|
||||
} else {
|
||||
Expression::Literal {
|
||||
value: Literal::Boolean(true),
|
||||
}
|
||||
};
|
||||
self.consume(&Semicolon)
|
||||
.ok_or(ParserError::SemicolonAfterLoopConditionExpected(line))?;
|
||||
|
||||
let increment = if !self.check(&RightParen) {
|
||||
Some(self.expression()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.consume(&RightParen)
|
||||
.ok_or(ParserError::RightParenAfterForClausesExpected(line))?;
|
||||
|
||||
let body = self.statement()?;
|
||||
let body = match increment {
|
||||
Some(inc) => Statement::Block(vec![body, Statement::Expression(inc)]),
|
||||
None => body,
|
||||
};
|
||||
let body = Statement::While {
|
||||
condition,
|
||||
body: Box::new(body),
|
||||
};
|
||||
let body = match initializer {
|
||||
Some(initializer) => Statement::Block(vec![initializer, body]),
|
||||
None => body,
|
||||
};
|
||||
|
||||
Ok(body)
|
||||
}
|
||||
|
||||
fn if_statement(&mut self) -> Result<Statement, ParserError> {
|
||||
let line = self.current_token.line;
|
||||
self.consume(&LeftParen)
|
||||
.ok_or(ParserError::LeftParenAfterIfExpected(line))?;
|
||||
|
||||
let condition = self.expression()?;
|
||||
|
||||
self.consume(&RightParen)
|
||||
.ok_or(ParserError::RightParenAfterConditionExpected(line))?;
|
||||
|
||||
let then_branch = self.statement()?;
|
||||
let else_branch = if self.matches(&[Else]) {
|
||||
Some(Box::new(self.statement()?))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Statement::If {
|
||||
condition,
|
||||
then_branch: Box::new(then_branch),
|
||||
else_branch,
|
||||
})
|
||||
}
|
||||
|
||||
fn print_statement(&mut self) -> Result<Statement, ParserError> {
|
||||
let value = self.expression()?;
|
||||
let line = self.current_token.line;
|
||||
self.consume(&Semicolon)
|
||||
.ok_or(ParserError::SemicolonAfterValueExpected(line))?;
|
||||
|
||||
Ok(Statement::Print(value))
|
||||
}
|
||||
|
||||
fn return_statement(&mut self) -> Result<Statement, ParserError> {
|
||||
let keyword = self.previous()?.clone();
|
||||
let value = if self.check(&Semicolon) {
|
||||
None
|
||||
} else {
|
||||
Some(self.expression()?)
|
||||
};
|
||||
|
||||
self.consume(&Semicolon)
|
||||
.ok_or(ParserError::SemicolonAfterReturnExpected(keyword.line))?;
|
||||
|
||||
Ok(Statement::Return { keyword, value })
|
||||
}
|
||||
|
||||
fn class_declaration(&mut self) -> Result<Statement, ParserError> {
|
||||
let line = self.current_token.line;
|
||||
let name = self
|
||||
.consume(&Identifier)
|
||||
.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))?;
|
||||
|
||||
let mut methods = Vec::new();
|
||||
while !self.check(&RightBrace) && !self.is_at_end() {
|
||||
let method = self.function()?;
|
||||
methods.push(method);
|
||||
}
|
||||
|
||||
self.consume(&RightBrace)
|
||||
.ok_or(ParserError::RightBraceAfterClassExpected(line))?;
|
||||
|
||||
Ok(Statement::Class {
|
||||
name,
|
||||
superclass,
|
||||
methods,
|
||||
})
|
||||
}
|
||||
|
||||
fn var_declaration(&mut self) -> Result<Statement, ParserError> {
|
||||
let line = self.current_token.line;
|
||||
let name = self
|
||||
.consume(&Identifier)
|
||||
.ok_or(ParserError::VariableNameExpected(line))?
|
||||
.clone();
|
||||
|
||||
let initializer = if self.matches(&[Equal]) {
|
||||
Some(self.expression()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.consume(&Semicolon)
|
||||
.ok_or(ParserError::SemicolonAfterExpressionExpected(line))?;
|
||||
|
||||
Ok(Statement::Var {
|
||||
name,
|
||||
initializer: Box::new(initializer),
|
||||
})
|
||||
}
|
||||
|
||||
fn while_statement(&mut self) -> Result<Statement, ParserError> {
|
||||
let line = self.current_token.line;
|
||||
self.consume(&LeftParen)
|
||||
.ok_or(ParserError::LeftParenAfterWhileExpected(line))?;
|
||||
|
||||
let condition = self.expression()?;
|
||||
|
||||
self.consume(&RightParen)
|
||||
.ok_or(ParserError::RightParenAfterConditionExpected(line))?;
|
||||
|
||||
let body = self.statement()?;
|
||||
|
||||
Ok(Statement::While {
|
||||
condition,
|
||||
body: Box::new(body),
|
||||
})
|
||||
}
|
||||
|
||||
fn expression_statement(&mut self) -> Result<Statement, ParserError> {
|
||||
let expr = self.expression()?;
|
||||
let line = self.current_token.line;
|
||||
self.consume(&Semicolon)
|
||||
.ok_or(ParserError::SemicolonAfterExpressionExpected(line))?;
|
||||
|
||||
Ok(Statement::Expression(expr))
|
||||
}
|
||||
|
||||
fn function(&mut self) -> Result<Statement, ParserError> {
|
||||
let line = self.current_token.line;
|
||||
let name = self
|
||||
.consume(&Identifier)
|
||||
.ok_or(ParserError::FunctionNameExpected(line))?
|
||||
.clone();
|
||||
|
||||
self.consume(&LeftParen)
|
||||
.ok_or(ParserError::LeftParenAfterFunctionNameExpected(line))?;
|
||||
|
||||
let mut params = Vec::new();
|
||||
if !self.check(&RightParen) {
|
||||
let param = self
|
||||
.consume(&Identifier)
|
||||
.ok_or(ParserError::ParamNameExpected(line))?
|
||||
.clone();
|
||||
params.push(param);
|
||||
|
||||
while self.matches(&[Comma]) {
|
||||
let param = self
|
||||
.consume(&Identifier)
|
||||
.ok_or(ParserError::ParamNameExpected(line))?
|
||||
.clone();
|
||||
params.push(param);
|
||||
}
|
||||
}
|
||||
|
||||
self.consume(&RightParen)
|
||||
.ok_or(ParserError::RightParenAfterParamsExpected(line))?;
|
||||
self.consume(&LeftBrace)
|
||||
.ok_or(ParserError::LeftBraceBeforeFunctionBodyExpected(line))?;
|
||||
|
||||
let body = self.block()?;
|
||||
|
||||
Ok(Statement::Function { name, params, body })
|
||||
}
|
||||
|
||||
fn block(&mut self) -> Result<Vec<Statement>, ParserError> {
|
||||
let mut statements = Vec::new();
|
||||
|
||||
while !self.check(&RightBrace) && !self.is_at_end() {
|
||||
statements.push(self.declaration()?);
|
||||
}
|
||||
|
||||
let line = self.previous()?.line;
|
||||
self.consume(&RightBrace)
|
||||
.ok_or(ParserError::RightBraceAfterBlockExpected(line))?;
|
||||
|
||||
Ok(statements)
|
||||
}
|
||||
|
||||
fn assignment(&mut self) -> Result<Expression, ParserError> {
|
||||
let expr = self.or()?;
|
||||
|
||||
if self.matches(&[Equal]) {
|
||||
let equals = self.previous()?.clone();
|
||||
let value = self.assignment()?;
|
||||
|
||||
if let Expression::Variable { name } = expr {
|
||||
Ok(Expression::Assign {
|
||||
name,
|
||||
value: Box::new(value),
|
||||
})
|
||||
} else if let Expression::Get { object, name } = expr {
|
||||
Ok(Expression::Set {
|
||||
object,
|
||||
name,
|
||||
value: Box::new(value),
|
||||
})
|
||||
} else {
|
||||
Err(ParserError::InvalidAssignmentTarget(equals.line))
|
||||
}
|
||||
} else {
|
||||
Ok(expr)
|
||||
}
|
||||
}
|
||||
|
||||
fn logical_operator<F>(
|
||||
&mut self,
|
||||
operator: TokenType,
|
||||
parse_fn: F,
|
||||
) -> Result<Expression, ParserError>
|
||||
where
|
||||
F: Fn(&mut Self) -> Result<Expression, ParserError>,
|
||||
{
|
||||
let mut expr = parse_fn(self)?;
|
||||
|
||||
while self.matches(&[operator]) {
|
||||
let operator = self.previous()?.clone();
|
||||
let right = parse_fn(self)?;
|
||||
|
||||
expr = Expression::Logical {
|
||||
left: Box::new(expr),
|
||||
operator,
|
||||
right: Box::new(right),
|
||||
};
|
||||
}
|
||||
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
fn or(&mut self) -> Result<Expression, ParserError> {
|
||||
self.logical_operator(Or, Self::and)
|
||||
}
|
||||
|
||||
fn and(&mut self) -> Result<Expression, ParserError> {
|
||||
self.logical_operator(And, Self::equality)
|
||||
}
|
||||
|
||||
/// equality -> comparison ( ( "!=" | "==" ) comparison )* ;
|
||||
fn equality(&mut self) -> Result<Expression, ParserError> {
|
||||
self.binary_expr(Self::comparison, &[BangEqual, EqualEqual])
|
||||
}
|
||||
|
||||
/// comparison -> term ( ( ">" | ">=" | "<" | "<=" ) term )* ;
|
||||
fn comparison(&mut self) -> Result<Expression, ParserError> {
|
||||
self.binary_expr(Self::term, &[Greater, GreaterEqual, Less, LessEqual])
|
||||
}
|
||||
|
||||
/// term -> factor ( ( "-" | "+" ) factor )* ;
|
||||
fn term(&mut self) -> Result<Expression, ParserError> {
|
||||
self.binary_expr(Self::factor, &[Minus, Plus])
|
||||
}
|
||||
|
||||
/// factor -> unary ( ( "/" | "*" ) unary )* ;
|
||||
fn factor(&mut self) -> Result<Expression, ParserError> {
|
||||
self.binary_expr(Self::unary, &[Slash, Star])
|
||||
}
|
||||
|
||||
/// unary -> ( "!" | "-" ) unary | primary ;
|
||||
fn unary(&mut self) -> Result<Expression, ParserError> {
|
||||
if self.matches(&[Bang, Minus]) {
|
||||
let operator = self.previous()?.clone();
|
||||
let right = self.unary()?;
|
||||
|
||||
Ok(Expression::Unary {
|
||||
operator,
|
||||
right: Box::new(right),
|
||||
})
|
||||
} else {
|
||||
self.call()
|
||||
}
|
||||
}
|
||||
|
||||
fn call(&mut self) -> Result<Expression, ParserError> {
|
||||
let mut expr = self.primary()?;
|
||||
|
||||
loop {
|
||||
if self.matches(&[LeftParen]) {
|
||||
expr = self.finish_call(expr)?;
|
||||
} else if self.matches(&[Dot]) {
|
||||
let line = self.current_token.line;
|
||||
|
||||
let name = self
|
||||
.consume(&Identifier)
|
||||
.ok_or(ParserError::PropertyNameAfterDotExpected(line))?
|
||||
.clone();
|
||||
expr = Expression::Get {
|
||||
object: Box::new(expr),
|
||||
name,
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
fn finish_call(&mut self, callee: Expression) -> Result<Expression, ParserError> {
|
||||
let mut args = Vec::new();
|
||||
|
||||
if !self.check(&RightParen) {
|
||||
args.push(self.expression()?);
|
||||
|
||||
while self.matches(&[Comma]) {
|
||||
args.push(self.expression()?);
|
||||
}
|
||||
}
|
||||
|
||||
let line = self.current_token.line;
|
||||
let paren = self
|
||||
.consume(&RightParen)
|
||||
.ok_or(ParserError::RightParenAfterArgumentsExpected(line))?
|
||||
.clone();
|
||||
|
||||
Ok(Expression::Call {
|
||||
callee: Box::new(callee),
|
||||
paren,
|
||||
args,
|
||||
})
|
||||
}
|
||||
|
||||
/// primary -> NUMBER | STRING | "true" | "false" | "nil" | "(" expression ")" ;
|
||||
fn primary(&mut self) -> Result<Expression, ParserError> {
|
||||
if self.matches(&[False]) {
|
||||
Ok(Expression::Literal {
|
||||
value: token::Literal::Boolean(false),
|
||||
})
|
||||
} else if self.matches(&[True]) {
|
||||
Ok(Expression::Literal {
|
||||
value: token::Literal::Boolean(true),
|
||||
})
|
||||
} else if self.matches(&[Nil]) {
|
||||
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(),
|
||||
})
|
||||
} else if self.matches(&[Identifier]) {
|
||||
let prev = self.previous()?.clone();
|
||||
Ok(Expression::Variable { name: prev })
|
||||
} else if self.matches(&[Number, String]) {
|
||||
let prev = self.previous()?;
|
||||
let value = prev
|
||||
.literal
|
||||
.clone()
|
||||
.ok_or(ParserError::LiteralExpected(prev.line))?;
|
||||
|
||||
Ok(Expression::Literal { value })
|
||||
} else if self.matches(&[LeftParen]) {
|
||||
let expr = self.expression()?;
|
||||
let line = self.current_token.line;
|
||||
self.consume(&RightParen)
|
||||
.ok_or(ParserError::ParenAfterExpression(line))?;
|
||||
|
||||
Ok(Expression::Grouping {
|
||||
expression: Box::new(expr),
|
||||
})
|
||||
} else {
|
||||
let prev = self.previous()?;
|
||||
Err(ParserError::ExpressionExpected(prev.line))
|
||||
}
|
||||
}
|
||||
|
||||
fn synchronize(&mut self) {
|
||||
let _ = self.advance();
|
||||
while !self.is_at_end()
|
||||
&& self.previous().unwrap().token_type != Semicolon
|
||||
&& !&[Class, Fun, Var, For, If, While, Print, Return]
|
||||
.contains(&self.current_token.token_type)
|
||||
{
|
||||
let _ = self.advance();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to parse the provided tokens into an AST.
|
||||
pub fn ast(tokens: Vec<Token>) -> Result<Vec<Statement>, ParserError> {
|
||||
let mut parser = Parser::new(tokens)?;
|
||||
parser.run()
|
||||
}
|
408
src/resolver.rs
Normal file
408
src/resolver.rs
Normal file
|
@ -0,0 +1,408 @@
|
|||
use crate::{expression::Expression, interpreter::Interpreter, statement::Statement, token::Token};
|
||||
use std::collections::HashMap;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ResolverError {
|
||||
#[error("line {0}: can not read local variable in its own initializer.")]
|
||||
ReadVarInOwnInitializer(usize),
|
||||
#[error("line {0}: already a variable with this name in this scope.")]
|
||||
NameRedefinition(usize),
|
||||
#[error("line {0}: can not return from top-level code.")]
|
||||
TopLevelReturn(usize),
|
||||
#[error("line {0}: can not use 'this' outside of a class.")]
|
||||
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)]
|
||||
enum FunctionType {
|
||||
None,
|
||||
Function,
|
||||
Initializer,
|
||||
Method,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum ClassType {
|
||||
None,
|
||||
Class,
|
||||
Subclass,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Resolver<'a> {
|
||||
scopes: Vec<HashMap<String, bool>>,
|
||||
interpreter: &'a mut Interpreter,
|
||||
current_fun: FunctionType,
|
||||
current_class: ClassType,
|
||||
}
|
||||
|
||||
impl<'a> Resolver<'a> {
|
||||
pub fn new(interpreter: &'a mut Interpreter) -> Self {
|
||||
Self {
|
||||
scopes: Vec::default(),
|
||||
interpreter,
|
||||
current_fun: FunctionType::None,
|
||||
current_class: ClassType::None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve(&mut self, statements: &[Statement]) -> Result<(), ResolverError> {
|
||||
for stm in statements {
|
||||
self.resolve_statement(stm)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_statement(&mut self, statement: &Statement) -> Result<(), ResolverError> {
|
||||
match statement {
|
||||
Statement::Block(statements) => self.resolve_block(statements),
|
||||
Statement::Print(expression) => self.resolve_expression(expression),
|
||||
Statement::Expression(expression) => self.resolve_expression(expression),
|
||||
Statement::Var { name, initializer } => self.resolve_var(name, *initializer.clone()),
|
||||
Statement::If {
|
||||
condition,
|
||||
then_branch,
|
||||
else_branch,
|
||||
} => self.resolve_if_stmt(
|
||||
condition,
|
||||
then_branch,
|
||||
else_branch.as_ref().map(|boxed| *boxed.clone()),
|
||||
),
|
||||
Statement::While { condition, body } => self.resolve_while_stmt(condition, body),
|
||||
Statement::Function { name, params, body } => {
|
||||
self.resolve_function_stmt(name, params, body)
|
||||
}
|
||||
Statement::Return { keyword, value } => self.resolve_return_stmt(keyword, value),
|
||||
Statement::Class {
|
||||
name,
|
||||
superclass,
|
||||
methods,
|
||||
} => self.resolve_class_stmt(name, superclass, methods),
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_expression(&mut self, expr: &Expression) -> Result<(), ResolverError> {
|
||||
match expr {
|
||||
Expression::Assign { name, value } => self.resolve_assign_expr(expr, name, value),
|
||||
Expression::Binary {
|
||||
left,
|
||||
operator: _,
|
||||
right,
|
||||
} => self.resolve_binary_expr(left, right),
|
||||
Expression::Call {
|
||||
callee,
|
||||
paren: _,
|
||||
args,
|
||||
} => self.resolve_call_expr(callee, args),
|
||||
Expression::Grouping { expression } => self.resolve_expression(expression),
|
||||
Expression::Literal { value: _ } => Ok(()),
|
||||
Expression::Logical {
|
||||
left,
|
||||
operator: _,
|
||||
right,
|
||||
} => self.resolve_binary_expr(left, right),
|
||||
Expression::Unary { operator: _, right } => self.resolve_expression(right),
|
||||
Expression::Variable { name } => self.resolve_var_expr(expr, name),
|
||||
Expression::Get { object, name: _ } => self.resolve_expression(object),
|
||||
Expression::Set {
|
||||
object,
|
||||
name: _,
|
||||
value,
|
||||
} => self.resolve_set_expression(object, value),
|
||||
Expression::This { keyword } => {
|
||||
if self.current_class == ClassType::None {
|
||||
return Err(ResolverError::ThisOutsideClass(keyword.line));
|
||||
}
|
||||
|
||||
self.resolve_local(expr, keyword)
|
||||
}
|
||||
Expression::Super { keyword, method: _ } => {
|
||||
self.resolve_super_expression(keyword, expr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn begin_scope(&mut self) {
|
||||
self.scopes.push(HashMap::new());
|
||||
}
|
||||
|
||||
fn end_scope(&mut self) {
|
||||
self.scopes.pop();
|
||||
}
|
||||
|
||||
fn declare(&mut self, name: &Token) -> Result<(), ResolverError> {
|
||||
if let Some(scope) = self.scopes.last_mut() {
|
||||
if scope.contains_key(&name.lexeme) {
|
||||
return Err(ResolverError::NameRedefinition(name.line));
|
||||
}
|
||||
|
||||
scope.insert(name.lexeme.clone(), false);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn define(&mut self, name: &Token) {
|
||||
if let Some(scope) = self.scopes.last_mut() {
|
||||
scope.insert(name.lexeme.clone(), true);
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_block(&mut self, statements: &[Statement]) -> Result<(), ResolverError> {
|
||||
self.begin_scope();
|
||||
self.resolve(statements)?;
|
||||
self.end_scope();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_class_stmt(
|
||||
&mut self,
|
||||
name: &Token,
|
||||
superclass: &Option<Expression>,
|
||||
methods: &[Statement],
|
||||
) -> Result<(), ResolverError> {
|
||||
let enclosing_class = self.current_class;
|
||||
self.current_class = ClassType::Class;
|
||||
|
||||
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() {
|
||||
scope.insert("this".to_string(), true);
|
||||
}
|
||||
|
||||
for method in methods {
|
||||
if let Statement::Function { name, params, body } = method {
|
||||
let declaration = if name.lexeme == "init" {
|
||||
FunctionType::Initializer
|
||||
} else {
|
||||
FunctionType::Method
|
||||
};
|
||||
self.resolve_function(params, body, declaration)?;
|
||||
}
|
||||
}
|
||||
|
||||
self.end_scope();
|
||||
|
||||
if superclass.is_some() {
|
||||
self.end_scope();
|
||||
}
|
||||
|
||||
self.current_class = enclosing_class;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_return_stmt(
|
||||
&mut self,
|
||||
keyword: &Token,
|
||||
value: &Option<Expression>,
|
||||
) -> Result<(), ResolverError> {
|
||||
if self.current_fun == FunctionType::None {
|
||||
return Err(ResolverError::TopLevelReturn(keyword.line));
|
||||
}
|
||||
if let Some(value) = value {
|
||||
if self.current_fun == FunctionType::Initializer {
|
||||
return Err(ResolverError::ReturnFromInitializer(keyword.line));
|
||||
}
|
||||
self.resolve_expression(value)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_var(
|
||||
&mut self,
|
||||
name: &Token,
|
||||
initializer: Option<Expression>,
|
||||
) -> Result<(), ResolverError> {
|
||||
self.declare(name)?;
|
||||
|
||||
if let Some(initializer) = initializer {
|
||||
self.resolve_expression(&initializer)?;
|
||||
}
|
||||
|
||||
self.define(name);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_var_expr(&mut self, expr: &Expression, name: &Token) -> Result<(), ResolverError> {
|
||||
if let Some(scope) = self.scopes.last() {
|
||||
if !scope.get(&name.lexeme).unwrap_or(&true) {
|
||||
return Err(ResolverError::ReadVarInOwnInitializer(name.line));
|
||||
}
|
||||
}
|
||||
self.resolve_local(expr, name)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_assign_expr(
|
||||
&mut self,
|
||||
expr: &Expression,
|
||||
name: &Token,
|
||||
val: &Expression,
|
||||
) -> Result<(), ResolverError> {
|
||||
self.resolve_expression(val)?;
|
||||
self.resolve_local(expr, name)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_binary_expr(
|
||||
&mut self,
|
||||
left: &Expression,
|
||||
right: &Expression,
|
||||
) -> Result<(), ResolverError> {
|
||||
self.resolve_expression(left)?;
|
||||
self.resolve_expression(right)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_function_stmt(
|
||||
&mut self,
|
||||
name: &Token,
|
||||
params: &[Token],
|
||||
body: &[Statement],
|
||||
) -> Result<(), ResolverError> {
|
||||
self.declare(name)?;
|
||||
self.define(name);
|
||||
self.resolve_function(params, body, FunctionType::Function)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_call_expr(
|
||||
&mut self,
|
||||
callee: &Expression,
|
||||
args: &[Expression],
|
||||
) -> Result<(), ResolverError> {
|
||||
self.resolve_expression(callee)?;
|
||||
|
||||
for arg in args {
|
||||
self.resolve_expression(arg)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_set_expression(
|
||||
&mut self,
|
||||
object: &Expression,
|
||||
value: &Expression,
|
||||
) -> Result<(), ResolverError> {
|
||||
self.resolve_expression(value)?;
|
||||
self.resolve_expression(object)?;
|
||||
|
||||
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,
|
||||
then_branch: &Statement,
|
||||
else_branch: Option<Statement>,
|
||||
) -> Result<(), ResolverError> {
|
||||
self.resolve_expression(condition)?;
|
||||
self.resolve_statement(then_branch)?;
|
||||
|
||||
if let Some(stmt) = else_branch {
|
||||
self.resolve_statement(&stmt)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_while_stmt(
|
||||
&mut self,
|
||||
condition: &Expression,
|
||||
body: &Statement,
|
||||
) -> Result<(), ResolverError> {
|
||||
self.resolve_expression(condition)?;
|
||||
self.resolve_statement(body)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_function(
|
||||
&mut self,
|
||||
params: &[Token],
|
||||
body: &[Statement],
|
||||
fun_type: FunctionType,
|
||||
) -> Result<(), ResolverError> {
|
||||
let enclosing_fun = self.current_fun;
|
||||
self.current_fun = fun_type;
|
||||
|
||||
self.begin_scope();
|
||||
|
||||
for param in params {
|
||||
self.declare(param)?;
|
||||
self.define(param);
|
||||
}
|
||||
self.resolve(body)?;
|
||||
|
||||
self.end_scope();
|
||||
|
||||
self.current_fun = enclosing_fun;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_local(
|
||||
&mut self,
|
||||
expression: &Expression,
|
||||
name: &Token,
|
||||
) -> Result<(), ResolverError> {
|
||||
for (idx, scope) in self.scopes.iter().rev().enumerate() {
|
||||
if scope.contains_key(&name.lexeme) {
|
||||
self.interpreter.resolve(expression.clone(), idx);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
374
src/scanner.rs
Normal file
374
src/scanner.rs
Normal file
|
@ -0,0 +1,374 @@
|
|||
//! Scan source code to create tokens out of it.
|
||||
|
||||
use crate::{
|
||||
token::{Token, TokenType::Eof},
|
||||
tokenizer::{
|
||||
comment::Comment, identifier::Identifier, interface::Tokenizer, lookahead::Lookahead,
|
||||
newline::Newline, number::Number, single_char::SingleChar, string::String,
|
||||
whitespace::Whitespace,
|
||||
},
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use tracing::error;
|
||||
|
||||
lazy_static! {
|
||||
/// Tokenizers to use in scanning. They are tried in the exact order in which they appear in
|
||||
/// the list.
|
||||
static ref TOKENIZERS: Vec<Box<dyn Tokenizer>> = vec![
|
||||
Box::new(SingleChar),
|
||||
Box::new(Whitespace),
|
||||
Box::new(Newline),
|
||||
Box::new(Lookahead),
|
||||
Box::new(Comment),
|
||||
Box::new(String),
|
||||
Box::new(Number),
|
||||
Box::new(Identifier),
|
||||
];
|
||||
}
|
||||
|
||||
/// Take source code as input and return a list of tokens representing it.
|
||||
pub fn tokenize(source: &str) -> Vec<Token> {
|
||||
let mut tokens: Vec<Token> = Vec::new();
|
||||
let mut source_chars = source.char_indices().peekable();
|
||||
|
||||
let mut line = 1;
|
||||
while let Some(c) = source_chars.next() {
|
||||
// careful, a tokenizer run can move the iterator but I have not found a more ergonomic variant yet
|
||||
match TOKENIZERS
|
||||
.iter()
|
||||
.find_map(|x| x.run(c, &mut source_chars, source, line))
|
||||
{
|
||||
Some((line_advance, token)) => {
|
||||
// I do not like handling it this way, but it suffices for now
|
||||
line += line_advance;
|
||||
|
||||
if let Some(token) = token {
|
||||
tokens.push(token);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
error!("unexpected character: {}", c.1)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Eof is always the last token
|
||||
tokens.push(Token {
|
||||
token_type: Eof,
|
||||
lexeme: "".to_string(),
|
||||
literal: None,
|
||||
line,
|
||||
});
|
||||
|
||||
tokens
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ordered_float::OrderedFloat;
|
||||
|
||||
use crate::token::Literal;
|
||||
use crate::token::Token;
|
||||
use crate::token::TokenType::*;
|
||||
|
||||
use super::tokenize;
|
||||
|
||||
const FIBONACCI: &str = r#"
|
||||
// first 21 elements in the Fibonacci sequence
|
||||
var a = 0;
|
||||
var temp;
|
||||
|
||||
for (var b = 1; a < 10000; b = temp + b) {
|
||||
print a;
|
||||
temp = a;
|
||||
a = b;
|
||||
}
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn floating_points() {
|
||||
assert_eq!(
|
||||
vec![Token {
|
||||
token_type: Number,
|
||||
lexeme: "0".into(),
|
||||
literal: Some(Literal::Number(OrderedFloat(0.0))),
|
||||
line: 1
|
||||
},],
|
||||
vec![Token {
|
||||
token_type: Number,
|
||||
lexeme: "0".into(),
|
||||
literal: Some(Literal::Number(OrderedFloat(0.0))),
|
||||
line: 1
|
||||
},]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiline_string() {
|
||||
let input = r#""Hello,
|
||||
world
|
||||
!""#;
|
||||
let tokens = tokenize(input);
|
||||
|
||||
assert_eq!(
|
||||
tokens,
|
||||
vec![
|
||||
Token {
|
||||
token_type: String,
|
||||
lexeme: "\"Hello,\n world\n!\"".into(),
|
||||
literal: Some(Literal::String("Hello,\n world\n!".into())),
|
||||
line: 1
|
||||
},
|
||||
Token {
|
||||
token_type: Eof,
|
||||
lexeme: "".into(),
|
||||
literal: None,
|
||||
line: 3
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fibonacci_tokens() {
|
||||
let tokens = tokenize(FIBONACCI);
|
||||
assert_eq!(
|
||||
tokens,
|
||||
vec![
|
||||
Token {
|
||||
token_type: Var,
|
||||
lexeme: "var".into(),
|
||||
literal: None,
|
||||
line: 3
|
||||
},
|
||||
Token {
|
||||
token_type: Identifier,
|
||||
lexeme: "a".into(),
|
||||
literal: None,
|
||||
line: 3
|
||||
},
|
||||
Token {
|
||||
token_type: Equal,
|
||||
lexeme: "=".into(),
|
||||
literal: None,
|
||||
line: 3
|
||||
},
|
||||
Token {
|
||||
token_type: Number,
|
||||
lexeme: "0".into(),
|
||||
literal: Some(Literal::Number(OrderedFloat(0.0))),
|
||||
line: 3
|
||||
},
|
||||
Token {
|
||||
token_type: Semicolon,
|
||||
lexeme: ";".into(),
|
||||
literal: None,
|
||||
line: 3
|
||||
},
|
||||
Token {
|
||||
token_type: Var,
|
||||
lexeme: "var".into(),
|
||||
literal: None,
|
||||
line: 4
|
||||
},
|
||||
Token {
|
||||
token_type: Identifier,
|
||||
lexeme: "temp".into(),
|
||||
literal: None,
|
||||
line: 4
|
||||
},
|
||||
Token {
|
||||
token_type: Semicolon,
|
||||
lexeme: ";".into(),
|
||||
literal: None,
|
||||
line: 4
|
||||
},
|
||||
Token {
|
||||
token_type: For,
|
||||
lexeme: "for".into(),
|
||||
literal: None,
|
||||
line: 6
|
||||
},
|
||||
Token {
|
||||
token_type: LeftParen,
|
||||
lexeme: "(".into(),
|
||||
literal: None,
|
||||
line: 6
|
||||
},
|
||||
Token {
|
||||
token_type: Var,
|
||||
lexeme: "var".into(),
|
||||
literal: None,
|
||||
line: 6
|
||||
},
|
||||
Token {
|
||||
token_type: Identifier,
|
||||
lexeme: "b".into(),
|
||||
literal: None,
|
||||
line: 6
|
||||
},
|
||||
Token {
|
||||
token_type: Equal,
|
||||
lexeme: "=".into(),
|
||||
literal: None,
|
||||
line: 6
|
||||
},
|
||||
Token {
|
||||
token_type: Number,
|
||||
lexeme: "1".into(),
|
||||
literal: Some(Literal::Number(OrderedFloat(1.0))),
|
||||
line: 6
|
||||
},
|
||||
Token {
|
||||
token_type: Semicolon,
|
||||
lexeme: ";".into(),
|
||||
literal: None,
|
||||
line: 6
|
||||
},
|
||||
Token {
|
||||
token_type: Identifier,
|
||||
lexeme: "a".into(),
|
||||
literal: None,
|
||||
line: 6
|
||||
},
|
||||
Token {
|
||||
token_type: Less,
|
||||
lexeme: "<".into(),
|
||||
literal: None,
|
||||
line: 6
|
||||
},
|
||||
Token {
|
||||
token_type: Number,
|
||||
lexeme: "10000".into(),
|
||||
literal: Some(Literal::Number(OrderedFloat(10000.0))),
|
||||
line: 6
|
||||
},
|
||||
Token {
|
||||
token_type: Semicolon,
|
||||
lexeme: ";".into(),
|
||||
literal: None,
|
||||
line: 6
|
||||
},
|
||||
Token {
|
||||
token_type: Identifier,
|
||||
lexeme: "b".into(),
|
||||
literal: None,
|
||||
line: 6
|
||||
},
|
||||
Token {
|
||||
token_type: Equal,
|
||||
lexeme: "=".into(),
|
||||
literal: None,
|
||||
line: 6
|
||||
},
|
||||
Token {
|
||||
token_type: Identifier,
|
||||
lexeme: "temp".into(),
|
||||
literal: None,
|
||||
line: 6
|
||||
},
|
||||
Token {
|
||||
token_type: Plus,
|
||||
lexeme: "+".into(),
|
||||
literal: None,
|
||||
line: 6
|
||||
},
|
||||
Token {
|
||||
token_type: Identifier,
|
||||
lexeme: "b".into(),
|
||||
literal: None,
|
||||
line: 6
|
||||
},
|
||||
Token {
|
||||
token_type: RightParen,
|
||||
lexeme: ")".into(),
|
||||
literal: None,
|
||||
line: 6
|
||||
},
|
||||
Token {
|
||||
token_type: LeftBrace,
|
||||
lexeme: "{".into(),
|
||||
literal: None,
|
||||
line: 6
|
||||
},
|
||||
Token {
|
||||
token_type: Print,
|
||||
lexeme: "print".into(),
|
||||
literal: None,
|
||||
line: 7
|
||||
},
|
||||
Token {
|
||||
token_type: Identifier,
|
||||
lexeme: "a".into(),
|
||||
literal: None,
|
||||
line: 7
|
||||
},
|
||||
Token {
|
||||
token_type: Semicolon,
|
||||
lexeme: ";".into(),
|
||||
literal: None,
|
||||
line: 7
|
||||
},
|
||||
Token {
|
||||
token_type: Identifier,
|
||||
lexeme: "temp".into(),
|
||||
literal: None,
|
||||
line: 8
|
||||
},
|
||||
Token {
|
||||
token_type: Equal,
|
||||
lexeme: "=".into(),
|
||||
literal: None,
|
||||
line: 8
|
||||
},
|
||||
Token {
|
||||
token_type: Identifier,
|
||||
lexeme: "a".into(),
|
||||
literal: None,
|
||||
line: 8
|
||||
},
|
||||
Token {
|
||||
token_type: Semicolon,
|
||||
lexeme: ";".into(),
|
||||
literal: None,
|
||||
line: 8
|
||||
},
|
||||
Token {
|
||||
token_type: Identifier,
|
||||
lexeme: "a".into(),
|
||||
literal: None,
|
||||
line: 9
|
||||
},
|
||||
Token {
|
||||
token_type: Equal,
|
||||
lexeme: "=".into(),
|
||||
literal: None,
|
||||
line: 9
|
||||
},
|
||||
Token {
|
||||
token_type: Identifier,
|
||||
lexeme: "b".into(),
|
||||
literal: None,
|
||||
line: 9
|
||||
},
|
||||
Token {
|
||||
token_type: Semicolon,
|
||||
lexeme: ";".into(),
|
||||
literal: None,
|
||||
line: 9
|
||||
},
|
||||
Token {
|
||||
token_type: RightBrace,
|
||||
lexeme: "}".into(),
|
||||
literal: None,
|
||||
line: 10
|
||||
},
|
||||
Token {
|
||||
token_type: Eof,
|
||||
lexeme: "".into(),
|
||||
literal: None,
|
||||
line: 11
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
36
src/statement.rs
Normal file
36
src/statement.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
use crate::{expression::Expression, token::Token};
|
||||
|
||||
/// Enumeration of all types of statements.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Statement {
|
||||
Block(Vec<Statement>),
|
||||
Print(Expression),
|
||||
Expression(Expression),
|
||||
Var {
|
||||
name: Token,
|
||||
initializer: Box<Option<Expression>>,
|
||||
},
|
||||
If {
|
||||
condition: Expression,
|
||||
then_branch: Box<Statement>,
|
||||
else_branch: Option<Box<Statement>>,
|
||||
},
|
||||
While {
|
||||
condition: Expression,
|
||||
body: Box<Statement>,
|
||||
},
|
||||
Function {
|
||||
name: Token,
|
||||
params: Vec<Token>,
|
||||
body: Vec<Statement>,
|
||||
},
|
||||
Return {
|
||||
keyword: Token,
|
||||
value: Option<Expression>,
|
||||
},
|
||||
Class {
|
||||
name: Token,
|
||||
superclass: Option<Expression>,
|
||||
methods: Vec<Statement>,
|
||||
},
|
||||
}
|
93
src/token.rs
Normal file
93
src/token.rs
Normal file
|
@ -0,0 +1,93 @@
|
|||
//! Token values and data structs.
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use ordered_float::OrderedFloat;
|
||||
|
||||
/// Exhaustive enumeration of all types of different tokens.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum TokenType {
|
||||
// Single-character tokens.
|
||||
LeftParen,
|
||||
RightParen,
|
||||
LeftBrace,
|
||||
RightBrace,
|
||||
Comma,
|
||||
Dot,
|
||||
Minus,
|
||||
Plus,
|
||||
Semicolon,
|
||||
Slash,
|
||||
Star,
|
||||
|
||||
// One or two character tokens.
|
||||
Bang,
|
||||
BangEqual,
|
||||
Equal,
|
||||
EqualEqual,
|
||||
Greater,
|
||||
GreaterEqual,
|
||||
Less,
|
||||
LessEqual,
|
||||
|
||||
// Literals.
|
||||
Identifier,
|
||||
String,
|
||||
Number,
|
||||
|
||||
// Keywords.
|
||||
And,
|
||||
Class,
|
||||
Else,
|
||||
False,
|
||||
Fun,
|
||||
For,
|
||||
If,
|
||||
Nil,
|
||||
Or,
|
||||
Print,
|
||||
Return,
|
||||
Super,
|
||||
This,
|
||||
True,
|
||||
Var,
|
||||
While,
|
||||
|
||||
Eof,
|
||||
}
|
||||
|
||||
/// Literal value.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Literal {
|
||||
/// String literal.
|
||||
String(String),
|
||||
// Number literal, represented as f64 (thus it can be decimal).
|
||||
Number(OrderedFloat<f64>),
|
||||
/// Boolean literal.
|
||||
Boolean(bool),
|
||||
/// Null literal.
|
||||
Nil,
|
||||
}
|
||||
|
||||
/// Consumed token.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Token {
|
||||
/// Type of the token.
|
||||
pub token_type: TokenType,
|
||||
/// Lexeme that was consumed to create this token.
|
||||
pub lexeme: String,
|
||||
/// Literal value of the token, if any.
|
||||
pub literal: Option<Literal>,
|
||||
/// Starting line on which the token was oonsumed from the source.
|
||||
pub line: usize,
|
||||
}
|
||||
|
||||
impl Display for Token {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}: {:?} {} {:?}",
|
||||
self.line, self.token_type, self.lexeme, self.literal
|
||||
)
|
||||
}
|
||||
}
|
43
src/tokenizer/comment.rs
Normal file
43
src/tokenizer/comment.rs
Normal file
|
@ -0,0 +1,43 @@
|
|||
use crate::token::TokenType::Slash;
|
||||
use std::{iter::Peekable, str::CharIndices};
|
||||
|
||||
use crate::token::Token;
|
||||
|
||||
use super::interface::Tokenizer;
|
||||
|
||||
/// Consume comments.
|
||||
///
|
||||
/// A comment starts with '//' and runs until the end of the line.
|
||||
/// If only one '/' is seen, it is consumed as a Slash token.
|
||||
pub struct Comment;
|
||||
impl Tokenizer for Comment {
|
||||
fn run(
|
||||
&self,
|
||||
c: (usize, char),
|
||||
chars: &mut Peekable<CharIndices<'_>>,
|
||||
source: &str,
|
||||
line: usize,
|
||||
) -> Option<(usize, Option<Token>)> {
|
||||
match c.1 {
|
||||
'/' => {
|
||||
let (line_advance, token) = if chars.next_if(|(_, peek)| *peek == '/').is_some() {
|
||||
while chars.next_if(|(_, peek)| *peek != '\n').is_some() {}
|
||||
chars.next();
|
||||
(1, None)
|
||||
} else {
|
||||
(
|
||||
0,
|
||||
Some(Token {
|
||||
token_type: Slash,
|
||||
lexeme: source[c.0..=c.0].to_string(),
|
||||
literal: None,
|
||||
line,
|
||||
}),
|
||||
)
|
||||
};
|
||||
Some((line_advance, token))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
46
src/tokenizer/identifier.rs
Normal file
46
src/tokenizer/identifier.rs
Normal file
|
@ -0,0 +1,46 @@
|
|||
use crate::{
|
||||
keywords::KEYWORDS,
|
||||
token::{Token, TokenType},
|
||||
};
|
||||
use std::{iter::Peekable, str::CharIndices};
|
||||
|
||||
use super::interface::Tokenizer;
|
||||
|
||||
/// Consume an identifier which also might be a keyword.
|
||||
///
|
||||
/// An identifier starts with an alphabetic character and goes on consuming alphanumeric and
|
||||
/// underscore characters until the first different one.
|
||||
pub struct Identifier;
|
||||
impl Tokenizer for Identifier {
|
||||
fn run(
|
||||
&self,
|
||||
c: (usize, char),
|
||||
chars: &mut Peekable<CharIndices<'_>>,
|
||||
source: &str,
|
||||
line: usize,
|
||||
) -> Option<(usize, Option<Token>)> {
|
||||
if c.1.is_alphabetic() || c.1 == '_' {
|
||||
let mut end_idx = c.0;
|
||||
while let Some((idx, _)) = chars.next_if(|(_, x)| x.is_alphanumeric() || c.1 == '_') {
|
||||
end_idx = idx;
|
||||
}
|
||||
|
||||
let lexeme = source[c.0..=end_idx].to_string();
|
||||
let token_type = match KEYWORDS.get(&lexeme) {
|
||||
Some(token_type) => *token_type,
|
||||
None => TokenType::Identifier,
|
||||
};
|
||||
Some((
|
||||
0,
|
||||
Some(Token {
|
||||
token_type,
|
||||
lexeme,
|
||||
literal: None,
|
||||
line,
|
||||
}),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
18
src/tokenizer/interface.rs
Normal file
18
src/tokenizer/interface.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
use std::{iter::Peekable, str::CharIndices};
|
||||
|
||||
use crate::token::Token;
|
||||
|
||||
/// Interface to implement by a tokenizer.
|
||||
pub trait Tokenizer: Send + Sync {
|
||||
/// Take a tuple consisting of the index of a char and the char itself, the whole source code
|
||||
/// iterator, the source itself and the current line. Return None if you can not handle the
|
||||
/// current lexeme or an Option consisting of a tuple where the first element is how much the
|
||||
/// current line moved and the second element is an Option that can have the consumed token.
|
||||
fn run(
|
||||
&self,
|
||||
c: (usize, char),
|
||||
chars: &mut Peekable<CharIndices<'_>>,
|
||||
source: &str,
|
||||
line: usize,
|
||||
) -> Option<(usize, Option<Token>)>;
|
||||
}
|
97
src/tokenizer/lookahead.rs
Normal file
97
src/tokenizer/lookahead.rs
Normal file
|
@ -0,0 +1,97 @@
|
|||
use super::interface::Tokenizer;
|
||||
use crate::token::TokenType::*;
|
||||
use crate::token::{Token, TokenType};
|
||||
use lazy_static::lazy_static;
|
||||
use std::{collections::HashMap, iter::Peekable, str::CharIndices};
|
||||
|
||||
/// Data for one and two character lexemes.
|
||||
struct LookaheadEntry {
|
||||
/// TokenType if a lexeme is a one character one.
|
||||
default_token: TokenType,
|
||||
/// Mapping of second level character to a TokenType.
|
||||
lookahead_map: HashMap<char, TokenType>,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
/// Mapping of one and two character lexemes, specifiyng the one character variant and as many
|
||||
/// two character ones as needed.
|
||||
static ref LOOKAHEAD_TOKENS: HashMap<char, LookaheadEntry> = {
|
||||
let mut m = HashMap::new();
|
||||
|
||||
let mut bang_map = HashMap::new();
|
||||
bang_map.insert('=', BangEqual);
|
||||
m.insert(
|
||||
'!',
|
||||
LookaheadEntry {
|
||||
default_token: Bang,
|
||||
lookahead_map: bang_map,
|
||||
},
|
||||
);
|
||||
|
||||
let mut equal_map = HashMap::new();
|
||||
equal_map.insert('=', EqualEqual);
|
||||
m.insert(
|
||||
'=',
|
||||
LookaheadEntry {
|
||||
default_token: Equal,
|
||||
lookahead_map: equal_map,
|
||||
},
|
||||
);
|
||||
|
||||
let mut less_map = HashMap::new();
|
||||
less_map.insert('=', LessEqual);
|
||||
m.insert(
|
||||
'<',
|
||||
LookaheadEntry {
|
||||
default_token: Less,
|
||||
lookahead_map: less_map,
|
||||
},
|
||||
);
|
||||
|
||||
let mut greater_map = HashMap::new();
|
||||
greater_map.insert('=', GreaterEqual);
|
||||
m.insert(
|
||||
'>',
|
||||
LookaheadEntry {
|
||||
default_token: Greater,
|
||||
lookahead_map: greater_map,
|
||||
},
|
||||
);
|
||||
|
||||
m
|
||||
};
|
||||
}
|
||||
|
||||
/// Consume lexemes that consist of exactly one or two characters.
|
||||
pub struct Lookahead;
|
||||
impl Tokenizer for Lookahead {
|
||||
fn run(
|
||||
&self,
|
||||
c: (usize, char),
|
||||
chars: &mut Peekable<CharIndices<'_>>,
|
||||
source: &str,
|
||||
line: usize,
|
||||
) -> Option<(usize, Option<Token>)> {
|
||||
LOOKAHEAD_TOKENS.get(&c.1).map(|entry| {
|
||||
let (lexeme, token_type) = if let Some(&(_, peeked)) = chars.peek() {
|
||||
if let Some(&token_type) = entry.lookahead_map.get(&peeked) {
|
||||
chars.next();
|
||||
(source[c.0..=c.0 + 1].to_string(), token_type)
|
||||
} else {
|
||||
(source[c.0..=c.0].to_string(), entry.default_token)
|
||||
}
|
||||
} else {
|
||||
(source[c.0..=c.0].to_string(), entry.default_token)
|
||||
};
|
||||
(
|
||||
0,
|
||||
Some(Token {
|
||||
token_type,
|
||||
lexeme,
|
||||
literal: None,
|
||||
line,
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
20
src/tokenizer/newline.rs
Normal file
20
src/tokenizer/newline.rs
Normal file
|
@ -0,0 +1,20 @@
|
|||
use super::interface::Tokenizer;
|
||||
use crate::token::Token;
|
||||
use std::{iter::Peekable, str::CharIndices};
|
||||
|
||||
/// Consume newlines. Do not yield a token but increase the current line.
|
||||
pub struct Newline;
|
||||
impl Tokenizer for Newline {
|
||||
fn run(
|
||||
&self,
|
||||
c: (usize, char),
|
||||
_chars: &mut Peekable<CharIndices<'_>>,
|
||||
_source: &str,
|
||||
_line: usize,
|
||||
) -> Option<(usize, Option<Token>)> {
|
||||
match c.1 {
|
||||
'\n' => Some((1, None)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
47
src/tokenizer/number.rs
Normal file
47
src/tokenizer/number.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
use crate::token::{Literal, Token, TokenType};
|
||||
use ordered_float::OrderedFloat;
|
||||
use std::{iter::Peekable, str::CharIndices};
|
||||
use tracing::error;
|
||||
|
||||
use super::interface::Tokenizer;
|
||||
|
||||
/// Consume a number literal. Numbers can have one decimal point.
|
||||
pub struct Number;
|
||||
impl Tokenizer for Number {
|
||||
fn run(
|
||||
&self,
|
||||
c: (usize, char),
|
||||
chars: &mut Peekable<CharIndices<'_>>,
|
||||
source: &str,
|
||||
line: usize,
|
||||
) -> Option<(usize, Option<Token>)> {
|
||||
if c.1.is_ascii_digit() {
|
||||
let mut end_idx = c.0;
|
||||
while let Some((idx, _)) = chars.next_if(|(_, x)| x.is_ascii_digit()) {
|
||||
end_idx = idx;
|
||||
}
|
||||
chars.next_if(|(_, x)| *x == '.');
|
||||
while let Some((idx, _)) = chars.next_if(|(_, x)| x.is_ascii_digit()) {
|
||||
end_idx = idx;
|
||||
}
|
||||
|
||||
let lexeme = source[c.0..=end_idx].to_string();
|
||||
let token = match lexeme.parse::<f64>() {
|
||||
Ok(literal) => Some(Token {
|
||||
token_type: TokenType::Number,
|
||||
lexeme,
|
||||
literal: Some(Literal::Number(OrderedFloat(literal))),
|
||||
line,
|
||||
}),
|
||||
Err(e) => {
|
||||
error!("failed to parse number: {e}");
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
Some((0, token))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
51
src/tokenizer/single_char.rs
Normal file
51
src/tokenizer/single_char.rs
Normal file
|
@ -0,0 +1,51 @@
|
|||
use super::interface::Tokenizer;
|
||||
use crate::token::{
|
||||
Token,
|
||||
TokenType::{self, *},
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use std::{collections::HashMap, iter::Peekable, str::CharIndices};
|
||||
|
||||
lazy_static! {
|
||||
/// Mapping of single characters to their respective TokenType.
|
||||
static ref SINGLE_CHAR_TOKENS: HashMap<char, TokenType> = {
|
||||
let mut m = HashMap::new();
|
||||
m.insert('(', LeftParen);
|
||||
m.insert(')', RightParen);
|
||||
m.insert('{', LeftBrace);
|
||||
m.insert('}', RightBrace);
|
||||
m.insert(',', Comma);
|
||||
m.insert('.', Dot);
|
||||
m.insert('-', Minus);
|
||||
m.insert('+', Plus);
|
||||
m.insert(';', Semicolon);
|
||||
m.insert('*', Star);
|
||||
m
|
||||
};
|
||||
}
|
||||
|
||||
/// Consume a single character and produce its corresponding token.
|
||||
pub struct SingleChar;
|
||||
impl Tokenizer for SingleChar {
|
||||
fn run(
|
||||
&self,
|
||||
c: (usize, char),
|
||||
_chars: &mut Peekable<CharIndices<'_>>,
|
||||
source: &str,
|
||||
line: usize,
|
||||
) -> Option<(usize, Option<Token>)> {
|
||||
let lexeme = source[c.0..=c.0].to_string();
|
||||
|
||||
SINGLE_CHAR_TOKENS.get(&c.1).map(|token_type| {
|
||||
(
|
||||
0,
|
||||
Some(Token {
|
||||
token_type: *token_type,
|
||||
lexeme,
|
||||
literal: None,
|
||||
line,
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
51
src/tokenizer/string.rs
Normal file
51
src/tokenizer/string.rs
Normal file
|
@ -0,0 +1,51 @@
|
|||
use super::interface::Tokenizer;
|
||||
use crate::token::{Literal, Token, TokenType};
|
||||
use std::{iter::Peekable, str::CharIndices};
|
||||
use tracing::error;
|
||||
|
||||
/// Consume a string literal.
|
||||
///
|
||||
/// A string literal consists of everything between two '"' and can stretch across multiple lines.
|
||||
pub struct String;
|
||||
impl Tokenizer for String {
|
||||
fn run(
|
||||
&self,
|
||||
c: (usize, char),
|
||||
chars: &mut Peekable<CharIndices<'_>>,
|
||||
source: &str,
|
||||
line: usize,
|
||||
) -> Option<(usize, Option<Token>)> {
|
||||
match c.1 {
|
||||
'"' => {
|
||||
let mut lines = 0;
|
||||
while let Some((_, c)) = chars.next_if(|(_, peek)| *peek != '"') {
|
||||
if c == '\n' {
|
||||
lines += 1;
|
||||
}
|
||||
}
|
||||
|
||||
match chars.next_if(|(_, x)| *x == '"') {
|
||||
Some((end_idx, _)) => {
|
||||
let lexeme = source[c.0..=end_idx].to_string();
|
||||
let literal = source[c.0 + 1..end_idx].to_string();
|
||||
|
||||
Some((
|
||||
lines,
|
||||
Some(Token {
|
||||
token_type: TokenType::String,
|
||||
lexeme,
|
||||
literal: Some(Literal::String(literal)),
|
||||
line,
|
||||
}),
|
||||
))
|
||||
}
|
||||
None => {
|
||||
error!("unterminated string");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
20
src/tokenizer/whitespace.rs
Normal file
20
src/tokenizer/whitespace.rs
Normal file
|
@ -0,0 +1,20 @@
|
|||
use super::interface::Tokenizer;
|
||||
use crate::token::Token;
|
||||
use std::{iter::Peekable, str::CharIndices};
|
||||
|
||||
/// Consume and ignore whitespace characters.
|
||||
pub struct Whitespace;
|
||||
impl Tokenizer for Whitespace {
|
||||
fn run(
|
||||
&self,
|
||||
c: (usize, char),
|
||||
_chars: &mut Peekable<CharIndices<'_>>,
|
||||
_source: &str,
|
||||
_line: usize,
|
||||
) -> Option<(usize, Option<Token>)> {
|
||||
match c.1 {
|
||||
' ' | '\r' | '\t' => Some((0, None)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
59
src/value.rs
Normal file
59
src/value.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
use std::{cell::RefCell, fmt::Display, rc::Rc};
|
||||
|
||||
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>, CallableType)),
|
||||
String(String),
|
||||
Number(OrderedFloat<f64>),
|
||||
Boolean(bool),
|
||||
Instance(Rc<RefCell<instance::Instance>>),
|
||||
Nil,
|
||||
}
|
||||
|
||||
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.0),
|
||||
Value::String(x) => write!(f, "{x}"),
|
||||
Value::Number(x) => write!(f, "{x}"),
|
||||
Value::Boolean(x) => write!(f, "{x}"),
|
||||
Value::Instance(x) => write!(f, "{}", x.borrow()),
|
||||
Value::Nil => write!(f, "nil"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Value {
|
||||
/// Return false for nil or a false boolean, true otherwise.
|
||||
pub fn is_truthy(&self) -> bool {
|
||||
match self {
|
||||
Value::Nil => false,
|
||||
Value::Boolean(b) => *b,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Literal> for Value {
|
||||
fn from(value: Literal) -> Self {
|
||||
match value {
|
||||
Literal::String(x) => Value::String(x),
|
||||
Literal::Number(x) => Value::Number(x),
|
||||
Literal::Boolean(x) => Value::Boolean(x),
|
||||
Literal::Nil => Value::Nil,
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue