first documentation run

This commit is contained in:
Sebastian Hugentobler 2025-05-26 12:24:35 +02:00
parent e065108f58
commit f59e6a5fd5
Signed by: shu
SSH key fingerprint: SHA256:ppcx6MlixdNZd5EUM1nkHOKoyQYoJwzuQKXM6J/t66M
21 changed files with 64 additions and 23 deletions

View file

@ -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;

View file

@ -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 {

View file

@ -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.

View 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();

View file

@ -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 {

View file

@ -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));

View file

@ -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);
} }

View file

@ -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,

View file

@ -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(),

View file

@ -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()

View file

@ -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>>,

View file

@ -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>),

View file

@ -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,
} }

View file

@ -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(

View file

@ -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(

View file

@ -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

View file

@ -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(

View file

@ -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(

View file

@ -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(

View file

@ -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(

View file

@ -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 {