feat: finish chapter 6 (implement a parser)

there are challenges, which i wish to do (need to add support for stuff)
This commit is contained in:
Seymur Bagirov 2025-01-02 01:14:36 +04:00
parent 5f80dfe498
commit 91cfc0b938
6 changed files with 240 additions and 21 deletions

View File

@ -1,5 +1,6 @@
use crate::token::{LiteralType, Token}; use crate::token::{LiteralType, Token};
#[derive(Debug, Clone)]
pub enum Expr { pub enum Expr {
Binary { Binary {
left: Box<Expr>, left: Box<Expr>,

View File

@ -1,13 +1,17 @@
use std::{ use std::{
error::Error, error::Error,
fmt::Display,
fs, fs,
io::{self, Write}, io::{self, Write},
}; };
use parser::{ParseError, Parser};
use printer::pretty_print;
use scanner::Scanner; use scanner::Scanner;
use token::Token; use token::{Token, TokenType};
mod ast; mod ast;
mod parser;
mod printer; mod printer;
mod scanner; mod scanner;
mod token; mod token;
@ -16,26 +20,26 @@ mod utils;
pub fn run_file(path: &str) -> Result<(), Box<dyn Error>> { pub fn run_file(path: &str) -> Result<(), Box<dyn Error>> {
let file = fs::read_to_string(path)?; let file = fs::read_to_string(path)?;
run(&file); run(&file)?;
Ok(()) Ok(())
} }
pub fn run(src: &str) { pub fn run(src: &str) -> Result<(), Box<dyn Error>> {
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()?;
match tokens { let mut parser = Parser::new(tokens);
Err(ref errors) => {
for err in errors { let expression = parser.parse();
report(err.line, "", &err.msg);
} match expression {
} Ok(expr) => println!("{}", pretty_print(&expr)),
Ok(tokens) => { Err(e) => {
for token in tokens { error(e.token, &e.msg);
println!("{}", token);
}
} }
} }
Ok(())
} }
pub fn run_prompt() -> Result<(), Box<dyn Error>> { pub fn run_prompt() -> Result<(), Box<dyn Error>> {
@ -46,7 +50,7 @@ pub fn run_prompt() -> Result<(), Box<dyn Error>> {
print!("> "); print!("> ");
io::stdout().flush()?; io::stdout().flush()?;
stdin.read_line(input)?; stdin.read_line(input)?;
run(input); let _ = run(input);
} }
} }
@ -59,3 +63,10 @@ pub struct RloxError {
pub fn report(line: usize, location: &str, message: &str) { 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) {
match token.t_type {
TokenType::EOF => report(token.line, " at end", message),
_ => report(token.line, &format!(" at '{}'", token.lexeme), message),
}
}

189
src/parser.rs Normal file
View File

@ -0,0 +1,189 @@
use std::fmt::Display;
use crate::{
ast::Expr,
token::{LiteralType, Token, TokenType},
};
pub struct Parser<'a> {
tokens: &'a Vec<Token>,
current: usize,
}
#[derive(Debug)]
pub struct ParseError {
pub token: Token,
pub msg: String,
}
impl Parser<'_> {
pub fn new(tokens: &Vec<Token>) -> Parser<'_> {
Parser { tokens, current: 0 }
}
pub fn parse(&mut self) -> Result<Expr, ParseError> {
self.expression()
}
fn expression(&mut self) -> Result<Expr, ParseError> {
self.equality()
}
fn equality(&mut self) -> Result<Expr, ParseError> {
use TokenType::*;
self.left_association_binary(&[BangEqual, EqualEqual], Self::comparison)
}
fn comparison(&mut self) -> Result<Expr, ParseError> {
use TokenType::*;
self.left_association_binary(&[Greater, GreaterEqual, Less, LessEqual], Self::term)
}
fn term(&mut self) -> Result<Expr, ParseError> {
use TokenType::*;
self.left_association_binary(&[Minus, Plus], Self::factor)
}
fn factor(&mut self) -> Result<Expr, ParseError> {
use TokenType::*;
self.left_association_binary(&[Slash, Star], Self::unary)
}
fn unary(&mut self) -> Result<Expr, ParseError> {
use TokenType::*;
if self.match_token(&[Bang, Minus]) {
let op = self.previous().clone();
let right = self.unary()?;
return Ok(Expr::Unary {
op,
right: Box::new(right),
});
}
self.primary()
}
fn primary(&mut self) -> Result<Expr, ParseError> {
use LiteralType::*;
use TokenType::*;
fn create_literal(l_type: Option<LiteralType>) -> Expr {
Expr::Literal { value: l_type }
}
if self.match_token(&[False]) {
return Ok(create_literal(Some(Bool(false))));
}
if self.match_token(&[True]) {
return Ok(create_literal(Some(Bool(true))));
}
if self.match_token(&[TokenType::Number, TokenType::String]) {
return Ok(create_literal(self.previous().literal.clone()));
}
// i included the enum name bcs of ambiguity of LiteralType and TokenType
if self.match_token(&[TokenType::Nil]) {
return Ok(create_literal(Some(LiteralType::Nil)));
}
if self.match_token(&[LeftParen]) {
let expr = self.expression()?;
self.consume(RightParen, "Expect ')' after expression")?;
return Ok(Expr::Grouping {
expression: Box::new(expr),
});
}
Err(ParseError {
token: self.peek().clone(),
msg: "Expect expression.".to_string(),
})
}
fn consume(&mut self, t_type: TokenType, err_msg: &str) -> Result<Token, ParseError> {
if self.check(t_type) {
return Ok(self.advance().clone());
}
Err(ParseError {
token: self.peek().clone(),
msg: err_msg.to_string(),
})
}
// will not be used for the time being (per the book)
fn synchronize(&mut self) {
use TokenType::*;
self.advance();
while !self.is_at_end() {
if self.previous().t_type == TokenType::Semicolon {
return;
}
if let Class | Fun | Var | For | If | While | Print | Return = self.peek().t_type {
return;
}
}
self.advance();
}
fn left_association_binary(
&mut self,
types: &[TokenType],
expr_fn: fn(&mut Self) -> Result<Expr, ParseError>,
) -> Result<Expr, ParseError> {
let mut expr = expr_fn(self)?;
while self.match_token(types) {
let op = self.previous().clone();
let right = expr_fn(self)?;
expr = Expr::Binary {
left: Box::new(expr),
op,
right: Box::new(right),
}
}
Ok(expr)
}
fn match_token(&mut self, types: &[TokenType]) -> bool {
for t_type in types {
if self.check(*t_type) {
self.advance();
return true;
}
}
false
}
fn check(&self, t_type: TokenType) -> bool {
if self.is_at_end() {
return false;
}
self.peek().t_type == t_type
}
fn advance(&mut self) -> &Token {
if !self.is_at_end() {
self.current += 1;
}
self.previous()
}
fn is_at_end(&self) -> bool {
matches!(self.peek().t_type, TokenType::EOF)
}
fn peek(&self) -> &Token {
&self.tokens[self.current]
}
fn previous(&self) -> &Token {
&self.tokens[self.current - 1]
}
}

View File

@ -7,6 +7,8 @@ pub fn pretty_print(expr: &Expr) -> String {
Expr::Literal { value } => match value { Expr::Literal { value } => match value {
Some(LiteralType::String(v)) => v.to_string(), Some(LiteralType::String(v)) => v.to_string(),
Some(LiteralType::Number(v)) => v.to_string(), Some(LiteralType::Number(v)) => v.to_string(),
Some(LiteralType::Bool(v)) => v.to_string(),
Some(LiteralType::Nil) => "Nil".to_string(),
None => "None".to_string(), None => "None".to_string(),
}, },
Expr::Unary { op, right } => parenthesize(&op.lexeme, &[right]), Expr::Unary { op, right } => parenthesize(&op.lexeme, &[right]),

View File

@ -1,4 +1,4 @@
use std::{iter::Peekable, mem, str::Chars}; use std::{fmt::Display, iter::Peekable, mem, str::Chars};
use crate::{ use crate::{
token::{LiteralType, Token, TokenType}, token::{LiteralType, Token, TokenType},
@ -15,6 +15,19 @@ pub struct Scanner {
line: usize, line: usize,
} }
#[derive(Debug)]
pub struct ScannerError {
errors: Vec<RloxError>,
}
impl Display for ScannerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Errors: {:?}", self.errors)
}
}
impl std::error::Error for ScannerError {}
impl Scanner { impl Scanner {
pub fn new(source: String) -> Self { pub fn new(source: String) -> Self {
// the reason for using unsafe here is to have the ability to use utf-8 symbols // the reason for using unsafe here is to have the ability to use utf-8 symbols
@ -35,7 +48,7 @@ impl Scanner {
// this is so awful for me to write. This function needs to be not mutable in theory and it // this is so awful for me to write. This function needs to be not mutable in theory and it
// could be accomplished. TODO! // could be accomplished. TODO!
pub fn scan_tokens(&mut self) -> Result<&Vec<Token>, Vec<RloxError>> { pub fn scan_tokens(&mut self) -> Result<&Vec<Token>, ScannerError> {
let mut errors = Vec::new(); let mut errors = Vec::new();
while self.peek().is_some() { while self.peek().is_some() {
self.start = self.current; self.start = self.current;
@ -53,7 +66,7 @@ impl Scanner {
}); });
if !errors.is_empty() { if !errors.is_empty() {
return Err(errors); return Err(ScannerError { errors });
} }
Ok(&self.tokens) Ok(&self.tokens)
@ -331,6 +344,7 @@ mod tests {
let tokens = scanner.scan_tokens().expect_err("Should be an error"); let tokens = scanner.scan_tokens().expect_err("Should be an error");
let actual_error = tokens let actual_error = tokens
.errors
.iter() .iter()
.find(|e| e.msg == "Unterminated string") .find(|e| e.msg == "Unterminated string")
.expect("Error not found. There should be an error"); .expect("Error not found. There should be an error");

View File

@ -52,19 +52,21 @@ pub enum TokenType {
pub enum LiteralType { pub enum LiteralType {
String(String), String(String),
Number(f64), Number(f64),
Bool(bool),
Nil,
} }
impl LiteralType { impl LiteralType {
pub fn string_literal(val: &str) -> LiteralType { pub fn string_literal(val: &str) -> LiteralType {
return LiteralType::String(val.to_string()); LiteralType::String(val.to_string())
} }
pub fn number_literal(val: f64) -> LiteralType { pub fn number_literal(val: f64) -> LiteralType {
return LiteralType::Number(val); LiteralType::Number(val)
} }
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct Token { pub struct Token {
pub t_type: TokenType, pub t_type: TokenType,
pub lexeme: String, pub lexeme: String,