first documentation run
This commit is contained in:
parent
e065108f58
commit
f59e6a5fd5
21 changed files with 64 additions and 23 deletions
|
@ -13,7 +13,7 @@ pub enum CallingError {
|
||||||
CallInterpretationFailed(String),
|
CallInterpretationFailed(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implementations can be called.
|
/// Interface for callable objects in Lox (functions, methods, classes).
|
||||||
pub trait Callable {
|
pub trait Callable {
|
||||||
fn name(&self) -> String;
|
fn name(&self) -> String;
|
||||||
fn arity(&self) -> usize;
|
fn arity(&self) -> usize;
|
||||||
|
|
|
@ -21,6 +21,7 @@ impl Display for Class {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Class {
|
impl Class {
|
||||||
|
/// Create a new class with the given name, optional superclass, and methods.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
name: String,
|
name: String,
|
||||||
superclass: Option<Class>,
|
superclass: Option<Class>,
|
||||||
|
@ -35,6 +36,8 @@ impl Class {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Find a method by name in this class or any of its superclasses.
|
||||||
|
/// This is method inheritance.
|
||||||
pub fn find_method(&self, name: &str) -> Option<&Function> {
|
pub fn find_method(&self, name: &str) -> Option<&Function> {
|
||||||
self.methods.get(name).or_else(|| {
|
self.methods.get(name).or_else(|| {
|
||||||
if let Some(superclass) = &self.superclass {
|
if let Some(superclass) = &self.superclass {
|
||||||
|
|
|
@ -13,6 +13,7 @@ pub struct Cli {
|
||||||
pub command: Commands,
|
pub command: Commands,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Configuration for running a Lox source file
|
||||||
#[derive(Args, Clone)]
|
#[derive(Args, Clone)]
|
||||||
pub struct RunConfig {
|
pub struct RunConfig {
|
||||||
/// Path to Lox source file.
|
/// Path to Lox source file.
|
||||||
|
@ -20,6 +21,7 @@ pub struct RunConfig {
|
||||||
pub source: PathBuf,
|
pub source: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Available commands for the Lox interpreter
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
pub enum Commands {
|
pub enum Commands {
|
||||||
/// Run a Lox source file.
|
/// Run a Lox source file.
|
||||||
|
|
|
@ -12,8 +12,11 @@ pub enum EnvironmentError {
|
||||||
InvalidDistance,
|
InvalidDistance,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Environment mapping variable names to their respective values. Can have an optional enclosing
|
/// Environment mapping variable names to their respective values.
|
||||||
/// environment. The outermost (global) environment has no enclosing one.
|
/// Can have an optional enclosing environment. The outermost (global)
|
||||||
|
/// environment has no enclosing one.
|
||||||
|
///
|
||||||
|
/// This is for lexical scoping.
|
||||||
#[derive(Default, Debug, Clone)]
|
#[derive(Default, Debug, Clone)]
|
||||||
pub struct Environment {
|
pub struct Environment {
|
||||||
values: HashMap<String, Value>,
|
values: HashMap<String, Value>,
|
||||||
|
@ -52,6 +55,8 @@ impl Environment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Assign a value to a variable at a specific lexical distance (number of scopes away).
|
||||||
|
/// Used by the resolver to handle closures correctly.
|
||||||
pub fn assign_at(
|
pub fn assign_at(
|
||||||
&mut self,
|
&mut self,
|
||||||
distance: usize,
|
distance: usize,
|
||||||
|
@ -79,6 +84,8 @@ impl Environment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a variable's value from an environment at a specific lexical distance.
|
||||||
|
/// Used by the resolver to handle closures correctly.
|
||||||
pub fn get_at(&self, distance: usize, token: &Token) -> Result<Value, EnvironmentError> {
|
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()) {
|
if let Some(v) = self.ancestor(distance)?.values.get(token.lexeme.as_str()) {
|
||||||
Ok(v.clone())
|
Ok(v.clone())
|
||||||
|
@ -90,6 +97,7 @@ impl Environment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Find an environment at a specific lexical distance from the current one.
|
||||||
fn ancestor(&self, distance: usize) -> Result<Environment, EnvironmentError> {
|
fn ancestor(&self, distance: usize) -> Result<Environment, EnvironmentError> {
|
||||||
let mut environment = self.clone();
|
let mut environment = self.clone();
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::token::{self, Token};
|
use crate::token::{self, Token};
|
||||||
|
|
||||||
/// Enumeration of all types of expressions.
|
/// Enumeration of all types of expressions in the Lox language.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub enum Expression {
|
pub enum Expression {
|
||||||
Assign {
|
Assign {
|
||||||
|
|
|
@ -10,7 +10,7 @@ use crate::{
|
||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
use std::{cell::RefCell, fmt::Display, rc::Rc};
|
use std::{cell::RefCell, fmt::Display, rc::Rc};
|
||||||
|
|
||||||
/// A lox function.
|
/// A Lox function with name, parameters, body, and closure environment.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Function {
|
pub struct Function {
|
||||||
pub name: Token,
|
pub name: Token,
|
||||||
|
@ -62,6 +62,8 @@ impl Callable for Function {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Function {
|
impl Function {
|
||||||
|
/// Bind a method to an instance, creating a new function with "this" defined in its environment.
|
||||||
|
/// This allows methods to access the instance they belong to.
|
||||||
pub fn bind(&self, instance: Rc<RefCell<Instance>>) -> Self {
|
pub fn bind(&self, instance: Rc<RefCell<Instance>>) -> Self {
|
||||||
let mut env = Environment::with_enclosing(self.closure.clone());
|
let mut env = Environment::with_enclosing(self.closure.clone());
|
||||||
env.define("this".to_string(), Value::Instance(instance));
|
env.define("this".to_string(), Value::Instance(instance));
|
||||||
|
|
|
@ -14,6 +14,7 @@ pub enum InstanceError {
|
||||||
NoSuchProperty(usize, String),
|
NoSuchProperty(usize, String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An instance of a Lox class, containing fields and access to methods.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Instance {
|
pub struct Instance {
|
||||||
class: Class,
|
class: Class,
|
||||||
|
@ -27,6 +28,7 @@ impl Display for Instance {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Instance {
|
impl Instance {
|
||||||
|
/// Create a new instance of a class with empty fields.
|
||||||
pub fn new(class: Class) -> Rc<RefCell<Self>> {
|
pub fn new(class: Class) -> Rc<RefCell<Self>> {
|
||||||
Rc::new(RefCell::new(Self {
|
Rc::new(RefCell::new(Self {
|
||||||
class,
|
class,
|
||||||
|
@ -34,6 +36,8 @@ impl Instance {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a property from this instance. First check for the name in fields, then methods.
|
||||||
|
/// If a method is found, bind it to this instance.
|
||||||
pub fn get(&self, name: &Token, instance: Rc<RefCell<Self>>) -> Result<Value, InstanceError> {
|
pub fn get(&self, name: &Token, instance: Rc<RefCell<Self>>) -> Result<Value, InstanceError> {
|
||||||
if let Some(field) = self.fields.get(name.lexeme.as_str()) {
|
if let Some(field) = self.fields.get(name.lexeme.as_str()) {
|
||||||
Ok(field.clone())
|
Ok(field.clone())
|
||||||
|
@ -50,6 +54,7 @@ impl Instance {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set a field on this instance to the given value.
|
||||||
pub fn set(&mut self, name: &Token, value: Value) {
|
pub fn set(&mut self, name: &Token, value: Value) {
|
||||||
self.fields.insert(name.lexeme.clone(), value);
|
self.fields.insert(name.lexeme.clone(), value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ pub enum InterpreterError {
|
||||||
EnvironmentError(#[from] crate::environment::EnvironmentError),
|
EnvironmentError(#[from] crate::environment::EnvironmentError),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Interpreter for the Lox language.
|
/// Interpreter for the Lox language that executes statements and evaluates expressions.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Interpreter {
|
pub struct Interpreter {
|
||||||
pub globals: Rc<RefCell<Environment>>,
|
pub globals: Rc<RefCell<Environment>>,
|
||||||
|
@ -126,7 +126,7 @@ impl Interpreter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute all statements within a block, using a new environment (with the old one as the
|
/// 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
|
/// enclosing one). Immediately return the value of a Return Value when it is encountered after
|
||||||
/// execution of a statement.
|
/// execution of a statement.
|
||||||
pub fn block(
|
pub fn block(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
|
@ -11,6 +11,7 @@ use crate::{
|
||||||
value::{CallableType, Value},
|
value::{CallableType, Value},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Native clock function that returns the current time in seconds since the Unix epoch
|
||||||
struct Clock;
|
struct Clock;
|
||||||
|
|
||||||
impl Callable for Clock {
|
impl Callable for Clock {
|
||||||
|
@ -22,6 +23,7 @@ impl Callable for Clock {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the current system time in seconds since the Unix epoch
|
||||||
fn call(
|
fn call(
|
||||||
&self,
|
&self,
|
||||||
_interpreter: &mut Interpreter,
|
_interpreter: &mut Interpreter,
|
||||||
|
@ -36,6 +38,7 @@ impl Callable for Clock {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return all native functions available to the Lox interpreter
|
||||||
pub fn all() -> Vec<(String, Value)> {
|
pub fn all() -> Vec<(String, Value)> {
|
||||||
vec![(
|
vec![(
|
||||||
"clock".into(),
|
"clock".into(),
|
||||||
|
|
|
@ -71,7 +71,7 @@ pub enum ParserError {
|
||||||
SuperclassMethodNameExpected(usize),
|
SuperclassMethodNameExpected(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse the Lox language.
|
/// Parse the Lox language tokens into an abstract syntax tree.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct Parser {
|
struct Parser {
|
||||||
current: usize,
|
current: usize,
|
||||||
|
@ -655,7 +655,8 @@ impl Parser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to parse the provided tokens into an AST.
|
/// Try to parse the provided tokens into an Abstract Syntax Tree (AST).
|
||||||
|
/// Return a list of statements that can be executed by the interpreter.
|
||||||
pub fn ast(tokens: Vec<Token>) -> Result<Vec<Statement>, ParserError> {
|
pub fn ast(tokens: Vec<Token>) -> Result<Vec<Statement>, ParserError> {
|
||||||
let mut parser = Parser::new(tokens)?;
|
let mut parser = Parser::new(tokens)?;
|
||||||
parser.run()
|
parser.run()
|
||||||
|
|
|
@ -37,6 +37,7 @@ enum ClassType {
|
||||||
Subclass,
|
Subclass,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolve variable references and performs static analysis before interpretation.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Resolver<'a> {
|
pub struct Resolver<'a> {
|
||||||
scopes: Vec<HashMap<String, bool>>,
|
scopes: Vec<HashMap<String, bool>>,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{expression::Expression, token::Token};
|
use crate::{expression::Expression, token::Token};
|
||||||
|
|
||||||
/// Enumeration of all types of statements.
|
/// Enumeration of all types of statements in the Lox language.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Statement {
|
pub enum Statement {
|
||||||
Block(Vec<Statement>),
|
Block(Vec<Statement>),
|
||||||
|
|
|
@ -56,12 +56,13 @@ pub enum TokenType {
|
||||||
Eof,
|
Eof,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Literal value.
|
/// Literal value in the Lox language.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub enum Literal {
|
pub enum Literal {
|
||||||
/// String literal.
|
/// String literal.
|
||||||
String(String),
|
String(String),
|
||||||
// Number literal, represented as f64 (thus it can be decimal).
|
/// Number literal, represented as f64 (thus it can be decimal).
|
||||||
|
/// Must be Ord, otherwise I need to rewrite the locals implementation in the interpreter.
|
||||||
Number(OrderedFloat<f64>),
|
Number(OrderedFloat<f64>),
|
||||||
/// Boolean literal.
|
/// Boolean literal.
|
||||||
Boolean(bool),
|
Boolean(bool),
|
||||||
|
@ -78,7 +79,7 @@ pub struct Token {
|
||||||
pub lexeme: String,
|
pub lexeme: String,
|
||||||
/// Literal value of the token, if any.
|
/// Literal value of the token, if any.
|
||||||
pub literal: Option<Literal>,
|
pub literal: Option<Literal>,
|
||||||
/// Starting line on which the token was oonsumed from the source.
|
/// Starting line on which the token was consumed from the source.
|
||||||
pub line: usize,
|
pub line: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,10 @@ use crate::token::Token;
|
||||||
|
|
||||||
use super::interface::Tokenizer;
|
use super::interface::Tokenizer;
|
||||||
|
|
||||||
/// Consume comments.
|
/// Consume comments in the source code.
|
||||||
///
|
///
|
||||||
/// A comment starts with '//' and runs until the end of the line.
|
/// A comment starts with '//' and runs until the end of the line.
|
||||||
/// If only one '/' is seen, it is consumed as a Slash token.
|
/// If only one '/' is seen, it is consumed as a Slash token instead.
|
||||||
pub struct Comment;
|
pub struct Comment;
|
||||||
impl Tokenizer for Comment {
|
impl Tokenizer for Comment {
|
||||||
fn run(
|
fn run(
|
||||||
|
|
|
@ -8,8 +8,8 @@ use super::interface::Tokenizer;
|
||||||
|
|
||||||
/// Consume an identifier which also might be a keyword.
|
/// Consume an identifier which also might be a keyword.
|
||||||
///
|
///
|
||||||
/// An identifier starts with an alphabetic character and goes on consuming alphanumeric and
|
/// An identifier starts with an alphabetic character or underscore and continues with
|
||||||
/// underscore characters until the first different one.
|
/// alphanumeric and underscore characters until the first different character.
|
||||||
pub struct Identifier;
|
pub struct Identifier;
|
||||||
impl Tokenizer for Identifier {
|
impl Tokenizer for Identifier {
|
||||||
fn run(
|
fn run(
|
||||||
|
|
|
@ -2,7 +2,8 @@ use std::{iter::Peekable, str::CharIndices};
|
||||||
|
|
||||||
use crate::token::Token;
|
use crate::token::Token;
|
||||||
|
|
||||||
/// Interface to implement by a tokenizer.
|
/// Interface to implement by a tokenizer. Each tokenizer handles a specific type of token
|
||||||
|
/// in the Lox language.
|
||||||
pub trait Tokenizer: Send + Sync {
|
pub trait Tokenizer: Send + Sync {
|
||||||
/// Take a tuple consisting of the index of a char and the char itself, the whole source code
|
/// 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
|
/// iterator, the source itself and the current line. Return None if you can not handle the
|
||||||
|
|
|
@ -63,6 +63,7 @@ lazy_static! {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Consume lexemes that consist of exactly one or two characters.
|
/// Consume lexemes that consist of exactly one or two characters.
|
||||||
|
/// Handles operators like !=, ==, <=, and >=.
|
||||||
pub struct Lookahead;
|
pub struct Lookahead;
|
||||||
impl Tokenizer for Lookahead {
|
impl Tokenizer for Lookahead {
|
||||||
fn run(
|
fn run(
|
||||||
|
|
|
@ -5,7 +5,8 @@ use tracing::error;
|
||||||
|
|
||||||
use super::interface::Tokenizer;
|
use super::interface::Tokenizer;
|
||||||
|
|
||||||
/// Consume a number literal. Numbers can have one decimal point.
|
/// Consume a number literal from the source code.
|
||||||
|
/// Numbers can have one decimal point and consist of one or more digits.
|
||||||
pub struct Number;
|
pub struct Number;
|
||||||
impl Tokenizer for Number {
|
impl Tokenizer for Number {
|
||||||
fn run(
|
fn run(
|
||||||
|
|
|
@ -3,9 +3,10 @@ use crate::token::{Literal, Token, TokenType};
|
||||||
use std::{iter::Peekable, str::CharIndices};
|
use std::{iter::Peekable, str::CharIndices};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
/// Consume a string literal.
|
/// Consume a string literal from the source code.
|
||||||
///
|
///
|
||||||
/// A string literal consists of everything between two '"' and can stretch across multiple lines.
|
/// A string literal consists of everything between two double quotes (")
|
||||||
|
/// and can stretch across multiple lines.
|
||||||
pub struct String;
|
pub struct String;
|
||||||
impl Tokenizer for String {
|
impl Tokenizer for String {
|
||||||
fn run(
|
fn run(
|
||||||
|
|
|
@ -2,7 +2,7 @@ use super::interface::Tokenizer;
|
||||||
use crate::token::Token;
|
use crate::token::Token;
|
||||||
use std::{iter::Peekable, str::CharIndices};
|
use std::{iter::Peekable, str::CharIndices};
|
||||||
|
|
||||||
/// Consume and ignore whitespace characters.
|
/// Consume and ignore whitespace characters (spaces, tabs, carriage returns).
|
||||||
pub struct Whitespace;
|
pub struct Whitespace;
|
||||||
impl Tokenizer for Whitespace {
|
impl Tokenizer for Whitespace {
|
||||||
fn run(
|
fn run(
|
||||||
|
|
13
src/value.rs
13
src/value.rs
|
@ -4,21 +4,31 @@ use ordered_float::OrderedFloat;
|
||||||
|
|
||||||
use crate::{callable::Callable, instance, token::Literal};
|
use crate::{callable::Callable, instance, token::Literal};
|
||||||
|
|
||||||
|
/// Represents the type of a callable value in Lox.
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum CallableType {
|
pub enum CallableType {
|
||||||
|
/// A regular function or method
|
||||||
Function,
|
Function,
|
||||||
|
/// A class (which is callable to create instances)
|
||||||
Class(crate::class::Class),
|
Class(crate::class::Class),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Concrete value in the interpreter.
|
/// Concrete runtime value in the Lox interpreter.
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
|
/// Special value for function returns
|
||||||
Return(Box<Value>),
|
Return(Box<Value>),
|
||||||
|
/// Function, method, or class
|
||||||
Callable((Rc<dyn Callable>, CallableType)),
|
Callable((Rc<dyn Callable>, CallableType)),
|
||||||
|
/// String value
|
||||||
String(String),
|
String(String),
|
||||||
|
/// Numeric value
|
||||||
Number(OrderedFloat<f64>),
|
Number(OrderedFloat<f64>),
|
||||||
|
/// Boolean value
|
||||||
Boolean(bool),
|
Boolean(bool),
|
||||||
|
/// Class instance
|
||||||
Instance(Rc<RefCell<instance::Instance>>),
|
Instance(Rc<RefCell<instance::Instance>>),
|
||||||
|
/// Nil value (null)
|
||||||
Nil,
|
Nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,6 +47,7 @@ impl Display for Value {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Value {
|
impl Value {
|
||||||
|
/// Determine if a value is "truthy" according to Lox rules.
|
||||||
/// Return false for nil or a false boolean, true otherwise.
|
/// Return false for nil or a false boolean, true otherwise.
|
||||||
pub fn is_truthy(&self) -> bool {
|
pub fn is_truthy(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue