Compare commits

..

3 Commits

Author SHA1 Message Date
129d03b74d fix: fix comma precedence
it should be lower than assignement.
Now we can do stuff like a = 10, b = 30(before this commit we couldn't
this was a bug)
2025-01-13 08:46:39 +04:00
31e348643f fix: make repl remember variables between invocations
I forgot to make environment global between run() function calls.
To fix this, I just create the environment and pass it to interpret()
instead of creating it there
2025-01-13 08:41:04 +04:00
9262620e07 feat: finish chapter 8
challenges will be done as well, won't skip them
2025-01-13 08:25:34 +04:00
7 changed files with 323 additions and 30 deletions

View File

@ -22,4 +22,27 @@ pub enum Expr {
op: Token, op: Token,
right: Box<Expr>, right: Box<Expr>,
}, },
Variable {
name: Token,
},
Assign {
name: Token,
value: Box<Expr>,
},
}
pub enum Stmt {
Block {
statements: Vec<Stmt>,
},
Expression {
expression: Expr,
},
Print {
expression: Expr,
},
Var {
name: Token,
initializer: Option<Expr>,
},
} }

65
src/environment.rs Normal file
View File

@ -0,0 +1,65 @@
use std::{cell::RefCell, collections::HashMap, rc::Rc};
use crate::token::{LiteralType, Token};
pub struct Environment {
values: HashMap<String, LiteralType>,
enclosing: Option<Rc<RefCell<Environment>>>,
}
pub enum EnvironmentError {
AssignError,
}
impl Environment {
pub fn new() -> Self {
Self {
values: HashMap::new(),
enclosing: None,
}
}
pub fn with_enclosing(enclosing: &Rc<RefCell<Environment>>) -> Self {
Self {
values: HashMap::new(),
enclosing: Some(Rc::clone(enclosing)),
}
}
pub fn define(&mut self, name: &str, val: LiteralType) {
// do not like this at all. String is allocated each time a variable is defined. Might be
// bad or might be good. I don't know :D
self.values.insert(name.to_string(), val);
}
pub fn assign(&mut self, name: &Token, val: LiteralType) -> Result<(), EnvironmentError> {
let cloned = val.clone();
let assigned = self
.values
.get_mut(&name.lexeme)
.map(|l| *l = val)
.ok_or(EnvironmentError::AssignError);
if assigned.is_err() {
if let Some(enclosing) = &mut self.enclosing {
return enclosing.borrow_mut().assign(name, cloned);
}
}
assigned
}
pub fn get(&self, name: &Token) -> Option<LiteralType> {
//self.values.get(&name.lexeme).cloned()
let value = self.values.get(&name.lexeme);
if value.is_none() {
if let Some(enclosing) = &self.enclosing {
return enclosing.borrow().get(name);
}
}
value.cloned()
}
}

View File

@ -1,5 +1,8 @@
use std::{cell::RefCell, rc::Rc};
use crate::{ use crate::{
ast::Expr, ast::{Expr, Stmt},
environment::Environment,
token::{LiteralType, Token, TokenType}, token::{LiteralType, Token, TokenType},
}; };
@ -18,27 +21,99 @@ impl RuntimeError {
} }
} }
pub fn interpret(expr: &Expr) -> Result<LiteralType, RuntimeError> { pub fn interpret(
statements: &Vec<Stmt>,
environment: &Rc<RefCell<Environment>>,
) -> Result<(), RuntimeError> {
for statement in statements {
execute(statement, &environment)?;
}
Ok(())
}
fn execute(statement: &Stmt, environment: &Rc<RefCell<Environment>>) -> Result<(), RuntimeError> {
match statement {
Stmt::Expression { expression } => {
evaluate(expression, &mut environment.borrow_mut())?;
}
Stmt::Print { expression } => {
let expr = evaluate(expression, &mut environment.borrow_mut())?;
println!("{expr}");
}
Stmt::Var { name, initializer } => {
let value = if let Some(initializer) = initializer {
evaluate(initializer, &mut environment.borrow_mut())?
} else {
LiteralType::Nil
};
environment.borrow_mut().define(&name.lexeme, value);
}
Stmt::Block { statements } => {
execute_block(statements, environment)?;
}
}
Ok(())
}
fn execute_block(
statements: &Vec<Stmt>,
environment: &Rc<RefCell<Environment>>,
) -> Result<(), RuntimeError> {
let block_enviroment = Rc::new(RefCell::new(Environment::with_enclosing(environment)));
for stmt in statements {
execute(stmt, &block_enviroment)?;
}
Ok(())
}
fn evaluate(expr: &Expr, environment: &mut Environment) -> Result<LiteralType, RuntimeError> {
match expr { match expr {
Expr::Ternary { Expr::Ternary {
first, first,
second, second,
third, third,
.. ..
} => ternary(first, second, third), } => ternary(first, second, third, environment),
Expr::Binary { left, op, right } => binary(&interpret(left)?, &interpret(right)?, op), Expr::Binary { left, op, right } => binary(
Expr::Grouping { expression } => interpret(expression), &evaluate(left, environment)?,
&evaluate(right, environment)?,
op,
),
Expr::Grouping { expression } => evaluate(expression, environment),
Expr::Literal { value } => Ok(value.clone()), Expr::Literal { value } => Ok(value.clone()),
Expr::Unary { op, right } => Ok(unary(&interpret(right)?, op)), Expr::Unary { op, right } => Ok(unary(&evaluate(right, environment)?, op)),
Expr::Variable { name } => environment.get(name).ok_or_else(|| RuntimeError {
token: name.clone(),
message: format!("Undefined variable {}.", name.lexeme),
}),
Expr::Assign { name, value } => {
let value = evaluate(value, environment)?;
environment
.assign(name, value.clone())
.map_err(|_| RuntimeError {
token: name.clone(),
message: format!("Undefined variable {}.", name.lexeme),
})?;
Ok(value)
}
} }
} }
fn ternary(first: &Expr, second: &Expr, third: &Expr) -> Result<LiteralType, RuntimeError> { fn ternary(
let first = interpret(first)?; first: &Expr,
second: &Expr,
third: &Expr,
environment: &mut Environment,
) -> Result<LiteralType, RuntimeError> {
let first = evaluate(first, environment)?;
if is_truthy(&first) { if is_truthy(&first) {
return interpret(second); return evaluate(second, environment);
} }
interpret(third) evaluate(third, environment)
} }
fn binary( fn binary(
@ -71,7 +146,7 @@ fn binary(
(Greater | GreaterEqual | Less | LessEqual | Minus | Slash | Star, _, _) => Err(RuntimeError::new(op, "Operands must be numbers")), (Greater | GreaterEqual | Less | LessEqual | Minus | Slash | Star, _, _) => Err(RuntimeError::new(op, "Operands must be numbers")),
(Plus, _, _) => Err(RuntimeError::new(op, "Operands must be two numbers or two strings")), (Plus, _, _) => Err(RuntimeError::new(op, "Operands must be two numbers or two strings")),
_ => unreachable!("Shouldn't happen. Expr::Binary for interpret. Some case is a binary operation that wasn't matched") _ => unreachable!("Shouldn't happen. Expr::Binary for evaluate. Some case is a binary operation that wasn't matched")
} }
} }
@ -79,7 +154,7 @@ fn unary(right: &LiteralType, op: &Token) -> LiteralType {
match (op.t_type, &right) { match (op.t_type, &right) {
(TokenType::Minus, LiteralType::Number(num)) => LiteralType::Number(-num), (TokenType::Minus, LiteralType::Number(num)) => LiteralType::Number(-num),
(TokenType::Bang, _) => LiteralType::Bool(!is_truthy(right)), (TokenType::Bang, _) => LiteralType::Bool(!is_truthy(right)),
_ => unreachable!("Shouldn't happen. Expr::Unary for interpret"), _ => unreachable!("Shouldn't happen. Expr::Unary for evaluate"),
} }
} }

View File

@ -1,15 +1,19 @@
use std::{ use std::{
cell::RefCell,
error::Error, error::Error,
fs, fs,
io::{self, Write}, io::{self, Write},
rc::Rc,
}; };
use environment::Environment;
use interpreter::RuntimeError; use interpreter::RuntimeError;
use parser::Parser; use parser::{ParseError, Parser};
use scanner::Scanner; use scanner::Scanner;
use token::{Token, TokenType}; use token::{Token, TokenType};
mod ast; mod ast;
mod environment;
mod interpreter; mod interpreter;
mod parser; mod parser;
mod printer; mod printer;
@ -22,6 +26,7 @@ pub enum RunError {
FileReadError(io::Error), FileReadError(io::Error),
OtherError(Box<dyn Error>), // to be added, OtherError(Box<dyn Error>), // to be added,
RuntimeError(RuntimeError), RuntimeError(RuntimeError),
ParseError,
} }
impl<E: Error + 'static> From<E> for RunError { impl<E: Error + 'static> From<E> for RunError {
@ -32,37 +37,53 @@ impl<E: Error + 'static> From<E> for RunError {
pub fn run_file(path: &str) -> Result<(), RunError> { pub fn run_file(path: &str) -> Result<(), RunError> {
let file = fs::read_to_string(path).map_err(RunError::FileReadError)?; let file = fs::read_to_string(path).map_err(RunError::FileReadError)?;
let environment = Rc::new(RefCell::new(Environment::new()));
run(&file)?; run(&file, &environment)?;
Ok(()) Ok(())
} }
pub fn run(src: &str) -> Result<(), RunError> { pub fn run(src: &str, environment: &Rc<RefCell<Environment>>) -> Result<(), RunError> {
let mut scanner = Scanner::new(src.to_string()); let mut scanner = Scanner::new(src.to_string());
let tokens = scanner.scan_tokens()?; let tokens = scanner.scan_tokens()?;
let mut parser = Parser::new(tokens); let mut parser = Parser::new(tokens);
let expression = parser.parse().inspect_err(|e| error(&e.token, &e.msg))?; let statements = parser.parse();
let interpreted_value = interpreter::interpret(&expression) // i don't want to collect the errors and allocate a vec
let mut p_error = false;
for err in statements.iter().filter_map(|x| x.as_ref().err()) {
if !p_error {
p_error = true;
}
error(err);
}
if p_error {
return Err(RunError::ParseError);
}
let statements = statements.into_iter().flatten().collect();
interpreter::interpret(&statements, environment)
.inspect_err(runtime_error) .inspect_err(runtime_error)
.map_err(RunError::RuntimeError)?; .map_err(RunError::RuntimeError)?;
println!("{interpreted_value}");
Ok(()) Ok(())
} }
pub fn run_prompt() -> Result<(), Box<dyn Error>> { pub fn run_prompt() -> Result<(), Box<dyn Error>> {
let stdin = io::stdin(); let stdin = io::stdin();
let input = &mut String::new(); let input = &mut String::new();
let environment = Rc::new(RefCell::new(Environment::new()));
loop { loop {
input.clear(); input.clear();
print!("> "); print!("> ");
io::stdout().flush()?; io::stdout().flush()?;
stdin.read_line(input)?; stdin.read_line(input)?;
let _ = run(input); let _ = run(input, &environment);
} }
} }
@ -76,10 +97,10 @@ pub fn report(line: usize, location: &str, message: &str) {
eprintln!("[line {line}] Error {location}: {message}"); eprintln!("[line {line}] Error {location}: {message}");
} }
fn error(token: &Token, message: &str) { fn error(ParseError { token, msg }: &ParseError) {
match token.t_type { match token.t_type {
TokenType::EOF => report(token.line, " at end", message), TokenType::EOF => report(token.line, " at end", msg),
_ => report(token.line, &format!(" at '{}'", token.lexeme), message), _ => report(token.line, &format!("at '{}'", token.lexeme), msg),
} }
} }

View File

@ -23,6 +23,10 @@ fn main() -> ExitCode {
if let Err(RunError::RuntimeError(r)) = result { if let Err(RunError::RuntimeError(r)) = result {
return ExitCode::from(70); return ExitCode::from(70);
} }
if let Err(RunError::ParseError) = result {
return ExitCode::from(75);
}
} else { } else {
let result = run_prompt(); let result = run_prompt();

View File

@ -1,7 +1,7 @@
use std::fmt::Display; use std::fmt::Display;
use crate::{ use crate::{
ast::Expr, ast::{Expr, Stmt},
token::{LiteralType, Token, TokenType}, token::{LiteralType, Token, TokenType},
}; };
@ -29,8 +29,85 @@ impl Parser<'_> {
Parser { tokens, current: 0 } Parser { tokens, current: 0 }
} }
pub fn parse(&mut self) -> Result<Expr, ParseError> { //pub fn parse(&mut self) -> Result<Expr, ParseError> {
self.expression() // self.expression()
//}
// maps to program rule in the grammar
pub fn parse(&mut self) -> Vec<Result<Stmt, ParseError>> {
let mut statements = Vec::new();
while !self.is_at_end() {
statements.push(self.declaration());
}
statements
}
fn declaration(&mut self) -> Result<Stmt, ParseError> {
let stmt = if self.match_token(&[TokenType::Var]) {
self.var_declaration()
} else {
self.statement()
};
stmt.inspect_err(|_| self.synchronize())
}
fn var_declaration(&mut self) -> Result<Stmt, ParseError> {
let name = self.consume(TokenType::Identifier, "Expect variable name")?;
let initializer = if self.match_token(&[TokenType::Equal]) {
Some(self.expression()?)
} else {
None
};
self.consume(
TokenType::Semicolon,
"Expect ';' after variable declaration.",
)?;
Ok(Stmt::Var { name, initializer })
}
fn statement(&mut self) -> Result<Stmt, ParseError> {
if self.match_token(&[TokenType::Print]) {
return self.print_statement();
}
if self.match_token(&[TokenType::LeftBrace]) {
return Ok(Stmt::Block {
statements: self.block()?,
});
}
self.expression_statement()
}
fn block(&mut self) -> Result<Vec<Stmt>, ParseError> {
let mut statements = Vec::new();
while !self.check(TokenType::RightBrace) && !self.is_at_end() {
statements.push(self.declaration()?);
}
self.consume(TokenType::RightBrace, "Expect '}' after block.")?;
Ok(statements)
}
fn print_statement(&mut self) -> Result<Stmt, ParseError> {
let expression = self.expression()?;
self.consume(TokenType::Semicolon, "Expect ';' after value.")?;
Ok(Stmt::Print { expression })
}
fn expression_statement(&mut self) -> Result<Stmt, ParseError> {
let expression = self.expression()?;
self.consume(TokenType::Semicolon, "Expect ';' after expression.")?;
Ok(Stmt::Expression { expression })
} }
fn expression(&mut self) -> Result<Expr, ParseError> { fn expression(&mut self) -> Result<Expr, ParseError> {
@ -41,7 +118,29 @@ impl Parser<'_> {
// comma -> equality ("," equality)* ; // expression grammar // comma -> equality ("," equality)* ; // expression grammar
fn comma(&mut self) -> Result<Expr, ParseError> { fn comma(&mut self) -> Result<Expr, ParseError> {
use TokenType::*; use TokenType::*;
self.left_association_binary(&[Comma], Self::ternary) self.left_association_binary(&[Comma], Self::assignment)
}
fn assignment(&mut self) -> Result<Expr, ParseError> {
let expr = self.ternary()?;
if self.match_token(&[TokenType::Equal]) {
let value = self.assignment()?;
let equals = self.previous();
if let Expr::Variable { name } = expr {
return Ok(Expr::Assign {
name,
value: Box::new(value),
});
}
return Err(ParseError {
token: equals.clone(),
msg: "Invalid assignment target.".to_string(),
});
}
Ok(expr)
} }
// ternary -> equality ("?" expression : ternary)? // expression grammar // ternary -> equality ("?" expression : ternary)? // expression grammar
@ -140,6 +239,12 @@ impl Parser<'_> {
}); });
} }
if self.match_token(&[Identifier]) {
return Ok(Expr::Variable {
name: self.previous().clone(),
});
}
if self.match_token(&[Equal, BangEqual]) { if self.match_token(&[Equal, BangEqual]) {
let _ = self.equality(); let _ = self.equality();
return Err(ParseError { return Err(ParseError {
@ -189,7 +294,6 @@ impl Parser<'_> {
}) })
} }
// will not be used for the time being (per the book)
// used for error recovery // used for error recovery
fn synchronize(&mut self) { fn synchronize(&mut self) {
use TokenType::*; use TokenType::*;
@ -203,10 +307,9 @@ impl Parser<'_> {
if let Class | Fun | Var | For | If | While | Print | Return = self.peek().t_type { if let Class | Fun | Var | For | If | While | Print | Return = self.peek().t_type {
return; return;
} }
}
self.advance(); self.advance();
} }
}
fn left_association_binary( fn left_association_binary(
&mut self, &mut self,

View File

@ -16,6 +16,8 @@ pub fn pretty_print(expr: &Expr) -> String {
second, second,
third, third,
} => parenthesize("?:", &[first, second, third]), } => parenthesize("?:", &[first, second, third]),
Expr::Variable { name } => name.lexeme.clone(),
Expr::Assign { name, value } => parenthesize(&name.lexeme, &[value]),
} }
} }